mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
initial commit
This commit is contained in:
commit
c2da007f40
1610 changed files with 398047 additions and 0 deletions
946
crypto/vm/arithops.cpp
Normal file
946
crypto/vm/arithops.cpp
Normal file
|
|
@ -0,0 +1,946 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#include "vm/arithops.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "common/bigint.hpp"
|
||||
#include "common/refint.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
int exec_push_tinyint4(VmState* st, unsigned args) {
|
||||
int x = (int)((args + 5) & 15) - 5;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSHINT " << x;
|
||||
stack.push_smallint(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_push_tinyint4(CellSlice&, unsigned args) {
|
||||
int x = (int)((args + 5) & 15) - 5;
|
||||
std::ostringstream os{"PUSHINT "};
|
||||
os << x;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int exec_push_tinyint8(VmState* st, unsigned args) {
|
||||
int x = (signed char)args;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSHINT " << x;
|
||||
stack.push_smallint(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_op_tinyint8(const char* op_prefix, CellSlice&, unsigned args) {
|
||||
int x = (signed char)args;
|
||||
std::ostringstream os{op_prefix};
|
||||
os << x;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int exec_push_smallint(VmState* st, unsigned args) {
|
||||
int x = (short)args;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSHINT " << x;
|
||||
stack.push_smallint(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_push_smallint(CellSlice&, unsigned args) {
|
||||
int x = (short)args;
|
||||
std::ostringstream os{"PUSHINT "};
|
||||
os << x;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int exec_push_int(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) {
|
||||
int l = (int)(args & 31) + 2;
|
||||
if (!cs.have(pfx_bits + 3 + l * 8)) {
|
||||
throw VmError{Excno::inv_opcode, "not enough bits for integer constant in PUSHINT"};
|
||||
}
|
||||
cs.advance(pfx_bits);
|
||||
td::RefInt256 x = cs.fetch_int256(3 + l * 8);
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSHINT " << x;
|
||||
stack.push_int(std::move(x));
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_push_int(CellSlice& cs, unsigned args, int pfx_bits) {
|
||||
int l = (int)(args & 31) + 2;
|
||||
if (!cs.have(pfx_bits + 3 + l * 8)) {
|
||||
return "";
|
||||
}
|
||||
cs.advance(pfx_bits);
|
||||
td::RefInt256 x = cs.fetch_int256(3 + l * 8);
|
||||
std::ostringstream os{"PUSHINT "};
|
||||
os << x;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int compute_len_push_int(const CellSlice& cs, unsigned args, int pfx_bits) {
|
||||
int l = (int)(args & 31) + 2;
|
||||
if (!cs.have(pfx_bits + 3 + l * 8)) {
|
||||
return 0;
|
||||
} else {
|
||||
return pfx_bits + 3 + l * 8;
|
||||
}
|
||||
}
|
||||
|
||||
int exec_push_pow2(VmState* st, unsigned args) {
|
||||
int x = (args & 255) + 1;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSHPOW2 " << x;
|
||||
td::RefInt256 r{true};
|
||||
r.unique_write().set_pow2(x);
|
||||
stack.push(std::move(r));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_push_nan(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSHNAN";
|
||||
td::RefInt256 r{true};
|
||||
r.unique_write().invalidate();
|
||||
stack.push(std::move(r));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_push_pow2dec(VmState* st, unsigned args) {
|
||||
int x = (args & 255) + 1;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSHPOW2DEC " << x;
|
||||
td::RefInt256 r{true};
|
||||
r.unique_write().set_pow2(x).add_tiny(-1).normalize();
|
||||
stack.push(std::move(r));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_push_negpow2(VmState* st, unsigned args) {
|
||||
int x = (args & 255) + 1;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSHNEGPOW2 " << x;
|
||||
td::RefInt256 r{true};
|
||||
r.unique_write().set_pow2(x).negate().normalize();
|
||||
stack.push(std::move(r));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_int_const_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mkfixed(0x7, 4, 4, dump_push_tinyint4, exec_push_tinyint4))
|
||||
.insert(OpcodeInstr::mkfixed(0x80, 8, 8, std::bind(dump_op_tinyint8, "PUSHINT ", _1, _2), exec_push_tinyint8))
|
||||
.insert(OpcodeInstr::mkfixed(0x81, 8, 16, dump_push_smallint, exec_push_smallint))
|
||||
.insert(OpcodeInstr::mkextrange(0x82 << 5, (0x82 << 5) + 31, 13, 5, dump_push_int, exec_push_int,
|
||||
compute_len_push_int))
|
||||
.insert(OpcodeInstr::mkfixedrange(0x8300, 0x83ff, 16, 8, instr::dump_1c_l_add(1, "PUSHPOW2 "), exec_push_pow2))
|
||||
.insert(OpcodeInstr::mksimple(0x83ff, 16, "PUSHNAN", exec_push_nan))
|
||||
.insert(OpcodeInstr::mkfixed(0x84, 8, 8, instr::dump_1c_l_add(1, "PUSHPOW2DEC "), exec_push_pow2dec))
|
||||
.insert(OpcodeInstr::mkfixed(0x85, 8, 8, instr::dump_1c_l_add(1, "PUSHNEGPOW2 "), exec_push_negpow2));
|
||||
}
|
||||
|
||||
int exec_add(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ADD";
|
||||
stack.check_underflow(2);
|
||||
auto y = stack.pop_int();
|
||||
stack.push_int_quiet(stack.pop_int() + std::move(y), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_sub(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute SUB";
|
||||
stack.check_underflow(2);
|
||||
auto y = stack.pop_int();
|
||||
stack.push_int_quiet(stack.pop_int() - std::move(y), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_subr(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute SUBR";
|
||||
stack.check_underflow(2);
|
||||
auto y = stack.pop_int();
|
||||
stack.push_int_quiet(std::move(y) - stack.pop_int(), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_negate(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute NEGATE";
|
||||
stack.check_underflow(1);
|
||||
stack.push_int_quiet(-stack.pop_int(), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_inc(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute INC";
|
||||
stack.check_underflow(1);
|
||||
stack.push_int_quiet(stack.pop_int() + 1, quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dec(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DEC";
|
||||
stack.check_underflow(1);
|
||||
stack.push_int_quiet(stack.pop_int() - 1, quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_add_tinyint8(VmState* st, unsigned args, bool quiet) {
|
||||
int x = (signed char)args;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ADDINT " << x;
|
||||
stack.check_underflow(1);
|
||||
stack.push_int_quiet(stack.pop_int() + x, quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_mul_tinyint8(VmState* st, unsigned args, bool quiet) {
|
||||
int x = (signed char)args;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute MULINT " << x;
|
||||
stack.check_underflow(1);
|
||||
stack.push_int_quiet(stack.pop_int() * x, quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_mul(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute MUL";
|
||||
stack.check_underflow(2);
|
||||
auto y = stack.pop_int();
|
||||
stack.push_int_quiet(stack.pop_int() * std::move(y), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_add_mul_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xa0, 8, "ADD", std::bind(exec_add, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xa1, 8, "SUB", std::bind(exec_sub, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xa2, 8, "SUBR", std::bind(exec_subr, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xa3, 8, "NEGATE", std::bind(exec_negate, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xa4, 8, "INC", std::bind(exec_inc, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xa5, 8, "DEC", std::bind(exec_dec, _1, false)))
|
||||
.insert(OpcodeInstr::mkfixed(0xa6, 8, 8, std::bind(dump_op_tinyint8, "ADDINT ", _1, _2),
|
||||
std::bind(exec_add_tinyint8, _1, _2, false)))
|
||||
.insert(OpcodeInstr::mkfixed(0xa7, 8, 8, std::bind(dump_op_tinyint8, "MULINT ", _1, _2),
|
||||
std::bind(exec_mul_tinyint8, _1, _2, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xa8, 8, "MUL", std::bind(exec_mul, _1, false)));
|
||||
cp0.insert(OpcodeInstr::mksimple(0xb7a0, 16, "QADD", std::bind(exec_add, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7a1, 16, "QSUB", std::bind(exec_sub, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7a2, 16, "QSUBR", std::bind(exec_subr, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7a3, 16, "QNEGATE", std::bind(exec_negate, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7a4, 16, "QINC", std::bind(exec_inc, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7a5, 16, "QDEC", std::bind(exec_dec, _1, true)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7a6, 16, 8, std::bind(dump_op_tinyint8, "QADDINT ", _1, _2),
|
||||
std::bind(exec_add_tinyint8, _1, _2, true)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7a7, 16, 8, std::bind(dump_op_tinyint8, "QMULINT ", _1, _2),
|
||||
std::bind(exec_mul_tinyint8, _1, _2, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7a8, 16, "QMUL", std::bind(exec_mul, _1, true)));
|
||||
}
|
||||
|
||||
int exec_divmod(VmState* st, unsigned args, int quiet) {
|
||||
int round_mode = (int)(args & 3) - 1;
|
||||
if (!(args & 12) || round_mode == 2) {
|
||||
throw VmError{Excno::inv_opcode};
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DIV/MOD " << (args & 15);
|
||||
stack.check_underflow(2);
|
||||
auto y = stack.pop_int();
|
||||
auto x = stack.pop_int();
|
||||
switch ((args >> 2) & 3) {
|
||||
case 1:
|
||||
stack.push_int_quiet(td::div(std::move(x), std::move(y), round_mode), quiet);
|
||||
break;
|
||||
case 2:
|
||||
stack.push_int_quiet(td::mod(std::move(x), std::move(y), round_mode), quiet);
|
||||
break;
|
||||
case 3: {
|
||||
auto dm = td::divmod(std::move(x), std::move(y), round_mode);
|
||||
stack.push_int_quiet(std::move(dm.first), quiet);
|
||||
stack.push_int_quiet(std::move(dm.second), quiet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_divmod(CellSlice&, unsigned args, bool quiet) {
|
||||
int round_mode = (int)(args & 3);
|
||||
if (!(args & 12) || round_mode == 3) {
|
||||
return "";
|
||||
}
|
||||
std::string s = (args & 4) ? "DIV" : "";
|
||||
if (args & 8) {
|
||||
s += "MOD";
|
||||
}
|
||||
if (quiet) {
|
||||
s = "Q" + s;
|
||||
}
|
||||
return s + "FRC"[round_mode];
|
||||
}
|
||||
|
||||
int exec_shrmod(VmState* st, unsigned args, int mode) {
|
||||
int y = -1;
|
||||
if (mode & 2) {
|
||||
y = (args & 0xff) + 1;
|
||||
args >>= 8;
|
||||
}
|
||||
int round_mode = (int)(args & 3) - 1;
|
||||
if (!(args & 12) || round_mode == 2) {
|
||||
throw VmError{Excno::inv_opcode};
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute SHR/MOD " << (args & 15) << ',' << y;
|
||||
if (!(mode & 2)) {
|
||||
stack.check_underflow(2);
|
||||
y = stack.pop_smallint_range(256);
|
||||
} else {
|
||||
stack.check_underflow(1);
|
||||
}
|
||||
if (!y) {
|
||||
round_mode = -1;
|
||||
}
|
||||
auto x = stack.pop_int();
|
||||
switch ((args >> 2) & 3) {
|
||||
case 1:
|
||||
stack.push_int_quiet(td::rshift(std::move(x), y, round_mode), mode & 1);
|
||||
break;
|
||||
case 3:
|
||||
stack.push_int_quiet(td::rshift(x, y, round_mode), mode & 1);
|
||||
// fallthrough
|
||||
case 2:
|
||||
x.write().mod_pow2(y, round_mode).normalize();
|
||||
stack.push_int_quiet(std::move(x), mode & 1);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_shrmod(CellSlice&, unsigned args, int mode) {
|
||||
int y = -1;
|
||||
if (mode & 2) {
|
||||
y = (args & 0xff) + 1;
|
||||
args >>= 8;
|
||||
}
|
||||
int round_mode = (int)(args & 3);
|
||||
if (!(args & 12) || round_mode == 3) {
|
||||
return "";
|
||||
}
|
||||
std::string s;
|
||||
switch (args & 12) {
|
||||
case 4:
|
||||
s = "RSHIFT";
|
||||
break;
|
||||
case 8:
|
||||
s = "MODPOW2";
|
||||
break;
|
||||
case 12:
|
||||
s = "RSHIFTMOD";
|
||||
break;
|
||||
}
|
||||
if (mode & 1) {
|
||||
s = "Q" + s;
|
||||
}
|
||||
s += "FRC"[round_mode];
|
||||
if (mode & 2) {
|
||||
char buff[8];
|
||||
sprintf(buff, " %d", y);
|
||||
s += buff;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int exec_muldivmod(VmState* st, unsigned args, int quiet) {
|
||||
int round_mode = (int)(args & 3) - 1;
|
||||
if (!(args & 12) || round_mode == 2) {
|
||||
throw VmError{Excno::inv_opcode};
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute MULDIV/MOD " << (args & 15);
|
||||
stack.check_underflow(3);
|
||||
auto z = stack.pop_int();
|
||||
auto y = stack.pop_int();
|
||||
auto x = stack.pop_int();
|
||||
typename td::BigInt256::DoubleInt tmp{0};
|
||||
tmp.add_mul(*x, *y);
|
||||
auto q = td::RefInt256{true};
|
||||
tmp.mod_div(*z, q.unique_write(), round_mode);
|
||||
switch ((args >> 2) & 3) {
|
||||
case 1:
|
||||
q.unique_write().normalize();
|
||||
stack.push_int_quiet(std::move(q), quiet);
|
||||
break;
|
||||
case 3:
|
||||
q.unique_write().normalize();
|
||||
stack.push_int_quiet(std::move(q), quiet);
|
||||
// fallthrough
|
||||
case 2:
|
||||
stack.push_int_quiet(td::RefInt256{true, tmp}, quiet);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_muldivmod(CellSlice&, unsigned args, bool quiet) {
|
||||
int round_mode = (int)(args & 3);
|
||||
if (!(args & 12) || round_mode == 3) {
|
||||
return "";
|
||||
}
|
||||
std::string s = (args & 4) ? "MULDIV" : "MUL";
|
||||
if (args & 8) {
|
||||
s += "MOD";
|
||||
}
|
||||
if (quiet) {
|
||||
s = "Q" + s;
|
||||
}
|
||||
return s + "FRC"[round_mode];
|
||||
}
|
||||
|
||||
int exec_mulshrmod(VmState* st, unsigned args, int mode) {
|
||||
int z = -1;
|
||||
if (mode & 2) {
|
||||
z = (args & 0xff) + 1;
|
||||
args >>= 8;
|
||||
}
|
||||
int round_mode = (int)(args & 3) - 1;
|
||||
if (!(args & 12) || round_mode == 2) {
|
||||
throw VmError{Excno::inv_opcode};
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute MULSHR/MOD " << (args & 15) << ',' << z;
|
||||
if (!(mode & 2)) {
|
||||
stack.check_underflow(3);
|
||||
z = stack.pop_smallint_range(256);
|
||||
} else {
|
||||
stack.check_underflow(2);
|
||||
}
|
||||
if (!z) {
|
||||
round_mode = -1;
|
||||
}
|
||||
auto y = stack.pop_int();
|
||||
auto x = stack.pop_int();
|
||||
typename td::BigInt256::DoubleInt tmp{0};
|
||||
tmp.add_mul(*x, *y);
|
||||
switch ((args >> 2) & 3) {
|
||||
case 1:
|
||||
tmp.rshift(z, round_mode).normalize();
|
||||
stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1);
|
||||
break;
|
||||
case 3: {
|
||||
typename td::BigInt256::DoubleInt tmp2{tmp};
|
||||
tmp2.rshift(z, round_mode).normalize();
|
||||
stack.push_int_quiet(td::RefInt256{true, tmp2}, mode & 1);
|
||||
}
|
||||
// fallthrough
|
||||
case 2:
|
||||
tmp.mod_pow2(z, round_mode).normalize();
|
||||
stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_mulshrmod(CellSlice&, unsigned args, int mode) {
|
||||
int y = -1;
|
||||
if (mode & 2) {
|
||||
y = (args & 0xff) + 1;
|
||||
args >>= 8;
|
||||
}
|
||||
int round_mode = (int)(args & 3);
|
||||
if (!(args & 12) || round_mode == 3) {
|
||||
return "";
|
||||
}
|
||||
std::string s;
|
||||
switch (args & 12) {
|
||||
case 4:
|
||||
s = "MULRSHIFT";
|
||||
break;
|
||||
case 8:
|
||||
s = "MULMODPOW2";
|
||||
break;
|
||||
case 12:
|
||||
s = "MULRSHIFTMOD";
|
||||
break;
|
||||
}
|
||||
if (mode & 1) {
|
||||
s = "Q" + s;
|
||||
}
|
||||
s += "FRC"[round_mode];
|
||||
if (mode & 2) {
|
||||
char buff[8];
|
||||
sprintf(buff, " %d", y);
|
||||
s += buff;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int exec_shldivmod(VmState* st, unsigned args, int mode) {
|
||||
int y = -1;
|
||||
if (mode & 2) {
|
||||
y = (args & 0xff) + 1;
|
||||
args >>= 8;
|
||||
}
|
||||
int round_mode = (int)(args & 3) - 1;
|
||||
if (!(args & 12) || round_mode == 2) {
|
||||
throw VmError{Excno::inv_opcode};
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute SHLDIV/MOD " << (args & 15) << ',' << y;
|
||||
if (!(mode & 2)) {
|
||||
stack.check_underflow(3);
|
||||
y = stack.pop_smallint_range(256);
|
||||
} else {
|
||||
stack.check_underflow(2);
|
||||
}
|
||||
auto z = stack.pop_int();
|
||||
auto x = stack.pop_int();
|
||||
typename td::BigInt256::DoubleInt tmp{*x};
|
||||
tmp <<= y;
|
||||
switch ((args >> 2) & 3) {
|
||||
case 1: {
|
||||
auto q = td::RefInt256{true};
|
||||
tmp.mod_div(*z, q.unique_write(), round_mode);
|
||||
q.unique_write().normalize();
|
||||
stack.push_int_quiet(std::move(q), mode & 1);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
auto q = td::RefInt256{true};
|
||||
tmp.mod_div(*z, q.unique_write(), round_mode);
|
||||
q.unique_write().normalize();
|
||||
stack.push_int_quiet(std::move(q), mode & 1);
|
||||
stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
typename td::BigInt256::DoubleInt tmp2;
|
||||
tmp.mod_div(*z, tmp2, round_mode);
|
||||
stack.push_int_quiet(td::RefInt256{true, tmp}, mode & 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_shldivmod(CellSlice&, unsigned args, bool quiet) {
|
||||
int round_mode = (int)(args & 3);
|
||||
if (!(args & 12) || round_mode == 3) {
|
||||
return "";
|
||||
}
|
||||
std::string s = (args & 4) ? "LSHIFTDIV" : "LSHIFT";
|
||||
if (args & 8) {
|
||||
s += "MOD";
|
||||
}
|
||||
if (quiet) {
|
||||
s = "Q" + s;
|
||||
}
|
||||
return s + "FRC"[round_mode];
|
||||
}
|
||||
|
||||
void register_div_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mkfixed(0xa90, 12, 4, std::bind(dump_divmod, _1, _2, false),
|
||||
std::bind(exec_divmod, _1, _2, false)))
|
||||
.insert(OpcodeInstr::mkfixed(0xa92, 12, 4, std::bind(dump_shrmod, _1, _2, 0), std::bind(exec_shrmod, _1, _2, 0)))
|
||||
.insert(OpcodeInstr::mkfixed(0xa93, 12, 12, std::bind(dump_shrmod, _1, _2, 2), std::bind(exec_shrmod, _1, _2, 2)))
|
||||
.insert(OpcodeInstr::mkfixed(0xa98, 12, 4, std::bind(dump_muldivmod, _1, _2, false),
|
||||
std::bind(exec_muldivmod, _1, _2, false)))
|
||||
.insert(OpcodeInstr::mkfixed(0xa9a, 12, 4, std::bind(dump_mulshrmod, _1, _2, 0),
|
||||
std::bind(exec_mulshrmod, _1, _2, 0)))
|
||||
.insert(OpcodeInstr::mkfixed(0xa9b, 12, 12, std::bind(dump_mulshrmod, _1, _2, 2),
|
||||
std::bind(exec_mulshrmod, _1, _2, 2)))
|
||||
.insert(OpcodeInstr::mkfixed(0xa9c, 12, 4, std::bind(dump_shldivmod, _1, _2, 0),
|
||||
std::bind(exec_shldivmod, _1, _2, 0)))
|
||||
.insert(OpcodeInstr::mkfixed(0xa9d, 12, 12, std::bind(dump_shldivmod, _1, _2, 2),
|
||||
std::bind(exec_shldivmod, _1, _2, 2)));
|
||||
cp0.insert(OpcodeInstr::mkfixed(0xb7a90, 20, 4, std::bind(dump_divmod, _1, _2, true),
|
||||
std::bind(exec_divmod, _1, _2, true)))
|
||||
.insert(
|
||||
OpcodeInstr::mkfixed(0xb7a92, 20, 4, std::bind(dump_shrmod, _1, _2, 1), std::bind(exec_shrmod, _1, _2, 1)))
|
||||
// .insert(OpcodeInstr::mkfixed(0xb7a93, 20, 12, std::bind(dump_shrmod, _1, _2, 3), std::bind(exec_shrmod, _1, _2, 3)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7a98, 20, 4, std::bind(dump_muldivmod, _1, _2, true),
|
||||
std::bind(exec_muldivmod, _1, _2, true)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7a9a, 20, 4, std::bind(dump_mulshrmod, _1, _2, 1),
|
||||
std::bind(exec_mulshrmod, _1, _2, 1)))
|
||||
// .insert(OpcodeInstr::mkfixed(0xb7a9b, 20, 12, std::bind(dump_mulshrmod, _1, _2, 3), std::bind(exec_mulshrmod, _1, _2, 3)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7a9c, 20, 4, std::bind(dump_shldivmod, _1, _2, 1),
|
||||
std::bind(exec_shldivmod, _1, _2, 1)))
|
||||
// .insert(OpcodeInstr::mkfixed(0xb7a9d, 20, 12, std::bind(dump_shldivmod, _1, _2, 3), std::bind(exec_shldivmod, _1, _2, 3)))
|
||||
;
|
||||
}
|
||||
|
||||
int exec_lshift_tinyint8(VmState* st, unsigned args, bool quiet) {
|
||||
int x = (args & 0xff) + 1;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute LSHIFT " << x;
|
||||
stack.check_underflow(1);
|
||||
stack.push_int_quiet(stack.pop_int() << x, quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_rshift_tinyint8(VmState* st, unsigned args, bool quiet) {
|
||||
int x = (args & 0xff) + 1;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute RSHIFT " << x;
|
||||
stack.check_underflow(1);
|
||||
stack.push_int_quiet(stack.pop_int() >> x, quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_lshift(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute LSHIFT";
|
||||
stack.check_underflow(2);
|
||||
int x = stack.pop_smallint_range(1023);
|
||||
stack.push_int_quiet(stack.pop_int() << x, quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_rshift(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute RSHIFT";
|
||||
stack.check_underflow(2);
|
||||
int x = stack.pop_smallint_range(1023);
|
||||
stack.push_int_quiet(stack.pop_int() >> x, quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_pow2(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute POW2";
|
||||
stack.check_underflow(1);
|
||||
int x = stack.pop_smallint_range(1023);
|
||||
td::RefInt256 r{true};
|
||||
r.unique_write().set_pow2(x);
|
||||
stack.push_int_quiet(std::move(r), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_and(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute AND";
|
||||
stack.check_underflow(2);
|
||||
auto y = stack.pop_int();
|
||||
stack.push_int_quiet(stack.pop_int() & std::move(y), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_or(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute OR";
|
||||
stack.check_underflow(2);
|
||||
auto y = stack.pop_int();
|
||||
stack.push_int_quiet(stack.pop_int() | std::move(y), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xor(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XOR";
|
||||
stack.check_underflow(2);
|
||||
auto y = stack.pop_int();
|
||||
stack.push_int_quiet(stack.pop_int() ^ std::move(y), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_not(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute NOT";
|
||||
stack.check_underflow(1);
|
||||
stack.push_int_quiet(~stack.pop_int(), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_fits_tinyint8(VmState* st, unsigned args, bool quiet) {
|
||||
int y = (args & 0xff) + 1;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute FITS " << y;
|
||||
stack.check_underflow(1);
|
||||
auto x = stack.pop_int();
|
||||
if (!x->signed_fits_bits(y)) {
|
||||
x.write().invalidate();
|
||||
}
|
||||
stack.push_int_quiet(std::move(x), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_ufits_tinyint8(VmState* st, unsigned args, bool quiet) {
|
||||
int y = (args & 0xff) + 1;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute UFITS " << y;
|
||||
stack.check_underflow(1);
|
||||
auto x = stack.pop_int();
|
||||
if (!x->unsigned_fits_bits(y)) {
|
||||
x.write().invalidate();
|
||||
}
|
||||
stack.push_int_quiet(std::move(x), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_fits(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute FITSX";
|
||||
stack.check_underflow(2);
|
||||
int y = stack.pop_smallint_range(1023);
|
||||
auto x = stack.pop_int();
|
||||
if (!x->signed_fits_bits(y)) {
|
||||
x.write().invalidate();
|
||||
}
|
||||
stack.push_int_quiet(std::move(x), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_ufits(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute UFITSX";
|
||||
stack.check_underflow(2);
|
||||
int y = stack.pop_smallint_range(1023);
|
||||
auto x = stack.pop_int();
|
||||
if (!x->unsigned_fits_bits(y)) {
|
||||
x.write().invalidate();
|
||||
}
|
||||
stack.push_int_quiet(std::move(x), quiet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_bitsize(VmState* st, bool sgnd, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute " << (sgnd ? "" : "U") << "BITSIZE";
|
||||
stack.check_underflow(1);
|
||||
auto x = stack.pop_int();
|
||||
int y = x->bit_size(sgnd);
|
||||
if (y < 0x7fffffff) {
|
||||
stack.push_smallint(y);
|
||||
} else if (!quiet) {
|
||||
throw VmError{Excno::range_chk, "CHKSIZE for negative integer"};
|
||||
} else {
|
||||
stack.push_int_quiet(td::RefInt256{true}, quiet);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_shift_logic_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mkfixed(0xaa, 8, 8, instr::dump_1c_l_add(1, "LSHIFT "),
|
||||
std::bind(exec_lshift_tinyint8, _1, _2, false)))
|
||||
.insert(OpcodeInstr::mkfixed(0xab, 8, 8, instr::dump_1c_l_add(1, "RSHIFT "),
|
||||
std::bind(exec_rshift_tinyint8, _1, _2, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xac, 8, "LSHIFT", std::bind(exec_lshift, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xad, 8, "RSHIFT", std::bind(exec_rshift, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xae, 8, "POW2", std::bind(exec_pow2, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xb0, 8, "AND", std::bind(exec_and, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xb1, 8, "OR", std::bind(exec_or, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xb2, 8, "XOR", std::bind(exec_xor, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xb3, 8, "NOT", std::bind(exec_not, _1, false)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb4, 8, 8, instr::dump_1c_l_add(1, "FITS "),
|
||||
std::bind(exec_fits_tinyint8, _1, _2, false)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb5, 8, 8, instr::dump_1c_l_add(1, "UFITS "),
|
||||
std::bind(exec_ufits_tinyint8, _1, _2, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xb600, 16, "FITSX", std::bind(exec_fits, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xb601, 16, "UFITSX", std::bind(exec_ufits, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xb602, 16, "BITSIZE", std::bind(exec_bitsize, _1, true, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xb603, 16, "UBITSIZE", std::bind(exec_bitsize, _1, false, false)));
|
||||
cp0.insert(OpcodeInstr::mkfixed(0xb7aa, 16, 8, instr::dump_1c_l_add(1, "QLSHIFT "),
|
||||
std::bind(exec_lshift_tinyint8, _1, _2, true)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7ab, 16, 8, instr::dump_1c_l_add(1, "QRSHIFT "),
|
||||
std::bind(exec_rshift_tinyint8, _1, _2, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7ac, 16, "QLSHIFT", std::bind(exec_lshift, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7ad, 16, "QRSHIFT", std::bind(exec_rshift, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7ae, 16, "QPOW2", std::bind(exec_pow2, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b0, 16, "QAND", std::bind(exec_and, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b1, 16, "QOR", std::bind(exec_or, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b2, 16, "QXOR", std::bind(exec_xor, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b3, 16, "QNOT", std::bind(exec_not, _1, true)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7b4, 16, 8, instr::dump_1c_l_add(1, "QFITS "),
|
||||
std::bind(exec_fits_tinyint8, _1, _2, true)))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7b5, 16, 8, instr::dump_1c_l_add(1, "QUFITS "),
|
||||
std::bind(exec_ufits_tinyint8, _1, _2, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b600, 24, "QFITSX", std::bind(exec_fits, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b601, 24, "QUFITSX", std::bind(exec_ufits, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b602, 24, "QBITSIZE", std::bind(exec_bitsize, _1, true, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b603, 24, "QUBITSIZE", std::bind(exec_bitsize, _1, false, true)));
|
||||
}
|
||||
|
||||
int exec_minmax(VmState* st, int mode) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute MINMAXOP " << mode;
|
||||
stack.check_underflow(2);
|
||||
auto x = stack.pop_int();
|
||||
auto y = stack.pop_int();
|
||||
if (!x->is_valid()) {
|
||||
y = x;
|
||||
} else if (!y->is_valid()) {
|
||||
x = y;
|
||||
} else if (cmp(x, y) > 0) {
|
||||
swap(x, y);
|
||||
}
|
||||
if (mode & 2) {
|
||||
stack.push_int_quiet(std::move(x), mode & 1);
|
||||
}
|
||||
if (mode & 4) {
|
||||
stack.push_int_quiet(std::move(y), mode & 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_abs(VmState* st, bool quiet) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ABS";
|
||||
stack.check_underflow(1);
|
||||
auto x = stack.pop_int();
|
||||
if (x->is_valid() && x->sgn() < 0) {
|
||||
stack.push_int_quiet(-std::move(x), quiet);
|
||||
} else {
|
||||
stack.push_int_quiet(std::move(x), quiet);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_other_arith_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xb608, 16, "MIN", std::bind(exec_minmax, _1, 2)))
|
||||
.insert(OpcodeInstr::mksimple(0xb609, 16, "MAX", std::bind(exec_minmax, _1, 4)))
|
||||
.insert(OpcodeInstr::mksimple(0xb60a, 16, "MINMAX", std::bind(exec_minmax, _1, 6)))
|
||||
.insert(OpcodeInstr::mksimple(0xb60b, 16, "ABS", std::bind(exec_abs, _1, false)));
|
||||
cp0.insert(OpcodeInstr::mksimple(0xb7b608, 24, "QMIN", std::bind(exec_minmax, _1, 3)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b609, 24, "QMAX", std::bind(exec_minmax, _1, 5)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b60a, 24, "QMINMAX", std::bind(exec_minmax, _1, 7)))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b60b, 24, "QABS", std::bind(exec_abs, _1, true)));
|
||||
}
|
||||
|
||||
int exec_sgn(VmState* st, int mode, bool quiet, const char* name) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute " << name;
|
||||
stack.check_underflow(1);
|
||||
auto x = stack.pop_int();
|
||||
if (!x->is_valid()) {
|
||||
stack.push_int_quiet(std::move(x), quiet);
|
||||
} else {
|
||||
int y = td::sgn(std::move(x));
|
||||
stack.push_smallint(((mode >> (4 + y * 4)) & 15) - 8);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_cmp(VmState* st, int mode, bool quiet, const char* name) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute " << name;
|
||||
stack.check_underflow(2);
|
||||
auto y = stack.pop_int();
|
||||
auto x = stack.pop_int();
|
||||
if (!x->is_valid() || !y->is_valid()) {
|
||||
stack.push_int_quiet(std::move(x), quiet);
|
||||
} else {
|
||||
int z = td::cmp(std::move(x), std::move(y));
|
||||
stack.push_smallint(((mode >> (4 + z * 4)) & 15) - 8);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_cmp_int(VmState* st, unsigned args, int mode, bool quiet, const char* name) {
|
||||
int y = (signed char)args;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute " << name << "INT " << y;
|
||||
stack.check_underflow(1);
|
||||
auto x = stack.pop_int();
|
||||
if (!x->is_valid()) {
|
||||
stack.push_int_quiet(std::move(x), quiet);
|
||||
} else {
|
||||
int z = td::cmp(std::move(x), y);
|
||||
stack.push_smallint(((mode >> (4 + z * 4)) & 15) - 8);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_is_nan(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ISNAN";
|
||||
stack.check_underflow(1);
|
||||
auto x = stack.pop_int();
|
||||
stack.push_smallint(x->is_valid() ? 0 : -1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_chk_nan(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute CHKNAN";
|
||||
stack.check_underflow(1);
|
||||
auto x = stack.pop_int();
|
||||
stack.push_int(std::move(x));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_int_cmp_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xb8, 8, "SGN", std::bind(exec_sgn, _1, 0x987, false, "SGN")))
|
||||
.insert(OpcodeInstr::mksimple(0xb9, 8, "LESS", std::bind(exec_cmp, _1, 0x887, false, "LESS")))
|
||||
.insert(OpcodeInstr::mksimple(0xba, 8, "EQUAL", std::bind(exec_cmp, _1, 0x878, false, "EQUAL")))
|
||||
.insert(OpcodeInstr::mksimple(0xbb, 8, "LEQ", std::bind(exec_cmp, _1, 0x877, false, "LEQ")))
|
||||
.insert(OpcodeInstr::mksimple(0xbc, 8, "GREATER", std::bind(exec_cmp, _1, 0x788, false, "GREATER")))
|
||||
.insert(OpcodeInstr::mksimple(0xbd, 8, "NEQ", std::bind(exec_cmp, _1, 0x787, false, "NEQ")))
|
||||
.insert(OpcodeInstr::mksimple(0xbe, 8, "GEQ", std::bind(exec_cmp, _1, 0x778, false, "GEQ")))
|
||||
.insert(OpcodeInstr::mksimple(0xbf, 8, "CMP", std::bind(exec_cmp, _1, 0x987, false, "CMP")))
|
||||
.insert(OpcodeInstr::mkfixed(0xc0, 8, 8, std::bind(dump_op_tinyint8, "EQINT ", _1, _2),
|
||||
std::bind(exec_cmp_int, _1, _2, 0x878, false, "EQ")))
|
||||
.insert(OpcodeInstr::mkfixed(0xc1, 8, 8, std::bind(dump_op_tinyint8, "LESSINT ", _1, _2),
|
||||
std::bind(exec_cmp_int, _1, _2, 0x887, false, "LESS")))
|
||||
.insert(OpcodeInstr::mkfixed(0xc2, 8, 8, std::bind(dump_op_tinyint8, "GTINT ", _1, _2),
|
||||
std::bind(exec_cmp_int, _1, _2, 0x788, false, "GT")))
|
||||
.insert(OpcodeInstr::mkfixed(0xc3, 8, 8, std::bind(dump_op_tinyint8, "NEQINT ", _1, _2),
|
||||
std::bind(exec_cmp_int, _1, _2, 0x787, false, "NEQ")))
|
||||
.insert(OpcodeInstr::mksimple(0xc4, 8, "ISNAN", exec_is_nan))
|
||||
.insert(OpcodeInstr::mksimple(0xc5, 8, "CHKNAN", exec_chk_nan));
|
||||
cp0.insert(OpcodeInstr::mksimple(0xb7b8, 16, "QSGN", std::bind(exec_sgn, _1, 0x987, true, "QSGN")))
|
||||
.insert(OpcodeInstr::mksimple(0xb7b9, 16, "QLESS", std::bind(exec_cmp, _1, 0x887, true, "QLESS")))
|
||||
.insert(OpcodeInstr::mksimple(0xb7ba, 16, "QEQUAL", std::bind(exec_cmp, _1, 0x878, true, "QEQUAL")))
|
||||
.insert(OpcodeInstr::mksimple(0xb7bb, 16, "QLEQ", std::bind(exec_cmp, _1, 0x877, true, "QLEQ")))
|
||||
.insert(OpcodeInstr::mksimple(0xb7bc, 16, "QGREATER", std::bind(exec_cmp, _1, 0x788, true, "QGREATER")))
|
||||
.insert(OpcodeInstr::mksimple(0xb7bd, 16, "QNEQ", std::bind(exec_cmp, _1, 0x787, true, "QNEQ")))
|
||||
.insert(OpcodeInstr::mksimple(0xb7be, 16, "QGEQ", std::bind(exec_cmp, _1, 0x778, true, "QGEQ")))
|
||||
.insert(OpcodeInstr::mksimple(0xb7bf, 16, "QCMP", std::bind(exec_cmp, _1, 0x987, true, "QCMP")))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7c0, 16, 8, std::bind(dump_op_tinyint8, "QEQINT ", _1, _2),
|
||||
std::bind(exec_cmp_int, _1, _2, 0x878, true, "QEQ")))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7c1, 16, 8, std::bind(dump_op_tinyint8, "QLESSINT ", _1, _2),
|
||||
std::bind(exec_cmp_int, _1, _2, 0x887, true, "QLESS")))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7c2, 16, 8, std::bind(dump_op_tinyint8, "QGTINT ", _1, _2),
|
||||
std::bind(exec_cmp_int, _1, _2, 0x788, true, "QGT")))
|
||||
.insert(OpcodeInstr::mkfixed(0xb7c3, 16, 8, std::bind(dump_op_tinyint8, "QNEQINT ", _1, _2),
|
||||
std::bind(exec_cmp_int, _1, _2, 0x787, true, "QNEQ")));
|
||||
}
|
||||
|
||||
void register_arith_ops(OpcodeTable& cp0) {
|
||||
register_int_const_ops(cp0);
|
||||
register_add_mul_ops(cp0);
|
||||
register_div_ops(cp0);
|
||||
register_shift_logic_ops(cp0);
|
||||
register_other_arith_ops(cp0);
|
||||
register_int_cmp_ops(cp0);
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
27
crypto/vm/arithops.h
Normal file
27
crypto/vm/arithops.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace vm {
|
||||
|
||||
class OpcodeTable;
|
||||
|
||||
void register_arith_ops(OpcodeTable& cp0);
|
||||
|
||||
} // namespace vm
|
||||
97
crypto/vm/atom.cpp
Normal file
97
crypto/vm/atom.cpp
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "atom.h"
|
||||
|
||||
namespace vm {
|
||||
using td::Ref;
|
||||
|
||||
std::atomic<Atom*> Atom::hashtable[hashtable_size] = {};
|
||||
std::atomic<int> Atom::atoms_defined{0};
|
||||
std::atomic<int> Atom::anon_atoms{0};
|
||||
|
||||
void Atom::print_to(std::ostream& os) const {
|
||||
if (name_.empty()) {
|
||||
os << "atom#" << index_;
|
||||
} else {
|
||||
os << name_;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Atom::make_name() const {
|
||||
char buffer[16];
|
||||
sprintf(buffer, "atom#%d", index_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Atom& atom) {
|
||||
atom.print_to(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, Ref<Atom> atom_ref) {
|
||||
atom_ref->print_to(os);
|
||||
return os;
|
||||
}
|
||||
|
||||
std::pair<unsigned, unsigned> Atom::compute_hash(td::Slice name) {
|
||||
unsigned h1 = 1, h2 = 1;
|
||||
for (std::size_t i = 0; i < name.size(); i++) {
|
||||
h1 = (239 * h1 + (unsigned char)name[i]) % hashtable_size;
|
||||
h2 = (17 * h2 + (unsigned char)name[i]) % (hashtable_size - 1);
|
||||
}
|
||||
return std::make_pair(h1, h2 + 1);
|
||||
}
|
||||
|
||||
Ref<Atom> Atom::find(td::Slice name, bool create) {
|
||||
auto hash = compute_hash(name);
|
||||
while (true) {
|
||||
auto& pos = hashtable[hash.first];
|
||||
Atom* ptr = pos.load(std::memory_order_acquire);
|
||||
if (ptr) {
|
||||
if (ptr->name_as_slice() == name) {
|
||||
return Ref<Atom>(ptr);
|
||||
}
|
||||
} else if (!create) {
|
||||
return {};
|
||||
} else {
|
||||
Atom* p2 = new Atom(name.str(), hash.first);
|
||||
Atom* p1 = nullptr;
|
||||
if (pos.compare_exchange_strong(p1, p2)) {
|
||||
atoms_defined.fetch_add(1, std::memory_order_relaxed);
|
||||
return Ref<Atom>(p2);
|
||||
}
|
||||
delete p2;
|
||||
CHECK(p1);
|
||||
if (p1->name_as_slice() == name) {
|
||||
return Ref<Atom>(p1);
|
||||
}
|
||||
}
|
||||
hash.first += hash.second;
|
||||
if (hash.first >= hashtable_size) {
|
||||
hash.first -= hashtable_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Atom> Atom::anon() {
|
||||
int c = anon_atoms.fetch_add(1, std::memory_order_relaxed);
|
||||
return Ref<Atom>{true, "", ~c};
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
70
crypto/vm/atom.h
Normal file
70
crypto/vm/atom.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include "vm/stack.hpp"
|
||||
#include "td/utils/Slice.h"
|
||||
|
||||
namespace vm {
|
||||
using td::Ref;
|
||||
|
||||
class Atom : public td::CntObject {
|
||||
std::string name_;
|
||||
int index_;
|
||||
static constexpr unsigned hashtable_size = 170239;
|
||||
static std::atomic<Atom*> hashtable[hashtable_size];
|
||||
static std::atomic<int> atoms_defined;
|
||||
static std::atomic<int> anon_atoms;
|
||||
static std::pair<unsigned, unsigned> compute_hash(td::Slice name);
|
||||
|
||||
public:
|
||||
Atom(const Atom&) = delete;
|
||||
Atom &operator=(const Atom&) = delete;
|
||||
Atom(Atom&&) = delete;
|
||||
Atom &operator=(Atom&&) = delete;
|
||||
~Atom() override = default;
|
||||
Atom(std::string name, int index) : name_(name), index_(index) {
|
||||
}
|
||||
static Ref<Atom> anon();
|
||||
static Ref<Atom> find(td::Slice name, bool create = false);
|
||||
static Ref<Atom> create(td::Slice name) {
|
||||
return find(std::move(name), true);
|
||||
}
|
||||
std::string name() const {
|
||||
return name_;
|
||||
}
|
||||
std::string name_ext() const {
|
||||
return name_.empty() ? make_name() : name_;
|
||||
}
|
||||
td::Slice name_as_slice() const {
|
||||
return td::Slice{name_};
|
||||
}
|
||||
int index() const {
|
||||
return index_;
|
||||
}
|
||||
void print_to(std::ostream& os) const;
|
||||
|
||||
private:
|
||||
std::string make_name() const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Atom& atom);
|
||||
std::ostream& operator<<(std::ostream& os, Ref<Atom> atom_ref);
|
||||
|
||||
} // namespace vm
|
||||
1133
crypto/vm/boc.cpp
Normal file
1133
crypto/vm/boc.cpp
Normal file
File diff suppressed because it is too large
Load diff
314
crypto/vm/boc.h
Normal file
314
crypto/vm/boc.h
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include <set>
|
||||
#include "vm/cells.h"
|
||||
#include "td/utils/Status.h"
|
||||
#include "td/utils/buffer.h"
|
||||
#include "td/utils/HashMap.h"
|
||||
|
||||
namespace vm {
|
||||
using td::Ref;
|
||||
|
||||
td::Result<Ref<Cell>> std_boc_deserialize(td::Slice data, bool can_be_empty = false);
|
||||
td::Result<td::BufferSlice> std_boc_serialize(Ref<Cell> root, int mode = 0);
|
||||
|
||||
td::Result<std::vector<Ref<Cell>>> std_boc_deserialize_multi(td::Slice data);
|
||||
td::Result<td::BufferSlice> std_boc_serialize_multi(std::vector<Ref<Cell>> root, int mode = 0);
|
||||
|
||||
class NewCellStorageStat {
|
||||
public:
|
||||
NewCellStorageStat() {
|
||||
}
|
||||
|
||||
struct Stat {
|
||||
Stat() {
|
||||
}
|
||||
Stat(td::uint64 cells_, td::uint64 bits_, td::uint64 internal_refs_ = 0, td::uint64 external_refs_ = 0)
|
||||
: cells(cells_), bits(bits_), internal_refs(internal_refs_), external_refs(external_refs_) {
|
||||
}
|
||||
td::uint64 cells{0};
|
||||
td::uint64 bits{0};
|
||||
td::uint64 internal_refs{0};
|
||||
td::uint64 external_refs{0};
|
||||
|
||||
auto key() const {
|
||||
return std::make_tuple(cells, bits, internal_refs, external_refs);
|
||||
}
|
||||
bool operator==(const Stat& other) const {
|
||||
return key() == other.key();
|
||||
}
|
||||
Stat& operator=(const Stat& other) = default;
|
||||
Stat& operator+=(const Stat& other) {
|
||||
cells += other.cells;
|
||||
bits += other.bits;
|
||||
internal_refs += other.internal_refs;
|
||||
external_refs += other.external_refs;
|
||||
return *this;
|
||||
}
|
||||
Stat operator+(const Stat& other) const {
|
||||
return Stat{cells + other.cells, bits + other.bits, internal_refs + other.internal_refs,
|
||||
external_refs + other.external_refs};
|
||||
}
|
||||
bool fits_uint32() const {
|
||||
return !((cells | bits | internal_refs | external_refs) >> 32);
|
||||
}
|
||||
void set_zero() {
|
||||
cells = bits = internal_refs = external_refs = 0;
|
||||
}
|
||||
};
|
||||
|
||||
Stat get_stat() const {
|
||||
return stat_;
|
||||
}
|
||||
|
||||
Stat get_proof_stat() const {
|
||||
return proof_stat_;
|
||||
}
|
||||
|
||||
Stat get_total_stat() const {
|
||||
return stat_ + proof_stat_;
|
||||
}
|
||||
|
||||
void add_cell(Ref<Cell> cell);
|
||||
void add_proof(Ref<Cell> cell, const CellUsageTree* usage_tree);
|
||||
void add_cell_and_proof(Ref<Cell> cell, const CellUsageTree* usage_tree);
|
||||
Stat tentative_add_cell(Ref<Cell> cell) const;
|
||||
Stat tentative_add_proof(Ref<Cell> cell, const CellUsageTree* usage_tree) const;
|
||||
void set_zero() {
|
||||
stat_.set_zero();
|
||||
proof_stat_.set_zero();
|
||||
}
|
||||
|
||||
private:
|
||||
const CellUsageTree* usage_tree_;
|
||||
std::set<vm::Cell::Hash> seen_;
|
||||
Stat stat_;
|
||||
std::set<vm::Cell::Hash> proof_seen_;
|
||||
Stat proof_stat_;
|
||||
const NewCellStorageStat* parent_{nullptr};
|
||||
|
||||
void dfs(Ref<Cell> cell, bool need_stat, bool need_proof_stat);
|
||||
};
|
||||
|
||||
struct CellStorageStat {
|
||||
unsigned long long cells;
|
||||
unsigned long long bits;
|
||||
unsigned long long public_cells;
|
||||
std::set<vm::Cell::Hash> seen;
|
||||
CellStorageStat() : cells(0), bits(0), public_cells(0) {
|
||||
}
|
||||
bool clear_seen() {
|
||||
seen.clear();
|
||||
return true;
|
||||
}
|
||||
void clear() {
|
||||
cells = bits = public_cells = 0;
|
||||
clear_seen();
|
||||
}
|
||||
bool compute_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool compute_used_storage(const CellSlice& cs, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool compute_used_storage(CellSlice&& cs, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool compute_used_storage(Ref<vm::Cell> cell, bool kill_dup = true, bool skip_count_root = false);
|
||||
|
||||
bool add_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool add_used_storage(const CellSlice& cs, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool add_used_storage(CellSlice&& cs, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool add_used_storage(Ref<vm::Cell> cell, bool kill_dup = true, bool skip_count_root = false);
|
||||
};
|
||||
|
||||
struct CellSerializationInfo {
|
||||
bool special;
|
||||
Cell::LevelMask level_mask;
|
||||
|
||||
bool with_hashes;
|
||||
size_t hashes_offset;
|
||||
size_t depth_offset;
|
||||
|
||||
size_t data_offset;
|
||||
size_t data_len;
|
||||
bool data_with_bits;
|
||||
|
||||
size_t refs_offset;
|
||||
int refs_cnt;
|
||||
|
||||
size_t end_offset;
|
||||
|
||||
td::Status init(td::Slice data, int ref_byte_size);
|
||||
td::Status init(td::uint8 d1, td::uint8 d2, int ref_byte_size);
|
||||
td::Result<int> get_bits(td::Slice cell) const;
|
||||
|
||||
td::Result<Ref<DataCell>> create_data_cell(td::Slice data, td::Span<Ref<Cell>> refs) const;
|
||||
};
|
||||
|
||||
class BagOfCells {
|
||||
public:
|
||||
enum { hash_bytes = vm::Cell::hash_bytes };
|
||||
enum Mode { WithIndex = 1, WithCRC32C = 2, WithTopHash = 4, WithIntHashes = 8, WithCacheBits = 16, max = 31 };
|
||||
enum { max_cell_whs = 64 };
|
||||
using Hash = Cell::Hash;
|
||||
struct Info {
|
||||
enum : td::uint32 { boc_idx = 0x68ff65f3, boc_idx_crc32c = 0xacc3a728, boc_generic = 0xb5ee9c72 };
|
||||
|
||||
unsigned magic;
|
||||
int root_count;
|
||||
int cell_count;
|
||||
int absent_count;
|
||||
int ref_byte_size;
|
||||
int offset_byte_size;
|
||||
bool valid;
|
||||
bool has_index;
|
||||
bool has_roots{false};
|
||||
bool has_crc32c;
|
||||
bool has_cache_bits;
|
||||
unsigned long long roots_offset, index_offset, data_offset, data_size, total_size;
|
||||
Info() : magic(0), valid(false) {
|
||||
}
|
||||
void invalidate() {
|
||||
valid = false;
|
||||
}
|
||||
long long parse_serialized_header(const td::Slice& slice);
|
||||
unsigned long long read_int(const unsigned char* ptr, unsigned bytes);
|
||||
unsigned long long read_ref(const unsigned char* ptr) {
|
||||
return read_int(ptr, ref_byte_size);
|
||||
}
|
||||
unsigned long long read_offset(const unsigned char* ptr) {
|
||||
return read_int(ptr, offset_byte_size);
|
||||
}
|
||||
void write_int(unsigned char* ptr, unsigned long long value, int bytes);
|
||||
void write_ref(unsigned char* ptr, unsigned long long value) {
|
||||
write_int(ptr, value, ref_byte_size);
|
||||
}
|
||||
void write_offset(unsigned char* ptr, unsigned long long value) {
|
||||
write_int(ptr, value, offset_byte_size);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
int cell_count{0}, root_count{0}, dangle_count{0}, int_refs{0};
|
||||
int int_hashes{0}, top_hashes{0};
|
||||
int max_depth{1024};
|
||||
Info info;
|
||||
unsigned long long data_bytes{0};
|
||||
unsigned char* store_ptr{nullptr};
|
||||
unsigned char* store_end{nullptr};
|
||||
td::HashMap<Hash, int> cells;
|
||||
struct CellInfo {
|
||||
Ref<DataCell> dc_ref;
|
||||
std::array<int, 4> ref_idx;
|
||||
unsigned char ref_num;
|
||||
unsigned char wt;
|
||||
unsigned char hcnt;
|
||||
int new_idx;
|
||||
bool should_cache{false};
|
||||
bool is_root_cell{false};
|
||||
CellInfo() : ref_num(0) {
|
||||
}
|
||||
CellInfo(Ref<DataCell> _dc) : dc_ref(std::move(_dc)), ref_num(0) {
|
||||
}
|
||||
CellInfo(Ref<DataCell> _dc, int _refs, const std::array<int, 4>& _ref_list)
|
||||
: dc_ref(std::move(_dc)), ref_idx(_ref_list), ref_num(static_cast<unsigned char>(_refs)) {
|
||||
}
|
||||
bool is_special() const {
|
||||
return !wt;
|
||||
}
|
||||
};
|
||||
std::vector<CellInfo> cell_list_;
|
||||
struct RootInfo {
|
||||
RootInfo() = default;
|
||||
RootInfo(Ref<Cell> cell, int idx) : cell(std::move(cell)), idx(idx) {
|
||||
}
|
||||
Ref<Cell> cell;
|
||||
int idx{-1};
|
||||
};
|
||||
std::vector<CellInfo> cell_list_tmp;
|
||||
std::vector<RootInfo> roots;
|
||||
std::vector<unsigned char> serialized;
|
||||
const unsigned char* index_ptr{nullptr};
|
||||
const unsigned char* data_ptr{nullptr};
|
||||
std::vector<unsigned long long> custom_index;
|
||||
|
||||
public:
|
||||
void clear();
|
||||
int set_roots(const std::vector<td::Ref<vm::Cell>>& new_roots);
|
||||
int set_root(td::Ref<vm::Cell> new_root);
|
||||
int add_roots(const std::vector<td::Ref<vm::Cell>>& add_roots);
|
||||
int add_root(td::Ref<vm::Cell> add_root);
|
||||
td::Status import_cells() TD_WARN_UNUSED_RESULT;
|
||||
BagOfCells() = default;
|
||||
std::size_t estimate_serialized_size(int mode = 0);
|
||||
BagOfCells& serialize(int mode = 0);
|
||||
std::string serialize_to_string(int mode = 0);
|
||||
td::Result<td::BufferSlice> serialize_to_slice(int mode = 0);
|
||||
std::size_t serialize_to(unsigned char* buffer, std::size_t buff_size, int mode = 0);
|
||||
std::string extract_string() const;
|
||||
|
||||
td::Result<long long> deserialize(const td::Slice& data);
|
||||
td::Result<long long> deserialize(const unsigned char* buffer, std::size_t buff_size) {
|
||||
return deserialize(td::Slice{buffer, buff_size});
|
||||
}
|
||||
int get_root_count() const {
|
||||
return root_count;
|
||||
}
|
||||
Ref<Cell> get_root_cell(int idx = 0) const {
|
||||
return (idx >= 0 && idx < root_count) ? roots.at(idx).cell : Ref<Cell>{};
|
||||
}
|
||||
|
||||
static int precompute_cell_serialization_size(const unsigned char* cell, std::size_t len, int ref_size,
|
||||
int* refs_num_ptr = nullptr);
|
||||
|
||||
private:
|
||||
int rv_idx;
|
||||
td::Result<int> import_cell(td::Ref<vm::Cell> cell, int depth);
|
||||
void cells_clear() {
|
||||
cell_count = 0;
|
||||
int_refs = 0;
|
||||
data_bytes = 0;
|
||||
cells.clear();
|
||||
cell_list_.clear();
|
||||
}
|
||||
td::uint64 compute_sizes(int mode, int& r_size, int& o_size);
|
||||
void init_store(unsigned char* from, unsigned char* to) {
|
||||
store_ptr = from;
|
||||
store_end = to;
|
||||
}
|
||||
void store_chk() const {
|
||||
DCHECK(store_ptr <= store_end);
|
||||
}
|
||||
bool store_empty() const {
|
||||
return store_ptr == store_end;
|
||||
}
|
||||
void store_uint(unsigned long long value, unsigned bytes);
|
||||
void store_ref(unsigned long long value) {
|
||||
store_uint(value, info.ref_byte_size);
|
||||
}
|
||||
void store_offset(unsigned long long value) {
|
||||
store_uint(value, info.offset_byte_size);
|
||||
}
|
||||
void reorder_cells();
|
||||
int revisit(int cell_idx, int force = 0);
|
||||
unsigned long long get_idx_entry_raw(int index);
|
||||
unsigned long long get_idx_entry(int index);
|
||||
bool get_cache_entry(int index);
|
||||
td::Result<td::Slice> get_cell_slice(int index, td::Slice data);
|
||||
td::Result<td::Ref<vm::DataCell>> deserialize_cell(int index, td::Slice data, td::Span<td::Ref<DataCell>> cells,
|
||||
std::vector<td::uint8>* cell_should_cache);
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
63
crypto/vm/box.hpp
Normal file
63
crypto/vm/box.hpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/stack.hpp"
|
||||
|
||||
namespace vm {
|
||||
using td::Ref;
|
||||
|
||||
class Box : public td::CntObject {
|
||||
mutable StackEntry data_;
|
||||
|
||||
public:
|
||||
Box() = default;
|
||||
Box(const Box&) = default;
|
||||
Box(Box&&) = default;
|
||||
template <typename... Args>
|
||||
Box(Args... args) : data_{std::move(args...)} {
|
||||
}
|
||||
~Box() override = default;
|
||||
Box(const StackEntry& data) : data_(data) {
|
||||
}
|
||||
Box(StackEntry&& data) : data_(std::move(data)) {
|
||||
}
|
||||
void operator=(const StackEntry& data) const {
|
||||
data_ = data;
|
||||
}
|
||||
void operator=(StackEntry&& data) const {
|
||||
data_ = data;
|
||||
}
|
||||
void set(const StackEntry& data) const {
|
||||
data_ = data;
|
||||
}
|
||||
void set(StackEntry&& data) const {
|
||||
data_ = data;
|
||||
}
|
||||
const StackEntry& get() const {
|
||||
return data_;
|
||||
}
|
||||
void clear() const {
|
||||
data_.clear();
|
||||
}
|
||||
bool empty() const {
|
||||
return data_.empty();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
1420
crypto/vm/cellops.cpp
Normal file
1420
crypto/vm/cellops.cpp
Normal file
File diff suppressed because it is too large
Load diff
31
crypto/vm/cellops.h
Normal file
31
crypto/vm/cellops.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cellslice.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
class OpcodeTable;
|
||||
|
||||
void register_cell_ops(OpcodeTable &cp0);
|
||||
|
||||
std::string dump_push_ref(CellSlice &cs, unsigned args, int pfx_bits, std::string name);
|
||||
int compute_len_push_ref(const CellSlice &cs, unsigned args, int pfx_bits);
|
||||
|
||||
} // namespace vm
|
||||
105
crypto/vm/cellparse.hpp
Normal file
105
crypto/vm/cellparse.hpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells.h"
|
||||
#include "vm/cellslice.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
class CellRefParser {
|
||||
Ref<CellSlice> cs;
|
||||
bool state;
|
||||
|
||||
public:
|
||||
CellRefParser(Ref<CellSlice> _cs) : cs(std::move(_cs)), state(cs.not_null()) {
|
||||
}
|
||||
bool ok() const {
|
||||
return state;
|
||||
}
|
||||
operator bool() const {
|
||||
return state;
|
||||
}
|
||||
template <class T>
|
||||
CellRefParser& deserialize(T& val) {
|
||||
state = (state && val.deserialize(cs.write()));
|
||||
return *this;
|
||||
}
|
||||
//template <class T>
|
||||
//CellRefParser& deserialize(T& val) {
|
||||
// state = val.deserialize_ext(cs.write(), state);
|
||||
// return *this;
|
||||
//}
|
||||
template <class T>
|
||||
CellRefParser& deserialize(const T& val) {
|
||||
state = (state && val.deserialize(cs.write()));
|
||||
return *this;
|
||||
}
|
||||
//template <class T>
|
||||
//CellRefParser& deserialize(const T& val) {
|
||||
// state = val.deserialize_ext(cs.write(), state);
|
||||
// return *this;
|
||||
//}
|
||||
};
|
||||
|
||||
class CellParser {
|
||||
CellSlice& cs;
|
||||
bool state;
|
||||
|
||||
public:
|
||||
CellParser(CellSlice& _cs) : cs(_cs), state(true) {
|
||||
}
|
||||
bool ok() const {
|
||||
return state;
|
||||
}
|
||||
operator bool() const {
|
||||
return state;
|
||||
}
|
||||
template <class T>
|
||||
CellParser& deserialize(T& val) {
|
||||
state = (state && val.deserialize(cs));
|
||||
return *this;
|
||||
}
|
||||
// template <class T>
|
||||
// CellParser& deserialize(T& val) {
|
||||
// state = val.deserialize_ext(cs, state);
|
||||
// return *this;
|
||||
// }
|
||||
template <class T>
|
||||
CellParser& deserialize(const T& val) {
|
||||
state = (state && val.deserialize(cs));
|
||||
return *this;
|
||||
}
|
||||
// template <class T>
|
||||
// CellParser& deserialize(const T& val) {
|
||||
// state = val.deserialize_ext(cs, state);
|
||||
// return *this;
|
||||
// }
|
||||
};
|
||||
|
||||
template <class P, class T>
|
||||
P& operator>>(P& cp, T& val) {
|
||||
return cp.deserialize(val);
|
||||
}
|
||||
|
||||
template <class P, class T>
|
||||
P& operator>>(P& cp, const T& val) {
|
||||
return cp.deserialize(val);
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
73
crypto/vm/cells.h
Normal file
73
crypto/vm/cells.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#include "common/refcnt.hpp"
|
||||
#include "common/bitstring.h"
|
||||
#include "common/bigint.hpp"
|
||||
#include "common/refint.h"
|
||||
|
||||
#include "vm/cells/Cell.h"
|
||||
#include "vm/cells/CellBuilder.h"
|
||||
#include "vm/cells/DataCell.h"
|
||||
#include "vm/cells/UsageCell.h"
|
||||
#include "vm/cells/VirtualCell.h"
|
||||
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
#include "openssl/digest.h"
|
||||
|
||||
// H_i(cell) = H(cell_i)
|
||||
// cell.hash = sha256(
|
||||
// d1, d2,
|
||||
// (level == 0 || special_type == PrunnedBranch) ? data : cell_(level - 1).hash,
|
||||
// for child in refs:
|
||||
// child.depth, child.hash
|
||||
// )
|
||||
// lower hashes of Prunned branch are calculated from its data
|
||||
// cell_i.ref[j] = (special_type == MerkleProof || special_type == MerkleUpdate) ? cell.ref[j]_(i+1) : cell.ref[j]_i
|
||||
//
|
||||
// Ordinary cell:
|
||||
// cell_i.data = cell.data
|
||||
// cell_i.level_mask = cell.level_mask & ((1<<i) - 1)
|
||||
// (cell_i.level_mask = == BinaryOr(cell_i.ref[j].level_mask))
|
||||
// cell_i.level = 32 - count_leading_zeroes32(cell_i.level_mask)
|
||||
// cell_i.depth = if cell_i.has_ref then max(cell_i.ref[j].depth) + 1 else 0
|
||||
// cell_i.ref[j] = cell.ref[j]
|
||||
//
|
||||
//
|
||||
// Prunned branch
|
||||
// cell.level_mask = prunned_cell.level_mask + (1 << (cell.level + 1))
|
||||
// cell.level = <default> == cell.level + 1
|
||||
// cell_i = if i < cell.level then prunned_cell_i
|
||||
// prunned_cell.data = EXCEPTION
|
||||
// prunned_cell_i.hash = <from cell.data>
|
||||
// prunned_cell.level_mask = cell.level_mask ^ (1 << cell.level)
|
||||
// prunned_cell.level = <default>
|
||||
// prunned_cell_i.depth = <from cell.data>
|
||||
//
|
||||
// Merkle proof
|
||||
// cell.level_mask = proof_cell.level_mask >> 1
|
||||
// cell.level = <default> == max(0, proof.cell.level - 1)
|
||||
// cell_i.data = <default>
|
||||
// cell_i.level_mask = <default>
|
||||
// cell_i.ref[j] = cell.ref[j]_(i+1)
|
||||
// cell_i.depth = max_j(1 + cell_i.ref[j].depth)
|
||||
|
||||
59
crypto/vm/cells/Cell.cpp
Normal file
59
crypto/vm/cells/Cell.cpp
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/Cell.h"
|
||||
#include "vm/cells/VirtualCell.h"
|
||||
#include "vm/cells/DataCell.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace vm {
|
||||
td::Status Cell::check_equals_unloaded(const Ref<Cell>& other) const {
|
||||
auto level_mask = get_level_mask();
|
||||
if (level_mask != other->get_level_mask()) {
|
||||
return td::Status::Error("level mismatch");
|
||||
}
|
||||
auto level = level_mask.get_level();
|
||||
for (unsigned i = 0; i <= level; i++) {
|
||||
if (!get_level_mask().is_significant(i)) {
|
||||
continue;
|
||||
}
|
||||
if (get_hash(i) != other->get_hash(i)) {
|
||||
return td::Status::Error("hash mismatch");
|
||||
}
|
||||
}
|
||||
for (unsigned i = 0; i <= level; i++) {
|
||||
if (!get_level_mask().is_significant(i)) {
|
||||
continue;
|
||||
}
|
||||
if (get_depth(i) != other->get_depth(i)) {
|
||||
return td::Status::Error("depth mismatch");
|
||||
}
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
Ref<Cell> Cell::virtualize(VirtualizationParameters virt) const {
|
||||
return VirtualCell::create(virt, Ref<Cell>(this));
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Cell& c) {
|
||||
return os << c.get_hash().to_hex();
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
89
crypto/vm/cells/Cell.h
Normal file
89
crypto/vm/cells/Cell.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "common/refcnt.hpp"
|
||||
#include "common/bitstring.h"
|
||||
|
||||
#include "vm/cells/CellHash.h"
|
||||
#include "vm/cells/CellTraits.h"
|
||||
#include "vm/cells/CellUsageTree.h"
|
||||
#include "vm/cells/LevelMask.h"
|
||||
#include "vm/cells/VirtualizationParameters.h"
|
||||
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace vm {
|
||||
using td::Ref;
|
||||
class DataCell;
|
||||
|
||||
class Cell : public CellTraits {
|
||||
public:
|
||||
using LevelMask = detail::LevelMask;
|
||||
using VirtualizationParameters = detail::VirtualizationParameters;
|
||||
struct LoadedCell {
|
||||
Ref<DataCell> data_cell;
|
||||
VirtualizationParameters virt;
|
||||
CellUsageTree::NodePtr tree_node; // TODO: inline_vector?
|
||||
};
|
||||
|
||||
using Hash = CellHash;
|
||||
static_assert(std::is_standard_layout<Hash>::value, "Cell::Hash is not a standard layout type");
|
||||
static_assert(sizeof(Hash) == hash_bytes, "Cell::Hash size is not equal to hash_bytes");
|
||||
//typedef td::BitArray<hash_bits> hash_t;
|
||||
|
||||
Cell* make_copy() const final {
|
||||
throw WriteError();
|
||||
}
|
||||
|
||||
// load interface
|
||||
virtual td::Result<LoadedCell> load_cell() const = 0;
|
||||
virtual Ref<Cell> virtualize(VirtualizationParameters virt) const;
|
||||
virtual td::uint32 get_virtualization() const = 0;
|
||||
virtual CellUsageTree::NodePtr get_tree_node() const = 0;
|
||||
virtual bool is_loaded() const = 0;
|
||||
|
||||
// hash and level
|
||||
virtual LevelMask get_level_mask() const = 0;
|
||||
|
||||
// level helper function
|
||||
td::uint32 get_level() const {
|
||||
return get_level_mask().get_level();
|
||||
}
|
||||
|
||||
// hash helper functions
|
||||
const Hash get_hash(int level = max_level) const {
|
||||
return do_get_hash(level);
|
||||
}
|
||||
|
||||
// depth helper function
|
||||
td::uint16 get_depth(int level = max_level) const {
|
||||
return do_get_depth(level);
|
||||
}
|
||||
|
||||
td::Status check_equals_unloaded(const Ref<Cell>& other) const;
|
||||
|
||||
private:
|
||||
virtual td::uint16 do_get_depth(td::uint32 level) const = 0;
|
||||
virtual const Hash do_get_hash(td::uint32 level) const = 0;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Cell& c);
|
||||
} // namespace vm
|
||||
593
crypto/vm/cells/CellBuilder.cpp
Normal file
593
crypto/vm/cells/CellBuilder.cpp
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/CellBuilder.h"
|
||||
|
||||
#include "vm/cells/CellSlice.h"
|
||||
#include "vm/cells/DataCell.h"
|
||||
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/format.h"
|
||||
|
||||
#include "openssl/digest.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
using td::Ref;
|
||||
using td::RefAny;
|
||||
|
||||
/*
|
||||
*
|
||||
* CELL BUILDERS
|
||||
*
|
||||
*/
|
||||
|
||||
CellBuilder::~CellBuilder() {
|
||||
get_thread_safe_counter().add(-1);
|
||||
}
|
||||
|
||||
CellBuilder::CellBuilder() : bits(0), refs_cnt(0) {
|
||||
get_thread_safe_counter().add(+1);
|
||||
}
|
||||
|
||||
Ref<DataCell> CellBuilder::finalize_copy(bool special) const {
|
||||
auto* vm_state_interface = VmStateInterface::get();
|
||||
if (vm_state_interface) {
|
||||
vm_state_interface->register_cell_create();
|
||||
}
|
||||
auto res = DataCell::create(data, size(), td::span(refs.data(), size_refs()), special);
|
||||
if (res.is_error()) {
|
||||
LOG(ERROR) << res.error();
|
||||
throw CellWriteError{};
|
||||
}
|
||||
CHECK(res.ok().not_null());
|
||||
return res.move_as_ok();
|
||||
}
|
||||
|
||||
Ref<DataCell> CellBuilder::finalize_novm(bool special) {
|
||||
auto res = DataCell::create(data, size(), td::mutable_span(refs.data(), size_refs()), special);
|
||||
bits = refs_cnt = 0;
|
||||
if (res.is_error()) {
|
||||
LOG(ERROR) << res.error();
|
||||
throw CellWriteError{};
|
||||
}
|
||||
CHECK(res.ok().not_null());
|
||||
return res.move_as_ok();
|
||||
}
|
||||
|
||||
Ref<DataCell> CellBuilder::finalize(bool special) {
|
||||
auto* vm_state_interface = VmStateInterface::get();
|
||||
if (vm_state_interface) {
|
||||
vm_state_interface->register_cell_create();
|
||||
}
|
||||
return finalize_novm(special);
|
||||
}
|
||||
|
||||
Ref<Cell> CellBuilder::create_pruned_branch(Ref<Cell> cell, td::uint32 new_level, td::uint32 virt_level) {
|
||||
if (cell->is_loaded() && cell->get_level() <= virt_level && cell->get_virtualization() == 0) {
|
||||
CellSlice cs(NoVm{}, cell);
|
||||
if (cs.size_refs() == 0) {
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
return do_create_pruned_branch(std::move(cell), new_level, virt_level);
|
||||
}
|
||||
Ref<DataCell> CellBuilder::do_create_pruned_branch(Ref<Cell> cell, td::uint32 new_level, td::uint32 virt_level) {
|
||||
auto level_mask = cell->get_level_mask().apply(virt_level);
|
||||
auto level = level_mask.get_level();
|
||||
if (new_level < level + 1) {
|
||||
throw CellWriteError();
|
||||
}
|
||||
CellBuilder cb;
|
||||
cb.store_long(static_cast<td::uint8>(Cell::SpecialType::PrunnedBranch), 8);
|
||||
cb.store_long(level_mask.apply_or(Cell::LevelMask::one_level(new_level)).get_mask(), 8);
|
||||
for (td::uint32 i = 0; i <= level; i++) {
|
||||
if (level_mask.is_significant(i)) {
|
||||
cb.store_bytes(cell->get_hash(i).as_slice());
|
||||
}
|
||||
}
|
||||
for (td::uint32 i = 0; i <= level; i++) {
|
||||
if (level_mask.is_significant(i)) {
|
||||
cb.store_long(cell->get_depth(i), 16);
|
||||
}
|
||||
}
|
||||
return cb.finalize(true);
|
||||
}
|
||||
|
||||
Ref<DataCell> CellBuilder::create_merkle_proof(Ref<Cell> cell_proof) {
|
||||
CellBuilder cb;
|
||||
cb.store_long(static_cast<td::uint8>(Cell::SpecialType::MerkleProof), 8);
|
||||
cb.store_bytes(cell_proof->get_hash(0).as_slice());
|
||||
cb.store_long(cell_proof->get_depth(0), Cell::depth_bytes * 8);
|
||||
cb.store_ref(cell_proof);
|
||||
return cb.finalize(true);
|
||||
}
|
||||
|
||||
Ref<DataCell> CellBuilder::create_merkle_update(Ref<Cell> from_proof, Ref<Cell> to_proof) {
|
||||
CellBuilder cb;
|
||||
cb.store_long(static_cast<td::uint8>(Cell::SpecialType::MerkleUpdate), 8);
|
||||
cb.store_bytes(from_proof->get_hash(0).as_slice());
|
||||
cb.store_bytes(to_proof->get_hash(0).as_slice());
|
||||
cb.store_long(from_proof->get_depth(0), Cell::depth_bytes * 8);
|
||||
cb.store_long(to_proof->get_depth(0), Cell::depth_bytes * 8);
|
||||
cb.store_ref(from_proof);
|
||||
cb.store_ref(to_proof);
|
||||
return cb.finalize(true);
|
||||
}
|
||||
|
||||
void CellBuilder::reset(void) {
|
||||
while (refs_cnt > 0) {
|
||||
refs[--refs_cnt].clear();
|
||||
}
|
||||
bits = 0;
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::operator=(const CellBuilder& other) {
|
||||
bits = other.bits;
|
||||
refs_cnt = other.refs_cnt;
|
||||
refs = other.refs;
|
||||
std::memcpy(data, other.data, (bits + 7) >> 3);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::operator=(CellBuilder&& other) {
|
||||
bits = other.bits;
|
||||
refs_cnt = other.refs_cnt;
|
||||
refs = std::move(other.refs);
|
||||
other.refs_cnt = 0;
|
||||
std::memcpy(data, other.data, (bits + 7) >> 3);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CellBuilder::can_extend_by(std::size_t new_bits, unsigned new_refs) const {
|
||||
return (new_bits <= Cell::max_bits - bits && new_refs <= (unsigned)Cell::max_refs - refs_cnt);
|
||||
}
|
||||
|
||||
bool CellBuilder::can_extend_by(std::size_t new_bits) const {
|
||||
return new_bits <= Cell::max_bits - bits;
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bytes(const unsigned char* str, std::size_t len) {
|
||||
ensure_throw(len <= Cell::max_bytes);
|
||||
return store_bits(str, len * 8);
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bytes(const unsigned char* str, const unsigned char* end) {
|
||||
ensure_throw(end >= str && end <= str + Cell::max_bytes);
|
||||
return store_bits(str, (end - str) * 8);
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bytes(const char* str, std::size_t len) {
|
||||
return store_bytes((const unsigned char*)(str), len);
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bytes(const char* str, const char* end) {
|
||||
return store_bytes((const unsigned char*)(str), (const unsigned char*)(end));
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bytes(td::Slice s) {
|
||||
return store_bytes((const unsigned char*)(s.data()), (const unsigned char*)(s.data() + s.size()));
|
||||
}
|
||||
|
||||
bool CellBuilder::store_bytes_bool(const unsigned char* str, std::size_t len) {
|
||||
return len <= Cell::max_bytes && store_bits_bool(str, len * 8);
|
||||
}
|
||||
|
||||
bool CellBuilder::store_bytes_bool(const char* str, std::size_t len) {
|
||||
return len <= Cell::max_bytes && store_bits_bool((const unsigned char*)str, len * 8);
|
||||
}
|
||||
|
||||
bool CellBuilder::store_bytes_bool(td::Slice s) {
|
||||
return store_bytes_bool((const unsigned char*)s.data(), s.size());
|
||||
}
|
||||
|
||||
bool CellBuilder::store_bits_bool(const unsigned char* str, std::size_t bit_count, int bit_offset) {
|
||||
unsigned pos = bits;
|
||||
if (!prepare_reserve(bit_count)) {
|
||||
return false;
|
||||
}
|
||||
td::bitstring::bits_memcpy(data, pos, str, bit_offset, bit_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bits(const unsigned char* str, std::size_t bit_count, int bit_offset) {
|
||||
unsigned pos = bits;
|
||||
if (prepare_reserve(bit_count)) {
|
||||
td::bitstring::bits_memcpy(data, pos, str, bit_offset, bit_count);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bits(const td::BitSlice& bs) {
|
||||
return store_bits(bs.get_ptr(), bs.size(), bs.get_offs());
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bits(const char* str, std::size_t bit_count, int bit_offset) {
|
||||
return store_bits((const unsigned char*)str, bit_count, bit_offset);
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bits(td::ConstBitPtr bs, std::size_t bit_count) {
|
||||
return store_bits(bs.ptr, bit_count, bs.offs);
|
||||
}
|
||||
|
||||
bool CellBuilder::store_bits_bool(td::ConstBitPtr bs, std::size_t bit_count) {
|
||||
return store_bits_bool(bs.ptr, bit_count, bs.offs);
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_bits_same(std::size_t bit_count, bool val) {
|
||||
unsigned pos = bits;
|
||||
if (prepare_reserve(bit_count)) {
|
||||
td::bitstring::bits_memset(data, pos, val, bit_count);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CellBuilder::store_bits_same_bool(std::size_t bit_count, bool val) {
|
||||
unsigned pos = bits;
|
||||
if (!prepare_reserve(bit_count)) {
|
||||
return false;
|
||||
}
|
||||
td::bitstring::bits_memset(data, pos, val, bit_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool CellBuilder::prepare_reserve(std::size_t bit_count) {
|
||||
if (!can_extend_by(bit_count)) {
|
||||
return false;
|
||||
} else {
|
||||
bits += (unsigned)bit_count;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
td::BitSliceWrite CellBuilder::reserve_slice(std::size_t bit_count) {
|
||||
unsigned offs = bits;
|
||||
if (prepare_reserve(bit_count)) {
|
||||
return td::BitSliceWrite{Ref<CellBuilder>{this}, data, offs, (unsigned)bit_count};
|
||||
} else {
|
||||
return td::BitSliceWrite{};
|
||||
}
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::reserve_slice(std::size_t bit_count, td::BitSliceWrite& bsw) {
|
||||
unsigned offs = bits;
|
||||
if (prepare_reserve(bit_count)) {
|
||||
bsw.assign(Ref<CellBuilder>{this}, data, offs, (unsigned)bit_count);
|
||||
} else {
|
||||
bsw.forget();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CellBuilder::store_bool_bool(bool val) {
|
||||
if (can_extend_by_fast(1)) {
|
||||
store_long(val, 1);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CellBuilder::store_long_bool(long long val, unsigned val_bits) {
|
||||
if (val_bits > 64 || !can_extend_by(val_bits)) {
|
||||
return false;
|
||||
}
|
||||
store_long(val, val_bits);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CellBuilder::store_long_rchk_bool(long long val, unsigned val_bits) {
|
||||
if (val_bits > 64 || !can_extend_by(val_bits)) {
|
||||
return false;
|
||||
}
|
||||
if (val_bits < 64 && (val < static_cast<long long>(std::numeric_limits<td::uint64>::max() << (val_bits - 1)) ||
|
||||
val >= (1LL << (val_bits - 1)))) {
|
||||
return false;
|
||||
}
|
||||
store_long(val, val_bits);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CellBuilder::store_ulong_rchk_bool(unsigned long long val, unsigned val_bits) {
|
||||
if (val_bits > 64 || !can_extend_by(val_bits)) {
|
||||
return false;
|
||||
}
|
||||
if (val_bits < 64 && val >= (1ULL << val_bits)) {
|
||||
return false;
|
||||
}
|
||||
store_long(val, val_bits);
|
||||
return true;
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_long(long long val, unsigned val_bits) {
|
||||
return store_long_top(val << (64 - val_bits), val_bits);
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_long_top(unsigned long long val, unsigned top_bits) {
|
||||
unsigned pos = bits;
|
||||
auto reserve_ok = prepare_reserve(top_bits);
|
||||
ensure_throw(reserve_ok);
|
||||
td::bitstring::bits_store_long_top(data, pos, val, top_bits);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CellBuilder::store_uint_less(unsigned upper_bound, unsigned long long val) {
|
||||
return val < upper_bound && store_long_bool(val, 32 - td::count_leading_zeroes32(upper_bound - 1));
|
||||
}
|
||||
|
||||
bool CellBuilder::store_uint_leq(unsigned upper_bound, unsigned long long val) {
|
||||
return val <= upper_bound && store_long_bool(val, 32 - td::count_leading_zeroes32(upper_bound));
|
||||
}
|
||||
|
||||
bool CellBuilder::store_int256_bool(const td::BigInt256& val, unsigned val_bits, bool sgnd) {
|
||||
unsigned pos = bits;
|
||||
if (!prepare_reserve(val_bits)) {
|
||||
return false;
|
||||
}
|
||||
if (val.export_bits(data, pos, val_bits, sgnd)) {
|
||||
return true;
|
||||
} else {
|
||||
bits = pos;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_int256(const td::BigInt256& val, unsigned val_bits, bool sgnd) {
|
||||
return ensure_pass(store_int256_bool(val, val_bits, sgnd));
|
||||
}
|
||||
|
||||
bool CellBuilder::store_int256_bool(td::RefInt256 val, unsigned val_bits, bool sgnd) {
|
||||
return val.not_null() && store_int256_bool(*val, val_bits, sgnd);
|
||||
}
|
||||
|
||||
bool CellBuilder::store_builder_ref_bool(vm::CellBuilder&& cb) {
|
||||
return store_ref_bool(cb.finalize());
|
||||
}
|
||||
|
||||
bool CellBuilder::store_ref_bool(Ref<Cell> ref) {
|
||||
if (refs_cnt < Cell::max_refs && ref.not_null()) {
|
||||
refs[refs_cnt++] = std::move(ref);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::store_ref(Ref<Cell> ref) {
|
||||
return ensure_pass(store_ref_bool(std::move(ref)));
|
||||
}
|
||||
|
||||
bool CellBuilder::append_data_cell_bool(const DataCell& cell) {
|
||||
unsigned len = cell.size();
|
||||
if (can_extend_by(len, cell.size_refs())) {
|
||||
unsigned pos = bits;
|
||||
ensure_throw(prepare_reserve(len));
|
||||
td::bitstring::bits_memcpy(data, pos, cell.get_data(), 0, len);
|
||||
for (unsigned i = 0; i < cell.size_refs(); i++) {
|
||||
refs[refs_cnt++] = cell.get_ref(i);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::append_data_cell(const DataCell& cell) {
|
||||
return ensure_pass(append_data_cell_bool(cell));
|
||||
}
|
||||
|
||||
bool CellBuilder::append_data_cell_bool(Ref<DataCell> cell_ref) {
|
||||
return append_data_cell_bool(*cell_ref);
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::append_data_cell(Ref<DataCell> cell_ref) {
|
||||
return ensure_pass(append_data_cell_bool(std::move(cell_ref)));
|
||||
}
|
||||
|
||||
bool CellBuilder::append_builder_bool(const CellBuilder& cb) {
|
||||
unsigned len = cb.size();
|
||||
if (can_extend_by(len, cb.size_refs())) {
|
||||
unsigned pos = bits;
|
||||
ensure_throw(prepare_reserve(len));
|
||||
td::bitstring::bits_memcpy(data, pos, cb.get_data(), 0, len);
|
||||
for (unsigned i = 0; i < cb.size_refs(); i++) {
|
||||
refs[refs_cnt++] = cb.get_ref(i);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::append_builder(const CellBuilder& cb) {
|
||||
return ensure_pass(append_builder_bool(cb));
|
||||
}
|
||||
|
||||
bool CellBuilder::append_builder_bool(Ref<CellBuilder> cb_ref) {
|
||||
return append_builder_bool(*cb_ref);
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::append_builder(Ref<CellBuilder> cb_ref) {
|
||||
return ensure_pass(append_builder_bool(std::move(cb_ref)));
|
||||
}
|
||||
|
||||
bool CellBuilder::append_cellslice_bool(const CellSlice& cs) {
|
||||
unsigned len = cs.size();
|
||||
if (can_extend_by(len, cs.size_refs())) {
|
||||
int pos = bits;
|
||||
ensure_throw(prepare_reserve(len));
|
||||
td::bitstring::bits_memcpy(td::BitPtr{data, pos}, cs.data_bits(), len);
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
refs[refs_cnt++] = cs.prefetch_ref(i);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::append_cellslice(const CellSlice& cs) {
|
||||
return ensure_pass(append_cellslice_bool(cs));
|
||||
}
|
||||
|
||||
bool CellBuilder::append_cellslice_bool(Ref<CellSlice> cs_ref) {
|
||||
return cs_ref.not_null() && append_cellslice_bool(*cs_ref);
|
||||
}
|
||||
|
||||
CellBuilder& CellBuilder::append_cellslice(Ref<CellSlice> cs) {
|
||||
return ensure_pass(append_cellslice_bool(cs));
|
||||
}
|
||||
|
||||
bool CellBuilder::append_cellslice_chk(const CellSlice& cs, unsigned size_ext) {
|
||||
return cs.size_ext() == size_ext && append_cellslice_bool(cs);
|
||||
}
|
||||
|
||||
bool CellBuilder::append_cellslice_chk(Ref<CellSlice> cs_ref, unsigned size_ext) {
|
||||
return cs_ref.not_null() && append_cellslice_chk(*cs_ref, size_ext);
|
||||
}
|
||||
|
||||
bool CellBuilder::append_bitstring(const td::BitString& bs) {
|
||||
return store_bits_bool(bs.cbits(), bs.size());
|
||||
}
|
||||
|
||||
bool CellBuilder::append_bitstring(Ref<td::BitString> bs_ref) {
|
||||
return bs_ref.not_null() && append_bitstring(*bs_ref);
|
||||
}
|
||||
|
||||
bool CellBuilder::append_bitstring_chk(const td::BitString& bs, unsigned size) {
|
||||
return bs.size() == size && store_bits_bool(bs.cbits(), size);
|
||||
}
|
||||
|
||||
bool CellBuilder::append_bitstring_chk(Ref<td::BitString> bs_ref, unsigned size) {
|
||||
return bs_ref.not_null() && append_bitstring_chk(*bs_ref, size);
|
||||
}
|
||||
|
||||
bool CellBuilder::append_bitslice(const td::BitSlice& bs) {
|
||||
return store_bits_bool(bs.bits(), bs.size());
|
||||
}
|
||||
|
||||
bool CellBuilder::store_maybe_ref(Ref<Cell> cell) {
|
||||
if (cell.is_null()) {
|
||||
return store_long_bool(0, 1);
|
||||
} else {
|
||||
return store_long_bool(1, 1) && store_ref_bool(std::move(cell));
|
||||
}
|
||||
}
|
||||
|
||||
void CellBuilder::flush(unsigned char d[2]) const {
|
||||
assert(refs_cnt <= Cell::max_refs && bits <= Cell::max_bits);
|
||||
|
||||
unsigned l = (bits >> 3);
|
||||
if (bits & 7) {
|
||||
int m = (0x80 >> (bits & 7));
|
||||
data[l] = static_cast<unsigned char>((data[l] & -m) | m);
|
||||
d[1] = static_cast<unsigned char>(2 * l + 1);
|
||||
} else {
|
||||
d[1] = static_cast<unsigned char>(2 * l);
|
||||
}
|
||||
d[0] = static_cast<unsigned char>(refs_cnt);
|
||||
}
|
||||
|
||||
const unsigned char* CellBuilder::compute_hash(unsigned char buffer[Cell::hash_bytes]) const {
|
||||
unsigned char tmp[2];
|
||||
flush(tmp);
|
||||
digest::SHA256 hasher(tmp, 2);
|
||||
hasher.feed(data, (bits + 7) >> 3);
|
||||
for (unsigned i = 0; i < refs_cnt; i++) {
|
||||
hasher.feed(refs[i]->get_hash().as_slice().data(), Cell::hash_bytes);
|
||||
}
|
||||
auto extracted_size = hasher.extract(buffer);
|
||||
DCHECK(extracted_size == Cell::hash_bytes);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int CellBuilder::serialize(unsigned char* buff, int buff_size) const {
|
||||
int len = get_serialized_size();
|
||||
if (len > buff_size) {
|
||||
return 0;
|
||||
}
|
||||
flush(buff);
|
||||
std::memcpy(buff + 2, data, len - 2);
|
||||
return len;
|
||||
}
|
||||
|
||||
CellBuilder* CellBuilder::make_copy() const {
|
||||
CellBuilder* c = new CellBuilder();
|
||||
if (!c) {
|
||||
throw CellWriteError();
|
||||
}
|
||||
c->bits = bits;
|
||||
std::memcpy(c->data, data, (bits + 7) >> 3);
|
||||
c->refs_cnt = refs_cnt;
|
||||
for (unsigned i = 0; i < refs_cnt; i++) {
|
||||
c->refs[i] = refs[i];
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
CellSlice CellBuilder::as_cellslice() const& {
|
||||
return CellSlice{finalize_copy()};
|
||||
}
|
||||
|
||||
Ref<CellSlice> CellBuilder::as_cellslice_ref() const& {
|
||||
return Ref<CellSlice>{true, finalize_copy()};
|
||||
}
|
||||
|
||||
CellSlice CellBuilder::as_cellslice() && {
|
||||
return CellSlice{finalize()};
|
||||
}
|
||||
|
||||
Ref<CellSlice> CellBuilder::as_cellslice_ref() && {
|
||||
return Ref<CellSlice>{true, finalize()};
|
||||
}
|
||||
|
||||
bool CellBuilder::contents_equal(const CellSlice& cs) const {
|
||||
if (size() != cs.size() || size_refs() != cs.size_refs()) {
|
||||
return false;
|
||||
}
|
||||
if (td::bitstring::bits_memcmp(data_bits(), cs.data_bits(), size())) {
|
||||
return false;
|
||||
}
|
||||
for (unsigned i = 0; i < size_refs(); i++) {
|
||||
if (refs[i]->get_hash() != cs.prefetch_ref(i)->get_hash()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string CellBuilder::serialize() const {
|
||||
unsigned char buff[Cell::max_serialized_bytes];
|
||||
int len = serialize(buff, sizeof(buff));
|
||||
return std::string(buff, buff + len);
|
||||
}
|
||||
|
||||
std::string CellBuilder::to_hex() const {
|
||||
unsigned char buff[Cell::max_serialized_bytes];
|
||||
int len = serialize(buff, sizeof(buff));
|
||||
char hex_buff[Cell::max_serialized_bytes * 2 + 1];
|
||||
for (int i = 0; i < len; i++) {
|
||||
sprintf(hex_buff + 2 * i, "%02x", buff[i]);
|
||||
}
|
||||
return hex_buff;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const CellBuilder& cb) {
|
||||
return os << cb.to_hex();
|
||||
}
|
||||
} // namespace vm
|
||||
238
crypto/vm/cells/CellBuilder.h
Normal file
238
crypto/vm/cells/CellBuilder.h
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells/DataCell.h"
|
||||
#include "vm/cells/VirtualCell.h"
|
||||
#include "vm/vmstate.h"
|
||||
#include "common/refint.h"
|
||||
|
||||
#include "td/utils/ThreadSafeCounter.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
class CellSlice;
|
||||
class DataCell;
|
||||
|
||||
class CellBuilder : public td::CntObject {
|
||||
public:
|
||||
struct CellWriteError {};
|
||||
struct CellCreateError {};
|
||||
|
||||
private:
|
||||
unsigned bits;
|
||||
unsigned refs_cnt;
|
||||
std::array<Ref<Cell>, Cell::max_refs> refs;
|
||||
mutable unsigned char data[Cell::max_bytes];
|
||||
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
|
||||
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("CellBuilder");
|
||||
return res;
|
||||
}
|
||||
|
||||
public:
|
||||
CellBuilder();
|
||||
virtual ~CellBuilder() override;
|
||||
|
||||
static Ref<Cell> create_pruned_branch(Ref<Cell> cell, td::uint32 new_level, td::uint32 virt_level = Cell::max_level);
|
||||
static Ref<DataCell> do_create_pruned_branch(Ref<Cell> cell, td::uint32 new_level,
|
||||
td::uint32 virt_level = Cell::max_level);
|
||||
static Ref<DataCell> create_merkle_proof(Ref<Cell> cell_proof);
|
||||
static Ref<DataCell> create_merkle_update(Ref<Cell> from_proof, Ref<Cell> to_proof);
|
||||
|
||||
unsigned get_refs_cnt() const {
|
||||
return refs_cnt;
|
||||
}
|
||||
unsigned get_bits() const {
|
||||
return bits;
|
||||
}
|
||||
unsigned size_refs() const {
|
||||
return refs_cnt;
|
||||
}
|
||||
unsigned size() const {
|
||||
return bits;
|
||||
}
|
||||
unsigned size_ext() const {
|
||||
return (refs_cnt << 16) + bits;
|
||||
}
|
||||
unsigned remaining_bits() const {
|
||||
return Cell::max_bits - bits;
|
||||
}
|
||||
unsigned remaining_refs() const {
|
||||
return Cell::max_refs - refs_cnt;
|
||||
}
|
||||
const unsigned char* get_data() const {
|
||||
return data;
|
||||
}
|
||||
td::ConstBitPtr data_bits() const {
|
||||
return data;
|
||||
}
|
||||
Ref<Cell> get_ref(unsigned idx) const {
|
||||
return idx < refs_cnt ? refs[idx] : Ref<Cell>{};
|
||||
}
|
||||
void reset();
|
||||
CellBuilder& operator=(const CellBuilder&);
|
||||
CellBuilder& operator=(CellBuilder&&);
|
||||
CellBuilder& store_bytes(const char* str, std::size_t len);
|
||||
CellBuilder& store_bytes(const char* str, const char* end);
|
||||
CellBuilder& store_bytes(const unsigned char* str, std::size_t len);
|
||||
CellBuilder& store_bytes(const unsigned char* str, const unsigned char* end);
|
||||
CellBuilder& store_bytes(td::Slice s);
|
||||
bool store_bytes_bool(const unsigned char* str, std::size_t len);
|
||||
bool store_bytes_bool(const char* str, std::size_t len);
|
||||
bool store_bytes_bool(td::Slice s);
|
||||
CellBuilder& store_bits(const unsigned char* str, std::size_t bit_count, int bit_offset = 0);
|
||||
CellBuilder& store_bits(const char* str, std::size_t bit_count, int bit_offset = 0);
|
||||
CellBuilder& store_bits(td::ConstBitPtr bs, std::size_t bit_count);
|
||||
CellBuilder& store_bits(const td::BitSlice& bs);
|
||||
CellBuilder& store_bits_same(std::size_t bit_count, bool val);
|
||||
bool store_bits_bool(const unsigned char* str, std::size_t bit_count, int bit_offset = 0);
|
||||
bool store_bits_bool(td::ConstBitPtr bs, std::size_t bit_count);
|
||||
template <unsigned n>
|
||||
bool store_bits_bool(const td::BitArray<n>& ba) {
|
||||
return store_bits_bool(ba.cbits(), n);
|
||||
}
|
||||
bool store_bits_same_bool(std::size_t bit_count, bool val);
|
||||
CellBuilder& store_zeroes(std::size_t bit_count) {
|
||||
return store_bits_same(bit_count, false);
|
||||
}
|
||||
CellBuilder& store_ones(std::size_t bit_count) {
|
||||
return store_bits_same(bit_count, true);
|
||||
}
|
||||
bool store_zeroes_bool(std::size_t bit_count) {
|
||||
return store_bits_same_bool(bit_count, false);
|
||||
}
|
||||
bool store_ones_bool(std::size_t bit_count) {
|
||||
return store_bits_same_bool(bit_count, true);
|
||||
}
|
||||
td::BitSliceWrite reserve_slice(std::size_t bit_count);
|
||||
CellBuilder& reserve_slice(std::size_t bit_count, td::BitSliceWrite& bsw);
|
||||
bool store_long_bool(long long val, unsigned val_bits = 64);
|
||||
bool store_long_rchk_bool(long long val, unsigned val_bits = 64);
|
||||
bool store_ulong_rchk_bool(unsigned long long val, unsigned val_bits = 64);
|
||||
bool store_uint_less(unsigned upper_bound, unsigned long long val);
|
||||
bool store_uint_leq(unsigned upper_bound, unsigned long long val);
|
||||
CellBuilder& store_long(long long val, unsigned val_bits = 64);
|
||||
// bool store_long_top_bool(unsigned long long val, unsigned top_bits);
|
||||
CellBuilder& store_long_top(unsigned long long val, unsigned top_bits);
|
||||
bool store_bool_bool(bool val);
|
||||
bool store_int256_bool(const td::BigInt256& val, unsigned val_bits, bool sgnd = true);
|
||||
bool store_int256_bool(td::RefInt256 val, unsigned val_bits, bool sgnd = true);
|
||||
bool store_uint256_bool(const td::BigInt256& val, unsigned val_bits) {
|
||||
return store_int256_bool(val, val_bits, false);
|
||||
}
|
||||
bool store_uint256_bool(td::RefInt256 val, unsigned val_bits) {
|
||||
return store_int256_bool(std::move(val), val_bits, false);
|
||||
}
|
||||
CellBuilder& store_int256(const td::BigInt256& val, unsigned val_bits, bool sgnd = true);
|
||||
CellBuilder& store_uint256(const td::BigInt256& val, unsigned val_bits) {
|
||||
return store_int256(val, val_bits, false);
|
||||
}
|
||||
bool store_builder_ref_bool(vm::CellBuilder&& cb);
|
||||
bool store_ref_bool(Ref<Cell> r);
|
||||
CellBuilder& store_ref(Ref<Cell> r);
|
||||
bool append_data_cell_bool(const DataCell& cell);
|
||||
CellBuilder& append_data_cell(const DataCell& cell);
|
||||
bool append_data_cell_bool(Ref<DataCell> cell_ref);
|
||||
CellBuilder& append_data_cell(Ref<DataCell> cell_ref);
|
||||
bool append_builder_bool(const CellBuilder& cb);
|
||||
CellBuilder& append_builder(const CellBuilder& cb);
|
||||
bool append_builder_bool(Ref<CellBuilder> cb_ref);
|
||||
CellBuilder& append_builder(Ref<CellBuilder> cb_ref);
|
||||
bool append_cellslice_bool(const CellSlice& cs);
|
||||
CellBuilder& append_cellslice(const CellSlice& cs);
|
||||
bool append_cellslice_bool(Ref<CellSlice> cs_ref);
|
||||
CellBuilder& append_cellslice(Ref<CellSlice> cs_ref);
|
||||
bool append_cellslice_chk(const CellSlice& cs, unsigned size_ext);
|
||||
bool append_cellslice_chk(Ref<CellSlice> cs, unsigned size_ext);
|
||||
bool append_bitstring(const td::BitString& bs);
|
||||
bool append_bitstring(Ref<td::BitString> bs_ref);
|
||||
bool append_bitstring_chk(const td::BitString& bs, unsigned size);
|
||||
bool append_bitstring_chk(Ref<td::BitString> bs, unsigned size);
|
||||
bool append_bitslice(const td::BitSlice& bs);
|
||||
bool store_maybe_ref(Ref<Cell> cell);
|
||||
bool contents_equal(const CellSlice& cs) const;
|
||||
CellBuilder* make_copy() const override;
|
||||
bool can_extend_by(std::size_t bits) const;
|
||||
bool can_extend_by(std::size_t bits, unsigned refs) const;
|
||||
Ref<DataCell> finalize_copy(bool special = false) const;
|
||||
Ref<DataCell> finalize(bool special = false);
|
||||
Ref<DataCell> finalize_novm(bool special = false);
|
||||
bool finalize_to(Ref<Cell>& res, bool special = false) {
|
||||
return (res = finalize(special)).not_null();
|
||||
}
|
||||
CellSlice as_cellslice() const &;
|
||||
CellSlice as_cellslice() &&;
|
||||
Ref<CellSlice> as_cellslice_ref() const &;
|
||||
Ref<CellSlice> as_cellslice_ref() &&;
|
||||
static td::int64 get_total_cell_builders() {
|
||||
return get_thread_safe_counter().sum();
|
||||
}
|
||||
int get_serialized_size() const {
|
||||
return ((bits + 23) >> 3);
|
||||
}
|
||||
int serialize(unsigned char* buff, int buff_size) const;
|
||||
std::string serialize() const;
|
||||
std::string to_hex() const;
|
||||
const unsigned char* compute_hash(unsigned char buffer[Cell::hash_bytes]) const;
|
||||
|
||||
const CellBuilder& ensure_pass(bool cond) const {
|
||||
ensure_throw(cond);
|
||||
return *this;
|
||||
}
|
||||
CellBuilder& ensure_pass(bool cond) {
|
||||
ensure_throw(cond);
|
||||
return *this;
|
||||
}
|
||||
void ensure_throw(bool cond) const {
|
||||
if (!cond) {
|
||||
throw CellCreateError{};
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void flush(unsigned char d[2]) const;
|
||||
bool prepare_reserve(std::size_t bit_count);
|
||||
bool can_extend_by_fast(unsigned bits_req) const {
|
||||
return (int)bits <= (int)(Cell::max_bits - bits_req);
|
||||
}
|
||||
bool can_extend_by_fast(unsigned bits_req, unsigned refs_req) const {
|
||||
return (int)bits <= (int)(Cell::max_bits - bits_req) && (int)refs_cnt <= (int)(Cell::max_refs - refs_req);
|
||||
}
|
||||
bool can_extend_by_fast2(unsigned bits_req) const {
|
||||
return bits + bits_req <= Cell::max_bits;
|
||||
}
|
||||
bool can_extend_by_fast2(unsigned bits_req, unsigned refs_req) const {
|
||||
return bits + bits_req <= Cell::max_bits && refs_cnt + refs_req <= Cell::max_refs;
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const CellBuilder& cb);
|
||||
|
||||
template <class T>
|
||||
CellBuilder& operator<<(CellBuilder& cb, const T& val) {
|
||||
return cb.ensure_pass(val.serialize(cb));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Ref<CellBuilder>& operator<<(Ref<CellBuilder>& cb_ref, const T& val) {
|
||||
bool res = val.serialize(cb_ref.write());
|
||||
cb_ref->ensure_throw(res);
|
||||
return cb_ref;
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
28
crypto/vm/cells/CellHash.cpp
Normal file
28
crypto/vm/cells/CellHash.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/CellHash.h"
|
||||
|
||||
#include "td/utils/StringBuilder.h"
|
||||
#include "td/utils/Slice.h"
|
||||
|
||||
namespace vm {
|
||||
td::StringBuilder &operator<<(td::StringBuilder &sb, const CellHash &hash) {
|
||||
return sb << hash.to_hex();
|
||||
}
|
||||
} // namespace vm
|
||||
92
crypto/vm/cells/CellHash.h
Normal file
92
crypto/vm/cells/CellHash.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells/CellTraits.h"
|
||||
#include "common/bitstring.h"
|
||||
|
||||
#include "td/utils/as.h"
|
||||
|
||||
#include <array>
|
||||
namespace td {
|
||||
class StringBuilder;
|
||||
}
|
||||
|
||||
namespace vm {
|
||||
struct CellHash {
|
||||
public:
|
||||
td::Slice as_slice() const {
|
||||
return td::Slice(hash_.data(), hash_.size());
|
||||
}
|
||||
td::MutableSlice as_slice() {
|
||||
return td::MutableSlice(hash_.data(), hash_.size());
|
||||
}
|
||||
bool operator==(const CellHash& other) const {
|
||||
return hash_ == other.hash_;
|
||||
}
|
||||
bool operator<(const CellHash& other) const {
|
||||
return hash_ < other.hash_;
|
||||
}
|
||||
bool operator!=(const CellHash& other) const {
|
||||
return hash_ != other.hash_;
|
||||
}
|
||||
std::string to_hex() const {
|
||||
return td::ConstBitPtr{hash_.data()}.to_hex(hash_.size() * 8);
|
||||
}
|
||||
friend td::StringBuilder& operator<<(td::StringBuilder& sb, const CellHash& hash);
|
||||
td::ConstBitPtr bits() const {
|
||||
return td::ConstBitPtr{hash_.data()};
|
||||
}
|
||||
td::BitPtr bits() {
|
||||
return td::BitPtr{hash_.data()};
|
||||
}
|
||||
td::BitSlice as_bitslice() const {
|
||||
return td::BitSlice{hash_.data(), (unsigned int)hash_.size() * 8};
|
||||
}
|
||||
const std::array<td::uint8, CellTraits::hash_bytes>& as_array() const {
|
||||
return hash_;
|
||||
}
|
||||
|
||||
static CellHash from_slice(td::Slice slice) {
|
||||
CellHash res;
|
||||
CHECK(slice.size() == res.hash_.size());
|
||||
td::MutableSlice(res.hash_.data(), res.hash_.size()).copy_from(slice);
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<td::uint8, CellTraits::hash_bytes> hash_;
|
||||
};
|
||||
} // namespace vm
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<vm::CellHash> {
|
||||
typedef vm::CellHash argument_type;
|
||||
typedef std::size_t result_type;
|
||||
result_type operator()(argument_type const& s) const noexcept {
|
||||
return td::as<size_t>(s.as_slice().ubegin());
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
namespace vm {
|
||||
template <class H>
|
||||
H AbslHashValue(H h, const CellHash& cell_hash) {
|
||||
return H::combine(std::move(h), std::hash<vm::CellHash>()(cell_hash));
|
||||
}
|
||||
} // namespace vm
|
||||
1066
crypto/vm/cells/CellSlice.cpp
Normal file
1066
crypto/vm/cells/CellSlice.cpp
Normal file
File diff suppressed because it is too large
Load diff
328
crypto/vm/cells/CellSlice.h
Normal file
328
crypto/vm/cells/CellSlice.h
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "common/refcnt.hpp"
|
||||
#include "common/refint.h"
|
||||
#include "vm/cells.h"
|
||||
|
||||
namespace td {
|
||||
class StringBuilder;
|
||||
}
|
||||
namespace vm {
|
||||
|
||||
struct NoVm {};
|
||||
struct NoVmOrd {};
|
||||
struct NoVmSpec {};
|
||||
|
||||
class CellSlice : public td::CntObject {
|
||||
Cell::VirtualizationParameters virt;
|
||||
Ref<DataCell> cell;
|
||||
CellUsageTree::NodePtr tree_node;
|
||||
unsigned bits_st, refs_st;
|
||||
unsigned bits_en, refs_en;
|
||||
mutable const unsigned char* ptr{nullptr};
|
||||
mutable unsigned long long z;
|
||||
mutable unsigned zd;
|
||||
|
||||
public:
|
||||
static constexpr long long fetch_long_eof = (static_cast<unsigned long long>(-1LL) << 63);
|
||||
static constexpr unsigned long long fetch_ulong_eof = (unsigned long long)-1LL;
|
||||
struct CellReadError {};
|
||||
|
||||
CellSlice(NoVm, Ref<Cell> cell_ref);
|
||||
CellSlice(NoVmOrd, Ref<Cell> cell_ref);
|
||||
CellSlice(NoVmSpec, Ref<Cell> cell_ref);
|
||||
CellSlice(Ref<DataCell> dc_ref);
|
||||
CellSlice(VirtualCell::LoadedCell loaded_cell);
|
||||
/*
|
||||
CellSlice(Ref<DataCell> dc_ref, unsigned _bits_en, unsigned _refs_en, unsigned _bits_st = 0, unsigned _refs_st = 0);*/
|
||||
CellSlice(const CellSlice& cs, unsigned _bits_en, unsigned _refs_en);
|
||||
CellSlice(const CellSlice& cs, unsigned _bits_en, unsigned _refs_en, unsigned _bits_st, unsigned _refs_st);
|
||||
CellSlice(const CellSlice&);
|
||||
CellSlice& operator=(const CellSlice& other) = default;
|
||||
CellSlice();
|
||||
Cell::LoadedCell move_as_loaded_cell();
|
||||
td::CntObject* make_copy() const override {
|
||||
return new CellSlice{*this};
|
||||
}
|
||||
void clear();
|
||||
bool load(VirtualCell::LoadedCell loaded_cell);
|
||||
bool load(NoVm, Ref<Cell> cell_ref);
|
||||
bool load(NoVmOrd, Ref<Cell> cell_ref);
|
||||
bool load(NoVmSpec, Ref<Cell> cell_ref);
|
||||
bool load(Ref<DataCell> dc_ref);
|
||||
bool load(Ref<Cell> cell);
|
||||
bool load_ord(Ref<Cell> cell);
|
||||
unsigned size() const {
|
||||
return bits_en - bits_st;
|
||||
}
|
||||
bool is_special() const {
|
||||
return cell->is_special();
|
||||
}
|
||||
bool is_valid() const {
|
||||
return cell.not_null();
|
||||
}
|
||||
Cell::SpecialType special_type() const {
|
||||
return cell->special_type();
|
||||
}
|
||||
int child_merkle_depth(int merkle_depth) const {
|
||||
if (merkle_depth == Cell::VirtualizationParameters::max_level()) {
|
||||
return merkle_depth;
|
||||
}
|
||||
if (cell->special_type() == Cell::SpecialType::MerkleProof ||
|
||||
cell->special_type() == Cell::SpecialType::MerkleUpdate) {
|
||||
merkle_depth++;
|
||||
}
|
||||
return merkle_depth;
|
||||
}
|
||||
unsigned size_refs() const {
|
||||
return refs_en - refs_st;
|
||||
}
|
||||
unsigned size_ext() const {
|
||||
return size() + (size_refs() << 16);
|
||||
}
|
||||
bool have(unsigned bits) const {
|
||||
return bits <= size();
|
||||
}
|
||||
bool have(unsigned bits, unsigned refs) const {
|
||||
return bits <= size() && refs <= size_refs();
|
||||
}
|
||||
bool have_ext(unsigned ext_size) const {
|
||||
return have(ext_size & 0xffff, ext_size >> 16);
|
||||
}
|
||||
bool empty() const {
|
||||
return !size();
|
||||
}
|
||||
bool empty_ext() const {
|
||||
return !size() && !size_refs();
|
||||
}
|
||||
bool have_refs(unsigned refs = 1) const {
|
||||
return refs <= size_refs();
|
||||
}
|
||||
bool advance(unsigned bits);
|
||||
bool advance_refs(unsigned refs);
|
||||
bool advance_ext(unsigned bits_refs);
|
||||
bool advance_ext(unsigned bits, unsigned refs);
|
||||
unsigned cur_pos() const {
|
||||
return bits_st;
|
||||
}
|
||||
unsigned cur_ref() const {
|
||||
return refs_st;
|
||||
}
|
||||
const unsigned char* data() const {
|
||||
return cell->get_data();
|
||||
}
|
||||
td::ConstBitPtr data_bits() const {
|
||||
return td::ConstBitPtr{data(), (int)cur_pos()};
|
||||
}
|
||||
int subtract_base_ext(const CellSlice& base) {
|
||||
return (bits_st - base.bits_st) | ((refs_st - base.refs_st) << 16);
|
||||
}
|
||||
unsigned get_cell_level() const;
|
||||
unsigned get_level() const;
|
||||
int fetch_octet();
|
||||
int prefetch_octet() const;
|
||||
unsigned long long prefetch_ulong_top(unsigned& bits) const;
|
||||
unsigned long long fetch_ulong(unsigned bits);
|
||||
unsigned long long prefetch_ulong(unsigned bits) const;
|
||||
long long fetch_long(unsigned bits);
|
||||
long long prefetch_long(unsigned bits) const;
|
||||
bool fetch_long_bool(unsigned bits, long long& res);
|
||||
bool prefetch_long_bool(unsigned bits, long long& res) const;
|
||||
bool fetch_ulong_bool(unsigned bits, unsigned long long& res);
|
||||
bool prefetch_ulong_bool(unsigned bits, unsigned long long& res) const;
|
||||
bool fetch_bool_to(bool& res);
|
||||
bool fetch_bool_to(int& res);
|
||||
bool fetch_bool_to(int& res, int mask);
|
||||
bool fetch_uint_to(unsigned bits, unsigned long long& res);
|
||||
bool fetch_uint_to(unsigned bits, long long& res);
|
||||
bool fetch_uint_to(unsigned bits, unsigned long& res);
|
||||
bool fetch_uint_to(unsigned bits, long& res);
|
||||
bool fetch_uint_to(unsigned bits, unsigned& res);
|
||||
bool fetch_uint_to(unsigned bits, int& res);
|
||||
bool fetch_uint_less(unsigned upper_bound, int& res);
|
||||
bool fetch_uint_less(unsigned upper_bound, unsigned& res);
|
||||
bool fetch_uint_leq(unsigned upper_bound, int& res);
|
||||
bool fetch_uint_leq(unsigned upper_bound, unsigned& res);
|
||||
bool fetch_int_to(unsigned bits, long long& res);
|
||||
bool fetch_int_to(unsigned bits, int& res);
|
||||
int bselect(unsigned bits, unsigned long long mask) const;
|
||||
int bselect_ext(unsigned bits, unsigned long long mask) const;
|
||||
int bit_at(unsigned i) const {
|
||||
return have(i) ? data_bits()[i] : -1;
|
||||
}
|
||||
td::RefInt256 fetch_int256(unsigned bits, bool sgnd = true);
|
||||
td::RefInt256 prefetch_int256(unsigned bits, bool sgnd = true) const;
|
||||
td::RefInt256 prefetch_int256_zeroext(unsigned bits, bool sgnd = true) const;
|
||||
bool fetch_int256_to(unsigned bits, td::RefInt256& res, bool sgnd = true) {
|
||||
return (res = fetch_int256(bits, sgnd)).not_null();
|
||||
}
|
||||
bool fetch_uint256_to(unsigned bits, td::RefInt256& res) {
|
||||
return (res = fetch_int256(bits, false)).not_null();
|
||||
}
|
||||
Ref<Cell> prefetch_ref(unsigned offset = 0) const;
|
||||
Ref<Cell> fetch_ref();
|
||||
bool fetch_ref_to(Ref<Cell>& ref) {
|
||||
return (ref = fetch_ref()).not_null();
|
||||
}
|
||||
bool prefetch_ref_to(Ref<Cell>& ref, unsigned offset = 0) const {
|
||||
return (ref = prefetch_ref(offset)).not_null();
|
||||
}
|
||||
td::BitSlice fetch_bits(unsigned bits);
|
||||
td::BitSlice prefetch_bits(unsigned bits) const;
|
||||
td::Ref<CellSlice> fetch_subslice(unsigned bits, unsigned refs = 0);
|
||||
td::Ref<CellSlice> prefetch_subslice(unsigned bits, unsigned refs = 0) const;
|
||||
td::Ref<CellSlice> fetch_subslice_ext(unsigned size);
|
||||
td::Ref<CellSlice> prefetch_subslice_ext(unsigned size) const;
|
||||
td::Ref<td::BitString> fetch_bitstring(unsigned size);
|
||||
td::Ref<td::BitString> prefetch_bitstring(unsigned size) const;
|
||||
bool fetch_subslice_to(unsigned bits, td::Ref<CellSlice>& res) {
|
||||
return (res = fetch_subslice(bits)).not_null();
|
||||
}
|
||||
bool fetch_subslice_ext_to(unsigned bits, td::Ref<CellSlice>& res) {
|
||||
return (res = fetch_subslice_ext(bits)).not_null();
|
||||
}
|
||||
bool fetch_bitstring_to(unsigned bits, td::Ref<td::BitString>& res) {
|
||||
return (res = fetch_bitstring(bits)).not_null();
|
||||
}
|
||||
bool fetch_bits_to(td::BitPtr buffer, unsigned bits);
|
||||
bool prefetch_bits_to(td::BitPtr buffer, unsigned bits) const;
|
||||
template <unsigned n>
|
||||
bool fetch_bits_to(td::BitArray<n>& buffer) {
|
||||
return fetch_bits_to(buffer.bits(), n);
|
||||
}
|
||||
template <unsigned n>
|
||||
bool prefetch_bits_to(td::BitArray<n>& buffer) const {
|
||||
return prefetch_bits_to(buffer.bits(), n);
|
||||
}
|
||||
bool fetch_bytes(unsigned char* buffer, unsigned bytes);
|
||||
bool prefetch_bytes(unsigned char* buffer, unsigned bytes) const;
|
||||
td::BitSlice as_bitslice() const {
|
||||
return prefetch_bits(size());
|
||||
}
|
||||
bool begins_with(unsigned bits, unsigned long long value) const;
|
||||
bool begins_with(unsigned long long value) const;
|
||||
bool begins_with_skip(unsigned bits, unsigned long long value) {
|
||||
return begins_with(bits, value) && advance(bits);
|
||||
}
|
||||
bool begins_with_skip(unsigned long long value);
|
||||
bool only_first(unsigned bits, unsigned refs = 0);
|
||||
bool only_ext(unsigned size);
|
||||
bool skip_first(unsigned bits, unsigned refs = 0);
|
||||
bool skip_ext(unsigned size);
|
||||
bool only_last(unsigned bits, unsigned refs = 0);
|
||||
bool skip_last(unsigned bits, unsigned refs = 0);
|
||||
bool cut_tail(const CellSlice& tail_cs);
|
||||
int remove_trailing();
|
||||
int count_leading(bool bit) const;
|
||||
int count_trailing(bool bit) const;
|
||||
int lex_cmp(const CellSlice& cs2) const;
|
||||
int common_prefix_len(const CellSlice& cs2) const;
|
||||
bool is_prefix_of(const CellSlice& cs2) const;
|
||||
bool is_prefix_of(td::ConstBitPtr bs, unsigned len) const;
|
||||
bool is_suffix_of(const CellSlice& cs2) const;
|
||||
bool has_prefix(const CellSlice& cs2) const;
|
||||
bool has_prefix(td::ConstBitPtr bs, unsigned len) const;
|
||||
bool has_suffix(const CellSlice& cs2) const;
|
||||
bool is_proper_prefix_of(const CellSlice& cs2) const;
|
||||
bool is_proper_suffix_of(const CellSlice& cs2) const;
|
||||
// int common_prefix_len(const td::BitSlice& bs, unsigned offs = 0, unsigned max_len = 0xffffffffU) const;
|
||||
int common_prefix_len(td::ConstBitPtr bs, unsigned len) const;
|
||||
// bool is_prefix_of(const td::BitSlice& bs, unsigned offs = 0, unsigned max_len = 0xffffffffU) const;
|
||||
bool contents_equal(const CellSlice& cs2) const;
|
||||
void dump(std::ostream& os, int level = 0, bool endl = true) const;
|
||||
void dump_hex(std::ostream& os, int mode = 0, bool endl = false) const;
|
||||
void print_rec(std::ostream& os, int indent = 0) const;
|
||||
void error() const {
|
||||
throw CellReadError{};
|
||||
}
|
||||
bool chk(bool cond) const {
|
||||
if (!cond) {
|
||||
error();
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
bool have_chk(unsigned bits) const {
|
||||
return chk(have(bits));
|
||||
}
|
||||
bool have_chk(unsigned bits, unsigned refs) const {
|
||||
return chk(have(bits, refs));
|
||||
}
|
||||
bool have_refs_chk(unsigned refs = 1) const {
|
||||
return chk(have_refs(refs));
|
||||
}
|
||||
CellSlice operator+(unsigned offs) const {
|
||||
offs = std::min(offs, size());
|
||||
return CellSlice{*this, size() - offs, size_refs(), offs, 0};
|
||||
}
|
||||
|
||||
private:
|
||||
void init_bits_refs();
|
||||
void init_preload() const;
|
||||
void preload_at_least(unsigned req_bits) const;
|
||||
Cell::VirtualizationParameters child_virt() const {
|
||||
return Cell::VirtualizationParameters(static_cast<td::uint8>(child_merkle_depth(virt.get_level())),
|
||||
virt.get_virtualization());
|
||||
}
|
||||
};
|
||||
|
||||
td::StringBuilder& operator<<(td::StringBuilder& sb, const CellSlice& cs);
|
||||
|
||||
bool cell_builder_add_slice_bool(CellBuilder& cb, const CellSlice& cs);
|
||||
CellBuilder& cell_builder_add_slice(CellBuilder& cb, const CellSlice& cs);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, CellSlice cs);
|
||||
std::ostream& operator<<(std::ostream& os, Ref<CellSlice> cs_ref);
|
||||
|
||||
template <class T>
|
||||
CellSlice& operator>>(CellSlice& cs, T& val) {
|
||||
cs.chk(val.deserialize(cs));
|
||||
return cs;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Ref<CellSlice>& operator>>(Ref<CellSlice>& cs_ref, T& val) {
|
||||
bool res = val.deserialize(cs_ref.write());
|
||||
cs_ref->chk(res);
|
||||
return cs_ref;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
CellSlice& operator>>(CellSlice& cs, const T& val) {
|
||||
cs.chk(val.deserialize(cs));
|
||||
return cs;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Ref<CellSlice>& operator>>(Ref<CellSlice>& cs_ref, const T& val) {
|
||||
bool res = val.deserialize(cs_ref.write());
|
||||
cs_ref->chk(res);
|
||||
return cs_ref;
|
||||
}
|
||||
|
||||
// If can_be_special is not null, then it is allowed to load special cell
|
||||
// Flag whether loaded cell is actually special will be stored into can_be_special
|
||||
CellSlice load_cell_slice(const Ref<Cell>& cell);
|
||||
Ref<CellSlice> load_cell_slice_ref(const Ref<Cell>& cell);
|
||||
CellSlice load_cell_slice_special(const Ref<Cell>& cell, bool& is_special);
|
||||
Ref<CellSlice> load_cell_slice_ref_special(const Ref<Cell>& cell, bool& is_special);
|
||||
void print_load_cell(std::ostream& os, Ref<Cell> cell, int indent = 0);
|
||||
|
||||
} // namespace vm
|
||||
45
crypto/vm/cells/CellTraits.cpp
Normal file
45
crypto/vm/cells/CellTraits.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/CellTraits.h"
|
||||
|
||||
#include "td/utils/StringBuilder.h"
|
||||
#include "td/utils/Slice.h"
|
||||
|
||||
namespace vm {
|
||||
td::StringBuilder& operator<<(td::StringBuilder& sb, CellTraits::SpecialType special_type) {
|
||||
switch (special_type) {
|
||||
case CellTraits::SpecialType::Ordinary:
|
||||
sb << "Ordinary";
|
||||
break;
|
||||
case CellTraits::SpecialType::MerkleProof:
|
||||
sb << "MerkleProof";
|
||||
break;
|
||||
case CellTraits::SpecialType::MerkleUpdate:
|
||||
sb << "MerkleUpdate";
|
||||
break;
|
||||
case CellTraits::SpecialType::PrunnedBranch:
|
||||
sb << "PrunnedBranch";
|
||||
break;
|
||||
case CellTraits::SpecialType::Library:
|
||||
sb << "Library";
|
||||
break;
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
} // namespace vm
|
||||
52
crypto/vm/cells/CellTraits.h
Normal file
52
crypto/vm/cells/CellTraits.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/utils/int_types.h"
|
||||
#include "common/refcnt.hpp"
|
||||
|
||||
namespace td {
|
||||
class StringBuilder;
|
||||
}
|
||||
|
||||
namespace vm {
|
||||
class CellTraits : public td::CntObject {
|
||||
public:
|
||||
enum class SpecialType : td::uint8 {
|
||||
Ordinary = 0,
|
||||
PrunnedBranch = 1,
|
||||
Library = 2,
|
||||
MerkleProof = 3,
|
||||
MerkleUpdate = 4
|
||||
};
|
||||
enum {
|
||||
max_refs = 4,
|
||||
max_bytes = 128,
|
||||
max_bits = 1023,
|
||||
hash_bytes = 32,
|
||||
hash_bits = hash_bytes * 8,
|
||||
depth_bytes = 2,
|
||||
depth_bits = depth_bytes * 8,
|
||||
max_level = 3,
|
||||
max_depth = 1024,
|
||||
max_virtualization = 7,
|
||||
max_serialized_bytes = 2 + max_bytes + (max_level + 1) * (hash_bytes + depth_bytes)
|
||||
};
|
||||
};
|
||||
td::StringBuilder& operator<<(td::StringBuilder& sb, CellTraits::SpecialType special_type);
|
||||
} // namespace vm
|
||||
136
crypto/vm/cells/CellUsageTree.cpp
Normal file
136
crypto/vm/cells/CellUsageTree.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/CellUsageTree.h"
|
||||
|
||||
namespace vm {
|
||||
//
|
||||
// CellUsageTree::NodePtr
|
||||
//
|
||||
bool CellUsageTree::NodePtr::on_load() const {
|
||||
auto tree = tree_weak_.lock();
|
||||
if (!tree) {
|
||||
return false;
|
||||
}
|
||||
tree->on_load(node_id_);
|
||||
return true;
|
||||
}
|
||||
|
||||
CellUsageTree::NodePtr CellUsageTree::NodePtr::create_child(unsigned ref_id) const {
|
||||
auto tree = tree_weak_.lock();
|
||||
if (!tree) {
|
||||
return {};
|
||||
}
|
||||
return {tree_weak_, tree->create_child(node_id_, ref_id)};
|
||||
}
|
||||
|
||||
bool CellUsageTree::NodePtr::is_from_tree(const CellUsageTree* master_tree) const {
|
||||
DCHECK(master_tree);
|
||||
auto tree = tree_weak_.lock();
|
||||
if (tree.get() != master_tree) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CellUsageTree::NodePtr::mark_path(CellUsageTree* master_tree) const {
|
||||
DCHECK(master_tree);
|
||||
auto tree = tree_weak_.lock();
|
||||
if (tree.get() != master_tree) {
|
||||
return false;
|
||||
}
|
||||
master_tree->mark_path(node_id_);
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// CellUsageTree
|
||||
//
|
||||
CellUsageTree::NodePtr CellUsageTree::root_ptr() {
|
||||
return {shared_from_this(), 1};
|
||||
}
|
||||
|
||||
CellUsageTree::NodeId CellUsageTree::root_id() const {
|
||||
return 1;
|
||||
};
|
||||
|
||||
bool CellUsageTree::is_loaded(NodeId node_id) const {
|
||||
if (use_mark_) {
|
||||
return nodes_[node_id].has_mark;
|
||||
}
|
||||
return nodes_[node_id].is_loaded;
|
||||
}
|
||||
|
||||
bool CellUsageTree::has_mark(NodeId node_id) const {
|
||||
return nodes_[node_id].has_mark;
|
||||
}
|
||||
|
||||
void CellUsageTree::set_mark(NodeId node_id, bool mark) {
|
||||
if (node_id == 0) {
|
||||
return;
|
||||
}
|
||||
nodes_[node_id].has_mark = mark;
|
||||
}
|
||||
|
||||
void CellUsageTree::mark_path(NodeId node_id) {
|
||||
auto cur_node_id = get_parent(node_id);
|
||||
while (cur_node_id != 0) {
|
||||
if (has_mark(cur_node_id)) {
|
||||
break;
|
||||
}
|
||||
set_mark(cur_node_id);
|
||||
cur_node_id = get_parent(cur_node_id);
|
||||
}
|
||||
}
|
||||
|
||||
CellUsageTree::NodeId CellUsageTree::get_parent(NodeId node_id) {
|
||||
return nodes_[node_id].parent;
|
||||
}
|
||||
|
||||
CellUsageTree::NodeId CellUsageTree::get_child(NodeId node_id, unsigned ref_id) {
|
||||
DCHECK(ref_id < CellTraits::max_refs);
|
||||
return nodes_[node_id].children[ref_id];
|
||||
}
|
||||
|
||||
void CellUsageTree::set_use_mark_for_is_loaded(bool use_mark) {
|
||||
use_mark_ = use_mark;
|
||||
}
|
||||
|
||||
void CellUsageTree::on_load(NodeId node_id) {
|
||||
nodes_[node_id].is_loaded = true;
|
||||
}
|
||||
|
||||
CellUsageTree::NodeId CellUsageTree::create_child(NodeId node_id, unsigned ref_id) {
|
||||
DCHECK(ref_id < CellTraits::max_refs);
|
||||
NodeId res = nodes_[node_id].children[ref_id];
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
res = create_node(node_id);
|
||||
nodes_[node_id].children[ref_id] = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
CellUsageTree::NodeId CellUsageTree::create_node(NodeId parent) {
|
||||
NodeId res = static_cast<NodeId>(nodes_.size());
|
||||
nodes_.emplace_back();
|
||||
nodes_.back().parent = parent;
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
75
crypto/vm/cells/CellUsageTree.h
Normal file
75
crypto/vm/cells/CellUsageTree.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "vm/cells/CellTraits.h"
|
||||
|
||||
#include "td/utils/int_types.h"
|
||||
#include "td/utils/logging.h"
|
||||
|
||||
namespace vm {
|
||||
class CellUsageTree : public std::enable_shared_from_this<CellUsageTree> {
|
||||
public:
|
||||
using NodeId = td::uint32;
|
||||
|
||||
struct NodePtr {
|
||||
public:
|
||||
NodePtr() = default;
|
||||
NodePtr(std::weak_ptr<CellUsageTree> tree_weak, NodeId node_id)
|
||||
: tree_weak_(std::move(tree_weak)), node_id_(node_id) {
|
||||
}
|
||||
bool empty() const {
|
||||
return node_id_ == 0 || tree_weak_.expired();
|
||||
}
|
||||
|
||||
bool on_load() const;
|
||||
NodePtr create_child(unsigned ref_id) const;
|
||||
bool mark_path(CellUsageTree* master_tree) const;
|
||||
bool is_from_tree(const CellUsageTree* master_tree) const;
|
||||
|
||||
private:
|
||||
std::weak_ptr<CellUsageTree> tree_weak_;
|
||||
NodeId node_id_{0};
|
||||
};
|
||||
|
||||
NodePtr root_ptr();
|
||||
NodeId root_id() const;
|
||||
bool is_loaded(NodeId node_id) const;
|
||||
bool has_mark(NodeId node_id) const;
|
||||
void set_mark(NodeId node_id, bool mark = true);
|
||||
void mark_path(NodeId node_id);
|
||||
NodeId get_parent(NodeId node_id);
|
||||
NodeId get_child(NodeId node_id, unsigned ref_id);
|
||||
void set_use_mark_for_is_loaded(bool use_mark = true);
|
||||
NodeId create_child(NodeId node_id, unsigned ref_id);
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
bool is_loaded{false};
|
||||
bool has_mark{false};
|
||||
NodeId parent{0};
|
||||
std::array<td::uint32, CellTraits::max_refs> children{};
|
||||
};
|
||||
bool use_mark_{false};
|
||||
std::vector<Node> nodes_{2};
|
||||
|
||||
void on_load(NodeId node_id);
|
||||
NodeId create_node(NodeId parent);
|
||||
};
|
||||
} // namespace vm
|
||||
92
crypto/vm/cells/CellWithStorage.h
Normal file
92
crypto/vm/cells/CellWithStorage.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace vm {
|
||||
namespace detail {
|
||||
template <class CellT, size_t Size = 0>
|
||||
class CellWithArrayStorage : public CellT {
|
||||
public:
|
||||
template <class... ArgsT>
|
||||
CellWithArrayStorage(ArgsT&&... args) : CellT(std::forward<ArgsT>(args)...) {
|
||||
}
|
||||
~CellWithArrayStorage() {
|
||||
CellT::destroy_storage(get_storage());
|
||||
}
|
||||
template <class... ArgsT>
|
||||
static std::unique_ptr<CellT> create(size_t storage_size, ArgsT&&... args) {
|
||||
static_assert(CellT::max_storage_size <= 40 * 8, "");
|
||||
//size = 128 + 32 + 8;
|
||||
auto size = (storage_size + 7) / 8;
|
||||
#define CASE(size) \
|
||||
case (size): \
|
||||
return std::make_unique<CellWithArrayStorage<CellT, (size)*8>>(std::forward<ArgsT>(args)...);
|
||||
#define CASE2(offset) CASE(offset) CASE(offset + 1)
|
||||
#define CASE8(offset) CASE2(offset) CASE2(offset + 2) CASE2(offset + 4) CASE2(offset + 6)
|
||||
#define CASE32(offset) CASE8(offset) CASE8(offset + 8) CASE8(offset + 16) CASE8(offset + 24)
|
||||
switch (size) { CASE32(0) CASE8(32) }
|
||||
#undef CASE
|
||||
#undef CASE2
|
||||
#undef CASE8
|
||||
#undef CASE32
|
||||
LOG(FATAL) << "TOO BIG " << storage_size;
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
private:
|
||||
alignas(alignof(void*)) char storage_[Size];
|
||||
|
||||
const char* get_storage() const final {
|
||||
return storage_;
|
||||
}
|
||||
char* get_storage() final {
|
||||
return storage_;
|
||||
}
|
||||
};
|
||||
|
||||
template <class CellT>
|
||||
class CellWithUniquePtrStorage : public CellT {
|
||||
public:
|
||||
template <class... ArgsT>
|
||||
CellWithUniquePtrStorage(size_t storage_size, ArgsT&&... args)
|
||||
: CellT(std::forward<ArgsT>(args)...), storage_(std::make_unique<char[]>(storage_size)) {
|
||||
}
|
||||
~CellWithUniquePtrStorage() {
|
||||
CellT::destroy_storage(get_storage());
|
||||
}
|
||||
|
||||
template <class... ArgsT>
|
||||
static std::unique_ptr<CellT> create(size_t storage_size, ArgsT&&... args) {
|
||||
return std::make_unique<CellWithUniquePtrStorage>(storage_size, std::forward<ArgsT>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<char[]> storage_;
|
||||
|
||||
const char* get_storage() const final {
|
||||
CHECK(storage_);
|
||||
return storage_.get();
|
||||
}
|
||||
char* get_storage() final {
|
||||
CHECK(storage_);
|
||||
return storage_.get();
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
} // namespace vm
|
||||
368
crypto/vm/cells/DataCell.cpp
Normal file
368
crypto/vm/cells/DataCell.cpp
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/DataCell.h"
|
||||
|
||||
#include "openssl/digest.h"
|
||||
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
|
||||
#include "vm/cells/CellWithStorage.h"
|
||||
|
||||
namespace vm {
|
||||
std::unique_ptr<DataCell> DataCell::create_empty_data_cell(Info info) {
|
||||
return detail::CellWithUniquePtrStorage<DataCell>::create(info.get_storage_size(), info);
|
||||
}
|
||||
|
||||
DataCell::DataCell(Info info) : info_(std::move(info)) {
|
||||
get_thread_safe_counter().add(1);
|
||||
}
|
||||
DataCell::~DataCell() {
|
||||
get_thread_safe_counter().add(-1);
|
||||
}
|
||||
|
||||
void DataCell::destroy_storage(char* storage) {
|
||||
auto* refs = info_.get_refs(storage);
|
||||
for (size_t i = 0; i < get_refs_cnt(); i++) {
|
||||
Ref<Cell>(refs[i], Ref<Cell>::acquire_t{}); // call destructor
|
||||
}
|
||||
}
|
||||
|
||||
td::Result<Ref<DataCell>> DataCell::create(td::ConstBitPtr data, unsigned bits, td::Span<Ref<Cell>> refs,
|
||||
bool special) {
|
||||
std::array<Ref<Cell>, max_refs> copied_refs;
|
||||
CHECK(refs.size() <= copied_refs.size());
|
||||
for (size_t i = 0; i < refs.size(); i++) {
|
||||
copied_refs[i] = refs[i];
|
||||
}
|
||||
return create(std::move(data), bits, td::MutableSpan<Ref<Cell>>(copied_refs.data(), refs.size()), special);
|
||||
}
|
||||
|
||||
DataCell::SpecialType DataCell::special_type() const {
|
||||
if (is_special()) {
|
||||
return static_cast<SpecialType>(td::bitstring::bits_load_ulong(get_data(), 8));
|
||||
}
|
||||
return SpecialType::Ordinary;
|
||||
}
|
||||
|
||||
td::Result<Ref<DataCell>> DataCell::create(td::ConstBitPtr data, unsigned bits, td::MutableSpan<Ref<Cell>> refs,
|
||||
bool special) {
|
||||
for (auto& ref : refs) {
|
||||
if (ref.is_null()) {
|
||||
return td::Status::Error("Has null cell reference");
|
||||
}
|
||||
}
|
||||
|
||||
SpecialType type = SpecialType::Ordinary;
|
||||
if (special) {
|
||||
if (bits < 8) {
|
||||
return td::Status::Error("Not enough data for a special cell");
|
||||
}
|
||||
type = static_cast<SpecialType>(td::bitstring::bits_load_ulong(data, 8));
|
||||
if (type == SpecialType::Ordinary) {
|
||||
return td::Status::Error("Special cell has Ordinary type");
|
||||
}
|
||||
}
|
||||
|
||||
LevelMask level_mask;
|
||||
td::uint32 virtualization = 0;
|
||||
switch (type) {
|
||||
case SpecialType::Ordinary: {
|
||||
for (auto& ref : refs) {
|
||||
level_mask = level_mask.apply_or(ref->get_level_mask());
|
||||
virtualization = td::max(virtualization, ref->get_virtualization());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SpecialType::PrunnedBranch: {
|
||||
if (refs.size() != 0) {
|
||||
return td::Status::Error("PrunnedBranch special cell has a cell reference");
|
||||
}
|
||||
if (bits < 16) {
|
||||
return td::Status::Error("Not enough data for a PrunnedBranch special cell");
|
||||
}
|
||||
level_mask = LevelMask((td::bitstring::bits_load_ulong(data + 8, 8)) & 0xff);
|
||||
auto level = level_mask.get_level();
|
||||
if (level > max_level || level == 0) {
|
||||
return td::Status::Error("Prunned Branch has an invalid level");
|
||||
}
|
||||
if (bits != (2 + level_mask.apply(level - 1).get_hashes_count() * (hash_bytes + depth_bytes)) * 8) {
|
||||
return td::Status::Error("Not enouch data for a PrunnedBranch special cell");
|
||||
}
|
||||
// depth will be checked later!
|
||||
break;
|
||||
}
|
||||
|
||||
case SpecialType::Library: {
|
||||
if (bits != 8 + hash_bytes * 8) {
|
||||
return td::Status::Error("Not enouch data for a Library special cell");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SpecialType::MerkleProof: {
|
||||
if (bits != 8 + (hash_bytes + depth_bytes) * 8) {
|
||||
return td::Status::Error("Not enouch data for a MerkleProof special cell");
|
||||
}
|
||||
if (refs.size() != 1) {
|
||||
return td::Status::Error("Wrong references count for a MerkleProof special cell");
|
||||
}
|
||||
if (td::bitstring::bits_memcmp(data + 8, refs[0]->get_hash(0).as_bitslice().get_ptr(), hash_bits) != 0) {
|
||||
return td::Status::Error("Hash mismatch in a MerkleProof special cell");
|
||||
}
|
||||
if (td::bitstring::bits_load_ulong(data + 8 + hash_bits, depth_bytes * 8) != refs[0]->get_depth(0)) {
|
||||
return td::Status::Error("Depth mismatch in a MerkleProof special cell");
|
||||
}
|
||||
level_mask = refs[0]->get_level_mask().shift_right();
|
||||
virtualization = refs[0]->get_virtualization();
|
||||
break;
|
||||
}
|
||||
|
||||
case SpecialType::MerkleUpdate: {
|
||||
if (bits != 8 + (hash_bytes + depth_bytes) * 8 * 2) {
|
||||
return td::Status::Error("Not enouch data for a MerkleUpdate special cell");
|
||||
}
|
||||
if (refs.size() != 2) {
|
||||
return td::Status::Error("Wrong references count for a MerkleUpdate special cell");
|
||||
}
|
||||
if (td::bitstring::bits_memcmp(data + 8, refs[0]->get_hash(0).as_bitslice().get_ptr(), hash_bits) != 0) {
|
||||
return td::Status::Error("First hash mismatch in a MerkleProof special cell");
|
||||
}
|
||||
if (td::bitstring::bits_memcmp(data + 8 + hash_bits, refs[1]->get_hash(0).as_bitslice().get_ptr(), hash_bits) !=
|
||||
0) {
|
||||
return td::Status::Error("Second hash mismatch in a MerkleProof special cell");
|
||||
}
|
||||
if (td::bitstring::bits_load_ulong(data + 8 + 2 * hash_bits, depth_bytes * 8) != refs[0]->get_depth(0)) {
|
||||
return td::Status::Error("First depth mismatch in a MerkleProof special cell");
|
||||
}
|
||||
if (td::bitstring::bits_load_ulong(data + 8 + 2 * hash_bits + depth_bytes * 8, depth_bytes * 8) !=
|
||||
refs[1]->get_depth(0)) {
|
||||
return td::Status::Error("Second depth mismatch in a MerkleProof special cell");
|
||||
}
|
||||
|
||||
level_mask = refs[0]->get_level_mask().apply_or(refs[1]->get_level_mask()).shift_right();
|
||||
virtualization = td::max(refs[0]->get_virtualization(), refs[1]->get_virtualization());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return td::Status::Error("Unknown special cell type");
|
||||
}
|
||||
|
||||
Info info;
|
||||
if (td::unlikely(bits > max_bits)) {
|
||||
return td::Status::Error("Too many bits");
|
||||
}
|
||||
if (td::unlikely(refs.size() > max_refs)) {
|
||||
return td::Status::Error("Too many cell references");
|
||||
}
|
||||
if (td::unlikely(virtualization > max_virtualization)) {
|
||||
return td::Status::Error("Too big virtualization");
|
||||
}
|
||||
|
||||
CHECK(level_mask.get_level() <= max_level);
|
||||
|
||||
auto hash_count = type == SpecialType::PrunnedBranch ? 1 : level_mask.get_hashes_count();
|
||||
DCHECK(hash_count <= max_level + 1);
|
||||
|
||||
info.bits_ = bits;
|
||||
info.refs_count_ = refs.size() & 7;
|
||||
info.is_special_ = special;
|
||||
info.level_mask_ = level_mask.get_mask() & 7;
|
||||
info.hash_count_ = hash_count & 7;
|
||||
info.virtualization_ = virtualization & 7;
|
||||
|
||||
auto data_cell = create_empty_data_cell(info);
|
||||
auto* storage = data_cell->get_storage();
|
||||
|
||||
// init data
|
||||
auto* data_ptr = info.get_data(storage);
|
||||
td::BitPtr{data_ptr}.copy_from(data, bits);
|
||||
// prepare for serialization
|
||||
if (bits & 7) {
|
||||
int m = (0x80 >> (bits & 7));
|
||||
unsigned l = bits / 8;
|
||||
data_ptr[l] = static_cast<unsigned char>((data_ptr[l] & -m) | m);
|
||||
}
|
||||
|
||||
// init refs
|
||||
auto refs_ptr = info.get_refs(storage);
|
||||
for (size_t i = 0; i < refs.size(); i++) {
|
||||
refs_ptr[i] = refs[i].release();
|
||||
}
|
||||
|
||||
// init hashes and depth
|
||||
auto* hashes_ptr = info.get_hashes(storage);
|
||||
auto* depth_ptr = info.get_depth(storage);
|
||||
|
||||
// NB: be careful with special cells
|
||||
auto total_hash_count = level_mask.get_hashes_count();
|
||||
auto hash_i_offset = total_hash_count - hash_count;
|
||||
for (td::uint32 level_i = 0, hash_i = 0, level = level_mask.get_level(); level_i <= level; level_i++) {
|
||||
if (!level_mask.is_significant(level_i)) {
|
||||
continue;
|
||||
}
|
||||
SCOPE_EXIT {
|
||||
hash_i++;
|
||||
};
|
||||
if (hash_i < hash_i_offset) {
|
||||
continue;
|
||||
}
|
||||
unsigned char tmp[2];
|
||||
tmp[0] = info.d1(level_mask.apply(level_i));
|
||||
tmp[1] = info.d2();
|
||||
|
||||
static TD_THREAD_LOCAL digest::SHA256* hasher;
|
||||
td::init_thread_local<digest::SHA256>(hasher);
|
||||
hasher->reset();
|
||||
|
||||
hasher->feed(td::Slice(tmp, 2));
|
||||
|
||||
if (hash_i == hash_i_offset) {
|
||||
DCHECK(level_i == 0 || type == SpecialType::PrunnedBranch);
|
||||
hasher->feed(td::Slice(data_ptr, (bits + 7) >> 3));
|
||||
} else {
|
||||
DCHECK(level_i != 0 && type != SpecialType::PrunnedBranch);
|
||||
hasher->feed(hashes_ptr[hash_i - hash_i_offset - 1].as_slice());
|
||||
}
|
||||
|
||||
auto dest_i = hash_i - hash_i_offset;
|
||||
|
||||
// calc depth
|
||||
td::uint16 depth = 0;
|
||||
for (int i = 0; i < info.refs_count_; i++) {
|
||||
td::uint16 child_depth = 0;
|
||||
if (type == SpecialType::MerkleProof || type == SpecialType::MerkleUpdate) {
|
||||
child_depth = refs_ptr[i]->get_depth(level_i + 1);
|
||||
} else {
|
||||
child_depth = refs_ptr[i]->get_depth(level_i);
|
||||
}
|
||||
|
||||
// add depth into hash
|
||||
td::uint8 child_depth_buf[depth_bytes];
|
||||
store_depth(child_depth_buf, child_depth);
|
||||
hasher->feed(td::Slice(child_depth_buf, depth_bytes));
|
||||
|
||||
depth = std::max(depth, child_depth);
|
||||
}
|
||||
if (info.refs_count_ != 0) {
|
||||
if (depth >= max_depth) {
|
||||
return td::Status::Error("Depth is too big");
|
||||
}
|
||||
depth++;
|
||||
}
|
||||
depth_ptr[dest_i] = depth;
|
||||
|
||||
// children hash
|
||||
for (int i = 0; i < info.refs_count_; i++) {
|
||||
if (type == SpecialType::MerkleProof || type == SpecialType::MerkleUpdate) {
|
||||
hasher->feed(refs_ptr[i]->get_hash(level_i + 1).as_slice());
|
||||
} else {
|
||||
hasher->feed(refs_ptr[i]->get_hash(level_i).as_slice());
|
||||
}
|
||||
}
|
||||
auto extracted_size = hasher->extract(hashes_ptr[dest_i].as_slice());
|
||||
DCHECK(extracted_size == hash_bytes);
|
||||
}
|
||||
|
||||
return Ref<DataCell>(data_cell.release(), Ref<DataCell>::acquire_t{});
|
||||
}
|
||||
|
||||
const DataCell::Hash DataCell::do_get_hash(td::uint32 level) const {
|
||||
auto hash_i = get_level_mask().apply(level).get_hash_i();
|
||||
if (special_type() == SpecialType::PrunnedBranch) {
|
||||
auto this_hash_i = get_level_mask().get_hash_i();
|
||||
if (hash_i != this_hash_i) {
|
||||
return reinterpret_cast<const Hash*>(info_.get_data(get_storage()) + 2)[hash_i];
|
||||
}
|
||||
hash_i = 0;
|
||||
}
|
||||
return info_.get_hashes(get_storage())[hash_i];
|
||||
}
|
||||
|
||||
td::uint16 DataCell::do_get_depth(td::uint32 level) const {
|
||||
auto hash_i = get_level_mask().apply(level).get_hash_i();
|
||||
if (special_type() == SpecialType::PrunnedBranch) {
|
||||
auto this_hash_i = get_level_mask().get_hash_i();
|
||||
if (hash_i != this_hash_i) {
|
||||
return load_depth(info_.get_data(get_storage()) + 2 + hash_bytes * this_hash_i + hash_i * depth_bytes);
|
||||
}
|
||||
hash_i = 0;
|
||||
}
|
||||
return info_.get_depth(get_storage())[hash_i];
|
||||
}
|
||||
|
||||
int DataCell::serialize(unsigned char* buff, int buff_size, bool with_hashes) const {
|
||||
int len = get_serialized_size(with_hashes);
|
||||
if (len > buff_size) {
|
||||
return 0;
|
||||
}
|
||||
buff[0] = static_cast<unsigned char>(info_.d1() | (with_hashes * 16));
|
||||
buff[1] = info_.d2();
|
||||
int hs = 0;
|
||||
if (with_hashes) {
|
||||
hs = (get_level_mask().get_hashes_count()) * (hash_bytes + depth_bytes);
|
||||
assert(len >= 2 + hs);
|
||||
std::memset(buff + 2, 0, hs);
|
||||
auto dest = td::MutableSlice(buff + 2, hs);
|
||||
auto level = get_level();
|
||||
// TODO: optimize for prunned brandh
|
||||
for (unsigned i = 0; i <= level; i++) {
|
||||
if (!get_level_mask().is_significant(i)) {
|
||||
continue;
|
||||
}
|
||||
dest.copy_from(get_hash(i).as_slice());
|
||||
dest.remove_prefix(hash_bytes);
|
||||
}
|
||||
for (unsigned i = 0; i <= level; i++) {
|
||||
if (!get_level_mask().is_significant(i)) {
|
||||
continue;
|
||||
}
|
||||
store_depth(dest.ubegin(), get_depth(i));
|
||||
dest.remove_prefix(depth_bytes);
|
||||
}
|
||||
// buff[2] = 0; // for testing hash verification in deserialization
|
||||
buff += hs;
|
||||
len -= hs;
|
||||
}
|
||||
std::memcpy(buff + 2, get_data(), len - 2);
|
||||
return len + hs;
|
||||
}
|
||||
|
||||
std::string DataCell::serialize() const {
|
||||
unsigned char buff[max_serialized_bytes];
|
||||
int len = serialize(buff, sizeof(buff));
|
||||
return std::string(buff, buff + len);
|
||||
}
|
||||
|
||||
std::string DataCell::to_hex() const {
|
||||
unsigned char buff[max_serialized_bytes];
|
||||
int len = serialize(buff, sizeof(buff));
|
||||
char hex_buff[max_serialized_bytes * 2 + 1];
|
||||
for (int i = 0; i < len; i++) {
|
||||
sprintf(hex_buff + 2 * i, "%02x", buff[i]);
|
||||
}
|
||||
return hex_buff;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const DataCell& c) {
|
||||
return os << c.to_hex();
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
212
crypto/vm/cells/DataCell.h
Normal file
212
crypto/vm/cells/DataCell.h
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells/Cell.h"
|
||||
|
||||
#include "td/utils/Span.h"
|
||||
|
||||
#include "td/utils/ThreadSafeCounter.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
class DataCell : public Cell {
|
||||
public:
|
||||
DataCell(const DataCell& other) = delete;
|
||||
~DataCell() override;
|
||||
|
||||
static void store_depth(td::uint8* dest, td::uint16 depth) {
|
||||
td::bitstring::bits_store_long(dest, depth, depth_bits);
|
||||
}
|
||||
static td::uint16 load_depth(const td::uint8* src) {
|
||||
return td::bitstring::bits_load_ulong(src, depth_bits) & 0xff;
|
||||
}
|
||||
|
||||
protected:
|
||||
struct Info {
|
||||
unsigned bits_;
|
||||
|
||||
// d1
|
||||
unsigned char refs_count_ : 3;
|
||||
bool is_special_ : 1;
|
||||
unsigned char level_mask_ : 3;
|
||||
|
||||
unsigned char hash_count_ : 3;
|
||||
|
||||
unsigned char virtualization_ : 3;
|
||||
|
||||
unsigned char d1() const {
|
||||
return d1(LevelMask{level_mask_});
|
||||
}
|
||||
unsigned char d1(LevelMask level_mask) const {
|
||||
// d1 = refs_count + 8 * is_special + 32 * level
|
||||
// + 16 * with_hashes - for seriazlization
|
||||
// d1 = 7 + 16 + 32 * l - for absent cells
|
||||
return static_cast<unsigned char>(refs_count_ + 8 * is_special_ + 32 * level_mask.get_mask());
|
||||
}
|
||||
unsigned char d2() const {
|
||||
auto res = static_cast<unsigned char>((bits_ / 8) * 2);
|
||||
if ((bits_ & 7) != 0) {
|
||||
return static_cast<unsigned char>(res + 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
size_t get_hashes_offset() const {
|
||||
return 0;
|
||||
}
|
||||
size_t get_refs_offset() const {
|
||||
return get_hashes_offset() + hash_bytes * hash_count_;
|
||||
}
|
||||
size_t get_depth_offset() const {
|
||||
return get_refs_offset() + refs_count_ * sizeof(Cell*);
|
||||
}
|
||||
size_t get_data_offset() const {
|
||||
return get_depth_offset() + sizeof(td::uint16) * hash_count_;
|
||||
}
|
||||
size_t get_storage_size() const {
|
||||
return get_data_offset() + (bits_ + 7) / 8;
|
||||
}
|
||||
|
||||
const Hash* get_hashes(const char* storage) const {
|
||||
return reinterpret_cast<const Hash*>(storage + get_hashes_offset());
|
||||
}
|
||||
|
||||
Hash* get_hashes(char* storage) const {
|
||||
return reinterpret_cast<Hash*>(storage + get_hashes_offset());
|
||||
}
|
||||
|
||||
const td::uint16* get_depth(const char* storage) const {
|
||||
return reinterpret_cast<const td::uint16*>(storage + get_depth_offset());
|
||||
}
|
||||
|
||||
td::uint16* get_depth(char* storage) const {
|
||||
return reinterpret_cast<td::uint16*>(storage + get_depth_offset());
|
||||
}
|
||||
|
||||
const unsigned char* get_data(const char* storage) const {
|
||||
return reinterpret_cast<const unsigned char*>(storage + get_data_offset());
|
||||
}
|
||||
unsigned char* get_data(char* storage) const {
|
||||
return reinterpret_cast<unsigned char*>(storage + get_data_offset());
|
||||
}
|
||||
|
||||
Cell* const* get_refs(const char* storage) const {
|
||||
return reinterpret_cast<Cell* const*>(storage + get_refs_offset());
|
||||
}
|
||||
Cell** get_refs(char* storage) const {
|
||||
return reinterpret_cast<Cell**>(storage + get_refs_offset());
|
||||
}
|
||||
};
|
||||
|
||||
Info info_;
|
||||
virtual char* get_storage() = 0;
|
||||
virtual const char* get_storage() const = 0;
|
||||
// TODO: we may also save three different pointers
|
||||
|
||||
void destroy_storage(char* storage);
|
||||
|
||||
explicit DataCell(Info info);
|
||||
Cell* get_ref_raw_ptr(unsigned idx) const {
|
||||
DCHECK(idx < get_refs_cnt());
|
||||
return info_.get_refs(get_storage())[idx];
|
||||
}
|
||||
|
||||
public:
|
||||
td::Result<LoadedCell> load_cell() const override {
|
||||
return LoadedCell{Ref<DataCell>{this}, {}, {}};
|
||||
}
|
||||
unsigned get_refs_cnt() const {
|
||||
return info_.refs_count_;
|
||||
}
|
||||
unsigned get_bits() const {
|
||||
return info_.bits_;
|
||||
}
|
||||
unsigned size_refs() const {
|
||||
return info_.refs_count_;
|
||||
}
|
||||
unsigned size() const {
|
||||
return info_.bits_;
|
||||
}
|
||||
const unsigned char* get_data() const {
|
||||
return info_.get_data(get_storage());
|
||||
}
|
||||
Ref<Cell> get_ref(unsigned idx) const {
|
||||
if (idx >= get_refs_cnt()) {
|
||||
return Ref<Cell>{};
|
||||
}
|
||||
return Ref<Cell>(get_ref_raw_ptr(idx));
|
||||
}
|
||||
|
||||
td::uint32 get_virtualization() const override {
|
||||
return info_.virtualization_;
|
||||
}
|
||||
CellUsageTree::NodePtr get_tree_node() const override {
|
||||
return {};
|
||||
}
|
||||
bool is_loaded() const override {
|
||||
return true;
|
||||
}
|
||||
LevelMask get_level_mask() const override {
|
||||
return LevelMask{info_.level_mask_};
|
||||
}
|
||||
|
||||
bool is_special() const {
|
||||
return info_.is_special_;
|
||||
}
|
||||
SpecialType special_type() const;
|
||||
int get_serialized_size(bool with_hashes = false) const {
|
||||
return ((get_bits() + 23) >> 3) +
|
||||
(with_hashes ? get_level_mask().get_hashes_count() * (hash_bytes + depth_bytes) : 0);
|
||||
}
|
||||
int serialize(unsigned char* buff, int buff_size, bool with_hashes = false) const;
|
||||
std::string serialize() const;
|
||||
std::string to_hex() const;
|
||||
static td::int64 get_total_data_cells() {
|
||||
return get_thread_safe_counter().sum();
|
||||
}
|
||||
|
||||
template <class StorerT>
|
||||
void store(StorerT& storer) const {
|
||||
storer.template store_binary<td::uint8>(info_.d1());
|
||||
storer.template store_binary<td::uint8>(info_.d2());
|
||||
storer.store_slice(td::Slice(get_data(), (get_bits() + 7) / 8));
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr auto max_storage_size = max_refs * sizeof(void*) + (max_level + 1) * hash_bytes + max_bytes;
|
||||
|
||||
private:
|
||||
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
|
||||
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("DataCell");
|
||||
return res;
|
||||
}
|
||||
static std::unique_ptr<DataCell> create_empty_data_cell(Info info);
|
||||
|
||||
const Hash do_get_hash(td::uint32 level) const override;
|
||||
td::uint16 do_get_depth(td::uint32 level) const override;
|
||||
|
||||
friend class CellBuilder;
|
||||
static td::Result<Ref<DataCell>> create(td::ConstBitPtr data, unsigned bits, td::Span<Ref<Cell>> refs, bool special);
|
||||
static td::Result<Ref<DataCell>> create(td::ConstBitPtr data, unsigned bits, td::MutableSpan<Ref<Cell>> refs,
|
||||
bool special);
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const DataCell& c);
|
||||
|
||||
} // namespace vm
|
||||
|
||||
140
crypto/vm/cells/ExtCell.h
Normal file
140
crypto/vm/cells/ExtCell.h
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells/Cell.h"
|
||||
#include "vm/cells/PrunnedCell.h"
|
||||
#include "common/AtomicRef.h"
|
||||
|
||||
#include <mutex>
|
||||
#include "td/utils/port/thread_local.h"
|
||||
#include "td/utils/HazardPointers.h"
|
||||
#include "td/utils/optional.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
template <class ExtraT, class Loader>
|
||||
class ExtCell : public Cell {
|
||||
private:
|
||||
struct PrivateTag {};
|
||||
|
||||
public:
|
||||
static td::Result<Ref<ExtCell<ExtraT, Loader>>> create(const PrunnedCellInfo& prunned_cell_info, ExtraT&& extra) {
|
||||
TRY_RESULT(prunned_cell, PrunnedCell<ExtraT>::create(prunned_cell_info, std::move(extra)));
|
||||
return Ref<ExtCell<ExtraT, Loader>>(true, std::move(prunned_cell), PrivateTag{});
|
||||
}
|
||||
|
||||
ExtCell(Ref<PrunnedCell<ExtraT>> prunned_cell, PrivateTag) : prunned_cell_(std::move(prunned_cell)) {
|
||||
get_thread_safe_counter().add(1);
|
||||
get_thread_safe_counter_unloaded().add(prunned_cell_.load_unsafe().not_null());
|
||||
}
|
||||
~ExtCell() {
|
||||
get_thread_safe_counter().add(-1);
|
||||
get_thread_safe_counter_unloaded().add(-static_cast<int>(prunned_cell_.load_unsafe().not_null()));
|
||||
}
|
||||
|
||||
LevelMask get_level_mask() const override {
|
||||
return CellView(this)->get_level_mask();
|
||||
}
|
||||
|
||||
td::Result<LoadedCell> load_cell() const override {
|
||||
TRY_RESULT(data_cell, load_data_cell());
|
||||
return LoadedCell{std::move(data_cell), {}, {}};
|
||||
}
|
||||
td::uint32 get_virtualization() const override {
|
||||
return 0;
|
||||
}
|
||||
CellUsageTree::NodePtr get_tree_node() const override {
|
||||
return {};
|
||||
}
|
||||
bool is_loaded() const override {
|
||||
return CellView(this)->is_loaded();
|
||||
}
|
||||
|
||||
private:
|
||||
mutable td::AtomicRef<DataCell> data_cell_;
|
||||
mutable td::AtomicRef<PrunnedCell<ExtraT>> prunned_cell_;
|
||||
|
||||
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
|
||||
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("ExtCell");
|
||||
return res;
|
||||
}
|
||||
|
||||
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter_unloaded() {
|
||||
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("ExtCell.unloaded");
|
||||
return res;
|
||||
}
|
||||
|
||||
struct CellView {
|
||||
CellView(const ExtCell<ExtraT, Loader>* cell) {
|
||||
cell_ = cell->data_cell_.get_unsafe();
|
||||
if (cell_) {
|
||||
return;
|
||||
}
|
||||
|
||||
prunned_cell_ = cell->prunned_cell_.load();
|
||||
if (!prunned_cell_.is_null()) {
|
||||
cell_ = &*prunned_cell_;
|
||||
return;
|
||||
}
|
||||
cell_ = cell->data_cell_.get_unsafe();
|
||||
DCHECK(cell_);
|
||||
}
|
||||
|
||||
const Cell* operator->() const {
|
||||
return cell_;
|
||||
}
|
||||
|
||||
td::Ref<PrunnedCell<ExtraT>> prunned_cell_;
|
||||
const Cell* cell_;
|
||||
};
|
||||
|
||||
const Hash do_get_hash(td::uint32 level) const override {
|
||||
return CellView(this)->get_hash(level);
|
||||
}
|
||||
|
||||
td::uint16 do_get_depth(td::uint32 level) const override {
|
||||
return CellView(this)->get_depth(level);
|
||||
}
|
||||
|
||||
td::Result<Ref<DataCell>> load_data_cell() const {
|
||||
auto data_cell = data_cell_.get_unsafe();
|
||||
if (data_cell) {
|
||||
return Ref<DataCell>(data_cell);
|
||||
}
|
||||
|
||||
auto prunned_cell = prunned_cell_.load();
|
||||
|
||||
if (prunned_cell.is_null()) {
|
||||
data_cell = data_cell_.get_unsafe();
|
||||
DCHECK(data_cell);
|
||||
return Ref<DataCell>(data_cell);
|
||||
}
|
||||
|
||||
TRY_RESULT(new_data_cell, Loader::load_data_cell(*this, prunned_cell->get_extra()));
|
||||
TRY_STATUS(prunned_cell->check_equals_unloaded(new_data_cell));
|
||||
|
||||
if (data_cell_.store_if_empty(new_data_cell)) {
|
||||
prunned_cell_.store({});
|
||||
get_thread_safe_counter_unloaded().add(-1);
|
||||
}
|
||||
|
||||
return data_cell_.load_unsafe();
|
||||
}
|
||||
};
|
||||
} // namespace vm
|
||||
32
crypto/vm/cells/LevelMask.cpp
Normal file
32
crypto/vm/cells/LevelMask.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "LevelMask.h"
|
||||
|
||||
namespace vm {
|
||||
namespace detail {
|
||||
td::StringBuilder& operator<<(td::StringBuilder& sb, LevelMask level_mask) {
|
||||
sb << "LevelMask{";
|
||||
for (int i = 0, level = level_mask.get_level(); i < level; i++) {
|
||||
sb << "01"[(level_mask.get_mask() >> i) % 2];
|
||||
}
|
||||
sb << "}";
|
||||
return sb;
|
||||
}
|
||||
} // namespace detail
|
||||
} // namespace vm
|
||||
84
crypto/vm/cells/LevelMask.h
Normal file
84
crypto/vm/cells/LevelMask.h
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/bits.h"
|
||||
#include "td/utils/int_types.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
|
||||
namespace td {
|
||||
class StringBuilder;
|
||||
}
|
||||
|
||||
namespace vm {
|
||||
namespace detail {
|
||||
class LevelMask {
|
||||
public:
|
||||
explicit LevelMask(td::uint32 new_mask = 0) : mask_(new_mask) {
|
||||
}
|
||||
td::uint32 get_mask() const {
|
||||
return mask_;
|
||||
}
|
||||
td::uint32 get_level() const {
|
||||
return 32 - td::count_leading_zeroes32(mask_);
|
||||
}
|
||||
td::uint32 get_hash_i() const {
|
||||
return td::count_bits32(mask_);
|
||||
}
|
||||
td::uint32 get_hashes_count() const {
|
||||
return get_hash_i() + 1;
|
||||
}
|
||||
LevelMask apply(td::uint32 level) const {
|
||||
DCHECK(level < 32);
|
||||
return LevelMask{mask_ & ((1u << level) - 1)};
|
||||
}
|
||||
LevelMask apply_or(LevelMask other) const {
|
||||
return LevelMask{mask_ | other.mask_};
|
||||
}
|
||||
LevelMask shift_right() const {
|
||||
return LevelMask{mask_ >> 1};
|
||||
}
|
||||
bool is_significant(td::uint32 level) const {
|
||||
DCHECK(level < 32);
|
||||
bool res = level == 0 || ((mask_ >> (level - 1)) % 2 != 0);
|
||||
CHECK(res == (apply(level).get_level() == level));
|
||||
return res;
|
||||
}
|
||||
|
||||
bool operator==(const LevelMask& other) const {
|
||||
return mask_ == other.mask_;
|
||||
}
|
||||
bool operator!=(const LevelMask& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
static LevelMask one_level(td::uint32 level) {
|
||||
DCHECK(level < 32);
|
||||
if (level == 0) {
|
||||
return LevelMask(0);
|
||||
}
|
||||
return LevelMask(1 << (level - 1));
|
||||
}
|
||||
|
||||
private:
|
||||
td::uint32 mask_;
|
||||
};
|
||||
td::StringBuilder& operator<<(td::StringBuilder& sb, LevelMask level_mask);
|
||||
} // namespace detail
|
||||
} // namespace vm
|
||||
263
crypto/vm/cells/MerkleProof.cpp
Normal file
263
crypto/vm/cells/MerkleProof.cpp
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/MerkleProof.h"
|
||||
#include "vm/cells/CellBuilder.h"
|
||||
#include "vm/cells/CellSlice.h"
|
||||
|
||||
#include "td/utils/HashMap.h"
|
||||
#include "td/utils/HashSet.h"
|
||||
|
||||
namespace vm {
|
||||
namespace detail {
|
||||
class MerkleProofImpl {
|
||||
public:
|
||||
explicit MerkleProofImpl(MerkleProof::IsPrunnedFunction is_prunned) : is_prunned_(std::move(is_prunned)) {
|
||||
}
|
||||
explicit MerkleProofImpl(CellUsageTree *usage_tree) : usage_tree_(usage_tree) {
|
||||
}
|
||||
|
||||
Ref<Cell> create_from(Ref<Cell> cell) {
|
||||
if (!is_prunned_) {
|
||||
CHECK(usage_tree_);
|
||||
dfs_usage_tree(cell, usage_tree_->root_id());
|
||||
is_prunned_ = [this](const Ref<Cell> &cell) { return visited_cells_.count(cell->get_hash()) == 0; };
|
||||
}
|
||||
return dfs(cell, cell->get_level());
|
||||
}
|
||||
|
||||
private:
|
||||
using Key = std::pair<Cell::Hash, int>;
|
||||
td::HashMap<Key, Ref<Cell>> cells_;
|
||||
td::HashSet<Cell::Hash> visited_cells_;
|
||||
CellUsageTree *usage_tree_{nullptr};
|
||||
MerkleProof::IsPrunnedFunction is_prunned_;
|
||||
|
||||
void dfs_usage_tree(Ref<Cell> cell, CellUsageTree::NodeId node_id) {
|
||||
if (!usage_tree_->is_loaded(node_id)) {
|
||||
return;
|
||||
}
|
||||
visited_cells_.insert(cell->get_hash());
|
||||
CellSlice cs(NoVm(), cell);
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
dfs_usage_tree(cs.prefetch_ref(i), usage_tree_->get_child(node_id, i));
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Cell> dfs(Ref<Cell> cell, int merkle_depth) {
|
||||
CHECK(cell.not_null());
|
||||
Key key{cell->get_hash(), merkle_depth};
|
||||
{
|
||||
auto it = cells_.find(key);
|
||||
if (it != cells_.end()) {
|
||||
CHECK(it->second.not_null());
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_prunned_(cell)) {
|
||||
auto res = CellBuilder::create_pruned_branch(cell, merkle_depth + 1);
|
||||
CHECK(res.not_null());
|
||||
cells_.emplace(key, res);
|
||||
return res;
|
||||
}
|
||||
CellSlice cs(NoVm(), cell);
|
||||
int children_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
CellBuilder cb;
|
||||
cb.store_bits(cs.fetch_bits(cs.size()));
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
cb.store_ref(dfs(cs.prefetch_ref(i), children_merkle_depth));
|
||||
}
|
||||
auto res = cb.finalize(cs.is_special());
|
||||
CHECK(res.not_null());
|
||||
cells_.emplace(key, res);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
Ref<Cell> MerkleProof::generate_raw(Ref<Cell> cell, IsPrunnedFunction is_prunned) {
|
||||
return detail::MerkleProofImpl(is_prunned).create_from(cell);
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleProof::generate_raw(Ref<Cell> cell, CellUsageTree *usage_tree) {
|
||||
return detail::MerkleProofImpl(usage_tree).create_from(cell);
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleProof::virtualize_raw(Ref<Cell> cell, Cell::VirtualizationParameters virt) {
|
||||
return cell->virtualize(virt);
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleProof::generate(Ref<Cell> cell, IsPrunnedFunction is_prunned) {
|
||||
int cell_level = cell->get_level();
|
||||
if (cell_level != 0) {
|
||||
return {};
|
||||
}
|
||||
auto raw = generate_raw(std::move(cell), is_prunned);
|
||||
return CellBuilder::create_merkle_proof(std::move(raw));
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleProof::generate(Ref<Cell> cell, CellUsageTree *usage_tree) {
|
||||
int cell_level = cell->get_level();
|
||||
if (cell_level != 0) {
|
||||
return {};
|
||||
}
|
||||
auto raw = generate_raw(std::move(cell), usage_tree);
|
||||
return CellBuilder::create_merkle_proof(std::move(raw));
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> unpack_proof(Ref<Cell> cell) {
|
||||
CHECK(cell.not_null());
|
||||
td::uint8 level = static_cast<td::uint8>(cell->get_level());
|
||||
if (level != 0) {
|
||||
return td::Status::Error("Level of MerkleProof must be zero");
|
||||
}
|
||||
CellSlice cs(NoVm(), std::move(cell));
|
||||
if (cs.special_type() != Cell::SpecialType::MerkleProof) {
|
||||
return td::Status::Error("Not a MekleProof cell");
|
||||
}
|
||||
return cs.fetch_ref();
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleProof::virtualize(Ref<Cell> cell, int virtualization) {
|
||||
auto r_raw = unpack_proof(std::move(cell));
|
||||
if (r_raw.is_error()) {
|
||||
return {};
|
||||
}
|
||||
return virtualize_raw(r_raw.move_as_ok(), {0 /*level*/, static_cast<td::uint8>(virtualization)});
|
||||
}
|
||||
|
||||
class MerkleProofCombine {
|
||||
public:
|
||||
MerkleProofCombine(Ref<Cell> a, Ref<Cell> b) : a_(std::move(a)), b_(std::move(b)) {
|
||||
}
|
||||
td::Result<Ref<Cell>> run() {
|
||||
TRY_RESULT(a, unpack_proof(a_));
|
||||
TRY_RESULT(b, unpack_proof(b_));
|
||||
if (a->get_hash(0) != b->get_hash(0)) {
|
||||
return td::Status::Error("Can't combine MerkleProofs with different roots");
|
||||
}
|
||||
dfs(a, 0);
|
||||
dfs(b, 0);
|
||||
return CellBuilder::create_merkle_proof(create_A(a, 0, 0));
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<Cell> a_;
|
||||
Ref<Cell> b_;
|
||||
|
||||
struct Info {
|
||||
Ref<Cell> cell_;
|
||||
Ref<Cell> prunned_cells_[Cell::max_level]; // Cache prunned cells with different levels to reuse them
|
||||
|
||||
Ref<Cell> get_prunned_cell(int depth) {
|
||||
if (depth < Cell::max_level) {
|
||||
return prunned_cells_[depth];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
Ref<Cell> get_any_cell() const {
|
||||
if (cell_.not_null()) {
|
||||
return cell_;
|
||||
}
|
||||
for (auto &cell : prunned_cells_) {
|
||||
if (cell.not_null()) {
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
};
|
||||
|
||||
using Key = std::pair<Cell::Hash, int>;
|
||||
td::HashMap<Cell::Hash, Info> cells_;
|
||||
td::HashMap<Key, Ref<Cell>> create_A_res_;
|
||||
td::HashSet<Key> visited_;
|
||||
|
||||
void dfs(Ref<Cell> cell, int merkle_depth) {
|
||||
if (!visited_.emplace(cell->get_hash(), merkle_depth).second) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &info = cells_[cell->get_hash(merkle_depth)];
|
||||
CellSlice cs(NoVm(), cell);
|
||||
// check if prunned cell is bounded
|
||||
if (cs.special_type() == Cell::SpecialType::PrunnedBranch && static_cast<int>(cell->get_level()) > merkle_depth) {
|
||||
info.prunned_cells_[cell->get_level() - 1] = std::move(cell);
|
||||
return;
|
||||
}
|
||||
info.cell_ = std::move(cell);
|
||||
|
||||
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
for (size_t i = 0, size = cs.size_refs(); i < size; i++) {
|
||||
dfs(cs.fetch_ref(), child_merkle_depth);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Cell> create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
|
||||
merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
|
||||
auto key = Key(cell->get_hash(merkle_depth), a_merkle_depth);
|
||||
auto it = create_A_res_.find(key);
|
||||
if (it != create_A_res_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto res = do_create_A(std::move(cell), merkle_depth, a_merkle_depth);
|
||||
create_A_res_.emplace(key, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Cell> do_create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
|
||||
auto &info = cells_[cell->get_hash(merkle_depth)];
|
||||
|
||||
if (info.cell_.is_null()) {
|
||||
Ref<Cell> res = info.get_prunned_cell(a_merkle_depth);
|
||||
if (res.is_null()) {
|
||||
res = CellBuilder::create_pruned_branch(info.get_any_cell(), a_merkle_depth + 1, merkle_depth);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
CHECK(info.cell_.not_null());
|
||||
CellSlice cs(NoVm(), info.cell_);
|
||||
|
||||
//CHECK(cs.size_refs() != 0);
|
||||
if (cs.size_refs() == 0) {
|
||||
return info.cell_;
|
||||
}
|
||||
|
||||
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
auto child_a_merkle_depth = cs.child_merkle_depth(a_merkle_depth);
|
||||
|
||||
CellBuilder cb;
|
||||
cb.store_bits(cs.fetch_bits(cs.size()));
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
cb.store_ref(create_A(cs.prefetch_ref(i), child_merkle_depth, child_a_merkle_depth));
|
||||
}
|
||||
return cb.finalize(cs.is_special());
|
||||
}
|
||||
};
|
||||
|
||||
Ref<Cell> MerkleProof::combine(Ref<Cell> a, Ref<Cell> b) {
|
||||
auto res = MerkleProofCombine(std::move(a), std::move(b)).run();
|
||||
if (res.is_error()) {
|
||||
return {};
|
||||
}
|
||||
return res.move_as_ok();
|
||||
}
|
||||
} // namespace vm
|
||||
46
crypto/vm/cells/MerkleProof.h
Normal file
46
crypto/vm/cells/MerkleProof.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells/Cell.h"
|
||||
|
||||
#include <utility>
|
||||
#include <functional>
|
||||
|
||||
namespace vm {
|
||||
class MerkleProof {
|
||||
public:
|
||||
using IsPrunnedFunction = std::function<bool(const Ref<Cell> &)>;
|
||||
|
||||
// works with proofs wrapped in MerkleProof special cell
|
||||
// cells must have zero level
|
||||
static Ref<Cell> generate(Ref<Cell> cell, IsPrunnedFunction is_prunned);
|
||||
static Ref<Cell> generate(Ref<Cell> cell, CellUsageTree *usage_tree);
|
||||
|
||||
// cell must have zero level and must be a MerkleProof
|
||||
static Ref<Cell> virtualize(Ref<Cell> cell, int virtualization);
|
||||
|
||||
static Ref<Cell> combine(Ref<Cell> a, Ref<Cell> b);
|
||||
|
||||
// works with upwrapped proofs
|
||||
// works fine with cell of non-zero level, but this is not supported (yet?) in MerkeProof special cell
|
||||
static Ref<Cell> generate_raw(Ref<Cell> cell, IsPrunnedFunction is_prunned);
|
||||
static Ref<Cell> generate_raw(Ref<Cell> cell, CellUsageTree *usage_tree);
|
||||
static Ref<Cell> virtualize_raw(Ref<Cell> cell, Cell::VirtualizationParameters virt);
|
||||
};
|
||||
} // namespace vm
|
||||
510
crypto/vm/cells/MerkleUpdate.cpp
Normal file
510
crypto/vm/cells/MerkleUpdate.cpp
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/MerkleUpdate.h"
|
||||
#include "vm/cells/MerkleProof.h"
|
||||
|
||||
#include "td/utils/HashMap.h"
|
||||
#include "td/utils/HashSet.h"
|
||||
|
||||
namespace vm {
|
||||
namespace detail {
|
||||
class MerkleUpdateApply {
|
||||
public:
|
||||
Ref<Cell> apply(Ref<Cell> from, Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
|
||||
td::uint32 to_level) {
|
||||
if (from_level != from->get_level()) {
|
||||
return {};
|
||||
}
|
||||
dfs_both(from, update_from, from_level);
|
||||
return dfs(update_to, to_level);
|
||||
}
|
||||
|
||||
private:
|
||||
using Key = std::pair<Cell::Hash, int>;
|
||||
td::HashMap<Cell::Hash, Ref<Cell>> known_cells_;
|
||||
td::HashMap<Key, Ref<Cell>> ready_cells_;
|
||||
|
||||
void dfs_both(Ref<Cell> original, Ref<Cell> update_from, int merkle_depth) {
|
||||
CellSlice cs_update_from(NoVm(), update_from);
|
||||
known_cells_.emplace(original->get_hash(merkle_depth), original);
|
||||
if (cs_update_from.special_type() == Cell::SpecialType::PrunnedBranch) {
|
||||
return;
|
||||
}
|
||||
int child_merkle_depth = cs_update_from.child_merkle_depth(merkle_depth);
|
||||
|
||||
CellSlice cs_original(NoVm(), original);
|
||||
for (unsigned i = 0; i < cs_original.size_refs(); i++) {
|
||||
dfs_both(cs_original.prefetch_ref(i), cs_update_from.prefetch_ref(i), child_merkle_depth);
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Cell> dfs(Ref<Cell> cell, int merkle_depth) {
|
||||
CellSlice cs(NoVm(), cell);
|
||||
if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
|
||||
if ((int)cell->get_level() == merkle_depth + 1) {
|
||||
auto it = known_cells_.find(cell->get_hash(merkle_depth));
|
||||
if (it != known_cells_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
Key key{cell->get_hash(), merkle_depth};
|
||||
{
|
||||
auto it = ready_cells_.find(key);
|
||||
if (it != ready_cells_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
|
||||
CellBuilder cb;
|
||||
cb.store_bits(cs.fetch_bits(cs.size()));
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
auto ref = dfs(cs.prefetch_ref(i), child_merkle_depth);
|
||||
if (ref.is_null()) {
|
||||
return {};
|
||||
}
|
||||
cb.store_ref(std::move(ref));
|
||||
}
|
||||
auto res = cb.finalize(cs.is_special());
|
||||
ready_cells_.emplace(key, res);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
class MerkleUpdateValidator {
|
||||
public:
|
||||
td::Status validate(Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level, td::uint32 to_level) {
|
||||
dfs_from(update_from, from_level);
|
||||
return dfs_to(update_to, to_level);
|
||||
}
|
||||
|
||||
private:
|
||||
td::HashSet<Cell::Hash> known_cells_;
|
||||
using Key = std::pair<Cell::Hash, int>;
|
||||
td::HashSet<Key> visited_from_;
|
||||
td::HashSet<Key> visited_to_;
|
||||
|
||||
void dfs_from(Ref<Cell> cell, int merkle_depth) {
|
||||
if (!visited_from_.emplace(cell->get_hash(), merkle_depth).second) {
|
||||
return;
|
||||
}
|
||||
CellSlice cs(NoVm(), cell);
|
||||
known_cells_.insert(cell->get_hash(merkle_depth));
|
||||
if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
|
||||
return;
|
||||
}
|
||||
int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
dfs_from(cs.prefetch_ref(i), child_merkle_depth);
|
||||
}
|
||||
}
|
||||
|
||||
td::Status dfs_to(Ref<Cell> cell, int merkle_depth) {
|
||||
if (!visited_to_.emplace(cell->get_hash(), merkle_depth).second) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
CellSlice cs(NoVm(), cell);
|
||||
if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
|
||||
if ((int)cell->get_level() == merkle_depth + 1) {
|
||||
if (known_cells_.count(cell->get_hash(merkle_depth)) == 0) {
|
||||
return td::Status::Error(PSLICE()
|
||||
<< "Unknown prunned cell (validate): " << cell->get_hash(merkle_depth).to_hex());
|
||||
}
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
TRY_STATUS(dfs_to(cs.prefetch_ref(i), child_merkle_depth));
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
td::Status MerkleUpdate::may_apply(Ref<Cell> from, Ref<Cell> update) {
|
||||
if (update->get_level() != 0 || from->get_level() != 0) {
|
||||
return td::Status::Error("Level of update of from is not zero");
|
||||
}
|
||||
CellSlice cs(NoVm(), std::move(update));
|
||||
if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
|
||||
return td::Status::Error("Update cell is not a MerkeUpdate");
|
||||
}
|
||||
auto update_from = cs.fetch_ref();
|
||||
if (from->get_hash(0) != update_from->get_hash(0)) {
|
||||
return td::Status::Error("Hash mismatch");
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleUpdate::apply(Ref<Cell> from, Ref<Cell> update) {
|
||||
if (update->get_level() != 0 || from->get_level() != 0) {
|
||||
return {};
|
||||
}
|
||||
CellSlice cs(NoVm(), std::move(update));
|
||||
if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
|
||||
return {};
|
||||
}
|
||||
auto update_from = cs.fetch_ref();
|
||||
auto update_to = cs.fetch_ref();
|
||||
return apply_raw(std::move(from), std::move(update_from), std::move(update_to), 0, 0);
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleUpdate::apply_raw(Ref<Cell> from, Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
|
||||
td::uint32 to_level) {
|
||||
if (from->get_hash(from_level) != update_from->get_hash(from_level)) {
|
||||
LOG(DEBUG) << "invalid Merkle update: expected old value hash = " << update_from->get_hash(from_level).to_hex()
|
||||
<< ", applied to value with hash = " << from->get_hash(from_level).to_hex();
|
||||
return {};
|
||||
}
|
||||
return detail::MerkleUpdateApply().apply(from, std::move(update_from), std::move(update_to), from_level, to_level);
|
||||
}
|
||||
|
||||
std::pair<Ref<Cell>, Ref<Cell>> MerkleUpdate::generate_raw(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree) {
|
||||
// create Merkle update cell->new_cell
|
||||
auto update_to = MerkleProof::generate_raw(to, [tree = usage_tree](const Ref<Cell> &cell) {
|
||||
auto loaded_cell = cell->load_cell().move_as_ok(); // FIXME
|
||||
if (loaded_cell.data_cell->size_refs() == 0) {
|
||||
return false;
|
||||
}
|
||||
return !loaded_cell.tree_node.empty() && loaded_cell.tree_node.mark_path(tree);
|
||||
});
|
||||
usage_tree->set_use_mark_for_is_loaded(true);
|
||||
auto update_from = MerkleProof::generate_raw(from, usage_tree);
|
||||
|
||||
return {std::move(update_from), std::move(update_to)};
|
||||
}
|
||||
|
||||
td::Status MerkleUpdate::validate_raw(Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
|
||||
td::uint32 to_level) {
|
||||
return detail::MerkleUpdateValidator().validate(std::move(update_from), std::move(update_to), from_level, to_level);
|
||||
}
|
||||
|
||||
td::Status MerkleUpdate::validate(Ref<Cell> update) {
|
||||
if (update->get_level() != 0) {
|
||||
return td::Status::Error("nonzero level");
|
||||
}
|
||||
CellSlice cs(NoVm(), std::move(update));
|
||||
if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
|
||||
return td::Status::Error("not a MerkleUpdate cell");
|
||||
}
|
||||
auto update_from = cs.fetch_ref();
|
||||
auto update_to = cs.fetch_ref();
|
||||
return validate_raw(std::move(update_from), std::move(update_to), 0, 0);
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleUpdate::generate(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree) {
|
||||
auto from_level = from->get_level();
|
||||
auto to_level = to->get_level();
|
||||
if (from_level != 0 || to_level != 0) {
|
||||
return {};
|
||||
}
|
||||
auto res = generate_raw(std::move(from), std::move(to), usage_tree);
|
||||
return CellBuilder::create_merkle_update(res.first, res.second);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
class MerkleCombine {
|
||||
public:
|
||||
MerkleCombine(Ref<Cell> AB, Ref<Cell> CD) : AB_(std::move(AB)), CD_(std::move(CD)) {
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> run() {
|
||||
TRY_RESULT(AB, unpack_update(std::move(AB_)));
|
||||
TRY_RESULT(CD, unpack_update(std::move(CD_)));
|
||||
std::tie(A_, B_) = AB;
|
||||
std::tie(C_, D_) = CD;
|
||||
if (B_->get_hash(0) != C_->get_hash(0)) {
|
||||
return td::Status::Error("Impossible to combine merkle updates");
|
||||
}
|
||||
|
||||
auto log = [](td::Slice name, auto cell) {
|
||||
CellSlice cs(NoVm(), cell);
|
||||
LOG(ERROR) << name << " " << cell->get_level();
|
||||
cs.print_rec(std::cerr);
|
||||
};
|
||||
if (0) {
|
||||
log("A", A_);
|
||||
log("B", B_);
|
||||
log("C", C_);
|
||||
log("D", D_);
|
||||
}
|
||||
|
||||
// We have four bags of cells. A, B, C and D.
|
||||
// X = Virtualize(A), A is subtree (merkle proof) of X
|
||||
// Y = Virtualize(B) = Virtualize(C), B and C are subrees of Y
|
||||
// Z = Virtualize(D), D is subtree of Z
|
||||
//
|
||||
// Prunned cells bounded by merkle proof P are essentially cells which are impossible to load during traversal of Virtualize(P)
|
||||
//
|
||||
// We want to create new_A and new_D
|
||||
// Virtualize(new_A) = X
|
||||
// Virtualize(new_D) = Z
|
||||
// All prunned branches bounded by new_D must be in new_A
|
||||
// i.e. if we have all cells reachable in Virtualize(new_A) we may construct Z from them (and from new_D)
|
||||
//
|
||||
// Main idea is following
|
||||
// 1. Create maximum subtrees of X and Z with all cells in A, B, C and D
|
||||
// Max(V) - such maximum subtree
|
||||
//
|
||||
// 2. Max(A) and Max(D) should be merkle update already. But win want to minimize it
|
||||
// So we cut all branches of Max(D) which are in maxA
|
||||
// When we cut branch q in Max(D) we mark some path to q in Max(A)
|
||||
// Then we cut all branches of Max(A) which are not marked.
|
||||
//
|
||||
// How to create Max(A)?
|
||||
// We just store all cells reachable from A, B, C and D in big cache.
|
||||
// It we reach bounded prunned cell during traversion we may continue traversial with a cell from the cache.
|
||||
//
|
||||
//
|
||||
// 1. load_cells(root) - caches all cell reachable in Virtualize(root);
|
||||
visited_.clear();
|
||||
load_cells(A_, 0);
|
||||
visited_.clear();
|
||||
load_cells(B_, 0);
|
||||
visited_.clear();
|
||||
load_cells(C_, 0);
|
||||
visited_.clear();
|
||||
load_cells(D_, 0);
|
||||
|
||||
// 2. mark_A(A) - Traverse Max(A), but uses all cached cells from step 1. Mark all visited cells
|
||||
A_usage_tree_ = std::make_shared<CellUsageTree>();
|
||||
mark_A(A_, 0, A_usage_tree_->root_id());
|
||||
|
||||
// 3. create_D(D) - create new_D. Traverse Max(D), and stop at marked cells. Mark path in A to marked cells
|
||||
auto new_D = create_D(D_, 0, 0);
|
||||
if (new_D.is_null()) {
|
||||
return td::Status::Error("Failed to combine updates. One of them is probably an invalid update");
|
||||
}
|
||||
|
||||
// 4. create_A(A) - create new_A. Traverse Max(A), and stop at cells not marked at step 3.
|
||||
auto new_A = create_A(A_, 0, 0);
|
||||
if (0) {
|
||||
log("NewD", new_D);
|
||||
}
|
||||
|
||||
return CellBuilder::create_merkle_update(new_A, new_D);
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<Cell> AB_, CD_;
|
||||
Ref<Cell> A_, B_, C_, D_;
|
||||
|
||||
std::shared_ptr<CellUsageTree> A_usage_tree_;
|
||||
|
||||
struct Info {
|
||||
Ref<Cell> cell_;
|
||||
Ref<Cell> prunned_cells_[Cell::max_level]; // Cache prunned cells with different levels to reuse them
|
||||
CellUsageTree::NodeId A_node_id{0};
|
||||
|
||||
Ref<Cell> get_prunned_cell(int depth) {
|
||||
if (depth < Cell::max_level) {
|
||||
return prunned_cells_[depth];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
Ref<Cell> get_any_cell() const {
|
||||
if (cell_.not_null()) {
|
||||
return cell_;
|
||||
}
|
||||
for (auto &cell : prunned_cells_) {
|
||||
if (cell.not_null()) {
|
||||
return cell;
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
};
|
||||
using Key = std::pair<Cell::Hash, int>;
|
||||
td::HashMap<Cell::Hash, Info> cells_;
|
||||
td::HashMap<Key, Ref<Cell>> create_A_res_;
|
||||
td::HashMap<Key, Ref<Cell>> create_D_res_;
|
||||
td::HashSet<Key> visited_;
|
||||
|
||||
void load_cells(Ref<Cell> cell, int merkle_depth) {
|
||||
if (!visited_.emplace(cell->get_hash(), merkle_depth).second) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &info = cells_[cell->get_hash(merkle_depth)];
|
||||
CellSlice cs(NoVm(), cell);
|
||||
|
||||
// check if prunned cell is bounded
|
||||
if (cs.special_type() == Cell::SpecialType::PrunnedBranch && static_cast<int>(cell->get_level()) > merkle_depth) {
|
||||
info.prunned_cells_[cell->get_level() - 1] = std::move(cell);
|
||||
return;
|
||||
}
|
||||
|
||||
info.cell_ = std::move(cell);
|
||||
|
||||
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
for (size_t i = 0, size = cs.size_refs(); i < size; i++) {
|
||||
load_cells(cs.fetch_ref(), child_merkle_depth);
|
||||
}
|
||||
}
|
||||
|
||||
void mark_A(Ref<Cell> cell, int merkle_depth, CellUsageTree::NodeId node_id) {
|
||||
CHECK(node_id != 0);
|
||||
|
||||
// cell in cache may be virtualized with different level
|
||||
// so we make merkle_depth as small as possible
|
||||
merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
|
||||
|
||||
auto &info = cells_[cell->get_hash(merkle_depth)];
|
||||
if (info.A_node_id != 0) {
|
||||
return;
|
||||
}
|
||||
info.A_node_id = node_id;
|
||||
if (info.cell_.is_null()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CellSlice cs(NoVm(), info.cell_);
|
||||
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
for (int i = 0, size = cs.size_refs(); i < size; i++) {
|
||||
mark_A(cs.fetch_ref(), child_merkle_depth, A_usage_tree_->create_child(node_id, i));
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Cell> create_D(Ref<Cell> cell, int merkle_depth, int d_merkle_depth) {
|
||||
merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
|
||||
auto key = Key(cell->get_hash(merkle_depth), d_merkle_depth);
|
||||
auto it = create_D_res_.find(key);
|
||||
if (it != create_D_res_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto res = do_create_D(std::move(cell), merkle_depth, d_merkle_depth);
|
||||
if (res.is_null()) {
|
||||
return {};
|
||||
}
|
||||
create_D_res_.emplace(key, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Cell> do_create_D(Ref<Cell> cell, int merkle_depth, int d_merkle_depth) {
|
||||
auto &info = cells_[cell->get_hash(merkle_depth)];
|
||||
if (info.A_node_id != 0) {
|
||||
A_usage_tree_->mark_path(info.A_node_id);
|
||||
Ref<Cell> res = info.get_prunned_cell(d_merkle_depth);
|
||||
if (res.is_null()) {
|
||||
res = CellBuilder::create_pruned_branch(info.get_any_cell(), d_merkle_depth + 1, merkle_depth);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
if (info.cell_.is_null()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
CellSlice cs(NoVm(), info.cell_);
|
||||
|
||||
if (cs.size_refs() == 0) {
|
||||
return info.cell_;
|
||||
}
|
||||
|
||||
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
auto child_d_merkle_depth = cs.child_merkle_depth(d_merkle_depth);
|
||||
|
||||
CellBuilder cb;
|
||||
cb.store_bits(cs.fetch_bits(cs.size()));
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
auto ref = create_D(cs.prefetch_ref(i), child_merkle_depth, child_d_merkle_depth);
|
||||
if (ref.is_null()) {
|
||||
return {};
|
||||
}
|
||||
cb.store_ref(std::move(ref));
|
||||
}
|
||||
return cb.finalize(cs.is_special());
|
||||
}
|
||||
|
||||
Ref<Cell> create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
|
||||
merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
|
||||
auto key = Key(cell->get_hash(merkle_depth), a_merkle_depth);
|
||||
auto it = create_A_res_.find(key);
|
||||
if (it != create_A_res_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto res = do_create_A(std::move(cell), merkle_depth, a_merkle_depth);
|
||||
create_A_res_.emplace(key, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Cell> do_create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
|
||||
auto &info = cells_[cell->get_hash(merkle_depth)];
|
||||
|
||||
CHECK(info.A_node_id != 0);
|
||||
if (!A_usage_tree_->has_mark(info.A_node_id)) {
|
||||
Ref<Cell> res = info.get_prunned_cell(a_merkle_depth);
|
||||
if (res.is_null()) {
|
||||
res = CellBuilder::create_pruned_branch(info.get_any_cell(), a_merkle_depth + 1, merkle_depth);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
CHECK(info.cell_.not_null());
|
||||
CellSlice cs(NoVm(), info.cell_);
|
||||
|
||||
CHECK(cs.size_refs() != 0);
|
||||
if (cs.size_refs() == 0) {
|
||||
return info.cell_;
|
||||
}
|
||||
|
||||
auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
|
||||
auto child_a_merkle_depth = cs.child_merkle_depth(a_merkle_depth);
|
||||
|
||||
CellBuilder cb;
|
||||
cb.store_bits(cs.fetch_bits(cs.size()));
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
cb.store_ref(create_A(cs.prefetch_ref(i), child_merkle_depth, child_a_merkle_depth));
|
||||
}
|
||||
return cb.finalize(cs.is_special());
|
||||
}
|
||||
|
||||
td::Result<std::pair<Ref<Cell>, Ref<Cell>>> unpack_update(Ref<Cell> update) const {
|
||||
if (update->get_level() != 0) {
|
||||
return td::Status::Error("level is not zero");
|
||||
}
|
||||
CellSlice cs(NoVm(), std::move(update));
|
||||
if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
|
||||
return td::Status::Error("Not a Merkle Update cell");
|
||||
}
|
||||
auto update_from = cs.fetch_ref();
|
||||
auto update_to = cs.fetch_ref();
|
||||
return std::make_pair(std::move(update_from), std::move(update_to));
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
Ref<Cell> MerkleUpdate::combine(Ref<Cell> ab, Ref<Cell> bc) {
|
||||
detail::MerkleCombine combine(ab, bc);
|
||||
auto res = combine.run();
|
||||
if (res.is_error()) {
|
||||
return {};
|
||||
}
|
||||
return res.move_as_ok();
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
50
crypto/vm/cells/MerkleUpdate.h
Normal file
50
crypto/vm/cells/MerkleUpdate.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells/Cell.h"
|
||||
#include "vm/cells/CellSlice.h"
|
||||
#include "vm/cells/CellBuilder.h"
|
||||
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace vm {
|
||||
class MerkleUpdate {
|
||||
public:
|
||||
// from + update == to
|
||||
static Ref<Cell> generate(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree);
|
||||
// Returns empty Ref<Cell> if something go wrong. If validate(from).is_ok() and may_apply(from, to).is_ok(), then it
|
||||
// must not fail.
|
||||
static Ref<Cell> apply(Ref<Cell> from, Ref<Cell> update);
|
||||
|
||||
// check if update is valid
|
||||
static TD_WARN_UNUSED_RESULT td::Status validate(Ref<Cell> update);
|
||||
// check that hash in from is same as hash stored in update. Do not validate update
|
||||
static TD_WARN_UNUSED_RESULT td::Status may_apply(Ref<Cell> from, Ref<Cell> update);
|
||||
|
||||
static Ref<Cell> apply_raw(Ref<Cell> from, Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
|
||||
td::uint32 to_level);
|
||||
static std::pair<Ref<Cell>, Ref<Cell>> generate_raw(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree);
|
||||
static td::Status validate_raw(Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
|
||||
td::uint32 to_level);
|
||||
|
||||
static Ref<Cell> combine(Ref<Cell> ab, Ref<Cell> bc);
|
||||
};
|
||||
} // namespace vm
|
||||
139
crypto/vm/cells/PrunnedCell.h
Normal file
139
crypto/vm/cells/PrunnedCell.h
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells/CellWithStorage.h"
|
||||
#include "vm/cells/Cell.h"
|
||||
|
||||
namespace vm {
|
||||
struct PrunnedCellInfo {
|
||||
Cell::LevelMask level_mask;
|
||||
td::Slice hash;
|
||||
td::Slice depth;
|
||||
};
|
||||
|
||||
template <class ExtraT>
|
||||
class PrunnedCell : public Cell {
|
||||
public:
|
||||
const ExtraT& get_extra() const {
|
||||
return extra_;
|
||||
}
|
||||
|
||||
static td::Result<Ref<PrunnedCell<ExtraT>>> create(const PrunnedCellInfo& prunned_cell_info, ExtraT&& extra) {
|
||||
auto level_mask = prunned_cell_info.level_mask;
|
||||
if (level_mask.get_level() > max_level) {
|
||||
return td::Status::Error("Level is too big");
|
||||
}
|
||||
Info info(level_mask);
|
||||
auto prunned_cell =
|
||||
detail::CellWithUniquePtrStorage<PrunnedCell<ExtraT>>::create(info.get_storage_size(), info, std::move(extra));
|
||||
TRY_STATUS(prunned_cell->init(prunned_cell_info));
|
||||
return Ref<PrunnedCell<ExtraT>>(prunned_cell.release(), typename Ref<PrunnedCell<ExtraT>>::acquire_t{});
|
||||
}
|
||||
|
||||
LevelMask get_level_mask() const override {
|
||||
return LevelMask(info_.level_mask_);
|
||||
}
|
||||
|
||||
protected:
|
||||
struct Info {
|
||||
Info(LevelMask level_mask) {
|
||||
level_mask_ = level_mask.get_mask() & 7;
|
||||
hash_count_ = level_mask.get_hashes_count() & 7;
|
||||
}
|
||||
unsigned char level_mask_ : 3;
|
||||
unsigned char hash_count_ : 3;
|
||||
size_t get_hashes_offset() const {
|
||||
return 0;
|
||||
}
|
||||
size_t get_depth_offset() const {
|
||||
return get_hashes_offset() + hash_bytes * hash_count_;
|
||||
}
|
||||
size_t get_storage_size() const {
|
||||
return get_depth_offset() + sizeof(td::uint16) * hash_count_;
|
||||
}
|
||||
const Hash* get_hashes(const char* storage) const {
|
||||
return reinterpret_cast<const Hash*>(storage + get_hashes_offset());
|
||||
}
|
||||
Hash* get_hashes(char* storage) const {
|
||||
return reinterpret_cast<Hash*>(storage + get_hashes_offset());
|
||||
}
|
||||
const td::uint16* get_depth(const char* storage) const {
|
||||
return reinterpret_cast<const td::uint16*>(storage + get_depth_offset());
|
||||
}
|
||||
td::uint16* get_depth(char* storage) const {
|
||||
return reinterpret_cast<td::uint16*>(storage + get_depth_offset());
|
||||
}
|
||||
};
|
||||
|
||||
Info info_;
|
||||
ExtraT extra_;
|
||||
virtual char* get_storage() = 0;
|
||||
virtual const char* get_storage() const = 0;
|
||||
void destroy_storage(char* storage) {
|
||||
// noop
|
||||
}
|
||||
|
||||
td::Status init(const PrunnedCellInfo& prunned_cell_info) {
|
||||
auto storage = get_storage();
|
||||
auto& new_hash = prunned_cell_info.hash;
|
||||
auto* hash = info_.get_hashes(storage);
|
||||
size_t n = prunned_cell_info.level_mask.get_hashes_count();
|
||||
CHECK(new_hash.size() == n * hash_bytes);
|
||||
for (td::uint32 i = 0; i < n; i++) {
|
||||
hash[i].as_slice().copy_from(new_hash.substr(i * Cell::hash_bytes, Cell::hash_bytes));
|
||||
}
|
||||
|
||||
auto& new_depth = prunned_cell_info.depth;
|
||||
CHECK(new_depth.size() == n * depth_bytes);
|
||||
auto* depth = info_.get_depth(storage);
|
||||
for (td::uint32 i = 0; i < n; i++) {
|
||||
depth[i] = DataCell::load_depth(new_depth.substr(i * Cell::depth_bytes, Cell::depth_bytes).ubegin());
|
||||
if (depth[i] > max_depth) {
|
||||
return td::Status::Error("Depth is too big");
|
||||
}
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
explicit PrunnedCell(Info info, ExtraT&& extra) : info_(info), extra_(std::move(extra)) {
|
||||
}
|
||||
td::uint32 get_virtualization() const override {
|
||||
return 0;
|
||||
}
|
||||
CellUsageTree::NodePtr get_tree_node() const override {
|
||||
return {};
|
||||
}
|
||||
bool is_loaded() const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const Hash do_get_hash(td::uint32 level) const override {
|
||||
return info_.get_hashes(get_storage())[get_level_mask().apply(level).get_hash_i()];
|
||||
}
|
||||
|
||||
td::uint16 do_get_depth(td::uint32 level) const override {
|
||||
return info_.get_depth(get_storage())[get_level_mask().apply(level).get_hash_i()];
|
||||
}
|
||||
|
||||
td::Result<LoadedCell> load_cell() const override {
|
||||
return td::Status::Error("Can't load prunned branch");
|
||||
}
|
||||
};
|
||||
} // namespace vm
|
||||
88
crypto/vm/cells/UsageCell.h
Normal file
88
crypto/vm/cells/UsageCell.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells/Cell.h"
|
||||
#include "vm/cells/CellUsageTree.h"
|
||||
|
||||
namespace vm {
|
||||
class UsageCell : public Cell {
|
||||
private:
|
||||
struct PrivateTag {};
|
||||
|
||||
public:
|
||||
UsageCell(Ref<Cell> cell, CellUsageTree::NodePtr tree_node, PrivateTag)
|
||||
: cell_(std::move(cell)), tree_node_(std::move(tree_node)) {
|
||||
}
|
||||
static Ref<Cell> create(Ref<Cell> cell, CellUsageTree::NodePtr tree_node) {
|
||||
if (tree_node.empty()) {
|
||||
return cell;
|
||||
}
|
||||
return Ref<UsageCell>{true, std::move(cell), std::move(tree_node), PrivateTag{}};
|
||||
}
|
||||
|
||||
// load interface
|
||||
td::Result<LoadedCell> load_cell() const override {
|
||||
TRY_RESULT(loaded_cell, cell_->load_cell());
|
||||
if (tree_node_.on_load()) {
|
||||
CHECK(loaded_cell.tree_node.empty());
|
||||
loaded_cell.tree_node = tree_node_;
|
||||
}
|
||||
return std::move(loaded_cell);
|
||||
}
|
||||
Ref<Cell> virtualize(VirtualizationParameters virt) const override {
|
||||
auto virtualized_cell = cell_->virtualize(virt);
|
||||
if (tree_node_.empty()) {
|
||||
return virtualized_cell;
|
||||
}
|
||||
if (virtualized_cell.get() == cell_.get()) {
|
||||
return Ref<Cell>(this);
|
||||
}
|
||||
return create(std::move(virtualized_cell), tree_node_);
|
||||
}
|
||||
|
||||
td::uint32 get_virtualization() const override {
|
||||
return cell_->get_virtualization();
|
||||
}
|
||||
|
||||
CellUsageTree::NodePtr get_tree_node() const override {
|
||||
return tree_node_;
|
||||
}
|
||||
|
||||
bool is_loaded() const override {
|
||||
return cell_->is_loaded();
|
||||
}
|
||||
|
||||
// hash and level
|
||||
LevelMask get_level_mask() const override {
|
||||
return cell_->get_level_mask();
|
||||
}
|
||||
|
||||
protected:
|
||||
const Hash do_get_hash(td::uint32 level) const override {
|
||||
return cell_->get_hash(level);
|
||||
}
|
||||
td::uint16 do_get_depth(td::uint32 level) const override {
|
||||
return cell_->get_depth(level);
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<Cell> cell_;
|
||||
CellUsageTree::NodePtr tree_node_;
|
||||
};
|
||||
} // namespace vm
|
||||
87
crypto/vm/cells/VirtualCell.h
Normal file
87
crypto/vm/cells/VirtualCell.h
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells/Cell.h"
|
||||
|
||||
namespace vm {
|
||||
class VirtualCell : public Cell {
|
||||
private:
|
||||
struct PrivateTag {};
|
||||
|
||||
public:
|
||||
static Ref<Cell> create(VirtualizationParameters virt, Ref<Cell> cell) {
|
||||
if (cell->get_level() <= virt.get_level()) {
|
||||
return cell;
|
||||
}
|
||||
return Ref<VirtualCell>{true, virt, std::move(cell), PrivateTag{}};
|
||||
}
|
||||
|
||||
VirtualCell(VirtualizationParameters virt, Ref<Cell> cell, PrivateTag) : virt_(virt), cell_(std::move(cell)) {
|
||||
CHECK(cell_->get_virtualization() <= virt_.get_virtualization());
|
||||
}
|
||||
|
||||
// load interface
|
||||
td::Result<LoadedCell> load_cell() const override {
|
||||
TRY_RESULT(loaded_cell, cell_->load_cell());
|
||||
loaded_cell.virt = loaded_cell.virt.apply(virt_);
|
||||
return std::move(loaded_cell);
|
||||
}
|
||||
|
||||
Ref<Cell> virtualize(VirtualizationParameters virt) const override {
|
||||
auto new_virt = virt_.apply(virt);
|
||||
if (new_virt == virt_) {
|
||||
return Ref<Cell>(this);
|
||||
}
|
||||
return create(new_virt, cell_);
|
||||
}
|
||||
|
||||
td::uint32 get_virtualization() const override {
|
||||
return virt_.get_virtualization();
|
||||
}
|
||||
|
||||
CellUsageTree::NodePtr get_tree_node() const override {
|
||||
return cell_->get_tree_node();
|
||||
}
|
||||
|
||||
bool is_loaded() const override {
|
||||
return cell_->is_loaded();
|
||||
}
|
||||
|
||||
// hash and level
|
||||
LevelMask get_level_mask() const override {
|
||||
return cell_->get_level_mask().apply(virt_.get_level());
|
||||
}
|
||||
|
||||
protected:
|
||||
const Hash do_get_hash(td::uint32 level) const override {
|
||||
return cell_->get_hash(fix_level(level));
|
||||
}
|
||||
td::uint16 do_get_depth(td::uint32 level) const override {
|
||||
return cell_->get_depth(fix_level(level));
|
||||
}
|
||||
|
||||
private:
|
||||
VirtualizationParameters virt_;
|
||||
Ref<Cell> cell_;
|
||||
|
||||
int fix_level(int level) const {
|
||||
return get_level_mask().apply(level).get_level();
|
||||
}
|
||||
};
|
||||
} // namespace vm
|
||||
72
crypto/vm/cells/VirtualizationParameters.h
Normal file
72
crypto/vm/cells/VirtualizationParameters.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/int_types.h"
|
||||
#include "td/utils/logging.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace vm {
|
||||
namespace detail {
|
||||
class VirtualizationParameters {
|
||||
public:
|
||||
static constexpr td::uint8 max_level() {
|
||||
return std::numeric_limits<td::uint8>::max();
|
||||
}
|
||||
|
||||
VirtualizationParameters() = default;
|
||||
|
||||
VirtualizationParameters(td::uint8 level, td::uint8 virtualization) : level_(level), virtualization_(virtualization) {
|
||||
CHECK(virtualization_ != 0 || empty());
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return level_ == max_level() && virtualization_ == 0;
|
||||
}
|
||||
|
||||
VirtualizationParameters apply(VirtualizationParameters outer) const {
|
||||
if (outer.level_ >= level_) {
|
||||
return *this;
|
||||
}
|
||||
CHECK(virtualization_ <= outer.virtualization_);
|
||||
return {outer.level_, outer.virtualization_};
|
||||
}
|
||||
|
||||
td::uint8 get_level() const {
|
||||
return level_;
|
||||
}
|
||||
|
||||
td::uint8 get_virtualization() const {
|
||||
return virtualization_;
|
||||
}
|
||||
|
||||
bool operator==(const VirtualizationParameters &other) const {
|
||||
return level_ == other.level_ && virtualization_ == other.virtualization_;
|
||||
}
|
||||
|
||||
private:
|
||||
td::uint8 level_ = max_level();
|
||||
td::uint8 virtualization_ = 0;
|
||||
};
|
||||
inline td::StringBuilder &operator<<(td::StringBuilder &sb, const VirtualizationParameters &virt) {
|
||||
return sb << "{level: " << virt.get_level() << ", virtualization: " << virt.get_virtualization() << "}";
|
||||
}
|
||||
} // namespace detail
|
||||
} // namespace vm
|
||||
19
crypto/vm/cellslice.h
Normal file
19
crypto/vm/cellslice.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/cells/CellSlice.h"
|
||||
877
crypto/vm/continuation.cpp
Normal file
877
crypto/vm/continuation.cpp
Normal file
|
|
@ -0,0 +1,877 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/dispatch.h"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/dict.h"
|
||||
#include "vm/log.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
int Continuation::jump_w(VmState* st) & {
|
||||
return static_cast<const Continuation*>(this)->jump(st);
|
||||
}
|
||||
|
||||
bool Continuation::has_c0() const {
|
||||
const ControlData* cont_data = get_cdata();
|
||||
return cont_data && cont_data->save.c[0].not_null();
|
||||
}
|
||||
|
||||
StackEntry ControlRegs::get(unsigned idx) const {
|
||||
if (idx < creg_num) {
|
||||
return get_c(idx);
|
||||
} else if (idx >= dreg_idx && idx < dreg_idx + dreg_num) {
|
||||
return get_d(idx);
|
||||
} else if (idx == 7) {
|
||||
return c7;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool ControlRegs::set(unsigned idx, StackEntry value) {
|
||||
if (idx < creg_num) {
|
||||
auto v = std::move(value).as_cont();
|
||||
return v.not_null() && set_c(idx, std::move(v));
|
||||
} else if (idx >= dreg_idx && idx < dreg_idx + dreg_num) {
|
||||
auto v = std::move(value).as_cell();
|
||||
return v.not_null() && set_d(idx, std::move(v));
|
||||
} else if (idx == 7) {
|
||||
auto v = std::move(value).as_tuple();
|
||||
return v.not_null() && set_c7(std::move(v));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ControlRegs::define(unsigned idx, StackEntry value) {
|
||||
if (idx < creg_num) {
|
||||
auto v = std::move(value).as_cont();
|
||||
return v.not_null() && define_c(idx, std::move(v));
|
||||
} else if (idx >= dreg_idx && idx < dreg_idx + dreg_num) {
|
||||
auto v = std::move(value).as_cell();
|
||||
return v.not_null() && define_d(idx, std::move(v));
|
||||
} else if (idx == 7) {
|
||||
auto v = std::move(value).as_tuple();
|
||||
return v.not_null() && define_c7(std::move(v));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ControlRegs& ControlRegs::operator^=(const ControlRegs& save) {
|
||||
for (int i = 0; i < creg_num; i++) {
|
||||
c[i] ^= save.c[i];
|
||||
}
|
||||
for (int i = 0; i < dreg_num; i++) {
|
||||
d[i] ^= save.d[i];
|
||||
}
|
||||
c7 ^= save.c7;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ControlRegs& ControlRegs::operator^=(ControlRegs&& save) {
|
||||
for (int i = 0; i < creg_num; i++) {
|
||||
c[i] ^= std::move(save.c[i]);
|
||||
}
|
||||
for (int i = 0; i < dreg_num; i++) {
|
||||
d[i] ^= std::move(save.d[i]);
|
||||
}
|
||||
c7 ^= std::move(save.c7);
|
||||
return *this;
|
||||
}
|
||||
|
||||
ControlRegs& ControlRegs::operator&=(const ControlRegs& save) {
|
||||
for (int i = 0; i < creg_num; i++) {
|
||||
c[i] &= save.c[i].is_null();
|
||||
}
|
||||
for (int i = 0; i < dreg_num; i++) {
|
||||
d[i] &= save.d[i].is_null();
|
||||
}
|
||||
c7 &= save.c7.is_null();
|
||||
return *this;
|
||||
}
|
||||
|
||||
int ExcQuitCont::jump(VmState* st) const & {
|
||||
int n = 0;
|
||||
try {
|
||||
n = st->get_stack().pop_smallint_range(0xffff);
|
||||
} catch (const VmError& vme) {
|
||||
n = vme.get_errno();
|
||||
}
|
||||
VM_LOG(st) << "default exception handler, terminating vm with exit code " << n;
|
||||
return ~n;
|
||||
}
|
||||
|
||||
int PushIntCont::jump(VmState* st) const & {
|
||||
VM_LOG(st) << "execute implicit PUSH " << push_val << " (slow)";
|
||||
st->get_stack().push_smallint(push_val);
|
||||
return st->jump(next);
|
||||
}
|
||||
|
||||
int PushIntCont::jump_w(VmState* st) & {
|
||||
VM_LOG(st) << "execute implicit PUSH " << push_val;
|
||||
st->get_stack().push_smallint(push_val);
|
||||
return st->jump(std::move(next));
|
||||
}
|
||||
|
||||
int ArgContExt::jump(VmState* st) const & {
|
||||
st->adjust_cr(data.save);
|
||||
if (data.cp != -1) {
|
||||
st->force_cp(data.cp);
|
||||
}
|
||||
return ext->jump(st);
|
||||
}
|
||||
|
||||
int ArgContExt::jump_w(VmState* st) & {
|
||||
st->adjust_cr(std::move(data.save));
|
||||
if (data.cp != -1) {
|
||||
st->force_cp(data.cp);
|
||||
}
|
||||
return st->jump_to(std::move(ext));
|
||||
}
|
||||
|
||||
int RepeatCont::jump(VmState* st) const & {
|
||||
VM_LOG(st) << "repeat " << count << " more times (slow)\n";
|
||||
if (count <= 0) {
|
||||
return st->jump(after);
|
||||
}
|
||||
if (body->has_c0()) {
|
||||
return st->jump(body);
|
||||
}
|
||||
st->set_c0(Ref<RepeatCont>{true, body, after, count - 1});
|
||||
return st->jump(body);
|
||||
}
|
||||
|
||||
int RepeatCont::jump_w(VmState* st) & {
|
||||
VM_LOG(st) << "repeat " << count << " more times\n";
|
||||
if (count <= 0) {
|
||||
body.clear();
|
||||
return st->jump(std::move(after));
|
||||
}
|
||||
if (body->has_c0()) {
|
||||
after.clear();
|
||||
return st->jump(std::move(body));
|
||||
}
|
||||
// optimization: since this is unique, reuse *this instead of creating new object
|
||||
--count;
|
||||
st->set_c0(Ref<RepeatCont>{this});
|
||||
return st->jump(body);
|
||||
}
|
||||
|
||||
int VmState::repeat(Ref<Continuation> body, Ref<Continuation> after, long long count) {
|
||||
if (count <= 0) {
|
||||
body.clear();
|
||||
return jump(std::move(after));
|
||||
} else {
|
||||
return jump(Ref<RepeatCont>{true, std::move(body), std::move(after), count});
|
||||
}
|
||||
}
|
||||
|
||||
int AgainCont::jump(VmState* st) const & {
|
||||
VM_LOG(st) << "again an infinite loop iteration (slow)\n";
|
||||
if (!body->has_c0()) {
|
||||
st->set_c0(Ref<AgainCont>{this});
|
||||
}
|
||||
return st->jump(body);
|
||||
}
|
||||
|
||||
int AgainCont::jump_w(VmState* st) & {
|
||||
VM_LOG(st) << "again an infinite loop iteration\n";
|
||||
if (!body->has_c0()) {
|
||||
st->set_c0(Ref<AgainCont>{this});
|
||||
return st->jump(body);
|
||||
} else {
|
||||
return st->jump(std::move(body));
|
||||
}
|
||||
}
|
||||
|
||||
int VmState::again(Ref<Continuation> body) {
|
||||
return jump(Ref<AgainCont>{true, std::move(body)});
|
||||
}
|
||||
|
||||
int UntilCont::jump(VmState* st) const & {
|
||||
VM_LOG(st) << "until loop body end (slow)\n";
|
||||
if (st->get_stack().pop_bool()) {
|
||||
VM_LOG(st) << "until loop terminated\n";
|
||||
return st->jump(after);
|
||||
}
|
||||
if (!body->has_c0()) {
|
||||
st->set_c0(Ref<UntilCont>{this});
|
||||
}
|
||||
return st->jump(body);
|
||||
}
|
||||
|
||||
int UntilCont::jump_w(VmState* st) & {
|
||||
VM_LOG(st) << "until loop body end\n";
|
||||
if (st->get_stack().pop_bool()) {
|
||||
VM_LOG(st) << "until loop terminated\n";
|
||||
body.clear();
|
||||
return st->jump(std::move(after));
|
||||
}
|
||||
if (!body->has_c0()) {
|
||||
st->set_c0(Ref<UntilCont>{this});
|
||||
return st->jump(body);
|
||||
} else {
|
||||
after.clear();
|
||||
return st->jump(std::move(body));
|
||||
}
|
||||
}
|
||||
|
||||
int VmState::until(Ref<Continuation> body, Ref<Continuation> after) {
|
||||
if (!body->has_c0()) {
|
||||
set_c0(Ref<UntilCont>{true, body, std::move(after)});
|
||||
}
|
||||
return jump(std::move(body));
|
||||
}
|
||||
|
||||
int WhileCont::jump(VmState* st) const & {
|
||||
if (chkcond) {
|
||||
VM_LOG(st) << "while loop condition end (slow)\n";
|
||||
if (!st->get_stack().pop_bool()) {
|
||||
VM_LOG(st) << "while loop terminated\n";
|
||||
return st->jump(after);
|
||||
}
|
||||
if (!body->has_c0()) {
|
||||
st->set_c0(Ref<WhileCont>{true, cond, body, after, false});
|
||||
}
|
||||
return st->jump(body);
|
||||
} else {
|
||||
VM_LOG(st) << "while loop body end (slow)\n";
|
||||
if (!cond->has_c0()) {
|
||||
st->set_c0(Ref<WhileCont>{true, cond, body, after, true});
|
||||
}
|
||||
return st->jump(cond);
|
||||
}
|
||||
}
|
||||
|
||||
int WhileCont::jump_w(VmState* st) & {
|
||||
if (chkcond) {
|
||||
VM_LOG(st) << "while loop condition end\n";
|
||||
if (!st->get_stack().pop_bool()) {
|
||||
VM_LOG(st) << "while loop terminated\n";
|
||||
cond.clear();
|
||||
body.clear();
|
||||
return st->jump(std::move(after));
|
||||
}
|
||||
if (!body->has_c0()) {
|
||||
chkcond = false; // re-use current object since we hold the unique pointer to it
|
||||
st->set_c0(Ref<WhileCont>{this});
|
||||
return st->jump(body);
|
||||
} else {
|
||||
cond.clear();
|
||||
after.clear();
|
||||
return st->jump(std::move(body));
|
||||
}
|
||||
} else {
|
||||
VM_LOG(st) << "while loop body end\n";
|
||||
if (!cond->has_c0()) {
|
||||
chkcond = true; // re-use current object
|
||||
st->set_c0(Ref<WhileCont>{this});
|
||||
return st->jump(cond);
|
||||
} else {
|
||||
body.clear();
|
||||
after.clear();
|
||||
return st->jump(std::move(cond));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int VmState::loop_while(Ref<Continuation> cond, Ref<Continuation> body, Ref<Continuation> after) {
|
||||
if (!cond->has_c0()) {
|
||||
set_c0(Ref<WhileCont>{true, cond, std::move(body), std::move(after), true});
|
||||
}
|
||||
return jump(std::move(cond));
|
||||
}
|
||||
|
||||
int OrdCont::jump(VmState* st) const & {
|
||||
st->adjust_cr(data.save);
|
||||
st->set_code(code, data.cp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int OrdCont::jump_w(VmState* st) & {
|
||||
st->adjust_cr(std::move(data.save));
|
||||
st->set_code(std::move(code), data.cp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void VmState::init_cregs(bool same_c3, bool push_0) {
|
||||
cr.set_c0(quit0);
|
||||
cr.set_c1(quit1);
|
||||
cr.set_c2(Ref<ExcQuitCont>{true});
|
||||
if (same_c3) {
|
||||
cr.set_c3(Ref<OrdCont>{true, code, cp});
|
||||
if (push_0) {
|
||||
VM_LOG(this) << "implicit PUSH 0 at start\n";
|
||||
get_stack().push_smallint(0);
|
||||
}
|
||||
} else {
|
||||
cr.set_c3(Ref<QuitCont>{true, 11});
|
||||
}
|
||||
if (cr.d[0].is_null() || cr.d[1].is_null()) {
|
||||
auto empty_cell = CellBuilder{}.finalize();
|
||||
for (int i = 0; i < ControlRegs::dreg_num; i++) {
|
||||
if (cr.d[i].is_null()) {
|
||||
cr.d[i] = empty_cell;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cr.c7.is_null()) {
|
||||
cr.set_c7(Ref<Tuple>{true});
|
||||
}
|
||||
}
|
||||
|
||||
VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) {
|
||||
ensure_throw(init_cp(0));
|
||||
init_cregs();
|
||||
}
|
||||
|
||||
VmState::VmState(Ref<CellSlice> _code)
|
||||
: code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) {
|
||||
ensure_throw(init_cp(0));
|
||||
init_cregs();
|
||||
}
|
||||
|
||||
VmState::VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries)
|
||||
: code(std::move(_code))
|
||||
, stack(std::move(_stack))
|
||||
, cp(-1)
|
||||
, dispatch(&dummy_dispatch_table)
|
||||
, quit0(true, 0)
|
||||
, quit1(true, 1)
|
||||
, log(log)
|
||||
, libraries(std::move(_libraries)) {
|
||||
ensure_throw(init_cp(0));
|
||||
set_c4(std::move(_data));
|
||||
init_cregs(flags & 1, flags & 2);
|
||||
}
|
||||
|
||||
VmState::VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& gas, int flags, Ref<Cell> _data, VmLog log,
|
||||
std::vector<Ref<Cell>> _libraries)
|
||||
: code(std::move(_code))
|
||||
, stack(std::move(_stack))
|
||||
, cp(-1)
|
||||
, dispatch(&dummy_dispatch_table)
|
||||
, quit0(true, 0)
|
||||
, quit1(true, 1)
|
||||
, log(log)
|
||||
, gas(gas)
|
||||
, libraries(std::move(_libraries)) {
|
||||
ensure_throw(init_cp(0));
|
||||
set_c4(std::move(_data));
|
||||
init_cregs(flags & 1, flags & 2);
|
||||
}
|
||||
|
||||
Ref<CellSlice> VmState::convert_code_cell(Ref<Cell> code_cell) {
|
||||
if (code_cell.is_null()) {
|
||||
return {};
|
||||
}
|
||||
Ref<CellSlice> csr{true, NoVmOrd(), code_cell};
|
||||
if (csr->is_valid()) {
|
||||
return csr;
|
||||
}
|
||||
return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize());
|
||||
}
|
||||
|
||||
bool VmState::init_cp(int new_cp) {
|
||||
const DispatchTable* dt = DispatchTable::get_table(new_cp);
|
||||
if (dt) {
|
||||
cp = new_cp;
|
||||
dispatch = dt;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool VmState::set_cp(int new_cp) {
|
||||
return new_cp == cp || init_cp(new_cp);
|
||||
}
|
||||
|
||||
void VmState::force_cp(int new_cp) {
|
||||
if (!set_cp(new_cp)) {
|
||||
throw VmError{Excno::inv_opcode, "unsupported codepage"};
|
||||
}
|
||||
}
|
||||
|
||||
// simple call to a continuation cont
|
||||
int VmState::call(Ref<Continuation> cont) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data) {
|
||||
if (cont_data->save.c[0].not_null()) {
|
||||
// call reduces to a jump
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
if (cont_data->stack.not_null() || cont_data->nargs >= 0) {
|
||||
// if cont has non-empty stack or expects fixed number of arguments, call is not simple
|
||||
return call(std::move(cont), -1, -1);
|
||||
}
|
||||
// create return continuation, to be stored into new c0
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
|
||||
cr.set_c0(
|
||||
std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
// create return continuation, to be stored into new c0
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
|
||||
// general implementation of a simple call
|
||||
cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
|
||||
// call with parameters to continuation cont
|
||||
int VmState::call(Ref<Continuation> cont, int pass_args, int ret_args) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data) {
|
||||
if (cont_data->save.c[0].not_null()) {
|
||||
// call reduces to a jump
|
||||
return jump(std::move(cont), pass_args);
|
||||
}
|
||||
int depth = stack->depth();
|
||||
if (pass_args > depth || cont_data->nargs > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"};
|
||||
}
|
||||
if (cont_data->nargs > pass_args && pass_args >= 0) {
|
||||
throw VmError{Excno::stk_und,
|
||||
"stack underflow while calling a closure continuation: not enough arguments passed"};
|
||||
}
|
||||
auto old_c0 = std::move(cr.c[0]);
|
||||
// optimization(?): decrease refcnts of unused continuations in c[i] as early as possible
|
||||
preclear_cr(cont_data->save);
|
||||
// no exceptions should be thrown after this point
|
||||
int copy = cont_data->nargs, skip = 0;
|
||||
if (pass_args >= 0) {
|
||||
if (copy >= 0) {
|
||||
skip = pass_args - copy;
|
||||
} else {
|
||||
copy = pass_args;
|
||||
}
|
||||
}
|
||||
// copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements.
|
||||
Ref<Stack> new_stk;
|
||||
if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) {
|
||||
// `cont` already has a stack, create resulting stack from it
|
||||
if (copy < 0) {
|
||||
copy = stack->depth();
|
||||
}
|
||||
if (cont->is_unique()) {
|
||||
// optimization: avoid copying stack if we hold the only copy of `cont`
|
||||
new_stk = std::move(cont.unique_write().get_cdata()->stack);
|
||||
} else {
|
||||
new_stk = cont_data->stack;
|
||||
}
|
||||
new_stk.write().move_from_stack(get_stack(), copy);
|
||||
if (skip > 0) {
|
||||
get_stack().pop_many(skip);
|
||||
}
|
||||
} else if (copy >= 0) {
|
||||
new_stk = get_stack().split_top(copy, skip);
|
||||
} else {
|
||||
new_stk = std::move(stack);
|
||||
stack.clear();
|
||||
}
|
||||
// create return continuation using the remainder of current stack
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0));
|
||||
Ref<OrdCont> ord_cont = static_cast<Ref<OrdCont>>(cont);
|
||||
set_stack(std::move(new_stk));
|
||||
cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0
|
||||
return jump_to(std::move(cont));
|
||||
} else {
|
||||
// have no continuation data, situation is somewhat simpler
|
||||
int depth = stack->depth();
|
||||
if (pass_args > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"};
|
||||
}
|
||||
// create new stack from the top `pass_args` elements of the current stack
|
||||
Ref<Stack> new_stk = (pass_args >= 0 ? get_stack().split_top(pass_args) : std::move(stack));
|
||||
// create return continuation using the remainder of the current stack
|
||||
Ref<OrdCont> ret = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), ret_args};
|
||||
ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0]));
|
||||
set_stack(std::move(new_stk));
|
||||
cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
// simple jump to continuation cont
|
||||
int VmState::jump(Ref<Continuation> cont) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) {
|
||||
// if cont has non-empty stack or expects fixed number of arguments, jump is not simple
|
||||
return jump(std::move(cont), -1);
|
||||
} else {
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
// general jump to continuation cont
|
||||
int VmState::jump(Ref<Continuation> cont, int pass_args) {
|
||||
const ControlData* cont_data = cont->get_cdata();
|
||||
if (cont_data) {
|
||||
// first do the checks
|
||||
int depth = stack->depth();
|
||||
if (pass_args > depth || cont_data->nargs > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"};
|
||||
}
|
||||
if (cont_data->nargs > pass_args && pass_args >= 0) {
|
||||
throw VmError{Excno::stk_und,
|
||||
"stack underflow while jumping to closure continuation: not enough arguments passed"};
|
||||
}
|
||||
// optimization(?): decrease refcnts of unused continuations in c[i] as early as possible
|
||||
preclear_cr(cont_data->save);
|
||||
// no exceptions should be thrown after this point
|
||||
int copy = cont_data->nargs;
|
||||
if (pass_args >= 0 && copy < 0) {
|
||||
copy = pass_args;
|
||||
}
|
||||
// copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder.
|
||||
if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) {
|
||||
// `cont` already has a stack, create resulting stack from it
|
||||
if (copy < 0) {
|
||||
copy = get_stack().depth();
|
||||
}
|
||||
Ref<Stack> new_stk;
|
||||
if (cont->is_unique()) {
|
||||
// optimization: avoid copying the stack if we hold the only copy of `cont`
|
||||
new_stk = std::move(cont.unique_write().get_cdata()->stack);
|
||||
} else {
|
||||
new_stk = cont_data->stack;
|
||||
}
|
||||
new_stk.write().move_from_stack(get_stack(), copy);
|
||||
set_stack(std::move(new_stk));
|
||||
} else {
|
||||
if (copy >= 0) {
|
||||
get_stack().drop_bottom(stack->depth() - copy);
|
||||
}
|
||||
}
|
||||
return jump_to(std::move(cont));
|
||||
} else {
|
||||
// have no continuation data, situation is somewhat simpler
|
||||
if (pass_args >= 0) {
|
||||
int depth = get_stack().depth();
|
||||
if (pass_args > depth) {
|
||||
throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"};
|
||||
}
|
||||
get_stack().drop_bottom(depth - pass_args);
|
||||
}
|
||||
return jump_to(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
int VmState::ret() {
|
||||
Ref<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret(int ret_args) {
|
||||
Ref<Continuation> cont = quit0;
|
||||
cont.swap(cr.c[0]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
int VmState::ret_alt() {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont));
|
||||
}
|
||||
|
||||
int VmState::ret_alt(int ret_args) {
|
||||
Ref<Continuation> cont = quit1;
|
||||
cont.swap(cr.c[1]);
|
||||
return jump(std::move(cont), ret_args);
|
||||
}
|
||||
|
||||
Ref<OrdCont> VmState::extract_cc(int save_cr, int stack_copy, int cc_args) {
|
||||
Ref<Stack> new_stk;
|
||||
if (stack_copy < 0 || stack_copy == stack->depth()) {
|
||||
new_stk = std::move(stack);
|
||||
stack.clear();
|
||||
} else if (stack_copy > 0) {
|
||||
stack->check_underflow(stack_copy);
|
||||
new_stk = get_stack().split_top(stack_copy);
|
||||
} else {
|
||||
new_stk = Ref<Stack>{true};
|
||||
}
|
||||
Ref<OrdCont> cc = Ref<OrdCont>{true, std::move(code), cp, std::move(stack), cc_args};
|
||||
stack = std::move(new_stk);
|
||||
if (save_cr & 7) {
|
||||
ControlData* cdata = cc.unique_write().get_cdata();
|
||||
if (save_cr & 1) {
|
||||
cdata->save.set_c0(std::move(cr.c[0]));
|
||||
cr.set_c0(quit0);
|
||||
}
|
||||
if (save_cr & 2) {
|
||||
cdata->save.set_c1(std::move(cr.c[1]));
|
||||
cr.set_c1(quit1);
|
||||
}
|
||||
if (save_cr & 4) {
|
||||
cdata->save.set_c2(std::move(cr.c[2]));
|
||||
// cr.set_c2(Ref<ExcQuitCont>{true});
|
||||
}
|
||||
}
|
||||
return cc;
|
||||
}
|
||||
|
||||
int VmState::throw_exception(int excno) {
|
||||
Stack& stack_ref = get_stack();
|
||||
stack_ref.clear();
|
||||
stack_ref.push_smallint(0);
|
||||
stack_ref.push_smallint(excno);
|
||||
code.clear();
|
||||
consume_gas(exception_gas_price);
|
||||
return jump(get_c2());
|
||||
}
|
||||
|
||||
int VmState::throw_exception(int excno, StackEntry&& arg) {
|
||||
Stack& stack_ref = get_stack();
|
||||
stack_ref.clear();
|
||||
stack_ref.push(std::move(arg));
|
||||
stack_ref.push_smallint(excno);
|
||||
code.clear();
|
||||
consume_gas(exception_gas_price);
|
||||
return jump(get_c2());
|
||||
}
|
||||
|
||||
void GasLimits::gas_exception() const {
|
||||
throw VmNoGas{};
|
||||
}
|
||||
|
||||
void GasLimits::set_limits(long long _max, long long _limit, long long _credit) {
|
||||
gas_max = _max;
|
||||
gas_limit = _limit;
|
||||
gas_credit = _credit;
|
||||
change_base(_limit + _credit);
|
||||
}
|
||||
|
||||
void GasLimits::change_limit(long long _limit) {
|
||||
_limit = std::min(std::max(_limit, 0LL), gas_max);
|
||||
gas_credit = 0;
|
||||
gas_limit = _limit;
|
||||
change_base(_limit);
|
||||
}
|
||||
|
||||
bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) {
|
||||
gas.set_limits(_max, _limit, _credit);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VmState::change_gas_limit(long long new_limit) {
|
||||
VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max);
|
||||
gas.change_limit(new_limit);
|
||||
}
|
||||
|
||||
int VmState::step() {
|
||||
assert(!code.is_null());
|
||||
//VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st));
|
||||
//VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl;
|
||||
if (stack_trace) {
|
||||
stack->dump(std::cerr);
|
||||
}
|
||||
++steps;
|
||||
if (code->size()) {
|
||||
return dispatch->dispatch(this, code.write());
|
||||
} else if (code->size_refs()) {
|
||||
VM_LOG(this) << "execute implicit JMPREF\n";
|
||||
Ref<Continuation> cont = Ref<OrdCont>{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()};
|
||||
return jump(std::move(cont));
|
||||
} else {
|
||||
VM_LOG(this) << "execute implicit RET\n";
|
||||
return ret();
|
||||
}
|
||||
}
|
||||
|
||||
int VmState::run() {
|
||||
int res;
|
||||
Guard guard(this);
|
||||
do {
|
||||
// LOG(INFO) << "[BS] data cells: " << DataCell::get_total_data_cells();
|
||||
try {
|
||||
try {
|
||||
res = step();
|
||||
gas.check();
|
||||
} catch (vm::CellBuilder::CellWriteError) {
|
||||
throw VmError{Excno::cell_ov};
|
||||
} catch (vm::CellBuilder::CellCreateError) {
|
||||
throw VmError{Excno::cell_ov};
|
||||
} catch (vm::CellSlice::CellReadError) {
|
||||
throw VmError{Excno::cell_und};
|
||||
}
|
||||
} catch (const VmError& vme) {
|
||||
VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg();
|
||||
try {
|
||||
// LOG(INFO) << "[EX] data cells: " << DataCell::get_total_data_cells();
|
||||
++steps;
|
||||
res = throw_exception(vme.get_errno());
|
||||
} catch (const VmError& vme2) {
|
||||
VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg();
|
||||
// LOG(INFO) << "[EXX] data cells: " << DataCell::get_total_data_cells();
|
||||
return ~vme2.get_errno();
|
||||
}
|
||||
} catch (VmNoGas vmoog) {
|
||||
++steps;
|
||||
VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed()
|
||||
<< ", limit=" << gas.gas_limit;
|
||||
get_stack().clear();
|
||||
get_stack().push_smallint(gas.gas_consumed());
|
||||
return vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible)
|
||||
}
|
||||
} while (!res);
|
||||
// LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells();
|
||||
return res;
|
||||
}
|
||||
|
||||
ControlData* force_cdata(Ref<Continuation>& cont) {
|
||||
if (!cont->get_cdata()) {
|
||||
cont = Ref<ArgContExt>{true, cont};
|
||||
return cont.unique_write().get_cdata();
|
||||
} else {
|
||||
return cont.write().get_cdata();
|
||||
}
|
||||
}
|
||||
|
||||
ControlRegs* force_cregs(Ref<Continuation>& cont) {
|
||||
return &force_cdata(cont)->save;
|
||||
}
|
||||
|
||||
int run_vm_code(Ref<CellSlice> code, Ref<Stack>& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries) {
|
||||
VmState vm{
|
||||
code, std::move(stack), gas_limits ? *gas_limits : GasLimits{}, flags, data_ptr ? *data_ptr : Ref<Cell>{},
|
||||
log, std::move(libraries)};
|
||||
int res = vm.run();
|
||||
stack = vm.get_stack_ref();
|
||||
if (res == -1 && data_ptr) {
|
||||
*data_ptr = vm.get_c4();
|
||||
}
|
||||
if (steps) {
|
||||
*steps = vm.get_steps_count();
|
||||
}
|
||||
if (gas_limits) {
|
||||
*gas_limits = vm.get_gas_limits();
|
||||
LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed()
|
||||
<< ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit
|
||||
<< ", credit=" << gas_limits->gas_credit;
|
||||
}
|
||||
if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) {
|
||||
VM_LOG(&vm) << "BEGIN_STACK_DUMP";
|
||||
for (int i = stack->depth(); i > 0; i--) {
|
||||
VM_LOG(&vm) << (*stack)[i - 1].to_string();
|
||||
}
|
||||
VM_LOG(&vm) << "END_STACK_DUMP";
|
||||
}
|
||||
|
||||
return ~res;
|
||||
}
|
||||
|
||||
int run_vm_code(Ref<CellSlice> code, Stack& stack, int flags, Ref<Cell>* data_ptr, VmLog log, long long* steps,
|
||||
GasLimits* gas_limits, std::vector<Ref<Cell>> libraries) {
|
||||
Ref<Stack> stk{true};
|
||||
stk.unique_write().set_contents(std::move(stack));
|
||||
stack.clear();
|
||||
int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries));
|
||||
CHECK(stack.is_unique());
|
||||
if (stk.is_null()) {
|
||||
stack.clear();
|
||||
} else if (&(*stk) != &stack) {
|
||||
VmState* st = nullptr;
|
||||
if (stk->is_unique()) {
|
||||
VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)";
|
||||
stack.set_contents(std::move(stk.unique_write()));
|
||||
} else {
|
||||
VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)";
|
||||
stack.set_contents(*stk);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// may throw a dictionary exception; returns nullptr if library is not found in context
|
||||
Ref<Cell> VmState::load_library(td::ConstBitPtr hash) {
|
||||
std::unique_ptr<VmStateInterface> tmp_ctx;
|
||||
// install temporary dummy vm state interface to prevent charging for cell load operations during library lookup
|
||||
VmStateInterface::Guard(tmp_ctx.get());
|
||||
for (const auto& lib_collection : libraries) {
|
||||
auto lib = lookup_library_in(hash, lib_collection);
|
||||
if (lib.not_null()) {
|
||||
return lib;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool VmState::register_library_collection(Ref<Cell> lib) {
|
||||
if (lib.is_null()) {
|
||||
return true;
|
||||
}
|
||||
libraries.push_back(std::move(lib));
|
||||
return true;
|
||||
}
|
||||
|
||||
void VmState::register_cell_load() {
|
||||
consume_gas(cell_load_gas_price);
|
||||
}
|
||||
|
||||
void VmState::register_cell_create() {
|
||||
consume_gas(cell_create_gas_price);
|
||||
}
|
||||
|
||||
td::BitArray<256> VmState::get_state_hash() const {
|
||||
// TODO: implement properly, by serializing the stack etc, and computing the Merkle hash
|
||||
td::BitArray<256> res;
|
||||
res.clear();
|
||||
return res;
|
||||
}
|
||||
|
||||
td::BitArray<256> VmState::get_final_state_hash(int exit_code) const {
|
||||
// TODO: implement properly, by serializing the stack etc, and computing the Merkle hash
|
||||
td::BitArray<256> res;
|
||||
res.clear();
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) {
|
||||
try {
|
||||
auto val = dict.lookup(key, 256);
|
||||
if (val.is_null() || !val->have_refs()) {
|
||||
return {};
|
||||
}
|
||||
auto root = val->prefetch_ref();
|
||||
if (root.not_null() && !root->get_hash().bits().compare(key, 256)) {
|
||||
return root;
|
||||
}
|
||||
return {};
|
||||
} catch (vm::VmError) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root) {
|
||||
if (lib_root.is_null()) {
|
||||
return lib_root;
|
||||
}
|
||||
vm::Dictionary dict{std::move(lib_root), 256};
|
||||
return lookup_library_in(key, dict);
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
585
crypto/vm/continuation.h
Normal file
585
crypto/vm/continuation.h
Normal file
|
|
@ -0,0 +1,585 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "common/refcnt.hpp"
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/vmstate.h"
|
||||
#include "vm/log.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
using td::Ref;
|
||||
|
||||
class VmState;
|
||||
class Continuation;
|
||||
class DispatchTable;
|
||||
|
||||
struct ControlRegs {
|
||||
static constexpr int creg_num = 4, dreg_num = 2, dreg_idx = 4;
|
||||
Ref<Continuation> c[creg_num]; // c0..c3
|
||||
Ref<Cell> d[dreg_num]; // c4..c5
|
||||
Ref<Tuple> c7; // c7
|
||||
Ref<Continuation> get_c(unsigned idx) const {
|
||||
return idx < creg_num ? c[idx] : Ref<Continuation>{};
|
||||
}
|
||||
Ref<Cell> get_d(unsigned idx) const {
|
||||
idx -= dreg_idx;
|
||||
return idx < dreg_num ? d[idx] : Ref<Cell>{};
|
||||
}
|
||||
Ref<Tuple> get_c7() const {
|
||||
return c7;
|
||||
}
|
||||
StackEntry get(unsigned idx) const;
|
||||
static bool valid_idx(unsigned idx) {
|
||||
return idx < creg_num || (idx >= dreg_idx && idx < dreg_idx + dreg_num) || idx == 7;
|
||||
}
|
||||
void set_c0(Ref<Continuation> cont) {
|
||||
c[0] = std::move(cont);
|
||||
}
|
||||
void set_c1(Ref<Continuation> cont) {
|
||||
c[1] = std::move(cont);
|
||||
}
|
||||
void set_c2(Ref<Continuation> cont) {
|
||||
c[2] = std::move(cont);
|
||||
}
|
||||
void set_c3(Ref<Continuation> cont) {
|
||||
c[3] = std::move(cont);
|
||||
}
|
||||
void set_c4(Ref<Cell> cell) {
|
||||
d[0] = std::move(cell);
|
||||
}
|
||||
bool set_c(unsigned idx, Ref<Continuation> cont) {
|
||||
if (idx < creg_num) {
|
||||
c[idx] = std::move(cont);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool set_d(unsigned idx, Ref<Cell> cell) {
|
||||
idx -= dreg_idx;
|
||||
if (idx < dreg_num) {
|
||||
d[idx] = std::move(cell);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool set_c7(Ref<Tuple> tuple) {
|
||||
c7 = std::move(tuple);
|
||||
return true;
|
||||
}
|
||||
bool set(unsigned idx, StackEntry value);
|
||||
void define_c0(Ref<Continuation> cont) {
|
||||
if (c[0].is_null()) {
|
||||
c[0] = std::move(cont);
|
||||
}
|
||||
}
|
||||
void define_c1(Ref<Continuation> cont) {
|
||||
if (c[1].is_null()) {
|
||||
c[1] = std::move(cont);
|
||||
}
|
||||
}
|
||||
void define_c2(Ref<Continuation> cont) {
|
||||
if (c[2].is_null()) {
|
||||
c[2] = std::move(cont);
|
||||
}
|
||||
}
|
||||
bool define_c(unsigned idx, Ref<Continuation> cont) {
|
||||
if (idx < creg_num && c[idx].is_null()) {
|
||||
c[idx] = std::move(cont);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool define_d(unsigned idx, Ref<Cell> cell) {
|
||||
idx -= dreg_idx;
|
||||
if (idx < dreg_num && d[idx].is_null()) {
|
||||
d[idx] = std::move(cell);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void define_c4(Ref<Cell> cell) {
|
||||
if (d[0].is_null()) {
|
||||
d[0] = std::move(cell);
|
||||
}
|
||||
}
|
||||
bool define_c7(Ref<Tuple> tuple) {
|
||||
if (c7.is_null()) {
|
||||
c7 = std::move(tuple);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool define(unsigned idx, StackEntry value);
|
||||
ControlRegs& operator&=(const ControlRegs& save); // clears all c[i]'s which are present in save
|
||||
ControlRegs& operator^=(const ControlRegs& save); // sets c[i]=save.c[i] for all save.c[i] != 0
|
||||
ControlRegs& operator^=(ControlRegs&& save);
|
||||
};
|
||||
|
||||
struct ControlData {
|
||||
Ref<Stack> stack;
|
||||
ControlRegs save;
|
||||
int nargs;
|
||||
int cp;
|
||||
ControlData() : nargs(-1), cp(-1) {
|
||||
}
|
||||
ControlData(int _cp) : nargs(-1), cp(_cp) {
|
||||
}
|
||||
ControlData(Ref<Stack> _stack) : stack(std::move(_stack)), nargs(-1), cp(-1) {
|
||||
}
|
||||
ControlData(int _cp, Ref<Stack> _stack, int _nargs = -1) : stack(std::move(_stack)), nargs(_nargs), cp(_cp) {
|
||||
}
|
||||
};
|
||||
|
||||
class Continuation : public td::CntObject {
|
||||
public:
|
||||
virtual int jump(VmState* st) const & = 0;
|
||||
virtual int jump_w(VmState* st) &;
|
||||
virtual ControlData* get_cdata() {
|
||||
return 0;
|
||||
}
|
||||
virtual const ControlData* get_cdata() const {
|
||||
return 0;
|
||||
}
|
||||
bool has_c0() const;
|
||||
Continuation() {
|
||||
}
|
||||
Continuation(const Continuation&) = default;
|
||||
Continuation(Continuation&&) {
|
||||
}
|
||||
Continuation& operator=(const Continuation&) {
|
||||
return *this;
|
||||
}
|
||||
Continuation& operator=(Continuation&&) {
|
||||
return *this;
|
||||
}
|
||||
~Continuation() override = default;
|
||||
};
|
||||
|
||||
class QuitCont : public Continuation {
|
||||
int exit_code;
|
||||
|
||||
public:
|
||||
QuitCont(int _code = 0) : exit_code(_code) {
|
||||
}
|
||||
~QuitCont() override = default;
|
||||
int jump(VmState* st) const & override {
|
||||
return ~exit_code;
|
||||
}
|
||||
};
|
||||
|
||||
class ExcQuitCont : public Continuation {
|
||||
public:
|
||||
ExcQuitCont() = default;
|
||||
~ExcQuitCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
};
|
||||
|
||||
class PushIntCont : public Continuation {
|
||||
int push_val;
|
||||
Ref<Continuation> next;
|
||||
|
||||
public:
|
||||
PushIntCont(int val, Ref<Continuation> _next) : push_val(val), next(_next) {
|
||||
}
|
||||
~PushIntCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
class RepeatCont : public Continuation {
|
||||
Ref<Continuation> body, after;
|
||||
long long count;
|
||||
|
||||
public:
|
||||
RepeatCont(Ref<Continuation> _body, Ref<Continuation> _after, long long _count)
|
||||
: body(std::move(_body)), after(std::move(_after)), count(_count) {
|
||||
}
|
||||
~RepeatCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
class AgainCont : public Continuation {
|
||||
Ref<Continuation> body;
|
||||
|
||||
public:
|
||||
AgainCont(Ref<Continuation> _body) : body(std::move(_body)) {
|
||||
}
|
||||
~AgainCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
class UntilCont : public Continuation {
|
||||
Ref<Continuation> body, after;
|
||||
|
||||
public:
|
||||
UntilCont(Ref<Continuation> _body, Ref<Continuation> _after) : body(std::move(_body)), after(std::move(_after)) {
|
||||
}
|
||||
~UntilCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
class WhileCont : public Continuation {
|
||||
Ref<Continuation> cond, body, after;
|
||||
bool chkcond;
|
||||
|
||||
public:
|
||||
WhileCont(Ref<Continuation> _cond, Ref<Continuation> _body, Ref<Continuation> _after, bool _chk = true)
|
||||
: cond(std::move(_cond)), body(std::move(_body)), after(std::move(_after)), chkcond(_chk) {
|
||||
}
|
||||
~WhileCont() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump_w(VmState* st) & override;
|
||||
};
|
||||
|
||||
class ArgContExt : public Continuation {
|
||||
ControlData data;
|
||||
Ref<Continuation> ext;
|
||||
|
||||
public:
|
||||
ArgContExt(Ref<Continuation> _ext) : data(), ext(_ext) {
|
||||
}
|
||||
ArgContExt(Ref<Continuation> _ext, Ref<Stack> _stack) : data(_stack), ext(_ext) {
|
||||
}
|
||||
ArgContExt(const ArgContExt&) = default;
|
||||
ArgContExt(ArgContExt&&) = default;
|
||||
~ArgContExt() override = default;
|
||||
int jump(VmState* st) const & override;
|
||||
int jump_w(VmState* st) & override;
|
||||
ControlData* get_cdata() override {
|
||||
return &data;
|
||||
}
|
||||
const ControlData* get_cdata() const override {
|
||||
return &data;
|
||||
}
|
||||
td::CntObject* make_copy() const override {
|
||||
return new ArgContExt{*this};
|
||||
}
|
||||
};
|
||||
|
||||
class OrdCont : public Continuation {
|
||||
ControlData data;
|
||||
Ref<CellSlice> code;
|
||||
friend class VmState;
|
||||
|
||||
public:
|
||||
OrdCont() : data(), code() {
|
||||
}
|
||||
//OrdCont(Ref<CellSlice> _code) : data(), code(std::move(_code)) {}
|
||||
OrdCont(Ref<CellSlice> _code, int _cp) : data(_cp), code(std::move(_code)) {
|
||||
}
|
||||
//OrdCont(Ref<CellSlice> _code, Ref<Stack> _stack) : data(std::move(_stack)), code(std::move(_code)) {}
|
||||
OrdCont(Ref<CellSlice> _code, int _cp, Ref<Stack> _stack, int nargs = -1)
|
||||
: data(_cp, std::move(_stack), nargs), code(std::move(_code)) {
|
||||
}
|
||||
OrdCont(const OrdCont&) = default;
|
||||
OrdCont(OrdCont&&) = default;
|
||||
~OrdCont() override = default;
|
||||
|
||||
td::CntObject* make_copy() const override {
|
||||
return new OrdCont{*this};
|
||||
}
|
||||
int jump(VmState* st) const & override;
|
||||
int jump_w(VmState* st) & override;
|
||||
|
||||
ControlData* get_cdata() override {
|
||||
return &data;
|
||||
}
|
||||
const ControlData* get_cdata() const override {
|
||||
return &data;
|
||||
}
|
||||
Stack& get_stack() {
|
||||
return data.stack.write();
|
||||
}
|
||||
const Stack& get_stack_const() const {
|
||||
return *(data.stack);
|
||||
}
|
||||
Ref<Stack> get_stack_ref() const {
|
||||
return data.stack;
|
||||
}
|
||||
Ref<OrdCont> copy_ord() const & {
|
||||
return Ref<OrdCont>{true, *this};
|
||||
}
|
||||
Ref<OrdCont> copy_ord() && {
|
||||
return Ref<OrdCont>{true, *this};
|
||||
}
|
||||
};
|
||||
|
||||
struct GasLimits {
|
||||
static constexpr long long infty = (1ULL << 63) - 1;
|
||||
long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base;
|
||||
GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) {
|
||||
}
|
||||
GasLimits(long long _limit, long long _max = infty, long long _credit = 0)
|
||||
: gas_max(_max)
|
||||
, gas_limit(_limit)
|
||||
, gas_credit(_credit)
|
||||
, gas_remaining(_limit + _credit)
|
||||
, gas_base(gas_remaining) {
|
||||
}
|
||||
long long gas_consumed() const {
|
||||
return gas_base - gas_remaining;
|
||||
}
|
||||
void set_limits(long long _max, long long _limit, long long _credit = 0);
|
||||
void change_base(long long _base) {
|
||||
gas_remaining += _base - gas_base;
|
||||
gas_base = _base;
|
||||
}
|
||||
void change_limit(long long _limit);
|
||||
void consume(long long amount) {
|
||||
// LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)";
|
||||
gas_remaining -= amount;
|
||||
}
|
||||
bool try_consume(long long amount) {
|
||||
// LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)";
|
||||
return (gas_remaining -= amount) >= 0;
|
||||
}
|
||||
void gas_exception() const;
|
||||
void gas_exception(bool cond) const {
|
||||
if (!cond) {
|
||||
gas_exception();
|
||||
}
|
||||
}
|
||||
void consume_chk(long long amount) {
|
||||
gas_exception(try_consume(amount));
|
||||
}
|
||||
void check() const {
|
||||
gas_exception(gas_remaining >= 0);
|
||||
}
|
||||
bool final_ok() const {
|
||||
return gas_remaining >= gas_credit;
|
||||
}
|
||||
};
|
||||
|
||||
class VmState final : public VmStateInterface {
|
||||
Ref<CellSlice> code;
|
||||
Ref<Stack> stack;
|
||||
ControlRegs cr;
|
||||
int cp;
|
||||
long long steps{0};
|
||||
const DispatchTable* dispatch;
|
||||
Ref<QuitCont> quit0, quit1;
|
||||
VmLog log;
|
||||
GasLimits gas;
|
||||
std::vector<Ref<Cell>> libraries;
|
||||
int stack_trace{0}, debug_off{0};
|
||||
|
||||
public:
|
||||
static constexpr unsigned cell_load_gas_price = 100, cell_create_gas_price = 500, exception_gas_price = 50,
|
||||
tuple_entry_gas_price = 1;
|
||||
VmState();
|
||||
VmState(Ref<CellSlice> _code);
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, int flags = 0, Ref<Cell> _data = {}, VmLog log = {},
|
||||
std::vector<Ref<Cell>> _libraries = {});
|
||||
VmState(Ref<CellSlice> _code, Ref<Stack> _stack, const GasLimits& _gas, int flags = 0, Ref<Cell> _data = {},
|
||||
VmLog log = {}, std::vector<Ref<Cell>> _libraries = {});
|
||||
template <typename... Args>
|
||||
VmState(Ref<Cell> code_cell, Args&&... args)
|
||||
: VmState(convert_code_cell(std::move(code_cell)), std::forward<Args>(args)...) {
|
||||
}
|
||||
bool set_gas_limits(long long _max, long long _limit, long long _credit = 0);
|
||||
bool final_gas_ok() const {
|
||||
return gas.final_ok();
|
||||
}
|
||||
long long gas_consumed() const {
|
||||
return gas.gas_consumed();
|
||||
}
|
||||
void consume_gas(long long amount) {
|
||||
gas.consume(amount);
|
||||
}
|
||||
void consume_tuple_gas(unsigned tuple_len) {
|
||||
consume_gas(tuple_len * tuple_entry_gas_price);
|
||||
}
|
||||
void consume_tuple_gas(const Ref<vm::Tuple>& tup) {
|
||||
if (tup.not_null()) {
|
||||
consume_tuple_gas((unsigned)tup->size());
|
||||
}
|
||||
}
|
||||
GasLimits get_gas_limits() const {
|
||||
return gas;
|
||||
}
|
||||
void change_gas_limit(long long new_limit);
|
||||
template <typename... Args>
|
||||
void check_underflow(Args... args) {
|
||||
stack->check_underflow(args...);
|
||||
}
|
||||
bool register_library_collection(Ref<Cell> lib);
|
||||
Ref<Cell> load_library(
|
||||
td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found
|
||||
void register_cell_load() override;
|
||||
void register_cell_create() override;
|
||||
bool init_cp(int new_cp);
|
||||
bool set_cp(int new_cp);
|
||||
void force_cp(int new_cp);
|
||||
int get_cp() const {
|
||||
return cp;
|
||||
}
|
||||
int incr_stack_trace(int v) {
|
||||
return stack_trace += v;
|
||||
}
|
||||
long long get_steps_count() const {
|
||||
return steps;
|
||||
}
|
||||
td::BitArray<256> get_state_hash() const;
|
||||
td::BitArray<256> get_final_state_hash(int exit_code) const;
|
||||
int step();
|
||||
int run();
|
||||
Stack& get_stack() {
|
||||
return stack.write();
|
||||
}
|
||||
const Stack& get_stack_const() const {
|
||||
return *stack;
|
||||
}
|
||||
Ref<Stack> get_stack_ref() const {
|
||||
return stack;
|
||||
}
|
||||
Ref<Continuation> get_c0() const {
|
||||
return cr.c[0];
|
||||
}
|
||||
Ref<Continuation> get_c1() const {
|
||||
return cr.c[1];
|
||||
}
|
||||
Ref<Continuation> get_c2() const {
|
||||
return cr.c[2];
|
||||
}
|
||||
Ref<Continuation> get_c3() const {
|
||||
return cr.c[3];
|
||||
}
|
||||
Ref<Cell> get_c4() const {
|
||||
return cr.d[0];
|
||||
}
|
||||
Ref<Tuple> get_c7() const {
|
||||
return cr.c7;
|
||||
}
|
||||
Ref<Continuation> get_c(unsigned idx) const {
|
||||
return cr.get_c(idx);
|
||||
}
|
||||
Ref<Cell> get_d(unsigned idx) const {
|
||||
return cr.get_d(idx);
|
||||
}
|
||||
StackEntry get(unsigned idx) const {
|
||||
return cr.get(idx);
|
||||
}
|
||||
const VmLog& get_log() const {
|
||||
return log;
|
||||
}
|
||||
void define_c0(Ref<Continuation> cont) {
|
||||
cr.define_c0(std::move(cont));
|
||||
}
|
||||
void set_c0(Ref<Continuation> cont) {
|
||||
cr.set_c0(std::move(cont));
|
||||
}
|
||||
void set_c1(Ref<Continuation> cont) {
|
||||
cr.set_c1(std::move(cont));
|
||||
}
|
||||
void set_c2(Ref<Continuation> cont) {
|
||||
cr.set_c2(std::move(cont));
|
||||
}
|
||||
bool set_c(unsigned idx, Ref<Continuation> val) {
|
||||
return cr.set_c(idx, std::move(val));
|
||||
}
|
||||
bool set_d(unsigned idx, Ref<Cell> val) {
|
||||
return cr.set_d(idx, std::move(val));
|
||||
}
|
||||
void set_c4(Ref<Cell> val) {
|
||||
cr.set_c4(std::move(val));
|
||||
}
|
||||
bool set_c7(Ref<Tuple> val) {
|
||||
return cr.set_c7(std::move(val));
|
||||
}
|
||||
bool set(unsigned idx, StackEntry val) {
|
||||
return cr.set(idx, std::move(val));
|
||||
}
|
||||
void set_stack(Ref<Stack> new_stk) {
|
||||
stack = std::move(new_stk);
|
||||
}
|
||||
Ref<Stack> swap_stack(Ref<Stack> new_stk) {
|
||||
stack.swap(new_stk);
|
||||
return new_stk;
|
||||
}
|
||||
void ensure_throw(bool cond) const {
|
||||
if (!cond) {
|
||||
fatal();
|
||||
}
|
||||
}
|
||||
void set_code(Ref<CellSlice> _code, int _cp) {
|
||||
code = std::move(_code);
|
||||
force_cp(_cp);
|
||||
}
|
||||
Ref<CellSlice> get_code() const {
|
||||
return code;
|
||||
}
|
||||
void push_code() {
|
||||
get_stack().push_cellslice(get_code());
|
||||
}
|
||||
void adjust_cr(const ControlRegs& save) {
|
||||
cr ^= save;
|
||||
}
|
||||
void adjust_cr(ControlRegs&& save) {
|
||||
cr ^= save;
|
||||
}
|
||||
void preclear_cr(const ControlRegs& save) {
|
||||
cr &= save;
|
||||
}
|
||||
int call(Ref<Continuation> cont);
|
||||
int call(Ref<Continuation> cont, int pass_args, int ret_args = -1);
|
||||
int jump(Ref<Continuation> cont);
|
||||
int jump(Ref<Continuation> cont, int pass_args);
|
||||
int ret();
|
||||
int ret(int ret_args);
|
||||
int ret_alt();
|
||||
int ret_alt(int ret_args);
|
||||
int repeat(Ref<Continuation> body, Ref<Continuation> after, long long count);
|
||||
int again(Ref<Continuation> body);
|
||||
int until(Ref<Continuation> body, Ref<Continuation> after);
|
||||
int loop_while(Ref<Continuation> cond, Ref<Continuation> body, Ref<Continuation> after);
|
||||
int throw_exception(int excno, StackEntry&& arg);
|
||||
int throw_exception(int excno);
|
||||
Ref<OrdCont> extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1);
|
||||
void fatal(void) const {
|
||||
throw VmFatal{};
|
||||
}
|
||||
int jump_to(Ref<Continuation> cont) {
|
||||
return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this);
|
||||
}
|
||||
static Ref<CellSlice> convert_code_cell(Ref<Cell> code_cell);
|
||||
|
||||
private:
|
||||
void init_cregs(bool same_c3 = false, bool push_0 = true);
|
||||
};
|
||||
|
||||
int run_vm_code(Ref<CellSlice> _code, Ref<Stack>& _stack, int flags = 0, Ref<Cell>* data_ptr = 0, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {});
|
||||
int run_vm_code(Ref<CellSlice> _code, Stack& _stack, int flags = 0, Ref<Cell>* data_ptr = 0, VmLog log = {},
|
||||
long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector<Ref<Cell>> libraries = {});
|
||||
|
||||
ControlData* force_cdata(Ref<Continuation>& cont);
|
||||
ControlRegs* force_cregs(Ref<Continuation>& cont);
|
||||
|
||||
Ref<vm::Cell> lookup_library_in(td::ConstBitPtr key, Ref<vm::Cell> lib_root);
|
||||
|
||||
} // namespace vm
|
||||
1043
crypto/vm/contops.cpp
Normal file
1043
crypto/vm/contops.cpp
Normal file
File diff suppressed because it is too large
Load diff
28
crypto/vm/contops.h
Normal file
28
crypto/vm/contops.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace vm {
|
||||
|
||||
class OpcodeTable;
|
||||
|
||||
void register_continuation_ops(OpcodeTable& cp0);
|
||||
void register_codepage_ops(OpcodeTable& cp0);
|
||||
|
||||
} // namespace vm
|
||||
50
crypto/vm/cp0.cpp
Normal file
50
crypto/vm/cp0.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "cp0.h"
|
||||
#include "opctable.h"
|
||||
#include "stackops.h"
|
||||
#include "tupleops.h"
|
||||
#include "arithops.h"
|
||||
#include "cellops.h"
|
||||
#include "contops.h"
|
||||
#include "dictops.h"
|
||||
#include "debugops.h"
|
||||
#include "tonops.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
const OpcodeTable* init_op_cp0() {
|
||||
static const OpcodeTable* static_op_cp0 = [] {
|
||||
auto op_cp0 = new OpcodeTable("TEST CODEPAGE", Codepage::test_cp);
|
||||
register_stack_ops(*op_cp0); // stackops.cpp
|
||||
register_tuple_ops(*op_cp0); // tupleops.cpp
|
||||
register_arith_ops(*op_cp0); // arithops.cpp
|
||||
register_cell_ops(*op_cp0); // cellops.cpp
|
||||
register_continuation_ops(*op_cp0); // contops.cpp
|
||||
register_dictionary_ops(*op_cp0); // dictops.cpp
|
||||
register_ton_ops(*op_cp0); // tonops.cpp
|
||||
register_debug_ops(*op_cp0); // debugops.cpp
|
||||
register_codepage_ops(*op_cp0); // contops.cpp
|
||||
op_cp0->finalize()->register_table(Codepage::test_cp);
|
||||
return op_cp0;
|
||||
}();
|
||||
return static_op_cp0;
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
28
crypto/vm/cp0.h
Normal file
28
crypto/vm/cp0.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/dispatch.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
class OpcodeTable;
|
||||
|
||||
const OpcodeTable* init_op_cp0();
|
||||
|
||||
} // namespace vm
|
||||
181
crypto/vm/db/BlobView.cpp
Normal file
181
crypto/vm/db/BlobView.cpp
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/db/BlobView.h"
|
||||
|
||||
#include "td/utils/port/FileFd.h"
|
||||
#include "td/utils/HashMap.h"
|
||||
|
||||
#include "td/utils/format.h"
|
||||
#include "td/utils/port/RwMutex.h"
|
||||
#include "td/utils/port/MemoryMapping.h"
|
||||
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
|
||||
namespace vm {
|
||||
td::Result<td::Slice> BlobView::view(td::MutableSlice slice, td::uint64 offset) {
|
||||
if (offset > size() || slice.size() > size() - offset) {
|
||||
return td::Status::Error(PSLICE() << "BlobView: invalid range requested " << td::tag("slice offset", offset)
|
||||
<< td::tag("slice size", slice.size()) << td::tag("blob size", size()));
|
||||
}
|
||||
return view_impl(slice, offset);
|
||||
}
|
||||
namespace {
|
||||
class BufferSliceBlobViewImpl : public BlobView {
|
||||
public:
|
||||
BufferSliceBlobViewImpl(td::BufferSlice slice) : slice_(std::move(slice)) {
|
||||
}
|
||||
td::Result<td::Slice> view_impl(td::MutableSlice slice, td::uint64 offset) override {
|
||||
// optimize anyway
|
||||
if (offset > std::numeric_limits<std::size_t>::max()) {
|
||||
return td::Slice();
|
||||
}
|
||||
return slice_.as_slice().substr(static_cast<std::size_t>(offset), slice.size());
|
||||
}
|
||||
td::uint64 size() override {
|
||||
return slice_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
td::BufferSlice slice_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<BlobView> BufferSliceBlobView::create(td::BufferSlice slice) {
|
||||
return std::make_unique<BufferSliceBlobViewImpl>(std::move(slice));
|
||||
}
|
||||
|
||||
class FileBlobViewImpl : public BlobView {
|
||||
public:
|
||||
FileBlobViewImpl(td::FileFd fd, td::uint64 file_size) : fd_(std::move(fd)), file_size_(file_size) {
|
||||
}
|
||||
|
||||
td::uint64 size() override {
|
||||
return file_size_;
|
||||
}
|
||||
td::Result<td::Slice> view_impl(td::MutableSlice slice, td::uint64 offset) override {
|
||||
CHECK(offset < size());
|
||||
CHECK(size() - offset >= slice.size());
|
||||
slice.truncate(file_size_ - offset);
|
||||
auto first_page = offset / page_size;
|
||||
auto last_page = (offset + slice.size() - 1) / page_size;
|
||||
td::uint64 res_offset = 0;
|
||||
for (auto page_i = first_page; page_i <= last_page; page_i++) {
|
||||
auto page_offset = page_i * page_size;
|
||||
auto from = td::max(page_offset, offset);
|
||||
auto till = td::min(page_offset + page_size, offset + slice.size());
|
||||
CHECK(from < till);
|
||||
TRY_RESULT(page, load_page(page_i));
|
||||
auto len = till - from;
|
||||
slice.substr(res_offset, len).copy_from(page.substr(from - page_offset, len));
|
||||
res_offset += len;
|
||||
}
|
||||
CHECK(slice.size() == res_offset);
|
||||
total_view_size_ += slice.size();
|
||||
return slice;
|
||||
}
|
||||
~FileBlobViewImpl() {
|
||||
//LOG(ERROR) << "LOADED " << pages_.size() << " " << total_view_size_;
|
||||
}
|
||||
|
||||
private:
|
||||
td::FileFd fd_;
|
||||
td::uint64 file_size_;
|
||||
const td::uint64 page_size = 4096;
|
||||
td::uint64 total_view_size_{0};
|
||||
|
||||
td::RwMutex pages_rw_mutex_;
|
||||
td::HashMap<td::uint64, td::BufferSlice> pages_;
|
||||
|
||||
std::mutex fd_mutex_;
|
||||
|
||||
td::Result<td::Slice> load_page(td::uint64 page_i) {
|
||||
{
|
||||
auto pages_guard = pages_rw_mutex_.lock_read();
|
||||
auto it = pages_.find(page_i);
|
||||
if (it != pages_.end()) {
|
||||
return it->second.as_slice();
|
||||
}
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> fd_guard(fd_mutex_);
|
||||
{
|
||||
auto pages_guard = pages_rw_mutex_.lock_read();
|
||||
auto it = pages_.find(page_i);
|
||||
if (it != pages_.end()) {
|
||||
return it->second.as_slice();
|
||||
}
|
||||
}
|
||||
auto offset = page_i * page_size;
|
||||
|
||||
auto size = td::min(file_size_ - offset, page_size);
|
||||
auto buffer_slice = td::BufferSlice(size);
|
||||
TRY_RESULT(read_size, fd_.pread(buffer_slice.as_slice(), offset));
|
||||
if (read_size != buffer_slice.size()) {
|
||||
return td::Status::Error("not enough data in file");
|
||||
}
|
||||
|
||||
auto pages_guard = pages_rw_mutex_.lock_write();
|
||||
auto &res = pages_[page_i];
|
||||
res = std::move(buffer_slice);
|
||||
return res.as_slice();
|
||||
}
|
||||
};
|
||||
td::Result<std::unique_ptr<BlobView>> FileBlobView::create(td::CSlice file_path, td::uint64 file_size) {
|
||||
TRY_RESULT(fd, td::FileFd::open(file_path, td::FileFd::Flags::Read));
|
||||
TRY_RESULT(stat, fd.stat());
|
||||
if (file_size == 0) {
|
||||
file_size = stat.size_;
|
||||
} else if (file_size != (td::uint64)stat.size_) {
|
||||
return td::Status::Error("Wrong file size");
|
||||
}
|
||||
return std::make_unique<FileBlobViewImpl>(std::move(fd), file_size);
|
||||
}
|
||||
|
||||
class FileMemoryMappingBlobViewImpl : public BlobView {
|
||||
public:
|
||||
FileMemoryMappingBlobViewImpl(td::MemoryMapping mapping) : mapping_(std::move(mapping)) {
|
||||
}
|
||||
td::Result<td::Slice> view_impl(td::MutableSlice slice, td::uint64 offset) override {
|
||||
// optimize anyway
|
||||
return mapping_.as_slice().substr(offset, slice.size());
|
||||
}
|
||||
td::uint64 size() override {
|
||||
return mapping_.as_slice().size();
|
||||
}
|
||||
|
||||
private:
|
||||
td::MemoryMapping mapping_;
|
||||
};
|
||||
|
||||
td::Result<std::unique_ptr<BlobView>> FileMemoryMappingBlobView::create(td::CSlice file_path, td::uint64 file_size) {
|
||||
TRY_RESULT(fd, td::FileFd::open(file_path, td::FileFd::Flags::Read));
|
||||
TRY_RESULT(stat, fd.stat());
|
||||
if (file_size == 0) {
|
||||
file_size = stat.size_;
|
||||
} else if (file_size != (td::uint64)stat.size_) {
|
||||
return td::Status::Error("Wrong file size");
|
||||
}
|
||||
|
||||
TRY_RESULT(mapping, td::MemoryMapping::create_from_file(fd));
|
||||
|
||||
return std::make_unique<FileMemoryMappingBlobViewImpl>(std::move(mapping));
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
45
crypto/vm/db/BlobView.h
Normal file
45
crypto/vm/db/BlobView.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/utils/buffer.h"
|
||||
|
||||
namespace vm {
|
||||
class BlobView {
|
||||
public:
|
||||
virtual ~BlobView() = default;
|
||||
td::Result<td::Slice> view(td::MutableSlice slice, td::uint64 offset);
|
||||
virtual td::uint64 size() = 0;
|
||||
|
||||
private:
|
||||
virtual td::Result<td::Slice> view_impl(td::MutableSlice slice, td::uint64 offset) = 0;
|
||||
};
|
||||
|
||||
class BufferSliceBlobView {
|
||||
public:
|
||||
static std::unique_ptr<BlobView> create(td::BufferSlice slice);
|
||||
};
|
||||
class FileBlobView {
|
||||
public:
|
||||
static td::Result<std::unique_ptr<BlobView>> create(td::CSlice file_path, td::uint64 file_size = 0);
|
||||
};
|
||||
class FileMemoryMappingBlobView {
|
||||
public:
|
||||
static td::Result<std::unique_ptr<BlobView>> create(td::CSlice file_path, td::uint64 file_size = 0);
|
||||
};
|
||||
} // namespace vm
|
||||
71
crypto/vm/db/CellHashTable.h
Normal file
71
crypto/vm/db/CellHashTable.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/Slice.h"
|
||||
|
||||
#include <set>
|
||||
|
||||
namespace vm {
|
||||
template <class InfoT>
|
||||
class CellHashTable {
|
||||
public:
|
||||
template <class F>
|
||||
InfoT &apply(td::Slice hash, F &&f) {
|
||||
auto it = set_.find(hash);
|
||||
if (it != set_.end()) {
|
||||
auto &res = const_cast<InfoT &>(*it);
|
||||
f(res);
|
||||
return res;
|
||||
}
|
||||
InfoT info;
|
||||
f(info);
|
||||
auto &res = const_cast<InfoT &>(*(set_.insert(std::move(info)).first));
|
||||
return res;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void for_each(F &&f) {
|
||||
for (auto &info : set_) {
|
||||
f(info);
|
||||
}
|
||||
}
|
||||
template <class F>
|
||||
void filter(F &&f) {
|
||||
for (auto it = set_.begin(); it != set_.end();) {
|
||||
if (f(*it)) {
|
||||
it++;
|
||||
} else {
|
||||
it = set_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
void erase(td::Slice hash) {
|
||||
auto it = set_.find(hash);
|
||||
CHECK(it != set_.end());
|
||||
set_.erase(it);
|
||||
}
|
||||
size_t size() const {
|
||||
return set_.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::set<InfoT, std::less<>> set_;
|
||||
};
|
||||
} // namespace vm
|
||||
163
crypto/vm/db/CellStorage.cpp
Normal file
163
crypto/vm/db/CellStorage.cpp
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/db/CellStorage.h"
|
||||
#include "vm/db/DynamicBagOfCellsDb.h"
|
||||
#include "vm/boc.h"
|
||||
#include "td/utils/base64.h"
|
||||
#include "td/utils/tl_parsers.h"
|
||||
#include "td/utils/tl_helpers.h"
|
||||
|
||||
namespace vm {
|
||||
namespace {
|
||||
class RefcntCellStorer {
|
||||
public:
|
||||
RefcntCellStorer(td::int32 refcnt, const DataCell &cell) : refcnt_(refcnt), cell_(cell) {
|
||||
}
|
||||
|
||||
template <class StorerT>
|
||||
void store(StorerT &storer) const {
|
||||
using td::store;
|
||||
store(refcnt_, storer);
|
||||
store(cell_, storer);
|
||||
for (unsigned i = 0; i < cell_.size_refs(); i++) {
|
||||
auto cell = cell_.get_ref(i);
|
||||
auto level_mask = cell->get_level_mask();
|
||||
auto level = level_mask.get_level();
|
||||
td::uint8 x = static_cast<td::uint8>(level_mask.get_mask());
|
||||
storer.store_slice(td::Slice(&x, 1));
|
||||
for (unsigned level_i = 0; level_i <= level; level_i++) {
|
||||
if (!level_mask.is_significant(level_i)) {
|
||||
continue;
|
||||
}
|
||||
storer.store_slice(cell->get_hash(level_i).as_slice());
|
||||
}
|
||||
for (unsigned level_i = 0; level_i <= level; level_i++) {
|
||||
if (!level_mask.is_significant(level_i)) {
|
||||
continue;
|
||||
}
|
||||
td::uint8 depth_buf[Cell::depth_bytes];
|
||||
DataCell::store_depth(depth_buf, cell->get_depth(level_i));
|
||||
storer.store_slice(td::Slice(depth_buf, Cell::depth_bytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
td::int32 refcnt_;
|
||||
const DataCell &cell_;
|
||||
};
|
||||
|
||||
class RefcntCellParser {
|
||||
public:
|
||||
RefcntCellParser(bool need_data) : need_data_(need_data) {
|
||||
}
|
||||
td::int32 refcnt;
|
||||
Ref<DataCell> cell;
|
||||
|
||||
template <class ParserT>
|
||||
void parse(ParserT &parser, ExtCellCreator &ext_cell_creator) {
|
||||
using ::td::parse;
|
||||
parse(refcnt, parser);
|
||||
if (!need_data_) {
|
||||
return;
|
||||
}
|
||||
auto status = [&]() -> td::Status {
|
||||
TRY_STATUS(parser.get_status());
|
||||
auto size = parser.get_left_len();
|
||||
td::Slice data = parser.template fetch_string_raw<td::Slice>(size);
|
||||
CellSerializationInfo info;
|
||||
auto cell_data = data;
|
||||
TRY_STATUS(info.init(cell_data, 0 /*ref_byte_size*/));
|
||||
data = data.substr(info.end_offset);
|
||||
|
||||
Ref<Cell> refs[Cell::max_refs];
|
||||
for (int i = 0; i < info.refs_cnt; i++) {
|
||||
if (data.size() < 1) {
|
||||
return td::Status::Error("Not enought data");
|
||||
}
|
||||
Cell::LevelMask level_mask(data[0]);
|
||||
auto n = level_mask.get_hashes_count();
|
||||
auto end_offset = 1 + n * (Cell::hash_bytes + Cell::depth_bytes);
|
||||
if (data.size() < end_offset) {
|
||||
return td::Status::Error("Not enought data");
|
||||
}
|
||||
|
||||
TRY_RESULT(ext_cell, ext_cell_creator.ext_cell(level_mask, data.substr(1, n * Cell::hash_bytes),
|
||||
data.substr(1 + n * Cell::hash_bytes, n * Cell::depth_bytes)));
|
||||
refs[i] = std::move(ext_cell);
|
||||
CHECK(refs[i]->get_level() == level_mask.get_level());
|
||||
data = data.substr(end_offset);
|
||||
}
|
||||
if (!data.empty()) {
|
||||
return td::Status::Error("Too much data");
|
||||
}
|
||||
TRY_RESULT(data_cell, info.create_data_cell(cell_data, td::Span<Ref<Cell>>(refs, info.refs_cnt)));
|
||||
cell = std::move(data_cell);
|
||||
return td::Status::OK();
|
||||
}();
|
||||
if (status.is_error()) {
|
||||
parser.set_error(status.message().str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool need_data_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
CellLoader::CellLoader(std::shared_ptr<KeyValueReader> reader) : reader_(std::move(reader)) {
|
||||
CHECK(reader_);
|
||||
}
|
||||
|
||||
td::Result<CellLoader::LoadResult> CellLoader::load(td::Slice hash, bool need_data, ExtCellCreator &ext_cell_creator) {
|
||||
//LOG(ERROR) << "Storage: load cell " << hash.size() << " " << td::base64_encode(hash);
|
||||
LoadResult res;
|
||||
std::string serialized;
|
||||
TRY_RESULT(get_status, reader_->get(hash, serialized));
|
||||
if (get_status != KeyValue::GetStatus::Ok) {
|
||||
DCHECK(get_status == KeyValue::GetStatus::NotFound);
|
||||
return res;
|
||||
}
|
||||
|
||||
res.status = LoadResult::Ok;
|
||||
|
||||
RefcntCellParser refcnt_cell(need_data);
|
||||
td::TlParser parser(serialized);
|
||||
refcnt_cell.parse(parser, ext_cell_creator);
|
||||
TRY_STATUS(parser.get_status());
|
||||
|
||||
res.refcnt_ = refcnt_cell.refcnt;
|
||||
res.cell_ = std::move(refcnt_cell.cell);
|
||||
//CHECK(res.cell_->get_hash() == hash);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
CellStorer::CellStorer(KeyValue &kv) : kv_(kv) {
|
||||
}
|
||||
|
||||
td::Status CellStorer::erase(td::Slice hash) {
|
||||
return kv_.erase(hash);
|
||||
}
|
||||
|
||||
td::Status CellStorer::set(td::int32 refcnt, const DataCell &cell) {
|
||||
return kv_.set(cell.get_hash().as_slice(), td::serialize(RefcntCellStorer(refcnt, cell)));
|
||||
}
|
||||
} // namespace vm
|
||||
65
crypto/vm/db/CellStorage.h
Normal file
65
crypto/vm/db/CellStorage.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/db/KeyValue.h"
|
||||
#include "vm/db/DynamicBagOfCellsDb.h"
|
||||
#include "vm/cells.h"
|
||||
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
namespace vm {
|
||||
using KeyValue = td::KeyValue;
|
||||
using KeyValueReader = td::KeyValueReader;
|
||||
|
||||
class CellLoader {
|
||||
public:
|
||||
struct LoadResult {
|
||||
public:
|
||||
enum { Ok, NotFound } status{NotFound};
|
||||
|
||||
Ref<DataCell> &cell() {
|
||||
DCHECK(status == Ok);
|
||||
return cell_;
|
||||
}
|
||||
|
||||
td::int32 refcnt() const {
|
||||
return refcnt_;
|
||||
}
|
||||
|
||||
Ref<DataCell> cell_;
|
||||
td::int32 refcnt_{0};
|
||||
};
|
||||
CellLoader(std::shared_ptr<KeyValueReader> reader);
|
||||
td::Result<LoadResult> load(td::Slice hash, bool need_data, ExtCellCreator &ext_cell_creator);
|
||||
|
||||
private:
|
||||
std::shared_ptr<KeyValueReader> reader_;
|
||||
};
|
||||
|
||||
class CellStorer {
|
||||
public:
|
||||
CellStorer(KeyValue &kv);
|
||||
td::Status erase(td::Slice hash);
|
||||
td::Status set(td::int32 refcnt, const DataCell &cell);
|
||||
|
||||
private:
|
||||
KeyValue &kv_;
|
||||
};
|
||||
} // namespace vm
|
||||
500
crypto/vm/db/DynamicBagOfCellsDb.cpp
Normal file
500
crypto/vm/db/DynamicBagOfCellsDb.cpp
Normal file
|
|
@ -0,0 +1,500 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/db/DynamicBagOfCellsDb.h"
|
||||
#include "vm/db/CellStorage.h"
|
||||
#include "vm/db/CellHashTable.h"
|
||||
|
||||
#include "vm/cells/ExtCell.h"
|
||||
|
||||
#include "td/utils/base64.h"
|
||||
#include "td/utils/format.h"
|
||||
#include "td/utils/ThreadSafeCounter.h"
|
||||
|
||||
#include "vm/cellslice.h"
|
||||
|
||||
namespace vm {
|
||||
namespace {
|
||||
|
||||
class CellDbReader {
|
||||
public:
|
||||
virtual ~CellDbReader() = default;
|
||||
virtual td::Result<Ref<DataCell>> load_cell(td::Slice hash) = 0;
|
||||
};
|
||||
|
||||
struct DynamicBocExtCellExtra {
|
||||
std::shared_ptr<CellDbReader> reader;
|
||||
};
|
||||
|
||||
class DynamicBocCellLoader {
|
||||
public:
|
||||
static td::Result<Ref<DataCell>> load_data_cell(const Cell &cell, const DynamicBocExtCellExtra &extra) {
|
||||
return extra.reader->load_cell(cell.get_hash().as_slice());
|
||||
}
|
||||
};
|
||||
|
||||
using DynamicBocExtCell = ExtCell<DynamicBocExtCellExtra, DynamicBocCellLoader>;
|
||||
|
||||
struct CellInfo {
|
||||
bool sync_with_db{false};
|
||||
bool in_db{false};
|
||||
|
||||
bool was_dfs_new_cells{false};
|
||||
bool was{false};
|
||||
|
||||
td::int32 db_refcnt{0};
|
||||
td::int32 refcnt_diff{0};
|
||||
Ref<Cell> cell;
|
||||
Cell::Hash key() const {
|
||||
return cell->get_hash();
|
||||
}
|
||||
bool operator<(const CellInfo &other) const {
|
||||
return key() < other.key();
|
||||
}
|
||||
};
|
||||
|
||||
bool operator<(const CellInfo &a, td::Slice b) {
|
||||
return a.key().as_slice() < b;
|
||||
}
|
||||
|
||||
bool operator<(td::Slice a, const CellInfo &b) {
|
||||
return a < b.key().as_slice();
|
||||
}
|
||||
|
||||
class DynamicBagOfCellsDbImpl : public DynamicBagOfCellsDb, private ExtCellCreator {
|
||||
public:
|
||||
DynamicBagOfCellsDbImpl() {
|
||||
get_thread_safe_counter().add(1);
|
||||
}
|
||||
~DynamicBagOfCellsDbImpl() {
|
||||
get_thread_safe_counter().add(-1);
|
||||
reset_cell_db_reader();
|
||||
}
|
||||
td::Result<Ref<Cell>> ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) override {
|
||||
return get_cell_info_lazy(level_mask, hash, depth).cell;
|
||||
}
|
||||
td::Result<Ref<DataCell>> load_cell(td::Slice hash) override {
|
||||
TRY_RESULT(loaded_cell, get_cell_info_force(hash).cell->load_cell());
|
||||
return std::move(loaded_cell.data_cell);
|
||||
}
|
||||
CellInfo &get_cell_info_force(td::Slice hash) {
|
||||
return hash_table_.apply(hash, [&](CellInfo &info) { update_cell_info_force(info, hash); });
|
||||
}
|
||||
CellInfo &get_cell_info_lazy(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) {
|
||||
return hash_table_.apply(hash.substr(hash.size() - Cell::hash_bytes),
|
||||
[&](CellInfo &info) { update_cell_info_lazy(info, level_mask, hash, depth); });
|
||||
}
|
||||
CellInfo &get_cell_info(const Ref<Cell> &cell) {
|
||||
return hash_table_.apply(cell->get_hash().as_slice(), [&](CellInfo &info) { update_cell_info(info, cell); });
|
||||
}
|
||||
|
||||
void inc(const Ref<Cell> &cell) override {
|
||||
if (cell.is_null()) {
|
||||
return;
|
||||
}
|
||||
if (cell->get_virtualization() != 0) {
|
||||
return;
|
||||
}
|
||||
//LOG(ERROR) << "INC";
|
||||
//CellSlice(cell, nullptr).print_rec(std::cout);
|
||||
to_inc_.push_back(cell);
|
||||
}
|
||||
void dec(const Ref<Cell> &cell) override {
|
||||
if (cell.is_null()) {
|
||||
return;
|
||||
}
|
||||
if (cell->get_virtualization() != 0) {
|
||||
return;
|
||||
}
|
||||
//LOG(ERROR) << "DEC";
|
||||
//CellSlice(cell, nullptr).print_rec(std::cout);
|
||||
to_dec_.push_back(cell);
|
||||
}
|
||||
|
||||
bool is_prepared_for_commit() {
|
||||
return to_inc_.empty() && to_dec_.empty();
|
||||
}
|
||||
|
||||
Stats get_stats_diff() override {
|
||||
CHECK(is_prepared_for_commit());
|
||||
return stats_diff_;
|
||||
}
|
||||
|
||||
td::Status prepare_commit() override {
|
||||
if (is_prepared_for_commit()) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
//LOG(ERROR) << "dfs_new_cells_in_db";
|
||||
for (auto &new_cell : to_inc_) {
|
||||
auto &new_cell_info = get_cell_info(new_cell);
|
||||
dfs_new_cells_in_db(new_cell_info);
|
||||
}
|
||||
//return td::Status::OK();
|
||||
//LOG(ERROR) << "dfs_new_cells";
|
||||
for (auto &new_cell : to_inc_) {
|
||||
auto &new_cell_info = get_cell_info(new_cell);
|
||||
dfs_new_cells(new_cell_info);
|
||||
}
|
||||
|
||||
//LOG(ERROR) << "dfs_old_cells";
|
||||
for (auto &old_cell : to_dec_) {
|
||||
auto &old_cell_info = get_cell_info(old_cell);
|
||||
dfs_old_cells(old_cell_info);
|
||||
}
|
||||
|
||||
//LOG(ERROR) << "save_diff_prepare";
|
||||
save_diff_prepare();
|
||||
|
||||
to_inc_.clear();
|
||||
to_dec_.clear();
|
||||
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status commit(CellStorer &storer) override {
|
||||
prepare_commit();
|
||||
save_diff(storer);
|
||||
// Some elements are erased from hash table, to keep it small.
|
||||
// Hash table is no longer represents the difference between the loader and
|
||||
// the current bag of cells.
|
||||
reset_cell_db_reader();
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status set_loader(std::unique_ptr<CellLoader> loader) override {
|
||||
reset_cell_db_reader();
|
||||
loader_ = std::move(loader);
|
||||
//cell_db_reader_ = std::make_shared<CellDbReaderImpl>(this);
|
||||
// Temporary(?) fix to make ExtCell thread safe.
|
||||
// Downside(?) - loaded cells won't be cached
|
||||
cell_db_reader_ = std::make_shared<CellDbReaderImpl>(std::make_unique<CellLoader>(*loader_));
|
||||
stats_diff_ = {};
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<CellLoader> loader_;
|
||||
std::vector<Ref<Cell>> to_inc_;
|
||||
std::vector<Ref<Cell>> to_dec_;
|
||||
CellHashTable<CellInfo> hash_table_;
|
||||
std::vector<CellInfo *> visited_;
|
||||
Stats stats_diff_;
|
||||
|
||||
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
|
||||
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("DynamicBagOfCellsDb");
|
||||
return res;
|
||||
}
|
||||
|
||||
class CellDbReaderImpl : public CellDbReader,
|
||||
private ExtCellCreator,
|
||||
public std::enable_shared_from_this<CellDbReaderImpl> {
|
||||
public:
|
||||
CellDbReaderImpl(std::unique_ptr<CellLoader> cell_loader) : db_(nullptr), cell_loader_(std::move(cell_loader)) {
|
||||
if (cell_loader_) {
|
||||
get_thread_safe_counter().add(1);
|
||||
}
|
||||
}
|
||||
CellDbReaderImpl(DynamicBagOfCellsDb *db) : db_(db) {
|
||||
}
|
||||
~CellDbReaderImpl() {
|
||||
if (cell_loader_) {
|
||||
get_thread_safe_counter().add(-1);
|
||||
}
|
||||
}
|
||||
void set_loader(std::unique_ptr<CellLoader> cell_loader) {
|
||||
if (cell_loader_) {
|
||||
// avoid race
|
||||
return;
|
||||
}
|
||||
cell_loader_ = std::move(cell_loader);
|
||||
db_ = nullptr;
|
||||
if (cell_loader_) {
|
||||
get_thread_safe_counter().add(1);
|
||||
}
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) override {
|
||||
CHECK(!db_);
|
||||
TRY_RESULT(ext_cell, DynamicBocExtCell::create(PrunnedCellInfo{level_mask, hash, depth},
|
||||
DynamicBocExtCellExtra{shared_from_this()}));
|
||||
return std::move(ext_cell);
|
||||
}
|
||||
|
||||
td::Result<Ref<DataCell>> load_cell(td::Slice hash) override {
|
||||
if (db_) {
|
||||
return db_->load_cell(hash);
|
||||
}
|
||||
TRY_RESULT(load_result, cell_loader_->load(hash, true, *this));
|
||||
CHECK(load_result.status == CellLoader::LoadResult::Ok);
|
||||
return std::move(load_result.cell());
|
||||
}
|
||||
|
||||
private:
|
||||
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
|
||||
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("DynamicBagOfCellsDbLoader");
|
||||
return res;
|
||||
}
|
||||
DynamicBagOfCellsDb *db_;
|
||||
std::unique_ptr<CellLoader> cell_loader_;
|
||||
};
|
||||
|
||||
std::shared_ptr<CellDbReaderImpl> cell_db_reader_;
|
||||
|
||||
void reset_cell_db_reader() {
|
||||
if (!cell_db_reader_) {
|
||||
return;
|
||||
}
|
||||
cell_db_reader_->set_loader(std::move(loader_));
|
||||
cell_db_reader_.reset();
|
||||
//EXPERIMENTAL: clear cache to drop all references to old reader.
|
||||
hash_table_ = {};
|
||||
}
|
||||
|
||||
bool is_in_db(CellInfo &info) {
|
||||
if (info.in_db) {
|
||||
return true;
|
||||
}
|
||||
load_cell(info);
|
||||
return info.in_db;
|
||||
}
|
||||
bool is_loaded(CellInfo &info) {
|
||||
return info.sync_with_db;
|
||||
}
|
||||
|
||||
void load_cell(CellInfo &info) {
|
||||
if (is_loaded(info)) {
|
||||
return;
|
||||
}
|
||||
do_load_cell(info);
|
||||
}
|
||||
|
||||
bool dfs_new_cells_in_db(CellInfo &info) {
|
||||
if (info.sync_with_db) {
|
||||
return is_in_db(info);
|
||||
}
|
||||
if (info.in_db) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool not_in_db = false;
|
||||
for_each(
|
||||
info, [¬_in_db, this](auto &child_info) { not_in_db |= !dfs_new_cells_in_db(child_info); }, false);
|
||||
|
||||
if (not_in_db) {
|
||||
CHECK(!info.in_db);
|
||||
info.sync_with_db = true;
|
||||
}
|
||||
return is_in_db(info);
|
||||
}
|
||||
|
||||
void dfs_new_cells(CellInfo &info) {
|
||||
info.refcnt_diff++;
|
||||
if (!info.was) {
|
||||
info.was = true;
|
||||
visited_.push_back(&info);
|
||||
}
|
||||
//LOG(ERROR) << "dfs new " << td::format::escaped(info.cell->hash());
|
||||
|
||||
if (info.was_dfs_new_cells) {
|
||||
return;
|
||||
}
|
||||
info.was_dfs_new_cells = true;
|
||||
|
||||
if (is_in_db(info)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK(is_loaded(info));
|
||||
for_each(info, [this](auto &child_info) { dfs_new_cells(child_info); });
|
||||
}
|
||||
|
||||
void dfs_old_cells(CellInfo &info) {
|
||||
info.refcnt_diff--;
|
||||
if (!info.was) {
|
||||
info.was = true;
|
||||
visited_.push_back(&info);
|
||||
}
|
||||
//LOG(ERROR) << "dfs old " << td::format::escaped(info.cell->hash());
|
||||
|
||||
load_cell(info);
|
||||
|
||||
auto new_refcnt = info.refcnt_diff + info.db_refcnt;
|
||||
CHECK(new_refcnt >= 0);
|
||||
if (new_refcnt != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for_each(info, [this](auto &child_info) { dfs_old_cells(child_info); });
|
||||
}
|
||||
|
||||
void save_diff_prepare() {
|
||||
stats_diff_ = {};
|
||||
for (auto info_ptr : visited_) {
|
||||
save_cell_prepare(*info_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void save_diff(CellStorer &storer) {
|
||||
//LOG(ERROR) << hash_table_.size();
|
||||
for (auto info_ptr : visited_) {
|
||||
save_cell(*info_ptr, storer);
|
||||
}
|
||||
visited_.clear();
|
||||
}
|
||||
|
||||
void save_cell_prepare(CellInfo &info) {
|
||||
if (info.refcnt_diff == 0) {
|
||||
//CellSlice(info.cell, nullptr).print_rec(std::cout);
|
||||
return;
|
||||
}
|
||||
load_cell(info);
|
||||
|
||||
auto loaded_cell = info.cell->load_cell().move_as_ok();
|
||||
if (info.db_refcnt + info.refcnt_diff == 0) {
|
||||
CHECK(info.in_db);
|
||||
// erase
|
||||
stats_diff_.cells_total_count--;
|
||||
stats_diff_.cells_total_size -= loaded_cell.data_cell->get_serialized_size(true);
|
||||
} else {
|
||||
//save
|
||||
if (info.in_db == false) {
|
||||
stats_diff_.cells_total_count++;
|
||||
stats_diff_.cells_total_size += loaded_cell.data_cell->get_serialized_size(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void save_cell(CellInfo &info, CellStorer &storer) {
|
||||
auto guard = td::ScopeExit{} + [&] {
|
||||
info.was_dfs_new_cells = false;
|
||||
info.was = false;
|
||||
};
|
||||
if (info.refcnt_diff == 0) {
|
||||
//CellSlice(info.cell, nullptr).print_rec(std::cout);
|
||||
return;
|
||||
}
|
||||
CHECK(info.sync_with_db);
|
||||
|
||||
info.db_refcnt += info.refcnt_diff;
|
||||
info.refcnt_diff = 0;
|
||||
|
||||
if (info.db_refcnt == 0) {
|
||||
CHECK(info.in_db);
|
||||
//LOG(ERROR) << "ERASE";
|
||||
//CellSlice(NoVm(), info.cell).print_rec(std::cout);
|
||||
storer.erase(info.cell->get_hash().as_slice());
|
||||
info.in_db = false;
|
||||
hash_table_.erase(info.cell->get_hash().as_slice());
|
||||
guard.dismiss();
|
||||
} else {
|
||||
//LOG(ERROR) << "SAVE " << info.db_refcnt;
|
||||
//CellSlice(NoVm(), info.cell).print_rec(std::cout);
|
||||
auto loaded_cell = info.cell->load_cell().move_as_ok();
|
||||
storer.set(info.db_refcnt, *loaded_cell.data_cell);
|
||||
info.in_db = true;
|
||||
}
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void for_each(CellInfo &info, F &&f, bool force = true) {
|
||||
auto cell = info.cell;
|
||||
|
||||
if (!cell->is_loaded()) {
|
||||
if (!force) {
|
||||
return;
|
||||
}
|
||||
load_cell(info);
|
||||
cell = info.cell;
|
||||
}
|
||||
if (!cell->is_loaded()) {
|
||||
cell->load_cell().ensure();
|
||||
}
|
||||
CHECK(cell->is_loaded());
|
||||
vm::CellSlice cs(vm::NoVm{}, cell); // FIXME
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
//LOG(ERROR) << "---> " << td::format::escaped(cell->ref(i)->hash());
|
||||
f(get_cell_info(cs.prefetch_ref(i)));
|
||||
}
|
||||
}
|
||||
|
||||
void do_load_cell(CellInfo &info) {
|
||||
update_cell_info_force(info, info.cell->get_hash().as_slice());
|
||||
}
|
||||
|
||||
void update_cell_info(CellInfo &info, const Ref<Cell> &cell) {
|
||||
CHECK(!cell.is_null());
|
||||
if (info.sync_with_db) {
|
||||
return;
|
||||
}
|
||||
info.cell = cell;
|
||||
}
|
||||
|
||||
void update_cell_info_lazy(CellInfo &info, Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) {
|
||||
if (info.sync_with_db) {
|
||||
CHECK(info.cell.not_null());
|
||||
CHECK(info.cell->get_level_mask() == level_mask);
|
||||
return;
|
||||
}
|
||||
if (info.cell.is_null()) {
|
||||
auto ext_cell_r = create_empty_ext_cell(level_mask, hash, depth);
|
||||
if (ext_cell_r.is_error()) {
|
||||
//FIXME
|
||||
LOG(ERROR) << "Failed to create ext_cell" << ext_cell_r.error();
|
||||
return;
|
||||
}
|
||||
info.cell = ext_cell_r.move_as_ok();
|
||||
info.in_db = true; // TODO
|
||||
}
|
||||
}
|
||||
void update_cell_info_force(CellInfo &info, td::Slice hash) {
|
||||
if (info.sync_with_db) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
CHECK(loader_);
|
||||
auto r_res = loader_->load(hash, true, *this);
|
||||
if (r_res.is_error()) {
|
||||
//FIXME
|
||||
LOG(ERROR) << "Failed to load cell from db" << r_res.error();
|
||||
break;
|
||||
}
|
||||
auto res = r_res.move_as_ok();
|
||||
if (res.status != CellLoader::LoadResult::Ok) {
|
||||
break;
|
||||
}
|
||||
info.cell = std::move(res.cell());
|
||||
CHECK(info.cell->get_hash().as_slice() == hash);
|
||||
info.in_db = true;
|
||||
info.db_refcnt = res.refcnt();
|
||||
} while (false);
|
||||
info.sync_with_db = true;
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> create_empty_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) {
|
||||
TRY_RESULT(res, DynamicBocExtCell::create(PrunnedCellInfo{level_mask, hash, depth},
|
||||
DynamicBocExtCellExtra{cell_db_reader_}));
|
||||
return std::move(res);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<DynamicBagOfCellsDb> DynamicBagOfCellsDb::create() {
|
||||
return std::make_unique<DynamicBagOfCellsDbImpl>();
|
||||
}
|
||||
} // namespace vm
|
||||
62
crypto/vm/db/DynamicBagOfCellsDb.h
Normal file
62
crypto/vm/db/DynamicBagOfCellsDb.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells.h"
|
||||
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
namespace vm {
|
||||
class CellLoader;
|
||||
class CellStorer;
|
||||
} // namespace vm
|
||||
|
||||
namespace vm {
|
||||
class ExtCellCreator {
|
||||
public:
|
||||
virtual ~ExtCellCreator() = default;
|
||||
virtual td::Result<Ref<Cell>> ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth) = 0;
|
||||
};
|
||||
|
||||
class DynamicBagOfCellsDb {
|
||||
public:
|
||||
virtual ~DynamicBagOfCellsDb() = default;
|
||||
virtual td::Result<Ref<DataCell>> load_cell(td::Slice hash) = 0;
|
||||
struct Stats {
|
||||
td::int64 cells_total_count{0};
|
||||
td::int64 cells_total_size{0};
|
||||
void apply_diff(Stats diff) {
|
||||
cells_total_count += diff.cells_total_count;
|
||||
cells_total_size += diff.cells_total_size;
|
||||
}
|
||||
};
|
||||
virtual void inc(const Ref<Cell> &old_root) = 0;
|
||||
virtual void dec(const Ref<Cell> &old_root) = 0;
|
||||
|
||||
virtual td::Status prepare_commit() = 0;
|
||||
virtual Stats get_stats_diff() = 0;
|
||||
virtual td::Status commit(CellStorer &) = 0;
|
||||
|
||||
// restart with new loader will also reset stats_diff
|
||||
virtual td::Status set_loader(std::unique_ptr<CellLoader> loader) = 0;
|
||||
|
||||
static std::unique_ptr<DynamicBagOfCellsDb> create();
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
545
crypto/vm/db/StaticBagOfCellsDb.cpp
Normal file
545
crypto/vm/db/StaticBagOfCellsDb.cpp
Normal file
|
|
@ -0,0 +1,545 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/db/StaticBagOfCellsDb.h"
|
||||
|
||||
#include "vm/cells/CellWithStorage.h"
|
||||
#include "vm/boc.h"
|
||||
|
||||
#include "vm/cells/ExtCell.h"
|
||||
|
||||
#include "td/utils/crypto.h"
|
||||
#include "td/utils/format.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/port/RwMutex.h"
|
||||
#include "td/utils/ConcurrentHashTable.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace vm {
|
||||
//
|
||||
// Common interface
|
||||
//
|
||||
template <class ExtraT>
|
||||
class RootCell : public Cell {
|
||||
struct PrivateTag {};
|
||||
|
||||
public:
|
||||
td::Result<LoadedCell> load_cell() const override {
|
||||
return cell_->load_cell();
|
||||
}
|
||||
Ref<Cell> virtualize(VirtualizationParameters virt) const override {
|
||||
return cell_->virtualize(virt);
|
||||
}
|
||||
td::uint32 get_virtualization() const override {
|
||||
return cell_->get_virtualization();
|
||||
}
|
||||
CellUsageTree::NodePtr get_tree_node() const override {
|
||||
return cell_->get_tree_node();
|
||||
}
|
||||
bool is_loaded() const override {
|
||||
return cell_->is_loaded();
|
||||
}
|
||||
|
||||
// hash and level
|
||||
LevelMask get_level_mask() const override {
|
||||
return cell_->get_level_mask();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static Ref<Cell> create(Ref<Cell> cell, T&& extra) {
|
||||
return Ref<RootCell>(true, std::move(cell), std::forward<T>(extra), PrivateTag{});
|
||||
}
|
||||
template <class T>
|
||||
RootCell(Ref<Cell> cell, T&& extra, PrivateTag) : cell_(std::move(cell)), extra_(std::forward<T>(extra)) {
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<Cell> cell_;
|
||||
ExtraT extra_;
|
||||
td::uint16 do_get_depth(td::uint32 level) const override {
|
||||
return cell_->get_depth(level);
|
||||
}
|
||||
const Hash do_get_hash(td::uint32 level) const override {
|
||||
return cell_->get_hash(level);
|
||||
}
|
||||
};
|
||||
|
||||
class DataCellCacheNoop {
|
||||
public:
|
||||
Ref<DataCell> store(int idx, Ref<DataCell> cell) {
|
||||
return cell;
|
||||
}
|
||||
Ref<DataCell> load(int idx) {
|
||||
return {};
|
||||
}
|
||||
void clear() {
|
||||
}
|
||||
};
|
||||
class DataCellCacheMutex {
|
||||
public:
|
||||
Ref<DataCell> store(int idx, Ref<DataCell> cell) {
|
||||
auto lock = cells_rw_mutex_.lock_write();
|
||||
return cells_.emplace(idx, std::move(cell)).first->second;
|
||||
}
|
||||
Ref<DataCell> load(int idx) {
|
||||
auto lock = cells_rw_mutex_.lock_read();
|
||||
auto it = cells_.find(idx);
|
||||
if (it != cells_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
void clear() {
|
||||
auto guard = cells_rw_mutex_.lock_write();
|
||||
cells_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
td::RwMutex cells_rw_mutex_;
|
||||
td::HashMap<int, Ref<DataCell>> cells_;
|
||||
};
|
||||
|
||||
class DataCellCacheTdlib {
|
||||
public:
|
||||
Ref<DataCell> store(int idx, Ref<DataCell> cell) {
|
||||
return Ref<DataCell>(cells_.insert(as_key(idx), cell.get()));
|
||||
}
|
||||
Ref<DataCell> load(int idx) {
|
||||
return Ref<DataCell>(cells_.find(as_key(idx), nullptr));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
cells_.for_each([](auto key, auto value) { Ref<DataCell>{value}; });
|
||||
}
|
||||
|
||||
private:
|
||||
td::ConcurrentHashMap<td::uint32, const DataCell*> cells_;
|
||||
td::uint32 as_key(int idx) {
|
||||
td::uint32 key = static_cast<td::uint32>(idx + 1);
|
||||
key *= 1000000007;
|
||||
return key;
|
||||
}
|
||||
};
|
||||
|
||||
struct StaticBocExtCellExtra {
|
||||
int idx;
|
||||
std::weak_ptr<StaticBagOfCellsDb> deserializer;
|
||||
};
|
||||
|
||||
class StaticBocLoader {
|
||||
public:
|
||||
static td::Result<Ref<DataCell>> load_data_cell(const Cell& cell, const StaticBocExtCellExtra& extra) {
|
||||
auto deserializer = extra.deserializer.lock();
|
||||
if (!deserializer) {
|
||||
return td::Status::Error("StaticBocDb is already destroyed, cannot fetch cell");
|
||||
}
|
||||
return deserializer->load_by_idx(extra.idx);
|
||||
}
|
||||
};
|
||||
using StaticBocExtCell = ExtCell<StaticBocExtCellExtra, StaticBocLoader>;
|
||||
using StaticBocRootCell = RootCell<std::shared_ptr<StaticBagOfCellsDb>>;
|
||||
|
||||
td::Result<Ref<Cell>> StaticBagOfCellsDb::create_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth,
|
||||
int idx) {
|
||||
TRY_RESULT(res, StaticBocExtCell::create(PrunnedCellInfo{level_mask, hash, depth},
|
||||
StaticBocExtCellExtra{idx, shared_from_this()}));
|
||||
return std::move(res);
|
||||
}
|
||||
|
||||
//
|
||||
// Baseline implementation
|
||||
//
|
||||
class StaticBagOfCellsDbBaselineImpl : public StaticBagOfCellsDb {
|
||||
public:
|
||||
StaticBagOfCellsDbBaselineImpl(std::vector<Ref<Cell>> roots) : roots_(std::move(roots)) {
|
||||
}
|
||||
td::Result<size_t> get_root_count() override {
|
||||
return roots_.size();
|
||||
};
|
||||
td::Result<Ref<Cell>> get_root_cell(size_t idx) override {
|
||||
if (idx >= roots_.size()) {
|
||||
return td::Status::Error(PSLICE() << "invalid root_cell index: " << idx);
|
||||
}
|
||||
return roots_[idx];
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<Ref<Cell>> roots_;
|
||||
|
||||
td::Result<Ref<DataCell>> load_by_idx(int idx) override {
|
||||
UNREACHABLE();
|
||||
}
|
||||
};
|
||||
|
||||
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbBaseline::create(std::unique_ptr<BlobView> data) {
|
||||
std::string buf(data->size(), '\0');
|
||||
TRY_RESULT(slice, data->view(buf, 0));
|
||||
return create(slice);
|
||||
}
|
||||
|
||||
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbBaseline::create(td::Slice data) {
|
||||
BagOfCells boc;
|
||||
TRY_RESULT(x, boc.deserialize(data));
|
||||
if (x <= 0) {
|
||||
return td::Status::Error("failed to deserialize");
|
||||
}
|
||||
std::vector<Ref<Cell>> roots(boc.get_root_count());
|
||||
for (int i = 0; i < boc.get_root_count(); i++) {
|
||||
roots[i] = boc.get_root_cell(i);
|
||||
}
|
||||
return std::make_shared<StaticBagOfCellsDbBaselineImpl>(std::move(roots));
|
||||
}
|
||||
|
||||
//
|
||||
// Main implementation
|
||||
//
|
||||
class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb {
|
||||
public:
|
||||
explicit StaticBagOfCellsDbLazyImpl(std::unique_ptr<BlobView> data, StaticBagOfCellsDbLazy::Options options)
|
||||
: data_(std::move(data)), options_(std::move(options)) {
|
||||
get_thread_safe_counter().add(1);
|
||||
}
|
||||
td::Result<size_t> get_root_count() override {
|
||||
TRY_STATUS(check_status());
|
||||
TRY_STATUS(check_result(load_header()));
|
||||
return info_.root_count;
|
||||
};
|
||||
td::Result<Ref<Cell>> get_root_cell(size_t idx) override {
|
||||
TRY_STATUS(check_status());
|
||||
TRY_RESULT(root_count, get_root_count());
|
||||
if (idx >= root_count) {
|
||||
return td::Status::Error(PSLICE() << "invalid root_cell index: " << idx);
|
||||
}
|
||||
TRY_RESULT(cell_idx, load_root_idx(td::narrow_cast<int>(idx)));
|
||||
// Load DataCell in order to ensure lower hashes correctness
|
||||
// They will be valid for all non-root cell automaically
|
||||
TRY_RESULT(data_cell, check_result(load_data_cell(td::narrow_cast<int>(cell_idx))));
|
||||
return create_root_cell(std::move(data_cell));
|
||||
};
|
||||
|
||||
~StaticBagOfCellsDbLazyImpl() {
|
||||
//LOG(ERROR) << deserialize_cell_cnt_ << " " << deserialize_cell_hash_cnt_;
|
||||
get_thread_safe_counter().add(-1);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<bool> should_cache_cells_{true};
|
||||
std::unique_ptr<BlobView> data_;
|
||||
StaticBagOfCellsDbLazy::Options options_;
|
||||
bool has_info_{false};
|
||||
BagOfCells::Info info_;
|
||||
|
||||
std::mutex index_i_mutex_;
|
||||
td::RwMutex index_data_rw_mutex_;
|
||||
std::string index_data_;
|
||||
std::atomic<int> index_i_{0};
|
||||
size_t index_offset_{0};
|
||||
DataCellCacheMutex cells_;
|
||||
//DataCellCacheNoop cells_;
|
||||
//DataCellCacheTdlib cells_;
|
||||
int next_idx_{0};
|
||||
Ref<Cell> empty_cell_;
|
||||
|
||||
//stats
|
||||
td::ThreadSafeCounter deserialize_cell_cnt_;
|
||||
td::ThreadSafeCounter deserialize_cell_hash_cnt_;
|
||||
|
||||
std::atomic<bool> has_error_{false};
|
||||
std::mutex status_mutex_;
|
||||
td::Status status_;
|
||||
|
||||
static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() {
|
||||
static auto res = td::NamedThreadSafeCounter::get_default().get_counter("StaticBagOfCellsDbLazy");
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Status check_status() TD_WARN_UNUSED_RESULT {
|
||||
if (has_error_.load(std::memory_order_relaxed)) {
|
||||
std::lock_guard<std::mutex> guard(status_mutex_);
|
||||
return status_.clone();
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
template <class T>
|
||||
T check_result(T&& to_check) {
|
||||
CHECK(status_.is_ok());
|
||||
if (to_check.is_error()) {
|
||||
std::lock_guard<std::mutex> guard(status_mutex_);
|
||||
has_error_.store(true);
|
||||
status_ = to_check.error().clone();
|
||||
}
|
||||
return std::forward<T>(to_check);
|
||||
}
|
||||
|
||||
td::Result<Ref<DataCell>> load_by_idx(int idx) override {
|
||||
TRY_STATUS(check_status());
|
||||
return check_result(load_data_cell(idx));
|
||||
}
|
||||
|
||||
struct Ptr {
|
||||
td::MutableSlice as_slice() {
|
||||
return data;
|
||||
}
|
||||
td::string data;
|
||||
};
|
||||
// May be optimized
|
||||
auto alloc(size_t size) {
|
||||
//return td::StackAllocator::alloc(size);
|
||||
return Ptr{std::string(size, '\0')};
|
||||
}
|
||||
|
||||
td::Result<size_t> load_idx_offset(int idx) {
|
||||
if (idx < 0) {
|
||||
return 0;
|
||||
}
|
||||
td::Slice offset_view;
|
||||
CHECK(info_.offset_byte_size <= 8);
|
||||
char arr[8];
|
||||
td::RwMutex::ReadLock guard;
|
||||
if (info_.has_index) {
|
||||
TRY_RESULT(new_offset_view, data_->view(td::MutableSlice(arr, info_.offset_byte_size),
|
||||
info_.index_offset + idx * info_.offset_byte_size));
|
||||
offset_view = new_offset_view;
|
||||
} else {
|
||||
guard = index_data_rw_mutex_.lock_read().move_as_ok();
|
||||
offset_view = td::Slice(index_data_).substr(idx * info_.offset_byte_size, info_.offset_byte_size);
|
||||
}
|
||||
|
||||
CHECK(offset_view.size() == (size_t)info_.offset_byte_size);
|
||||
return td::narrow_cast<std::size_t>(info_.read_offset(offset_view.ubegin()));
|
||||
}
|
||||
|
||||
td::Result<td::int64> load_root_idx(int root_i) {
|
||||
CHECK(root_i >= 0 && root_i < info_.root_count);
|
||||
if (!info_.has_roots) {
|
||||
return 0;
|
||||
}
|
||||
char arr[8];
|
||||
TRY_RESULT(idx_view, data_->view(td::MutableSlice(arr, info_.ref_byte_size),
|
||||
info_.roots_offset + root_i * info_.ref_byte_size));
|
||||
CHECK(idx_view.size() == (size_t)info_.ref_byte_size);
|
||||
return info_.read_ref(idx_view.ubegin());
|
||||
}
|
||||
|
||||
struct CellLocation {
|
||||
std::size_t begin;
|
||||
std::size_t end;
|
||||
bool should_cache;
|
||||
};
|
||||
td::Result<CellLocation> get_cell_location(int idx) {
|
||||
CHECK(idx >= 0);
|
||||
CHECK(idx < info_.cell_count);
|
||||
TRY_STATUS(preload_index(idx));
|
||||
TRY_RESULT(from, load_idx_offset(idx - 1));
|
||||
TRY_RESULT(till, load_idx_offset(idx));
|
||||
CellLocation res;
|
||||
res.begin = from;
|
||||
res.end = till;
|
||||
res.should_cache = true;
|
||||
if (info_.has_cache_bits) {
|
||||
res.begin /= 2;
|
||||
res.should_cache = res.end % 2 == 1;
|
||||
res.end /= 2;
|
||||
}
|
||||
CHECK(std::numeric_limits<std::size_t>::max() - res.begin >= info_.data_offset);
|
||||
CHECK(std::numeric_limits<std::size_t>::max() - res.end >= info_.data_offset);
|
||||
res.begin += static_cast<std::size_t>(info_.data_offset);
|
||||
res.end += static_cast<std::size_t>(info_.data_offset);
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Status load_header() {
|
||||
if (has_info_) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
std::string header(1000, '\0');
|
||||
TRY_RESULT(header_view, data_->view(td::MutableSlice(header).truncate(data_->size()), 0))
|
||||
auto parse_res = info_.parse_serialized_header(header_view);
|
||||
if (parse_res <= 0) {
|
||||
return td::Status::Error("bag-of-cell error: failed to read header");
|
||||
}
|
||||
if (info_.total_size < data_->size()) {
|
||||
return td::Status::Error("bag-of-cell error: not enough data");
|
||||
}
|
||||
if (options_.check_crc32c && info_.has_crc32c) {
|
||||
std::string buf(td::narrow_cast<std::size_t>(info_.total_size), '\0');
|
||||
TRY_RESULT(data, data_->view(td::MutableSlice(buf), 0));
|
||||
unsigned crc_computed = td::crc32c(td::Slice{data.ubegin(), data.uend() - 4});
|
||||
unsigned crc_stored = td::as<unsigned>(data.uend() - 4);
|
||||
if (crc_computed != crc_stored) {
|
||||
return td::Status::Error(PSLICE()
|
||||
<< "bag-of-cells CRC32C mismatch: expected " << td::format::as_hex(crc_computed)
|
||||
<< ", found " << td::format::as_hex(crc_stored));
|
||||
}
|
||||
}
|
||||
has_info_ = true;
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status preload_index(int idx) {
|
||||
if (info_.has_index) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
CHECK(idx < info_.cell_count);
|
||||
if (index_i_.load(std::memory_order_relaxed) > idx) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> index_i_guard(index_i_mutex_);
|
||||
std::array<char, 1024> buf;
|
||||
auto buf_slice = td::MutableSlice(buf.data(), buf.size());
|
||||
for (; index_i_ <= idx; index_i_++) {
|
||||
auto offset = td::narrow_cast<size_t>(info_.data_offset + index_offset_);
|
||||
CHECK(data_->size() >= offset);
|
||||
TRY_RESULT(cell, data_->view(buf_slice.copy().truncate(data_->size() - offset), offset));
|
||||
CellSerializationInfo cell_info;
|
||||
TRY_STATUS(cell_info.init(cell, info_.ref_byte_size));
|
||||
index_offset_ += cell_info.end_offset;
|
||||
LOG_CHECK((unsigned)info_.offset_byte_size <= 8) << info_.offset_byte_size;
|
||||
td::uint8 tmp[8];
|
||||
info_.write_offset(tmp, index_offset_);
|
||||
auto guard = index_data_rw_mutex_.lock_write();
|
||||
index_data_.append(reinterpret_cast<const char*>(tmp), info_.offset_byte_size);
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
Ref<Cell> get_any_cell(int idx) {
|
||||
return get_data_cell(idx);
|
||||
}
|
||||
|
||||
Ref<DataCell> get_data_cell(int idx) {
|
||||
return cells_.load(idx);
|
||||
}
|
||||
|
||||
Ref<DataCell> set_data_cell(int idx, Ref<DataCell> cell) {
|
||||
if (/*idx >= info_.root_count || */ !should_cache_cells_.load(std::memory_order_relaxed)) {
|
||||
return cell;
|
||||
}
|
||||
CHECK(cell.not_null());
|
||||
return cells_.store(idx, std::move(cell));
|
||||
}
|
||||
|
||||
Ref<Cell> set_any_cell(int idx, Ref<Cell> cell) {
|
||||
auto data_cell = Ref<DataCell>(cell);
|
||||
if (data_cell.is_null()) {
|
||||
return cell;
|
||||
}
|
||||
return set_data_cell(idx, std::move(data_cell));
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> load_any_cell(int idx) {
|
||||
{
|
||||
auto cell = get_any_cell(idx);
|
||||
if (cell.not_null()) {
|
||||
return std::move(cell);
|
||||
}
|
||||
}
|
||||
|
||||
TRY_RESULT(cell_location, get_cell_location(idx));
|
||||
auto buf = alloc(cell_location.end - cell_location.begin);
|
||||
TRY_RESULT(cell_slice, data_->view(buf.as_slice(), cell_location.begin));
|
||||
TRY_RESULT(res, deserialize_any_cell(idx, cell_slice, cell_location.should_cache));
|
||||
return std::move(res);
|
||||
}
|
||||
|
||||
td::Result<Ref<DataCell>> load_data_cell(int idx) {
|
||||
{
|
||||
auto cell = get_data_cell(idx);
|
||||
if (cell.not_null()) {
|
||||
return std::move(cell);
|
||||
}
|
||||
}
|
||||
|
||||
TRY_RESULT(cell_location, get_cell_location(idx));
|
||||
auto buf = alloc(cell_location.end - cell_location.begin);
|
||||
TRY_RESULT(cell_slice, data_->view(buf.as_slice(), cell_location.begin));
|
||||
TRY_RESULT(res, deserialize_data_cell(idx, cell_slice, cell_location.should_cache));
|
||||
return std::move(res);
|
||||
}
|
||||
|
||||
td::Result<Ref<DataCell>> deserialize_data_cell(int idx, td::Slice cell_slice, bool should_cache) {
|
||||
CellSerializationInfo cell_info;
|
||||
TRY_STATUS(cell_info.init(cell_slice, info_.ref_byte_size));
|
||||
if (cell_slice.size() != cell_info.end_offset) {
|
||||
return td::Status::Error(PSLICE() << "unused space in cell #" << idx << " serialization");
|
||||
}
|
||||
return deserialize_data_cell(idx, cell_slice, cell_info, should_cache);
|
||||
}
|
||||
|
||||
td::Result<Ref<DataCell>> deserialize_data_cell(int idx, td::Slice cell_slice, const CellSerializationInfo& cell_info,
|
||||
bool should_cache) {
|
||||
deserialize_cell_cnt_.add(1);
|
||||
Ref<Cell> refs[4];
|
||||
CHECK(cell_info.refs_cnt <= 4);
|
||||
auto* ref_ptr = cell_slice.ubegin() + cell_info.refs_offset;
|
||||
for (int k = 0; k < cell_info.refs_cnt; k++, ref_ptr += info_.ref_byte_size) {
|
||||
int ref_idx = td::narrow_cast<int>(info_.read_ref(ref_ptr));
|
||||
if (ref_idx >= info_.cell_count) {
|
||||
return td::Status::Error(PSLICE() << "invalid bag-of-cells cell #" << idx << " refers to cell #" << ref_idx
|
||||
<< " which is too big " << td::tag("cell_count", info_.cell_count));
|
||||
}
|
||||
if (idx >= ref_idx) {
|
||||
return td::Status::Error(PSLICE() << "invalid bag-of-cells cell #" << idx << " refers to cell #" << ref_idx
|
||||
<< " which is a backward reference");
|
||||
}
|
||||
TRY_RESULT(ref, load_any_cell(ref_idx));
|
||||
refs[k] = std::move(ref);
|
||||
}
|
||||
|
||||
TRY_RESULT(data_cell, cell_info.create_data_cell(cell_slice, td::Span<Ref<Cell>>(refs, cell_info.refs_cnt)));
|
||||
if (!should_cache) {
|
||||
return std::move(data_cell);
|
||||
}
|
||||
return set_data_cell(idx, std::move(data_cell));
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> deserialize_any_cell(int idx, td::Slice cell_slice, bool should_cache) {
|
||||
CellSerializationInfo cell_info;
|
||||
TRY_STATUS(cell_info.init(cell_slice, info_.ref_byte_size));
|
||||
if (cell_info.with_hashes) {
|
||||
deserialize_cell_hash_cnt_.add(1);
|
||||
int n = cell_info.level_mask.get_hashes_count();
|
||||
return create_ext_cell(cell_info.level_mask, cell_slice.substr(cell_info.hashes_offset, n * Cell::hash_bytes),
|
||||
cell_slice.substr(cell_info.depth_offset, n * Cell::depth_bytes), idx);
|
||||
}
|
||||
TRY_RESULT(data_cell, deserialize_data_cell(idx, cell_slice, cell_info, should_cache));
|
||||
return std::move(data_cell);
|
||||
}
|
||||
td::Result<Ref<Cell>> create_root_cell(Ref<DataCell> data_cell) {
|
||||
return StaticBocRootCell::create(std::move(data_cell), shared_from_this());
|
||||
}
|
||||
};
|
||||
|
||||
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbLazy::create(std::unique_ptr<BlobView> data,
|
||||
Options options) {
|
||||
return std::make_shared<StaticBagOfCellsDbLazyImpl>(std::move(data), std::move(options));
|
||||
}
|
||||
|
||||
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbLazy::create(td::BufferSlice data, Options options) {
|
||||
return std::make_shared<StaticBagOfCellsDbLazyImpl>(vm::BufferSliceBlobView::create(std::move(data)),
|
||||
std::move(options));
|
||||
}
|
||||
|
||||
td::Result<std::shared_ptr<StaticBagOfCellsDb>> StaticBagOfCellsDbLazy::create(std::string data, Options options) {
|
||||
return create(BufferSliceBlobView::create(td::BufferSlice(data)), std::move(options));
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
60
crypto/vm/db/StaticBagOfCellsDb.h
Normal file
60
crypto/vm/db/StaticBagOfCellsDb.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "vm/cells.h"
|
||||
#include "vm/db/BlobView.h"
|
||||
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
namespace vm {
|
||||
class StaticBagOfCellsDb : public std::enable_shared_from_this<StaticBagOfCellsDb> {
|
||||
public:
|
||||
virtual ~StaticBagOfCellsDb() = default;
|
||||
// TODO: handle errors
|
||||
virtual td::Result<size_t> get_root_count() = 0;
|
||||
virtual td::Result<Ref<Cell>> get_root_cell(size_t idx) = 0;
|
||||
|
||||
protected:
|
||||
virtual td::Result<Ref<DataCell>> load_by_idx(int idx) = 0;
|
||||
friend class StaticBocLoader;
|
||||
friend class StaticBocRootLoader;
|
||||
td::Result<Ref<Cell>> create_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth, int idx);
|
||||
td::Result<Ref<Cell>> create_root_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth, int idx);
|
||||
};
|
||||
|
||||
class StaticBagOfCellsDbBaseline {
|
||||
public:
|
||||
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(std::unique_ptr<BlobView> data);
|
||||
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(td::Slice data);
|
||||
};
|
||||
|
||||
class StaticBagOfCellsDbLazy {
|
||||
public:
|
||||
struct Options {
|
||||
Options() {
|
||||
}
|
||||
bool check_crc32c{false};
|
||||
};
|
||||
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(std::unique_ptr<BlobView> data, Options options = {});
|
||||
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(td::BufferSlice data, Options options = {});
|
||||
static td::Result<std::shared_ptr<StaticBagOfCellsDb>> create(std::string data, Options options = {});
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
325
crypto/vm/db/TonDb.cpp
Normal file
325
crypto/vm/db/TonDb.cpp
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/db/TonDb.h"
|
||||
|
||||
#include "td/utils/tl_helpers.h"
|
||||
#include "td/utils/Random.h"
|
||||
|
||||
#if TDDB_USE_ROCKSDB
|
||||
#include "td/db/RocksDb.h"
|
||||
#endif
|
||||
|
||||
namespace vm {
|
||||
|
||||
template <class StorerT>
|
||||
void SmartContractMeta::store(StorerT &storer) const {
|
||||
using td::store;
|
||||
store(stats.cells_total_count, storer);
|
||||
store(stats.cells_total_size, storer);
|
||||
store(type, storer);
|
||||
}
|
||||
template <class ParserT>
|
||||
void SmartContractMeta::parse(ParserT &parser) {
|
||||
using td::parse;
|
||||
parse(stats.cells_total_count, parser);
|
||||
parse(stats.cells_total_size, parser);
|
||||
parse(type, parser);
|
||||
}
|
||||
|
||||
//
|
||||
// SmartContractDbImpl
|
||||
//
|
||||
Ref<Cell> SmartContractDbImpl::get_root() {
|
||||
if (sync_root_with_db_ || !new_root_.is_null()) {
|
||||
return new_root_;
|
||||
}
|
||||
|
||||
sync_root_with_db();
|
||||
return new_root_;
|
||||
}
|
||||
|
||||
void SmartContractDbImpl::set_root(Ref<Cell> new_root) {
|
||||
CHECK(new_root.not_null());
|
||||
sync_root_with_db();
|
||||
if (is_dynamic()) {
|
||||
cell_db_->dec(new_root_);
|
||||
}
|
||||
new_root_ = std::move(new_root);
|
||||
if (is_dynamic()) {
|
||||
cell_db_->inc(new_root_);
|
||||
}
|
||||
}
|
||||
|
||||
SmartContractDbImpl::SmartContractDbImpl(td::Slice hash, std::shared_ptr<KeyValueReader> kv)
|
||||
: hash_(hash.str()), kv_(std::move(kv)) {
|
||||
cell_db_ = DynamicBagOfCellsDb::create();
|
||||
}
|
||||
|
||||
SmartContractMeta SmartContractDbImpl::get_meta() {
|
||||
sync_root_with_db();
|
||||
return meta_;
|
||||
}
|
||||
td::Status SmartContractDbImpl::validate_meta() {
|
||||
if (!is_dynamic()) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
sync_root_with_db();
|
||||
TRY_RESULT(in_db, kv_->count({}));
|
||||
if (static_cast<td::int64>(in_db) != meta_.stats.cells_total_count + 2) {
|
||||
return td::Status::Error(PSLICE() << "Invalid meta " << td::tag("expected_count", in_db)
|
||||
<< td::tag("meta_count", meta_.stats.cells_total_count + 2));
|
||||
}
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
bool SmartContractDbImpl::is_dynamic() const {
|
||||
return meta_.type == SmartContractMeta::Dynamic;
|
||||
}
|
||||
|
||||
bool SmartContractDbImpl::is_root_changed() const {
|
||||
return !new_root_.is_null() && (db_root_.is_null() || db_root_->get_hash() != new_root_->get_hash());
|
||||
}
|
||||
|
||||
void SmartContractDbImpl::sync_root_with_db() {
|
||||
if (sync_root_with_db_) {
|
||||
return;
|
||||
}
|
||||
std::string root_hash;
|
||||
kv_->get("root", root_hash);
|
||||
std::string meta_serialized;
|
||||
kv_->get("meta", meta_serialized);
|
||||
// TODO: proper serialization
|
||||
td::unserialize(meta_, meta_serialized).ignore();
|
||||
sync_root_with_db_ = true;
|
||||
|
||||
if (root_hash.empty()) {
|
||||
meta_.type = SmartContractMeta::Static;
|
||||
//meta_.type = SmartContractMeta::Dynamic;
|
||||
} else {
|
||||
if (is_dynamic()) {
|
||||
//FIXME: error handling
|
||||
db_root_ = cell_db_->load_cell(root_hash).move_as_ok();
|
||||
} else {
|
||||
std::string boc_serialized;
|
||||
kv_->get("boc", boc_serialized);
|
||||
BagOfCells boc;
|
||||
//TODO: check error
|
||||
boc.deserialize(boc_serialized);
|
||||
db_root_ = boc.get_root_cell();
|
||||
}
|
||||
CHECK(db_root_->get_hash().as_slice() == root_hash);
|
||||
new_root_ = db_root_;
|
||||
}
|
||||
}
|
||||
|
||||
enum { boc_size = 2000 };
|
||||
void SmartContractDbImpl::prepare_commit_dynamic(bool force) {
|
||||
if (!is_dynamic()) {
|
||||
CHECK(force);
|
||||
meta_.stats = {};
|
||||
cell_db_->inc(new_root_);
|
||||
}
|
||||
cell_db_->prepare_commit();
|
||||
meta_.stats.apply_diff(cell_db_->get_stats_diff());
|
||||
|
||||
if (!force && meta_.stats.cells_total_size < boc_size) {
|
||||
//LOG(ERROR) << "DYNAMIC -> BOC";
|
||||
return prepare_commit_static(true);
|
||||
}
|
||||
is_dynamic_commit_ = true;
|
||||
};
|
||||
|
||||
void SmartContractDbImpl::prepare_commit_static(bool force) {
|
||||
BagOfCells boc;
|
||||
boc.add_root(new_root_);
|
||||
boc.import_cells().ensure(); // FIXME
|
||||
if (!force && boc.estimate_serialized_size(15) > boc_size) {
|
||||
//LOG(ERROR) << "BOC -> DYNAMIC ";
|
||||
return prepare_commit_dynamic(true);
|
||||
}
|
||||
if (is_dynamic()) {
|
||||
cell_db_->dec(new_root_);
|
||||
cell_db_->prepare_commit();
|
||||
// stats is invalid now
|
||||
}
|
||||
is_dynamic_commit_ = false;
|
||||
boc_to_commit_ = boc.serialize_to_string(15);
|
||||
meta_.stats = {};
|
||||
}
|
||||
|
||||
void SmartContractDbImpl::prepare_transaction() {
|
||||
sync_root_with_db();
|
||||
if (!is_root_changed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dynamic()) {
|
||||
prepare_commit_dynamic(false);
|
||||
} else {
|
||||
prepare_commit_static(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SmartContractDbImpl::commit_transaction(KeyValue &kv) {
|
||||
if (!is_root_changed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_dynamic_commit_) {
|
||||
//LOG(ERROR) << "STORE DYNAMIC";
|
||||
if (!is_dynamic() && db_root_.not_null()) {
|
||||
kv.erase("boc");
|
||||
}
|
||||
CellStorer storer(kv);
|
||||
cell_db_->commit(storer);
|
||||
meta_.type = SmartContractMeta::Dynamic;
|
||||
} else {
|
||||
//LOG(ERROR) << "STORE BOC";
|
||||
if (is_dynamic() && db_root_.not_null()) {
|
||||
//LOG(ERROR) << "Clear Dynamic db";
|
||||
CellStorer storer(kv);
|
||||
cell_db_->commit(storer);
|
||||
cell_db_ = DynamicBagOfCellsDb::create();
|
||||
}
|
||||
meta_.type = SmartContractMeta::Static;
|
||||
kv.set("boc", boc_to_commit_);
|
||||
boc_to_commit_ = {};
|
||||
}
|
||||
|
||||
kv.set("root", new_root_->get_hash().as_slice());
|
||||
kv.set("meta", td::serialize(meta_));
|
||||
db_root_ = new_root_;
|
||||
}
|
||||
|
||||
void SmartContractDbImpl::set_reader(std::shared_ptr<KeyValueReader> reader) {
|
||||
kv_ = std::move(reader);
|
||||
cell_db_->set_loader(std::make_unique<CellLoader>(kv_));
|
||||
}
|
||||
|
||||
//
|
||||
// TonDbTransactionImpl
|
||||
//
|
||||
SmartContractDb TonDbTransactionImpl::begin_smartcontract(td::Slice hash) {
|
||||
SmartContractDb res;
|
||||
contracts_.apply(hash, [&](auto &info) {
|
||||
if (!info.is_inited) {
|
||||
info.is_inited = true;
|
||||
info.hash = hash.str();
|
||||
info.smart_contract_db = std::make_unique<SmartContractDbImpl>(hash, nullptr);
|
||||
}
|
||||
LOG_CHECK(info.generation_ != generation_) << "Cannot begin one smartcontract twice during the same transaction";
|
||||
CHECK(info.smart_contract_db);
|
||||
info.smart_contract_db->set_reader(std::make_shared<td::PrefixedKeyValueReader>(reader_, hash));
|
||||
res = std::move(info.smart_contract_db);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
void TonDbTransactionImpl::commit_smartcontract(SmartContractDb txn) {
|
||||
commit_smartcontract(SmartContractDiff(std::move(txn)));
|
||||
}
|
||||
void TonDbTransactionImpl::commit_smartcontract(SmartContractDiff txn) {
|
||||
{
|
||||
td::PrefixedKeyValue kv(kv_, txn.hash());
|
||||
txn.commit_transaction(kv);
|
||||
}
|
||||
end_smartcontract(txn.extract_smartcontract());
|
||||
}
|
||||
|
||||
void TonDbTransactionImpl::abort_smartcontract(SmartContractDb txn) {
|
||||
end_smartcontract(std::move(txn));
|
||||
}
|
||||
void TonDbTransactionImpl::abort_smartcontract(SmartContractDiff txn) {
|
||||
end_smartcontract(txn.extract_smartcontract());
|
||||
}
|
||||
|
||||
TonDbTransactionImpl::TonDbTransactionImpl(std::shared_ptr<KeyValue> kv) : kv_(std::move(kv)) {
|
||||
CHECK(kv_ != nullptr);
|
||||
reader_.reset(kv_->snapshot().release());
|
||||
}
|
||||
|
||||
void TonDbTransactionImpl::begin() {
|
||||
kv_->begin_transaction();
|
||||
generation_++;
|
||||
}
|
||||
void TonDbTransactionImpl::commit() {
|
||||
kv_->commit_transaction();
|
||||
reader_.reset(kv_->snapshot().release());
|
||||
}
|
||||
void TonDbTransactionImpl::abort() {
|
||||
kv_->abort_transaction();
|
||||
}
|
||||
void TonDbTransactionImpl::clear_cache() {
|
||||
contracts_ = {};
|
||||
}
|
||||
|
||||
void TonDbTransactionImpl::end_smartcontract(SmartContractDb smart_contract) {
|
||||
contracts_.apply(smart_contract->hash(), [&](auto &info) {
|
||||
CHECK(info.hash == smart_contract->hash());
|
||||
CHECK(!info.smart_contract_db);
|
||||
info.smart_contract_db = std::move(smart_contract);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// TonDbImpl
|
||||
//
|
||||
TonDbImpl::TonDbImpl(std::unique_ptr<KeyValue> kv)
|
||||
: kv_(std::move(kv)), transaction_(std::make_unique<TonDbTransactionImpl>(kv_)) {
|
||||
}
|
||||
TonDbImpl::~TonDbImpl() {
|
||||
CHECK(transaction_);
|
||||
kv_->flush();
|
||||
}
|
||||
TonDbTransaction TonDbImpl::begin_transaction() {
|
||||
CHECK(transaction_);
|
||||
transaction_->begin();
|
||||
return std::move(transaction_);
|
||||
}
|
||||
void TonDbImpl::commit_transaction(TonDbTransaction transaction) {
|
||||
CHECK(!transaction_);
|
||||
CHECK(&transaction->kv() == kv_.get());
|
||||
transaction_ = std::move(transaction);
|
||||
transaction_->commit();
|
||||
}
|
||||
void TonDbImpl::abort_transaction(TonDbTransaction transaction) {
|
||||
CHECK(!transaction_);
|
||||
CHECK(&transaction->kv() == kv_.get());
|
||||
transaction_ = std::move(transaction);
|
||||
transaction_->abort();
|
||||
}
|
||||
void TonDbImpl::clear_cache() {
|
||||
CHECK(transaction_);
|
||||
transaction_->clear_cache();
|
||||
}
|
||||
|
||||
std::string TonDbImpl::stats() const {
|
||||
return kv_->stats();
|
||||
}
|
||||
|
||||
td::Result<TonDb> TonDbImpl::open(td::Slice path) {
|
||||
#if TDDB_USE_ROCKSDB
|
||||
TRY_RESULT(rocksdb, td::RocksDb::open(path.str()));
|
||||
return std::make_unique<TonDbImpl>(std::make_unique<td::RocksDb>(std::move(rocksdb)));
|
||||
#else
|
||||
return td::Status::Error("TonDb is not supported in this build");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
179
crypto/vm/db/TonDb.h
Normal file
179
crypto/vm/db/TonDb.h
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/cells.h"
|
||||
#include "vm/boc.h"
|
||||
#include "td/db/KeyValue.h"
|
||||
#include "vm/db/CellStorage.h"
|
||||
#include "vm/db/CellHashTable.h"
|
||||
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/Status.h"
|
||||
|
||||
namespace vm {
|
||||
class SmartContractDbImpl;
|
||||
using SmartContractDb = std::unique_ptr<SmartContractDbImpl>;
|
||||
using KeyValue = td::KeyValue;
|
||||
using KeyValueReader = td::KeyValueReader;
|
||||
|
||||
struct SmartContractMeta {
|
||||
DynamicBagOfCellsDb::Stats stats;
|
||||
enum BagOfCellsType { Dynamic, Static } type{Static};
|
||||
|
||||
template <class StorerT>
|
||||
void store(StorerT &storer) const;
|
||||
template <class ParserT>
|
||||
void parse(ParserT &parser);
|
||||
};
|
||||
|
||||
class SmartContractDbImpl {
|
||||
public:
|
||||
Ref<Cell> get_root();
|
||||
SmartContractMeta get_meta();
|
||||
td::Status validate_meta();
|
||||
|
||||
void set_root(Ref<Cell> new_root);
|
||||
|
||||
SmartContractDbImpl(td::Slice hash, std::shared_ptr<KeyValueReader> kv);
|
||||
|
||||
private:
|
||||
std::string hash_;
|
||||
std::shared_ptr<KeyValueReader> kv_;
|
||||
|
||||
bool sync_root_with_db_{false};
|
||||
Ref<Cell> db_root_;
|
||||
Ref<Cell> new_root_;
|
||||
SmartContractMeta meta_;
|
||||
bool is_dynamic_commit_;
|
||||
std::string boc_to_commit_;
|
||||
|
||||
std::unique_ptr<DynamicBagOfCellsDb> cell_db_;
|
||||
std::unique_ptr<BagOfCells> bag_of_cells_;
|
||||
|
||||
friend class SmartContractDiff;
|
||||
friend class TonDbTransactionImpl;
|
||||
|
||||
void sync_root_with_db();
|
||||
|
||||
td::Slice hash() const {
|
||||
return hash_;
|
||||
}
|
||||
|
||||
void prepare_transaction();
|
||||
void commit_transaction(KeyValue &kv);
|
||||
|
||||
void set_reader(std::shared_ptr<KeyValueReader> reader);
|
||||
|
||||
bool is_dynamic() const;
|
||||
void prepare_commit_dynamic(bool force);
|
||||
void prepare_commit_static(bool force);
|
||||
bool is_root_changed() const;
|
||||
};
|
||||
|
||||
class SmartContractDiff {
|
||||
public:
|
||||
explicit SmartContractDiff(SmartContractDb db) : db_(std::move(db)) {
|
||||
db_->prepare_transaction();
|
||||
}
|
||||
|
||||
SmartContractDb extract_smartcontract() {
|
||||
return std::move(db_);
|
||||
}
|
||||
|
||||
td::Slice hash() const {
|
||||
return db_->hash();
|
||||
}
|
||||
|
||||
void commit_transaction(KeyValue &kv) {
|
||||
db_->commit_transaction(kv);
|
||||
}
|
||||
|
||||
private:
|
||||
SmartContractDb db_;
|
||||
};
|
||||
|
||||
class TonDbTransactionImpl;
|
||||
using TonDbTransaction = std::unique_ptr<TonDbTransactionImpl>;
|
||||
class TonDbTransactionImpl {
|
||||
public:
|
||||
SmartContractDb begin_smartcontract(td::Slice hash = {});
|
||||
|
||||
void commit_smartcontract(SmartContractDb txn);
|
||||
void commit_smartcontract(SmartContractDiff txn);
|
||||
|
||||
void abort_smartcontract(SmartContractDb txn);
|
||||
void abort_smartcontract(SmartContractDiff txn);
|
||||
|
||||
TonDbTransactionImpl(std::shared_ptr<KeyValue> kv);
|
||||
|
||||
private:
|
||||
std::shared_ptr<KeyValue> kv_;
|
||||
std::shared_ptr<KeyValueReader> reader_;
|
||||
td::uint64 generation_{0};
|
||||
|
||||
struct SmartContractInfo {
|
||||
bool is_inited{false};
|
||||
td::uint64 generation_{0};
|
||||
std::string hash;
|
||||
SmartContractDb smart_contract_db;
|
||||
bool operator<(const SmartContractInfo &other) const {
|
||||
return hash < other.hash;
|
||||
}
|
||||
friend bool operator<(const SmartContractInfo &info, td::Slice hash) {
|
||||
return info.hash < hash;
|
||||
}
|
||||
friend bool operator<(td::Slice hash, const SmartContractInfo &info) {
|
||||
return hash < info.hash;
|
||||
}
|
||||
};
|
||||
|
||||
CellHashTable<SmartContractInfo> contracts_;
|
||||
|
||||
KeyValue &kv() {
|
||||
return *kv_;
|
||||
}
|
||||
friend class TonDbImpl;
|
||||
|
||||
void begin();
|
||||
void commit();
|
||||
void abort();
|
||||
void clear_cache();
|
||||
|
||||
void end_smartcontract(SmartContractDb smart_contract);
|
||||
};
|
||||
|
||||
class TonDbImpl;
|
||||
using TonDb = std::unique_ptr<TonDbImpl>;
|
||||
class TonDbImpl {
|
||||
public:
|
||||
TonDbImpl(std::unique_ptr<KeyValue> kv);
|
||||
~TonDbImpl();
|
||||
TonDbTransaction begin_transaction();
|
||||
void commit_transaction(TonDbTransaction transaction);
|
||||
void abort_transaction(TonDbTransaction transaction);
|
||||
void clear_cache();
|
||||
static td::Result<TonDb> open(td::Slice path);
|
||||
std::string stats() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<KeyValue> kv_;
|
||||
TonDbTransaction transaction_;
|
||||
};
|
||||
} // namespace vm
|
||||
110
crypto/vm/debugops.cpp
Normal file
110
crypto/vm/debugops.cpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#include "vm/debugops.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
|
||||
namespace vm {
|
||||
|
||||
bool vm_debug_enabled = true;
|
||||
|
||||
int exec_dummy_debug(VmState* st, int args) {
|
||||
VM_LOG(st) << "execute DEBUG " << (args & 0xff);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// similar to PUSHSLICE instruction in cellops.cpp
|
||||
int exec_dummy_debug_str(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) {
|
||||
int data_bits = ((args & 15) + 1) * 8;
|
||||
if (!cs.have(pfx_bits + data_bits)) {
|
||||
throw VmError{Excno::inv_opcode, "not enough data bits for a DEBUGSTR instruction"};
|
||||
}
|
||||
cs.advance(pfx_bits);
|
||||
auto slice = cs.fetch_subslice(data_bits);
|
||||
VM_LOG(st) << "execute DEBUGSTR " << slice->as_bitslice().to_hex();
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_dummy_debug_str(CellSlice& cs, unsigned args, int pfx_bits) {
|
||||
int data_bits = ((args & 15) + 1) * 8;
|
||||
if (!cs.have(pfx_bits + data_bits)) {
|
||||
return "";
|
||||
}
|
||||
cs.advance(pfx_bits);
|
||||
auto slice = cs.fetch_subslice(data_bits);
|
||||
slice.unique_write().remove_trailing();
|
||||
std::ostringstream os;
|
||||
os << "DEBUGSTR ";
|
||||
slice->dump_hex(os, 1, false);
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int compute_len_debug_str(const CellSlice& cs, unsigned args, int pfx_bits) {
|
||||
unsigned bits = pfx_bits + ((args & 15) + 1) * 8;
|
||||
return cs.have(bits) ? bits : 0;
|
||||
}
|
||||
|
||||
int exec_dump_stack(VmState* st) {
|
||||
VM_LOG(st) << "execute DUMPSTK";
|
||||
Stack& stack = st->get_stack();
|
||||
int d = stack.depth();
|
||||
std::cerr << "#DEBUG#: stack(" << d << " values) : ";
|
||||
if (d > 255) {
|
||||
std::cerr << "... ";
|
||||
d = 255;
|
||||
}
|
||||
for (int i = d; i > 0; i--) {
|
||||
std::cerr << stack[i - 1].to_string() << " ";
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dump_value(VmState* st, unsigned arg) {
|
||||
arg &= 15;
|
||||
VM_LOG(st) << "execute DUMP s" << arg;
|
||||
Stack& stack = st->get_stack();
|
||||
if ((int)arg < stack.depth()) {
|
||||
std::cerr << "#DEBUG#: s" << arg << " = " << stack[arg].to_string() << std::endl;
|
||||
} else {
|
||||
std::cerr << "#DEBUG#: s" << arg << " is absent" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_debug_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
if (!vm_debug_enabled) {
|
||||
cp0.insert(OpcodeInstr::mkfixedrange(0xfe00, 0xfef0, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug))
|
||||
.insert(OpcodeInstr::mkext(0xfef, 12, 4, dump_dummy_debug_str, exec_dummy_debug_str, compute_len_debug_str));
|
||||
} else {
|
||||
// NB: all non-redefined opcodes in fe00..feff should be redirected to dummy debug definitions
|
||||
cp0.insert(OpcodeInstr::mksimple(0xfe00, 16, "DUMPSTK", exec_dump_stack))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xfe01, 0xfe20, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug))
|
||||
.insert(OpcodeInstr::mkfixed(0xfe2, 12, 4, instr::dump_1sr("DUMP"), exec_dump_value))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xfe30, 0xfef0, 16, 8, instr::dump_1c_and(0xff, "DEBUG "), exec_dummy_debug))
|
||||
.insert(OpcodeInstr::mkext(0xfef, 12, 4, dump_dummy_debug_str, exec_dummy_debug_str, compute_len_debug_str));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
29
crypto/vm/debugops.h
Normal file
29
crypto/vm/debugops.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace vm {
|
||||
|
||||
class OpcodeTable;
|
||||
|
||||
extern bool vm_debug_enabled;
|
||||
|
||||
void register_debug_ops(OpcodeTable& cp0);
|
||||
|
||||
} // namespace vm
|
||||
2647
crypto/vm/dict.cpp
Normal file
2647
crypto/vm/dict.cpp
Normal file
File diff suppressed because it is too large
Load diff
474
crypto/vm/dict.h
Normal file
474
crypto/vm/dict.h
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "common/bitstring.h"
|
||||
#include "vm/cells.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include <functional>
|
||||
|
||||
namespace vm {
|
||||
using td::BitSlice;
|
||||
using td::Ref;
|
||||
|
||||
namespace dict {
|
||||
|
||||
struct LabelParser {
|
||||
enum { chk_none = 0, chk_min = 1, chk_size = 2, chk_all = 3 };
|
||||
Ref<CellSlice> remainder;
|
||||
int l_offs;
|
||||
int l_same;
|
||||
int l_bits;
|
||||
unsigned s_bits;
|
||||
LabelParser(Ref<CellSlice> cs, int max_label_len, int auto_validate = chk_all);
|
||||
LabelParser(Ref<Cell> cell, int max_label_len, int auto_validate = chk_all);
|
||||
int is_valid() const {
|
||||
return l_offs;
|
||||
}
|
||||
void validate() const;
|
||||
void validate_simple(int n) const;
|
||||
void validate_ext(int n) const;
|
||||
bool is_prefix_of(td::ConstBitPtr key, int len) const;
|
||||
bool has_prefix(td::ConstBitPtr key, int len) const;
|
||||
int common_prefix_len(td::ConstBitPtr key, int len) const;
|
||||
int extract_label_to(td::BitPtr to);
|
||||
int copy_label_prefix_to(td::BitPtr to, int max_len) const;
|
||||
td::ConstBitPtr bits() const {
|
||||
return remainder->data_bits();
|
||||
}
|
||||
td::ConstBitPtr bits_end() const {
|
||||
return bits() + l_bits;
|
||||
}
|
||||
void skip_label() {
|
||||
remainder.write().advance(s_bits);
|
||||
}
|
||||
void clear() {
|
||||
remainder.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
bool parse_label(CellSlice& cs, int max_label_len);
|
||||
};
|
||||
|
||||
struct AugmentationData {
|
||||
virtual ~AugmentationData() = default;
|
||||
virtual bool skip_extra(vm::CellSlice& cs) const = 0;
|
||||
virtual bool eval_leaf(vm::CellBuilder& cb, vm::CellSlice& val_cs) const = 0;
|
||||
virtual bool eval_fork(vm::CellBuilder& cb, vm::CellSlice& left_cs, vm::CellSlice& right_cs) const = 0;
|
||||
virtual bool eval_empty(vm::CellBuilder& cb) const = 0;
|
||||
virtual bool check_leaf(vm::CellSlice& cs, vm::CellSlice& val_cs) const;
|
||||
virtual bool check_fork(vm::CellSlice& cs, vm::CellSlice& left_cs, vm::CellSlice& right_cs) const;
|
||||
virtual bool check_empty(vm::CellSlice& cs) const;
|
||||
virtual bool check_leaf_key_extra(vm::CellSlice& val_cs, vm::CellSlice& extra_cs, td::ConstBitPtr key,
|
||||
int key_len) const {
|
||||
return check_leaf(extra_cs, val_cs);
|
||||
}
|
||||
Ref<vm::CellSlice> extract_extra(vm::CellSlice& cs) const;
|
||||
Ref<vm::CellSlice> extract_extra(Ref<vm::CellSlice> cs_ref) const;
|
||||
bool extract_extra_to(vm::CellSlice& cs, Ref<vm::CellSlice>& extra_csr) const {
|
||||
return (extra_csr = extract_extra(cs)).not_null();
|
||||
}
|
||||
bool extract_extra_to(Ref<vm::CellSlice> cs_ref, Ref<vm::CellSlice>& extra_csr) const {
|
||||
return (extra_csr = extract_extra(std::move(cs_ref))).not_null();
|
||||
}
|
||||
bool extract_extra_to(vm::CellSlice& cs, vm::CellSlice& extra) const;
|
||||
};
|
||||
|
||||
static inline bool store_cell_dict(vm::CellBuilder& cb, Ref<vm::Cell> dict_root) {
|
||||
return dict_root.not_null() ? cb.store_long_bool(1, 1) && cb.store_ref_bool(std::move(dict_root))
|
||||
: cb.store_long_bool(0, 1);
|
||||
}
|
||||
|
||||
} // namespace dict
|
||||
|
||||
struct CombineError {}; // thrown by Dictionary::combine_with
|
||||
struct CombineErrorValue {
|
||||
int arg_;
|
||||
};
|
||||
|
||||
struct DictNonEmpty {};
|
||||
struct DictAdvance {};
|
||||
|
||||
class DictionaryBase {
|
||||
protected:
|
||||
mutable Ref<CellSlice> root;
|
||||
Ref<Cell> root_cell;
|
||||
int key_bits;
|
||||
mutable int flags;
|
||||
enum { f_valid = 1, f_root_cached = 2, f_invalid = 0x80 };
|
||||
|
||||
public:
|
||||
enum class SetMode : int { Set = 3, Replace = 1, Add = 2 };
|
||||
enum { max_key_bits = 1023, max_key_bytes = (max_key_bits + 7) / 8 };
|
||||
|
||||
typedef std::function<bool(CellBuilder&)> store_value_func_t;
|
||||
|
||||
DictionaryBase(int _n, bool validate = true);
|
||||
DictionaryBase(Ref<CellSlice> _root, int _n, bool validate = true);
|
||||
DictionaryBase(const CellSlice& root_cs, int _n, bool validate = true);
|
||||
DictionaryBase(DictAdvance, CellSlice& root_cs, int _n, bool validate = true);
|
||||
DictionaryBase(Ref<Cell> cell, int _n, bool validate = true);
|
||||
DictionaryBase(DictNonEmpty, Ref<CellSlice> _root, int _n, bool validate = true);
|
||||
DictionaryBase(DictNonEmpty, const CellSlice& root_cs, int _n, bool validate = true);
|
||||
virtual ~DictionaryBase() = default;
|
||||
|
||||
static Ref<Cell> construct_root_from(const CellSlice& root_node_cs);
|
||||
Ref<CellSlice> get_root() const;
|
||||
Ref<CellSlice> extract_root() &&;
|
||||
Ref<Cell> get_root_cell() const {
|
||||
return root_cell;
|
||||
}
|
||||
Ref<Cell> extract_root_cell() && {
|
||||
return std::move(root_cell);
|
||||
}
|
||||
bool append_dict_to_bool(CellBuilder& cb) &&;
|
||||
bool append_dict_to_bool(CellBuilder& cb) const &;
|
||||
int get_key_bits() const {
|
||||
return key_bits;
|
||||
}
|
||||
bool is_valid() const {
|
||||
return flags & f_valid;
|
||||
}
|
||||
void reset() {
|
||||
root.clear();
|
||||
root_cell.clear();
|
||||
flags = f_valid;
|
||||
}
|
||||
virtual bool validate();
|
||||
void force_validate();
|
||||
bool is_empty() const {
|
||||
return root_cell.is_null();
|
||||
}
|
||||
static Ref<CellSlice> get_empty_dictionary();
|
||||
|
||||
protected:
|
||||
bool init_root_for_nonempty(const CellSlice& cs);
|
||||
bool invalidate() {
|
||||
flags |= f_invalid;
|
||||
return false;
|
||||
}
|
||||
bool compute_root() const;
|
||||
static Ref<CellSlice> new_empty_dictionary();
|
||||
void set_root_cell(Ref<Cell> cell) {
|
||||
root_cell = std::move(cell);
|
||||
flags &= ~f_root_cached;
|
||||
}
|
||||
};
|
||||
|
||||
class DictionaryFixed : public DictionaryBase {
|
||||
public:
|
||||
typedef std::function<int(vm::CellSlice&, td::ConstBitPtr, int)> filter_func_t;
|
||||
typedef std::function<bool(CellBuilder&, Ref<CellSlice>, Ref<CellSlice>)> simple_combine_func_t;
|
||||
typedef std::function<bool(CellBuilder&, Ref<CellSlice>, Ref<CellSlice>, td::ConstBitPtr, int)> combine_func_t;
|
||||
typedef std::function<bool(Ref<CellSlice>, td::ConstBitPtr, int)> foreach_func_t;
|
||||
typedef std::function<bool(td::ConstBitPtr, int, Ref<CellSlice>, Ref<CellSlice>)> scan_diff_func_t;
|
||||
|
||||
DictionaryFixed(int _n, bool validate = true) : DictionaryBase(_n, validate) {
|
||||
}
|
||||
DictionaryFixed(Ref<CellSlice> _root, int _n, bool validate = true) : DictionaryBase(std::move(_root), _n, validate) {
|
||||
}
|
||||
DictionaryFixed(const CellSlice& root_cs, int _n, bool validate = true) : DictionaryBase(root_cs, _n, validate) {
|
||||
}
|
||||
DictionaryFixed(DictAdvance, CellSlice& root_cs, int _n, bool validate = true)
|
||||
: DictionaryBase(DictAdvance(), root_cs, _n, validate) {
|
||||
}
|
||||
DictionaryFixed(Ref<Cell> cell, int _n, bool validate = true) : DictionaryBase(std::move(cell), _n, validate) {
|
||||
}
|
||||
DictionaryFixed(DictNonEmpty, Ref<CellSlice> _root, int _n, bool validate = true)
|
||||
: DictionaryBase(DictNonEmpty(), std::move(_root), _n, validate) {
|
||||
}
|
||||
DictionaryFixed(DictNonEmpty, const CellSlice& root_cs, int _n, bool validate = true)
|
||||
: DictionaryBase(DictNonEmpty(), root_cs, _n, validate) {
|
||||
}
|
||||
static BitSlice integer_key(td::RefInt256 x, unsigned n, bool sgnd = true, unsigned char buffer[128] = 0,
|
||||
bool quiet = false);
|
||||
static bool integer_key_simple(td::RefInt256 x, unsigned n, bool sgnd, td::BitPtr buffer, bool quiet = false);
|
||||
bool key_exists(td::ConstBitPtr key, int key_len);
|
||||
bool int_key_exists(long long key);
|
||||
bool uint_key_exists(unsigned long long key);
|
||||
Ref<CellSlice> lookup(td::ConstBitPtr key, int key_len);
|
||||
Ref<CellSlice> lookup_delete(td::ConstBitPtr key, int key_len);
|
||||
Ref<CellSlice> get_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max = false, bool invert_first = false);
|
||||
Ref<CellSlice> extract_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max = false,
|
||||
bool invert_first = false);
|
||||
Ref<CellSlice> lookup_nearest_key(td::BitPtr key_buffer, int key_len, bool fetch_next = false, bool allow_eq = false,
|
||||
bool invert_first = false);
|
||||
bool has_common_prefix(td::ConstBitPtr prefix, int prefix_len);
|
||||
int get_common_prefix(td::BitPtr buffer, unsigned buffer_len);
|
||||
bool cut_prefix_subdict(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix = false);
|
||||
Ref<vm::Cell> extract_prefix_subdict_root(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix = false);
|
||||
bool check_for_each(const foreach_func_t& foreach_func, bool invert_first = false);
|
||||
int filter(filter_func_t check);
|
||||
bool combine_with(DictionaryFixed& dict2, const combine_func_t& combine_func, int mode = 0);
|
||||
bool combine_with(DictionaryFixed& dict2, const simple_combine_func_t& simple_combine_func, int mode = 0);
|
||||
bool combine_with(DictionaryFixed& dict2);
|
||||
bool scan_diff(DictionaryFixed& dict2, const scan_diff_func_t& diff_func, int check_augm = 0);
|
||||
bool validate_check(const foreach_func_t& foreach_func, bool invert_first = false);
|
||||
bool validate_all();
|
||||
template <typename T>
|
||||
bool key_exists(const T& key) {
|
||||
return key_exists(key.bits(), key.size());
|
||||
}
|
||||
template <typename T>
|
||||
Ref<CellSlice> lookup(const T& key) {
|
||||
return lookup(key.bits(), key.size());
|
||||
}
|
||||
template <typename T>
|
||||
Ref<CellSlice> lookup_delete(const T& key) {
|
||||
return lookup_delete(key.bits(), key.size());
|
||||
}
|
||||
template <typename T>
|
||||
Ref<CellSlice> get_minmax_key(T& key_buffer, bool fetch_max = false, bool invert_first = false) {
|
||||
return get_minmax_key(key_buffer.bits(), key_buffer.size(), fetch_max, invert_first);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual int label_mode() const {
|
||||
return dict::LabelParser::chk_all;
|
||||
}
|
||||
virtual Ref<Cell> finish_create_leaf(CellBuilder& cb, const CellSlice& value) const;
|
||||
virtual Ref<Cell> finish_create_fork(CellBuilder& cb, Ref<Cell> c1, Ref<Cell> c2, int n) const;
|
||||
virtual bool check_fork(CellSlice& cs, Ref<Cell> c1, Ref<Cell> c2, int n) const {
|
||||
return true;
|
||||
}
|
||||
virtual bool check_leaf(CellSlice& cs, td::ConstBitPtr key, int key_len) const {
|
||||
return true;
|
||||
}
|
||||
bool check_leaf(Ref<CellSlice> cs_ref, td::ConstBitPtr key, int key_len) const {
|
||||
return check_leaf(cs_ref.write(), key, key_len);
|
||||
}
|
||||
bool check_fork_raw(Ref<CellSlice> cs_ref, int n) const;
|
||||
|
||||
private:
|
||||
std::pair<Ref<CellSlice>, Ref<Cell>> dict_lookup_delete(Ref<Cell> dict, td::ConstBitPtr key, int n) const;
|
||||
Ref<CellSlice> dict_lookup_minmax(Ref<Cell> dict, td::BitPtr key_buffer, int n, int mode) const;
|
||||
Ref<CellSlice> dict_lookup_nearest(Ref<Cell> dict, td::BitPtr key_buffer, int n, bool allow_eq, int mode) const;
|
||||
std::pair<Ref<Cell>, bool> extract_prefix_subdict_internal(Ref<Cell> dict, td::ConstBitPtr prefix, int prefix_len,
|
||||
bool remove_prefix = false) const;
|
||||
bool dict_check_for_each(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
|
||||
const foreach_func_t& foreach_func, bool invert_first = false) const;
|
||||
std::pair<Ref<Cell>, int> dict_filter(Ref<Cell> dict, td::BitPtr key, int n, const filter_func_t& check_leaf) const;
|
||||
Ref<Cell> dict_combine_with(Ref<Cell> dict1, Ref<Cell> dict2, td::BitPtr key_buffer, int n, int total_key_len,
|
||||
const combine_func_t& combine_func, int mode = 0, int skip1 = 0, int skip2 = 0) const;
|
||||
bool dict_scan_diff(Ref<Cell> dict1, Ref<Cell> dict2, td::BitPtr key_buffer, int n, int total_key_len,
|
||||
const scan_diff_func_t& diff_func, int mode = 0, int skip1 = 0, int skip2 = 0) const;
|
||||
bool dict_validate_check(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
|
||||
const foreach_func_t& foreach_func, bool invert_first = false) const;
|
||||
};
|
||||
|
||||
class Dictionary final : public DictionaryFixed {
|
||||
public:
|
||||
typedef std::function<bool(CellBuilder&, Ref<CellSlice>)> simple_map_func_t;
|
||||
typedef std::function<bool(CellBuilder&, Ref<CellSlice>, td::ConstBitPtr, int)> map_func_t;
|
||||
Dictionary(int _n, bool validate = true) : DictionaryFixed(_n, validate) {
|
||||
}
|
||||
Dictionary(Ref<CellSlice> _root, int _n, bool validate = true) : DictionaryFixed(std::move(_root), _n, validate) {
|
||||
}
|
||||
Dictionary(const CellSlice& root_cs, int _n, bool validate = true) : DictionaryFixed(root_cs, _n, validate) {
|
||||
}
|
||||
Dictionary(DictAdvance, CellSlice& root_cs, int _n, bool validate = true)
|
||||
: DictionaryFixed(DictAdvance(), root_cs, _n, validate) {
|
||||
}
|
||||
Dictionary(Ref<Cell> cell, int _n, bool validate = true) : DictionaryFixed(std::move(cell), _n, validate) {
|
||||
}
|
||||
Dictionary(DictNonEmpty, Ref<CellSlice> _root, int _n, bool validate = true)
|
||||
: DictionaryFixed(DictNonEmpty(), std::move(_root), _n, validate) {
|
||||
}
|
||||
Dictionary(DictNonEmpty, const CellSlice& root_cs, int _n, bool validate = true)
|
||||
: DictionaryFixed(DictNonEmpty(), root_cs, _n, validate) {
|
||||
}
|
||||
Ref<Cell> lookup_ref(td::ConstBitPtr key, int key_len);
|
||||
Ref<Cell> lookup_delete_ref(td::ConstBitPtr key, int key_len);
|
||||
bool set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode = SetMode::Set);
|
||||
bool set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode = SetMode::Set);
|
||||
bool set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode = SetMode::Set);
|
||||
bool set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& val_b, SetMode mode = SetMode::Set);
|
||||
bool set_gen(td::ConstBitPtr key, int key_len, const store_value_func_t& store_val, SetMode mode = SetMode::Set);
|
||||
Ref<CellSlice> lookup_set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode = SetMode::Set);
|
||||
Ref<Cell> lookup_set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode = SetMode::Set);
|
||||
Ref<CellSlice> lookup_set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b,
|
||||
SetMode mode = SetMode::Set);
|
||||
Ref<CellSlice> lookup_set_gen(td::ConstBitPtr key, int key_len, const store_value_func_t& store_val,
|
||||
SetMode mode = SetMode::Set);
|
||||
Ref<Cell> get_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max = false, bool invert_first = false);
|
||||
Ref<Cell> extract_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max = false,
|
||||
bool invert_first = false);
|
||||
void map(const map_func_t& map_func);
|
||||
void map(const simple_map_func_t& simple_map_func);
|
||||
template <typename T>
|
||||
Ref<Cell> lookup_ref(const T& key) {
|
||||
return lookup_ref(key.bits(), key.size());
|
||||
}
|
||||
template <typename T>
|
||||
Ref<Cell> lookup_delete_ref(const T& key) {
|
||||
return lookup_delete_ref(key.bits(), key.size());
|
||||
}
|
||||
template <typename T>
|
||||
bool set(const T& key, Ref<CellSlice> value, SetMode mode = SetMode::Set) {
|
||||
return set(key.bits(), key.size(), std::move(value), mode);
|
||||
}
|
||||
template <typename T>
|
||||
bool set_ref(const T& key, Ref<Cell> val_ref, SetMode mode = SetMode::Set) {
|
||||
return set_ref(key.bits(), key.size(), std::move(val_ref), mode);
|
||||
}
|
||||
template <typename T>
|
||||
bool set_builder(const T& key, const CellBuilder& val_b, SetMode mode = SetMode::Set) {
|
||||
return set_builder(key.bits(), key.size(), val_b, mode);
|
||||
}
|
||||
template <typename T>
|
||||
bool set_builder(const T& key, Ref<vm::CellBuilder> val_ref, SetMode mode = SetMode::Set) {
|
||||
return set_builder(key.bits(), key.size(), std::move(val_ref), mode);
|
||||
}
|
||||
template <typename T>
|
||||
Ref<CellSlice> lookup_set(const T& key, Ref<CellSlice> value, SetMode mode = SetMode::Set) {
|
||||
return lookup_set(key.bits(), key.size(), std::move(value), mode);
|
||||
}
|
||||
template <typename T>
|
||||
Ref<Cell> lookup_set_ref(const T& key, Ref<Cell> val_ref, SetMode mode = SetMode::Set) {
|
||||
return lookup_set_ref(key.bits(), key.size(), std::move(val_ref), mode);
|
||||
}
|
||||
template <typename T>
|
||||
Ref<CellSlice> lookup_set_builder(const T& key, const CellBuilder& val_b, SetMode mode = SetMode::Set) {
|
||||
return lookup_set_builder(key.bits(), key.size(), val_b, mode);
|
||||
}
|
||||
template <typename T>
|
||||
Ref<CellSlice> lookup_set_builder(const T& key, Ref<vm::CellBuilder> val_ref, SetMode mode = SetMode::Set) {
|
||||
return lookup_set_builder(key.bits(), key.size(), std::move(val_ref), mode);
|
||||
}
|
||||
|
||||
private:
|
||||
bool check_fork(CellSlice& cs, Ref<Cell> c1, Ref<Cell> c2, int n) const override {
|
||||
return cs.empty_ext();
|
||||
}
|
||||
static Ref<Cell> extract_value_ref(Ref<CellSlice> cs);
|
||||
std::pair<Ref<Cell>, int> dict_filter(Ref<Cell> dict, td::BitPtr key, int n, const filter_func_t& check_leaf) const;
|
||||
};
|
||||
|
||||
class PrefixDictionary final : public DictionaryBase {
|
||||
public:
|
||||
PrefixDictionary(int _n, bool validate = true) : DictionaryBase(_n, validate) {
|
||||
}
|
||||
PrefixDictionary(Ref<CellSlice> _root, int _n, bool validate = true)
|
||||
: DictionaryBase(std::move(_root), _n, validate) {
|
||||
}
|
||||
PrefixDictionary(Ref<Cell> cell, int _n, bool validate = true) : DictionaryBase(std::move(cell), _n, validate) {
|
||||
}
|
||||
Ref<CellSlice> lookup(td::ConstBitPtr key, int key_len);
|
||||
std::pair<Ref<CellSlice>, int> lookup_prefix(td::ConstBitPtr key, int key_len);
|
||||
Ref<CellSlice> lookup_delete(td::ConstBitPtr key, int key_len);
|
||||
bool set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode = SetMode::Set);
|
||||
bool set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode = SetMode::Set);
|
||||
bool set_gen(td::ConstBitPtr key, int key_len, const store_value_func_t& store_val, SetMode mode = SetMode::Set);
|
||||
};
|
||||
|
||||
using dict::AugmentationData;
|
||||
|
||||
class AugmentedDictionary final : public DictionaryFixed {
|
||||
const AugmentationData& aug;
|
||||
|
||||
public:
|
||||
typedef std::function<bool(Ref<CellSlice>, Ref<CellSlice>, td::ConstBitPtr, int)> foreach_extra_func_t;
|
||||
// return value of traverse_func: < 0 = error, 0 = skip, 1 = visit only left, 2 = visit only right, 5 = visit right, then left, 6 = visit left, then right
|
||||
// for leaf nodes, all >0 values mean accept and return node as the final result, 0 = skip (continue scanning)
|
||||
typedef std::function<int(td::ConstBitPtr key_prefix, int key_pfx_len, Ref<CellSlice> extra, Ref<CellSlice> value)>
|
||||
traverse_func_t;
|
||||
AugmentedDictionary(int _n, const AugmentationData& _aug, bool validate = true);
|
||||
AugmentedDictionary(Ref<CellSlice> _root, int _n, const AugmentationData& _aug, bool validate = true);
|
||||
AugmentedDictionary(Ref<Cell> cell, int _n, const AugmentationData& _aug, bool validate = true);
|
||||
AugmentedDictionary(DictNonEmpty, Ref<CellSlice> _root, int _n, const AugmentationData& _aug, bool validate = true);
|
||||
Ref<CellSlice> get_empty_dictionary() const;
|
||||
Ref<CellSlice> get_root() const;
|
||||
Ref<CellSlice> extract_root() &&;
|
||||
bool append_dict_to_bool(CellBuilder& cb) &&;
|
||||
bool append_dict_to_bool(CellBuilder& cb) const &;
|
||||
Ref<CellSlice> get_root_extra() const;
|
||||
Ref<CellSlice> lookup(td::ConstBitPtr key, int key_len);
|
||||
Ref<Cell> lookup_ref(td::ConstBitPtr key, int key_len);
|
||||
Ref<CellSlice> lookup_with_extra(td::ConstBitPtr key, int key_len);
|
||||
std::pair<Ref<CellSlice>, Ref<CellSlice>> lookup_extra(td::ConstBitPtr key, int key_len);
|
||||
std::pair<Ref<Cell>, Ref<CellSlice>> lookup_ref_extra(td::ConstBitPtr key, int key_len);
|
||||
Ref<CellSlice> lookup_delete(td::ConstBitPtr key, int key_len);
|
||||
Ref<Cell> lookup_delete_ref(td::ConstBitPtr key, int key_len);
|
||||
Ref<CellSlice> lookup_delete_with_extra(td::ConstBitPtr key, int key_len);
|
||||
std::pair<Ref<CellSlice>, Ref<CellSlice>> lookup_delete_extra(td::ConstBitPtr key, int key_len);
|
||||
std::pair<Ref<Cell>, Ref<CellSlice>> lookup_delete_ref_extra(td::ConstBitPtr key, int key_len);
|
||||
bool set(td::ConstBitPtr key, int key_len, const CellSlice& value, SetMode mode = SetMode::Set);
|
||||
bool set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode = SetMode::Set);
|
||||
bool set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode = SetMode::Set);
|
||||
bool set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& value, SetMode mode = SetMode::Set);
|
||||
bool check_for_each_extra(const foreach_extra_func_t& foreach_extra_func, bool invert_first = false);
|
||||
std::pair<Ref<CellSlice>, Ref<CellSlice>> traverse_extra(td::BitPtr key_buffer, int key_len,
|
||||
const traverse_func_t& traverse_node);
|
||||
bool validate_check_extra(const foreach_extra_func_t& foreach_extra_func, bool invert_first = false);
|
||||
bool validate() override;
|
||||
template <typename T>
|
||||
Ref<CellSlice> lookup(const T& key) {
|
||||
return lookup(key.bits(), key.size());
|
||||
}
|
||||
template <typename T>
|
||||
Ref<Cell> lookup_ref(const T& key) {
|
||||
return lookup_ref(key.bits(), key.size());
|
||||
}
|
||||
template <typename T>
|
||||
bool set(const T& key, Ref<CellSlice> val_ref, SetMode mode = SetMode::Set) {
|
||||
return set(key.bits(), key.size(), std::move(val_ref), mode);
|
||||
}
|
||||
template <typename T>
|
||||
bool set(const T& key, const CellSlice& value, SetMode mode = SetMode::Set) {
|
||||
return set(key.bits(), key.size(), value, mode);
|
||||
}
|
||||
template <typename T>
|
||||
bool set_ref(const T& key, Ref<Cell> val_ref, SetMode mode = SetMode::Set) {
|
||||
return set_ref(key.bits(), key.size(), std::move(val_ref), mode);
|
||||
}
|
||||
template <typename T>
|
||||
bool set_builder(const T& key, const CellBuilder& val_b, SetMode mode = SetMode::Set) {
|
||||
return set_builder(key.bits(), key.size(), val_b, mode);
|
||||
}
|
||||
template <typename T>
|
||||
Ref<CellSlice> lookup_delete(const T& key) {
|
||||
return lookup_delete(key.bits(), key.size());
|
||||
}
|
||||
template <typename T>
|
||||
Ref<Cell> lookup_delete_ref(const T& key) {
|
||||
return lookup_delete_ref(key.bits(), key.size());
|
||||
}
|
||||
|
||||
Ref<CellSlice> extract_value(Ref<CellSlice> value_extra) const;
|
||||
Ref<Cell> extract_value_ref(Ref<CellSlice> value_extra) const;
|
||||
std::pair<Ref<CellSlice>, Ref<CellSlice>> decompose_value_extra(Ref<CellSlice> value_extra) const;
|
||||
std::pair<Ref<Cell>, Ref<CellSlice>> decompose_value_ref_extra(Ref<CellSlice> value_extra) const;
|
||||
|
||||
private:
|
||||
bool compute_root() const;
|
||||
Ref<CellSlice> get_node_extra(Ref<Cell> cell_ref, int n) const;
|
||||
bool check_leaf(CellSlice& cs, td::ConstBitPtr key, int key_len) const override;
|
||||
bool check_fork(CellSlice& cs, Ref<Cell> c1, Ref<Cell> c2, int n) const override;
|
||||
Ref<Cell> finish_create_leaf(CellBuilder& cb, const CellSlice& value) const override;
|
||||
Ref<Cell> finish_create_fork(CellBuilder& cb, Ref<Cell> c1, Ref<Cell> c2, int n) const override;
|
||||
std::pair<Ref<Cell>, bool> dict_set(Ref<Cell> dict, td::ConstBitPtr key, int n, const CellSlice& value,
|
||||
SetMode mode = SetMode::Set) const;
|
||||
int label_mode() const override {
|
||||
return dict::LabelParser::chk_size;
|
||||
}
|
||||
std::pair<Ref<CellSlice>, Ref<CellSlice>> dict_traverse_extra(Ref<Cell> dict, td::BitPtr key_buffer, int n,
|
||||
const traverse_func_t& traverse_node) const;
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
811
crypto/vm/dictops.cpp
Normal file
811
crypto/vm/dictops.cpp
Normal file
|
|
@ -0,0 +1,811 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#include "vm/log.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "common/bigint.hpp"
|
||||
#include "common/refint.h"
|
||||
#include "vm/dictops.h"
|
||||
#include "vm/dict.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
template <typename T>
|
||||
void push_dict(Stack& stack, T&& dict) {
|
||||
stack.push_maybe_cell(std::move(dict).extract_root_cell());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void push_dict(Stack& stack, const T& dict) {
|
||||
stack.push_maybe_cell(dict.get_root_cell());
|
||||
}
|
||||
|
||||
int exec_dict_empty(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICTEMPTY";
|
||||
auto dict = stack.pop_cellslice();
|
||||
if (!dict->have(1)) {
|
||||
throw VmError{Excno::cell_und};
|
||||
}
|
||||
stack.push_smallint(~dict->prefetch_long(1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_store_dict(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute STDICT";
|
||||
stack.check_underflow(2);
|
||||
auto cb = stack.pop_builder();
|
||||
auto d = stack.pop_maybe_cell();
|
||||
if (!cb.write().store_maybe_ref(std::move(d))) {
|
||||
throw VmError{Excno::cell_ov};
|
||||
}
|
||||
stack.push_builder(std::move(cb));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dict_nonempty(const CellSlice& dict) {
|
||||
if (!dict.have(1)) {
|
||||
return -1;
|
||||
}
|
||||
int res = (int)dict.prefetch_ulong(1);
|
||||
return dict.have_refs(res) ? res : -1;
|
||||
}
|
||||
|
||||
int dict_nonempty_chk(const CellSlice& dict) {
|
||||
int res = dict_nonempty(dict);
|
||||
if (res < 0) {
|
||||
throw VmError{Excno::cell_und};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int exec_skip_dict(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute SKIPDICT\n";
|
||||
auto dict = stack.pop_cellslice();
|
||||
int res = dict_nonempty_chk(*dict);
|
||||
dict.write().advance_ext(1, res);
|
||||
stack.push_cellslice(std::move(dict));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_load_optref(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute LDOPTREF\n";
|
||||
auto dict = stack.pop_cellslice();
|
||||
int res = dict_nonempty_chk(*dict);
|
||||
dict.write().advance(1);
|
||||
if (res) {
|
||||
auto cell = dict.write().fetch_ref();
|
||||
stack.push_cellslice(std::move(dict));
|
||||
stack.push_cell(std::move(cell));
|
||||
} else {
|
||||
stack.push_cellslice(std::move(dict));
|
||||
}
|
||||
stack.push_smallint(-res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_preload_optref(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PLDOPTREF\n";
|
||||
auto dict = stack.pop_cellslice();
|
||||
int res = dict_nonempty_chk(*dict);
|
||||
if (res) {
|
||||
stack.push_cell(dict->prefetch_ref());
|
||||
}
|
||||
stack.push_smallint(-res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_load_dict_slice(VmState* st, unsigned args) {
|
||||
bool preload = args & 1, quiet = args & 2;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute " << (preload ? "P" : "") << "LDDICTS" << (quiet ? "Q\n" : "\n");
|
||||
auto cs = stack.pop_cellslice();
|
||||
int res = dict_nonempty(*cs);
|
||||
if (res < 0) {
|
||||
if (!quiet) {
|
||||
throw VmError{Excno::cell_und};
|
||||
}
|
||||
if (!preload) {
|
||||
stack.push_cellslice(std::move(cs));
|
||||
}
|
||||
} else {
|
||||
if (preload) {
|
||||
stack.push_cellslice(cs->prefetch_subslice(1, res));
|
||||
} else {
|
||||
stack.push_cellslice(cs.write().fetch_subslice(1, res));
|
||||
stack.push_cellslice(std::move(cs));
|
||||
}
|
||||
}
|
||||
if (quiet) {
|
||||
stack.push_bool(res >= 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_load_dict(VmState* st, unsigned args) {
|
||||
bool preload = args & 1, quiet = args & 2;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute " << (preload ? "P" : "") << "LDDICT" << (quiet ? "Q\n" : "\n");
|
||||
auto cs = stack.pop_cellslice();
|
||||
int res = dict_nonempty(*cs);
|
||||
if (res < 0) {
|
||||
if (!quiet) {
|
||||
throw VmError{Excno::cell_und};
|
||||
}
|
||||
if (!preload) {
|
||||
stack.push_cellslice(std::move(cs));
|
||||
}
|
||||
} else {
|
||||
stack.push_maybe_cell(res ? cs->prefetch_ref() : Ref<Cell>{});
|
||||
if (!preload) {
|
||||
cs.write().advance_ext(1, res);
|
||||
stack.push_cellslice(std::move(cs));
|
||||
}
|
||||
}
|
||||
if (quiet) {
|
||||
stack.push_bool(res >= 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_dictop(unsigned args, const char* name) {
|
||||
std::ostringstream os{"DICT"};
|
||||
if (args & 4) {
|
||||
os << (args & 2 ? 'U' : 'I');
|
||||
}
|
||||
os << name;
|
||||
if (args & 1) {
|
||||
os << "REF";
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string dump_dictop2(unsigned args, const char* name) {
|
||||
std::ostringstream os{"DICT"};
|
||||
if (args & 2) {
|
||||
os << (args & 1 ? 'U' : 'I');
|
||||
}
|
||||
os << name;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string dump_subdictop2(unsigned args, const char* name) {
|
||||
std::ostringstream os{"SUBDICT"};
|
||||
if (args & 2) {
|
||||
os << (args & 1 ? 'U' : 'I');
|
||||
}
|
||||
os << name;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int exec_dict_get(VmState* st, unsigned args) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << "GET" << (args & 1 ? "REF" : "");
|
||||
stack.check_underflow(3);
|
||||
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
BitSlice key;
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
if (args & 4) {
|
||||
key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer, true);
|
||||
if (!key.is_valid()) {
|
||||
stack.push_smallint(0);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
key = stack.pop_cellslice()->prefetch_bits(n);
|
||||
}
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
if (args & 1) {
|
||||
auto value = dict.lookup_ref(key);
|
||||
if (value.not_null()) {
|
||||
stack.push_cell(std::move(value));
|
||||
stack.push_smallint(-1);
|
||||
} else {
|
||||
stack.push_smallint(0);
|
||||
}
|
||||
} else {
|
||||
auto value = dict.lookup(key);
|
||||
if (value.not_null()) {
|
||||
stack.push_cellslice(std::move(value));
|
||||
stack.push_smallint(-1);
|
||||
} else {
|
||||
stack.push_smallint(0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dict_get_optref(VmState* st, unsigned args) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 2 ? (args & 1 ? "U" : "I") : "") << "GETOPTREF";
|
||||
stack.check_underflow(3);
|
||||
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
BitSlice key;
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
if (args & 2) {
|
||||
key = dict.integer_key(stack.pop_int(), n, !(args & 1), buffer, true);
|
||||
if (!key.is_valid()) {
|
||||
stack.push_null();
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
key = stack.pop_cellslice()->prefetch_bits(n);
|
||||
}
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
stack.push_maybe_cell(dict.lookup_ref(key));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dict_set(VmState* st, unsigned args, Dictionary::SetMode mode, const char* name, bool bld = false) {
|
||||
args <<= (bld ? 1 : 0);
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << name
|
||||
<< (args & 1 ? "REF" : (bld ? "B" : ""));
|
||||
stack.check_underflow(4);
|
||||
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
BitSlice key;
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
if (args & 4) {
|
||||
key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer);
|
||||
} else {
|
||||
key = stack.pop_cellslice()->prefetch_bits(n);
|
||||
}
|
||||
bool res;
|
||||
if (bld) {
|
||||
auto new_value = stack.pop_builder();
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
res = dict.set_builder(key, std::move(new_value), mode);
|
||||
} else if (!(args & 1)) {
|
||||
auto new_value = stack.pop_cellslice();
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
res = dict.set(key, std::move(new_value), mode);
|
||||
} else {
|
||||
auto new_value_ref = stack.pop_cell();
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
res = dict.set_ref(key, std::move(new_value_ref), mode);
|
||||
}
|
||||
push_dict(stack, std::move(dict));
|
||||
if (mode == Dictionary::SetMode::Set) {
|
||||
st->ensure_throw(res);
|
||||
} else {
|
||||
stack.push_bool(res);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dict_setget(VmState* st, unsigned args, Dictionary::SetMode mode, const char* name, bool bld = false) {
|
||||
args <<= (bld ? 1 : 0);
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << name
|
||||
<< (args & 1 ? "REF\n" : (bld ? "B\n" : "\n"));
|
||||
stack.check_underflow(4);
|
||||
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
BitSlice key;
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
if (args & 4) {
|
||||
key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer);
|
||||
} else {
|
||||
key = stack.pop_cellslice()->prefetch_bits(n);
|
||||
}
|
||||
bool ok_f = (mode != Dictionary::SetMode::Add);
|
||||
if (bld) {
|
||||
auto new_value = stack.pop_builder();
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
auto res = dict.lookup_set_builder(key, std::move(new_value), mode);
|
||||
push_dict(stack, std::move(dict));
|
||||
if (res.not_null()) {
|
||||
stack.push_cellslice(std::move(res));
|
||||
stack.push_bool(ok_f);
|
||||
} else {
|
||||
stack.push_bool(!ok_f);
|
||||
}
|
||||
} else if (!(args & 1)) {
|
||||
auto new_value = stack.pop_cellslice();
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
auto res = dict.lookup_set(key, std::move(new_value), mode);
|
||||
push_dict(stack, std::move(dict));
|
||||
if (res.not_null()) {
|
||||
stack.push_cellslice(std::move(res));
|
||||
stack.push_bool(ok_f);
|
||||
} else {
|
||||
stack.push_bool(!ok_f);
|
||||
}
|
||||
} else {
|
||||
auto new_value_ref = stack.pop_cell();
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
auto res = dict.lookup_set_ref(key, std::move(new_value_ref), mode);
|
||||
push_dict(stack, std::move(dict));
|
||||
if (res.not_null()) {
|
||||
stack.push_cell(std::move(res));
|
||||
stack.push_bool(ok_f);
|
||||
} else {
|
||||
stack.push_bool(!ok_f);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dict_delete(VmState* st, unsigned args) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 2 ? (args & 1 ? "U" : "I") : "") << "DEL\n";
|
||||
stack.check_underflow(3);
|
||||
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
BitSlice key;
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
if (args & 2) {
|
||||
key = dict.integer_key(stack.pop_int(), n, !(args & 1), buffer);
|
||||
if (!key.is_valid()) {
|
||||
push_dict(stack, std::move(dict));
|
||||
stack.push_smallint(0);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
key = stack.pop_cellslice()->prefetch_bits(n);
|
||||
}
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
bool res = dict.lookup_delete(key).not_null();
|
||||
push_dict(stack, std::move(dict));
|
||||
stack.push_bool(res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dict_deleteget(VmState* st, unsigned args) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << "DELGET" << (args & 1 ? "REF\n" : "\n");
|
||||
stack.check_underflow(3);
|
||||
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
BitSlice key;
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
if (args & 4) {
|
||||
key = dict.integer_key(stack.pop_int(), n, !(args & 2), buffer);
|
||||
if (!key.is_valid()) {
|
||||
push_dict(stack, std::move(dict));
|
||||
stack.push_smallint(0);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
key = stack.pop_cellslice()->prefetch_bits(n);
|
||||
}
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
if (!(args & 1)) {
|
||||
auto res = dict.lookup_delete(key);
|
||||
push_dict(stack, std::move(dict));
|
||||
bool ok = res.not_null();
|
||||
if (ok) {
|
||||
stack.push_cellslice(std::move(res));
|
||||
}
|
||||
stack.push_bool(ok);
|
||||
} else {
|
||||
auto res = dict.lookup_delete_ref(key);
|
||||
push_dict(stack, std::move(dict));
|
||||
bool ok = res.not_null();
|
||||
if (ok) {
|
||||
stack.push_cell(std::move(res));
|
||||
}
|
||||
stack.push_bool(ok);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dict_setget_optref(VmState* st, unsigned args) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 2 ? (args & 1 ? "U" : "I") : "") << "SETGETOPTREF";
|
||||
stack.check_underflow(4);
|
||||
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
BitSlice key;
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
if (args & 2) {
|
||||
key = dict.integer_key(stack.pop_int(), n, !(args & 1), buffer);
|
||||
} else {
|
||||
key = stack.pop_cellslice()->prefetch_bits(n);
|
||||
}
|
||||
auto new_value = stack.pop_maybe_cell();
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key"};
|
||||
}
|
||||
Ref<vm::Cell> value;
|
||||
if (new_value.not_null()) {
|
||||
value = dict.lookup_set_ref(key, std::move(new_value));
|
||||
} else {
|
||||
value = dict.lookup_delete_ref(key);
|
||||
}
|
||||
push_dict(stack, std::move(dict));
|
||||
stack.push_maybe_cell(std::move(value));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dict_getmin(VmState* st, unsigned args) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 4 ? (args & 2 ? "U" : "I") : "") << (args & 16 ? "REM" : "")
|
||||
<< (args & 8 ? "MAX" : "MIN") << (args & 1 ? "REF\n" : "\n");
|
||||
stack.check_underflow(2);
|
||||
int n = stack.pop_smallint_range(args & 4 ? (args & 2 ? 256 : 257) : Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
bool flip_first = !(args & 2);
|
||||
if (!(args & 1)) {
|
||||
auto res = (args & 16) ? dict.extract_minmax_key(buffer, n, args & 8, flip_first)
|
||||
: dict.get_minmax_key(buffer, n, args & 8, flip_first);
|
||||
if (args & 16) {
|
||||
push_dict(stack, std::move(dict));
|
||||
}
|
||||
if (res.is_null()) {
|
||||
stack.push_bool(false);
|
||||
return 0;
|
||||
}
|
||||
stack.push_cellslice(std::move(res));
|
||||
} else {
|
||||
auto res = (args & 16) ? dict.extract_minmax_key_ref(buffer, n, args & 8, flip_first)
|
||||
: dict.get_minmax_key_ref(buffer, n, args & 8, flip_first);
|
||||
if (args & 16) {
|
||||
push_dict(stack, std::move(dict));
|
||||
}
|
||||
if (res.is_null()) {
|
||||
stack.push_bool(false);
|
||||
return 0;
|
||||
}
|
||||
stack.push_cell(std::move(res));
|
||||
}
|
||||
if (args & 4) {
|
||||
td::RefInt256 x{true};
|
||||
x.unique_write().import_bits(td::ConstBitPtr{buffer}, n, !(args & 2));
|
||||
stack.push_int(std::move(x));
|
||||
} else {
|
||||
stack.push_cellslice(Ref<CellSlice>{true, CellBuilder().store_bits(td::ConstBitPtr{buffer}, n).finalize()});
|
||||
}
|
||||
stack.push_bool(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_dictop_getnear(CellSlice& cs, unsigned args) {
|
||||
std::ostringstream os{"DICT"};
|
||||
if (args & 8) {
|
||||
os << (args & 4 ? 'U' : 'I');
|
||||
}
|
||||
os << "GET" << (args & 2 ? "PREV" : "NEXT") << (args & 1 ? "EQ" : "");
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int exec_dict_getnear(VmState* st, unsigned args) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 8 ? (args & 4 ? "U" : "I") : "") << "GET" << (args & 2 ? "PREV" : "NEXT")
|
||||
<< (args & 1 ? "EQ\n" : "\n");
|
||||
stack.check_underflow(3);
|
||||
int n = stack.pop_smallint_range(args & 8 ? (args & 4 ? 256 : 257) : Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
bool sgnd = !(args & 4), go_up = !(args & 2), allow_eq = args & 1;
|
||||
if (!(args & 8)) {
|
||||
auto key_hint = stack.pop_cellslice()->prefetch_bits(n);
|
||||
if (!key_hint.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key hint"};
|
||||
}
|
||||
td::BitPtr{buffer}.copy_from(key_hint.bits(), n);
|
||||
key_hint.forget();
|
||||
auto res = dict.lookup_nearest_key(td::BitPtr{buffer}, n, go_up, allow_eq, false);
|
||||
if (res.is_null()) {
|
||||
stack.push_bool(false);
|
||||
return 0;
|
||||
}
|
||||
stack.push_cellslice(std::move(res));
|
||||
stack.push_cellslice(Ref<CellSlice>{true, CellBuilder().store_bits(td::ConstBitPtr{buffer}, n).finalize()});
|
||||
} else {
|
||||
auto key = stack.pop_int_finite();
|
||||
Ref<CellSlice> res;
|
||||
if (key->export_bits(td::BitPtr{buffer}, n, sgnd)) {
|
||||
res = dict.lookup_nearest_key(buffer, n, go_up, allow_eq, sgnd);
|
||||
} else if ((td::sgn(key) >= 0) ^ go_up) {
|
||||
res = dict.get_minmax_key(buffer, n, !go_up, sgnd);
|
||||
}
|
||||
if (res.is_null()) {
|
||||
stack.push_bool(false);
|
||||
return 0;
|
||||
}
|
||||
stack.push_cellslice(std::move(res));
|
||||
key.write().import_bits(td::ConstBitPtr{buffer}, n, sgnd);
|
||||
stack.push_int(std::move(key));
|
||||
}
|
||||
stack.push_bool(true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_pfx_dict_set(VmState* st, Dictionary::SetMode mode, const char* name) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PFXDICT" << name;
|
||||
stack.check_underflow(3);
|
||||
int n = stack.pop_smallint_range(PrefixDictionary::max_key_bits);
|
||||
PrefixDictionary dict{stack.pop_maybe_cell(), n};
|
||||
auto key_slice = stack.pop_cellslice();
|
||||
auto new_value = stack.pop_cellslice();
|
||||
bool res = dict.set(key_slice->data_bits(), key_slice->size(), std::move(new_value), mode);
|
||||
push_dict(stack, std::move(dict));
|
||||
stack.push_bool(res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_pfx_dict_delete(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PFXDICTDEL\n";
|
||||
stack.check_underflow(2);
|
||||
int n = stack.pop_smallint_range(PrefixDictionary::max_key_bits);
|
||||
PrefixDictionary dict{stack.pop_maybe_cell(), n};
|
||||
auto key_slice = stack.pop_cellslice();
|
||||
bool res = dict.lookup_delete(key_slice->data_bits(), key_slice->size()).not_null();
|
||||
push_dict(stack, std::move(dict));
|
||||
stack.push_bool(res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dict_get_exec(VmState* st, unsigned args) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DICT" << (args & 1 ? 'U' : 'I') << "GET" << (args & 2 ? "EXEC\n" : "JMP\n");
|
||||
stack.check_underflow(3);
|
||||
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
dict.integer_key_simple(stack.pop_int(), n, !(args & 1), td::BitPtr{buffer});
|
||||
auto value = dict.lookup(td::BitPtr{buffer}, n);
|
||||
if (value.not_null()) {
|
||||
Ref<OrdCont> cont{true, std::move(value), st->get_cp()};
|
||||
return (args & 2) ? st->call(std::move(cont)) : st->jump(std::move(cont));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string dump_dict_get_exec(CellSlice& cs, unsigned args) {
|
||||
return std::string{"DICT"} + (args & 1 ? 'U' : 'I') + "GET" + (args & 2 ? "EXEC" : "JMP");
|
||||
}
|
||||
|
||||
int exec_push_const_dict(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) {
|
||||
if (!cs.have(pfx_bits)) {
|
||||
throw VmError{Excno::inv_opcode, "not enough data bits for a DICTPUSHCONST instruction"};
|
||||
}
|
||||
if (!cs.have_refs(1)) {
|
||||
throw VmError{Excno::inv_opcode, "not enough references for a DICTPUSHCONST instruction"};
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
cs.advance(pfx_bits - 11);
|
||||
auto slice = cs.fetch_subslice(1, 1);
|
||||
int n = (int)cs.fetch_ulong(10);
|
||||
VM_LOG(st) << "execute DICTPUSHCONST " << n << " (" << slice << ")";
|
||||
stack.push_cell(slice->prefetch_ref());
|
||||
stack.push_smallint(n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_push_const_dict(CellSlice& cs, int pfx_bits, const char* name) {
|
||||
if (!cs.have(pfx_bits, 1)) {
|
||||
return "";
|
||||
}
|
||||
cs.advance(pfx_bits - 11);
|
||||
auto slice = cs.fetch_subslice(1, 1);
|
||||
int n = (int)cs.fetch_ulong(10);
|
||||
std::ostringstream os{name};
|
||||
os << ' ' << n << " (";
|
||||
slice->dump_hex(os, false);
|
||||
os << ')';
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int compute_len_push_const_dict(const CellSlice& cs, unsigned args, int pfx_bits) {
|
||||
if (!cs.have(pfx_bits, 1)) {
|
||||
return 0;
|
||||
}
|
||||
return 0x10000 + pfx_bits;
|
||||
}
|
||||
|
||||
int exec_pfx_dict_get(VmState* st, int op, const char* name_suff) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PFXDICTGET" << name_suff;
|
||||
stack.check_underflow(3);
|
||||
int n = stack.pop_smallint_range(PrefixDictionary::max_key_bits);
|
||||
PrefixDictionary dict{stack.pop_maybe_cell(), n};
|
||||
auto cs = stack.pop_cellslice();
|
||||
auto res = dict.lookup_prefix(cs->data_bits(), cs->size());
|
||||
if (res.first.is_null()) {
|
||||
if (op & 1) {
|
||||
throw VmError{Excno::cell_und, "cannot parse a prefix belonging to a given prefix code dictionary"};
|
||||
}
|
||||
stack.push_cellslice(std::move(cs));
|
||||
if (!op) {
|
||||
stack.push_bool(false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
stack.push_cellslice(cs.write().fetch_subslice(res.second));
|
||||
if (!(op & 2)) {
|
||||
stack.push_cellslice(std::move(res.first));
|
||||
}
|
||||
stack.push_cellslice(std::move(cs));
|
||||
if (!op) {
|
||||
stack.push_bool(true);
|
||||
return 0;
|
||||
}
|
||||
if (op == 1) {
|
||||
return 0;
|
||||
}
|
||||
Ref<OrdCont> cont{true, std::move(res.first), st->get_cp()};
|
||||
return op & 1 ? st->call(std::move(cont)) : st->jump(std::move(cont));
|
||||
}
|
||||
|
||||
int exec_const_pfx_dict_switch(VmState* st, CellSlice& cs, unsigned args, int pfx_bits) {
|
||||
if (!cs.have(pfx_bits)) {
|
||||
throw VmError{Excno::inv_opcode, "not enough data bits for a PFXDICTSWITCH instruction"};
|
||||
}
|
||||
if (!cs.have_refs(1)) {
|
||||
throw VmError{Excno::inv_opcode, "not enough references for a PFXDICTSWITCH instruction"};
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
cs.advance(pfx_bits - 11);
|
||||
|
||||
auto dict_slice = cs.fetch_subslice(1, 1);
|
||||
int n = (int)cs.fetch_ulong(10);
|
||||
|
||||
VM_LOG(st) << "execute PFXDICTSWITCH " << n << " (" << dict_slice << ")";
|
||||
|
||||
PrefixDictionary dict{std::move(dict_slice), n};
|
||||
auto cs1 = stack.pop_cellslice();
|
||||
auto res = dict.lookup_prefix(cs1->data_bits(), cs1->size());
|
||||
|
||||
if (res.first.is_null()) {
|
||||
stack.push_cellslice(std::move(cs1));
|
||||
return 0;
|
||||
} else {
|
||||
stack.push_cellslice(cs1.write().fetch_subslice(res.second));
|
||||
stack.push_cellslice(std::move(cs1));
|
||||
Ref<OrdCont> cont{true, std::move(res.first), st->get_cp()};
|
||||
return st->jump(std::move(cont));
|
||||
}
|
||||
}
|
||||
|
||||
int exec_subdict_get(VmState* st, unsigned args) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute SUBDICT" << (args & 2 ? (args & 1 ? "U" : "I") : "") << (args & 4 ? "RP" : "") << "GET";
|
||||
stack.check_underflow(4);
|
||||
int n = stack.pop_smallint_range(Dictionary::max_key_bits);
|
||||
Dictionary dict{stack.pop_maybe_cell(), n};
|
||||
int mk = (args & 2 ? (args & 1 ? 256 : 257) : Dictionary::max_key_bits);
|
||||
int k = stack.pop_smallint_range(std::min(mk, n));
|
||||
BitSlice key;
|
||||
unsigned char buffer[Dictionary::max_key_bytes];
|
||||
if (args & 2) {
|
||||
key = dict.integer_key(stack.pop_int(), k, !(args & 1), buffer, true);
|
||||
} else {
|
||||
key = stack.pop_cellslice()->prefetch_bits(k);
|
||||
}
|
||||
if (!key.is_valid()) {
|
||||
throw VmError{Excno::cell_und, "not enough bits for a dictionary key prefix"};
|
||||
}
|
||||
if (!dict.cut_prefix_subdict(key.bits(), k, args & 4)) {
|
||||
throw VmError{Excno::dict_err, "cannot construct subdictionary by key prefix"};
|
||||
}
|
||||
push_dict(stack, std::move(dict));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_dictionary_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xf400, 16, "STDICT", exec_store_dict))
|
||||
.insert(OpcodeInstr::mksimple(0xf401, 16, "SKIPDICT", exec_skip_dict))
|
||||
.insert(OpcodeInstr::mksimple(0xf402, 16, "LDDICTS", std::bind(exec_load_dict_slice, _1, 0)))
|
||||
.insert(OpcodeInstr::mksimple(0xf403, 16, "PLDDICTS", std::bind(exec_load_dict_slice, _1, 1)))
|
||||
.insert(OpcodeInstr::mksimple(0xf404, 16, "LDDICT", std::bind(exec_load_dict, _1, 0)))
|
||||
.insert(OpcodeInstr::mksimple(0xf405, 16, "PLDDICT", std::bind(exec_load_dict, _1, 1)))
|
||||
.insert(OpcodeInstr::mksimple(0xf406, 16, "LDDICTQ", std::bind(exec_load_dict, _1, 2)))
|
||||
.insert(OpcodeInstr::mksimple(0xf407, 16, "PLDDICTQ", std::bind(exec_load_dict, _1, 3)))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf40a, 0xf410, 16, 3, std::bind(dump_dictop, _2, "GET"), exec_dict_get))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf412, 0xf418, 16, 3, std::bind(dump_dictop, _2, "SET"),
|
||||
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Set, "SET", false)))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf41a, 0xf420, 16, 3, std::bind(dump_dictop, _2, "SETGET"),
|
||||
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Set, "SETGET", false)))
|
||||
.insert(
|
||||
OpcodeInstr::mkfixedrange(0xf422, 0xf428, 16, 3, std::bind(dump_dictop, _2, "REPLACE"),
|
||||
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Replace, "REPLACE", false)))
|
||||
.insert(OpcodeInstr::mkfixedrange(
|
||||
0xf42a, 0xf430, 16, 3, std::bind(dump_dictop, _2, "REPLACEGET"),
|
||||
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Replace, "REPLACEGET", false)))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf432, 0xf438, 16, 3, std::bind(dump_dictop, _2, "ADD"),
|
||||
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Add, "ADD", false)))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf43a, 0xf440, 16, 3, std::bind(dump_dictop, _2, "ADDGET"),
|
||||
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Add, "ADDGET", false)))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf441, 0xf444, 16, 2, std::bind(dump_dictop2, _2, "SETB"),
|
||||
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Set, "SET", true)))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf445, 0xf448, 16, 2, std::bind(dump_dictop2, _2, "SETGETB"),
|
||||
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Set, "SETGET", true)))
|
||||
.insert(
|
||||
OpcodeInstr::mkfixedrange(0xf449, 0xf44c, 16, 2, std::bind(dump_dictop2, _2, "REPLACEB"),
|
||||
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Replace, "REPLACE", true)))
|
||||
.insert(OpcodeInstr::mkfixedrange(
|
||||
0xf44d, 0xf450, 16, 2, std::bind(dump_dictop2, _2, "REPLACEGETB"),
|
||||
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Replace, "REPLACEGET", true)))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf451, 0xf454, 16, 2, std::bind(dump_dictop2, _2, "ADDB"),
|
||||
std::bind(exec_dict_set, _1, _2, Dictionary::SetMode::Add, "ADD", true)))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf455, 0xf458, 16, 2, std::bind(dump_dictop2, _2, "ADDGETB"),
|
||||
std::bind(exec_dict_setget, _1, _2, Dictionary::SetMode::Add, "ADDGET", true)))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf459, 0xf45c, 16, 2, std::bind(dump_dictop2, _2, "DEL"), exec_dict_delete))
|
||||
.insert(
|
||||
OpcodeInstr::mkfixedrange(0xf462, 0xf468, 16, 3, std::bind(dump_dictop, _2, "DELGET"), exec_dict_deleteget))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf469, 0xf46c, 16, 2, std::bind(dump_dictop2, _2, "GETOPTREF"),
|
||||
exec_dict_get_optref))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf46d, 0xf470, 16, 2, std::bind(dump_dictop2, _2, "SETGETOPTREF"),
|
||||
exec_dict_setget_optref))
|
||||
.insert(OpcodeInstr::mksimple(0xf470, 16, "PFXDICTSET",
|
||||
std::bind(exec_pfx_dict_set, _1, PrefixDictionary::SetMode::Set, "SET")))
|
||||
.insert(OpcodeInstr::mksimple(0xf471, 16, "PFXDICTREPLACE",
|
||||
std::bind(exec_pfx_dict_set, _1, PrefixDictionary::SetMode::Replace, "REPLACE")))
|
||||
.insert(OpcodeInstr::mksimple(0xf472, 16, "PFXDICTADD",
|
||||
std::bind(exec_pfx_dict_set, _1, PrefixDictionary::SetMode::Add, "ADD")))
|
||||
.insert(OpcodeInstr::mksimple(0xf473, 16, "PFXDICTDEL", exec_pfx_dict_delete))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf474, 0xf480, 16, 4, dump_dictop_getnear, exec_dict_getnear))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf482, 0xf488, 16, 5, std::bind(dump_dictop, _2, "MIN"), exec_dict_getmin))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf48a, 0xf490, 16, 5, std::bind(dump_dictop, _2, "MAX"), exec_dict_getmin))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf492, 0xf498, 16, 5, std::bind(dump_dictop, _2, "REMMIN"), exec_dict_getmin))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf49a, 0xf4a0, 16, 5, std::bind(dump_dictop, _2, "REMMAX"), exec_dict_getmin))
|
||||
.insert(OpcodeInstr::mkfixed(0xf4a0 >> 2, 14, 2, dump_dict_get_exec, exec_dict_get_exec))
|
||||
.insert(OpcodeInstr::mkextrange(0xf4a400, 0xf4a800, 24, 11,
|
||||
std::bind(dump_push_const_dict, _1, _3, "DICTPUSHCONST"), exec_push_const_dict,
|
||||
compute_len_push_const_dict))
|
||||
.insert(OpcodeInstr::mksimple(0xf4a8, 16, "PFXDICTGETQ", std::bind(exec_pfx_dict_get, _1, 0, "Q")))
|
||||
.insert(OpcodeInstr::mksimple(0xf4a9, 16, "PFXDICTGET", std::bind(exec_pfx_dict_get, _1, 1, "")))
|
||||
.insert(OpcodeInstr::mksimple(0xf4aa, 16, "PFXDICTGETJMP", std::bind(exec_pfx_dict_get, _1, 2, "JMP")))
|
||||
.insert(OpcodeInstr::mksimple(0xf4ab, 16, "PFXDICTGETEXEC", std::bind(exec_pfx_dict_get, _1, 3, "EXEC")))
|
||||
.insert(OpcodeInstr::mkextrange(0xf4ac00, 0xf4b000, 24, 11,
|
||||
std::bind(dump_push_const_dict, _1, _3, "PFXDICTSWITCH"),
|
||||
exec_const_pfx_dict_switch, compute_len_push_const_dict))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf4b1, 0xf4b4, 16, 3, std::bind(dump_subdictop2, _2, "GET"), exec_subdict_get))
|
||||
.insert(
|
||||
OpcodeInstr::mkfixedrange(0xf4b5, 0xf4b8, 16, 3, std::bind(dump_subdictop2, _2, "RPGET"), exec_subdict_get));
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
27
crypto/vm/dictops.h
Normal file
27
crypto/vm/dictops.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace vm {
|
||||
|
||||
class OpcodeTable;
|
||||
|
||||
void register_dictionary_ops(OpcodeTable& cp0);
|
||||
|
||||
} // namespace vm
|
||||
71
crypto/vm/dispatch.cpp
Normal file
71
crypto/vm/dispatch.cpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/dispatch.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include <cassert>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
namespace vm {
|
||||
|
||||
namespace {
|
||||
std::mutex dispatch_tables_mutex;
|
||||
std::map<int, const DispatchTable*> dispatch_tables;
|
||||
} // namespace
|
||||
|
||||
DummyDispatchTable dummy_dispatch_table;
|
||||
|
||||
bool DispatchTable::register_table(Codepage _cp, const DispatchTable& dt) {
|
||||
assert(dt.is_final());
|
||||
int cp = (int)_cp;
|
||||
if (cp < -0x8000 || cp >= 0x8000 || cp == -1) {
|
||||
return false;
|
||||
} else {
|
||||
std::lock_guard<std::mutex> guard(dispatch_tables_mutex);
|
||||
return dispatch_tables.emplace(cp, &dt).second;
|
||||
}
|
||||
}
|
||||
|
||||
bool DispatchTable::register_table(Codepage cp) const {
|
||||
return register_table(cp, *this);
|
||||
}
|
||||
|
||||
const DispatchTable* DispatchTable::get_table(Codepage cp) {
|
||||
return get_table((int)cp);
|
||||
}
|
||||
|
||||
const DispatchTable* DispatchTable::get_table(int cp) {
|
||||
std::lock_guard<std::mutex> guard(dispatch_tables_mutex);
|
||||
auto entry = dispatch_tables.find(cp);
|
||||
return entry != dispatch_tables.end() ? entry->second : 0;
|
||||
}
|
||||
|
||||
int DummyDispatchTable::dispatch(VmState* st, CellSlice& cs) const {
|
||||
throw VmError{Excno::inv_opcode, "empty opcode table"};
|
||||
}
|
||||
|
||||
std::string DummyDispatchTable::dump_instr(CellSlice& cs) const {
|
||||
return "";
|
||||
}
|
||||
|
||||
int DummyDispatchTable::instr_len(const CellSlice& cs) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
62
crypto/vm/dispatch.h
Normal file
62
crypto/vm/dispatch.h
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace vm {
|
||||
|
||||
class VmState;
|
||||
class CellSlice;
|
||||
|
||||
enum class Codepage { test_cp = 0 };
|
||||
|
||||
class DispatchTable {
|
||||
public:
|
||||
DispatchTable() = default;
|
||||
virtual ~DispatchTable() = default;
|
||||
virtual int dispatch(VmState* st, CellSlice& cs) const = 0;
|
||||
virtual std::string dump_instr(CellSlice& cs) const = 0;
|
||||
virtual int instr_len(const CellSlice& cs) const = 0;
|
||||
virtual DispatchTable* finalize() = 0;
|
||||
virtual bool is_final() const = 0;
|
||||
static const DispatchTable* get_table(Codepage cp);
|
||||
static const DispatchTable* get_table(int cp);
|
||||
static bool register_table(Codepage cp, const DispatchTable& dt);
|
||||
bool register_table(Codepage cp) const;
|
||||
};
|
||||
|
||||
class DummyDispatchTable : public DispatchTable {
|
||||
public:
|
||||
DummyDispatchTable() : DispatchTable() {
|
||||
}
|
||||
~DummyDispatchTable() override = default;
|
||||
int dispatch(VmState* st, CellSlice& cs) const override;
|
||||
std::string dump_instr(CellSlice& cs) const override;
|
||||
int instr_len(const CellSlice& cs) const override;
|
||||
DispatchTable* finalize() override {
|
||||
return this;
|
||||
}
|
||||
bool is_final() const override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
extern DummyDispatchTable dummy_dispatch_table;
|
||||
|
||||
} // namespace vm
|
||||
98
crypto/vm/excno.hpp
Normal file
98
crypto/vm/excno.hpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace vm {
|
||||
|
||||
enum class Excno : int {
|
||||
none = 0,
|
||||
alt = 1,
|
||||
stk_und = 2,
|
||||
stk_ov = 3,
|
||||
int_ov = 4,
|
||||
range_chk = 5,
|
||||
inv_opcode = 6,
|
||||
type_chk = 7,
|
||||
cell_ov = 8,
|
||||
cell_und = 9,
|
||||
dict_err = 10,
|
||||
unknown = 11,
|
||||
fatal = 12,
|
||||
out_of_gas = 13,
|
||||
virt_err = 14,
|
||||
total
|
||||
};
|
||||
|
||||
const char* get_exception_msg(Excno exc_no);
|
||||
|
||||
class VmError {
|
||||
Excno exc_no;
|
||||
const char* msg;
|
||||
long long arg;
|
||||
|
||||
public:
|
||||
VmError(Excno _excno, const char* _msg) : exc_no(_excno), msg(_msg), arg(0) {
|
||||
}
|
||||
VmError(Excno _excno) : exc_no(_excno), msg(0), arg(0) {
|
||||
}
|
||||
VmError(Excno _excno, const char* _msg, long long _arg) : exc_no(_excno), msg(_msg), arg(_arg) {
|
||||
}
|
||||
int get_errno() const {
|
||||
return static_cast<int>(exc_no);
|
||||
}
|
||||
const char* get_msg() const {
|
||||
return msg ? msg : get_exception_msg(exc_no);
|
||||
}
|
||||
long long get_arg() const {
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
|
||||
struct VmNoGas {
|
||||
VmNoGas() = default;
|
||||
int get_errno() const {
|
||||
return static_cast<int>(Excno::out_of_gas);
|
||||
}
|
||||
const char* get_msg() const {
|
||||
return "out of gas";
|
||||
}
|
||||
operator VmError() const {
|
||||
return VmError{Excno::out_of_gas, "out of gas"};
|
||||
}
|
||||
};
|
||||
|
||||
struct VmVirtError {
|
||||
int virtualization{0};
|
||||
VmVirtError() = default;
|
||||
VmVirtError(int virtualization) : virtualization(virtualization) {
|
||||
}
|
||||
int get_errno() const {
|
||||
return static_cast<int>(Excno::virt_err);
|
||||
}
|
||||
const char* get_msg() const {
|
||||
return "prunned branch";
|
||||
}
|
||||
operator VmError() const {
|
||||
return VmError{Excno::virt_err, "prunned branch", virtualization};
|
||||
}
|
||||
};
|
||||
|
||||
struct VmFatal {};
|
||||
|
||||
} // namespace vm
|
||||
503
crypto/vm/fmt.hpp
Normal file
503
crypto/vm/fmt.hpp
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "common/refint.h"
|
||||
#include "vm/cells.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/cellparse.hpp"
|
||||
#include <type_traits>
|
||||
|
||||
namespace vm {
|
||||
|
||||
namespace fmt {
|
||||
|
||||
// main idea: use cs >> i(32,x) >> ... or cs >> i32(x) to deserialize integers, instead of cs.write().fetch_long(32, true)
|
||||
// and cb << i(32,x+y) or cb << i32(x+y) will serialize 32-bit integers
|
||||
|
||||
// i, u, i16, u16, i32, u32, ub=u1, ib=i1 for integers
|
||||
|
||||
template <bool S, class T, bool C = true>
|
||||
class ConstInt {
|
||||
int bits;
|
||||
T value;
|
||||
|
||||
public:
|
||||
ConstInt(int _bits, T _val) : bits(_bits), value(_val) {
|
||||
}
|
||||
bool serialize(CellBuilder& cb) const {
|
||||
if (C) {
|
||||
return (S ? cb.store_long_rchk_bool(value, bits) : cb.store_ulong_rchk_bool(value, bits));
|
||||
} else {
|
||||
return cb.store_long_bool(value, bits);
|
||||
}
|
||||
}
|
||||
bool deserialize(CellSlice& cs) const {
|
||||
if (S) {
|
||||
long long x;
|
||||
return cs.fetch_long_bool(bits, x) && x == value;
|
||||
} else {
|
||||
unsigned long long x;
|
||||
return cs.fetch_ulong_bool(bits, x) && x == value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <bool S, class T, bool C = true>
|
||||
class Int {
|
||||
int bits;
|
||||
T& value;
|
||||
|
||||
public:
|
||||
Int(int _bits, T& _val) : bits(_bits), value(_val) {
|
||||
}
|
||||
bool deserialize(CellSlice& cs) const {
|
||||
if (S) {
|
||||
long long x;
|
||||
if (cs.fetch_long_bool(bits, x)) {
|
||||
value = static_cast<T>(x);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
unsigned long long x;
|
||||
if (cs.fetch_ulong_bool(bits, x)) {
|
||||
value = static_cast<T>(x);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool serialize(CellBuilder& cb) const {
|
||||
if (C) {
|
||||
return S ? cb.store_long_rchk_bool(value, bits) : cb.store_ulong_rchk_bool(value, bits);
|
||||
} else {
|
||||
return cb.store_long_bool(value, bits);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <bool S, class T>
|
||||
class PrefetchInt {
|
||||
int bits;
|
||||
T& value;
|
||||
|
||||
public:
|
||||
PrefetchInt(int _bits, T& _val) : bits(_bits), value(_val) {
|
||||
}
|
||||
bool deserialize(CellSlice& cs) const {
|
||||
if (S) {
|
||||
long long x;
|
||||
if (cs.prefetch_long_bool(bits, x)) {
|
||||
value = static_cast<T>(x);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
unsigned long long x;
|
||||
if (cs.prefetch_ulong_bool(bits, x)) {
|
||||
value = static_cast<T>(x);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <bool S>
|
||||
class ConstRefInt {
|
||||
int bits;
|
||||
const td::RefInt256& value;
|
||||
|
||||
public:
|
||||
ConstRefInt(int _bits, const td::RefInt256& _val) : bits(_bits), value(_val) {
|
||||
}
|
||||
bool serialize(CellBuilder& cb) const {
|
||||
return value.not_null() && cb.store_int256_bool(*value, bits, S);
|
||||
}
|
||||
};
|
||||
|
||||
template <bool S>
|
||||
class ConstRefIntVal {
|
||||
int bits;
|
||||
td::RefInt256 value;
|
||||
|
||||
public:
|
||||
ConstRefIntVal(int _bits, const td::RefInt256& _val) : bits(_bits), value(_val) {
|
||||
}
|
||||
ConstRefIntVal(int _bits, td::RefInt256&& _val) : bits(_bits), value(std::move(_val)) {
|
||||
}
|
||||
bool serialize(CellBuilder& cb) const {
|
||||
return value.not_null() && cb.store_int256_bool(*value, bits, S);
|
||||
}
|
||||
};
|
||||
|
||||
template <bool S>
|
||||
class ConstBigInt {
|
||||
int bits;
|
||||
const td::BigInt256& value;
|
||||
|
||||
public:
|
||||
ConstBigInt(int _bits, const td::BigInt256& _val) : bits(_bits), value(_val) {
|
||||
}
|
||||
bool serialize(CellBuilder& cb) const {
|
||||
return cb.store_int256_bool(value, bits, S);
|
||||
}
|
||||
};
|
||||
|
||||
template <bool S>
|
||||
class RefInt {
|
||||
int bits;
|
||||
td::RefInt256& value;
|
||||
|
||||
public:
|
||||
RefInt(int _bits, td::RefInt256& _val) : bits(_bits), value(_val) {
|
||||
}
|
||||
bool deserialize(CellSlice& cs) const {
|
||||
value = cs.fetch_int256(bits, S);
|
||||
return value.not_null();
|
||||
}
|
||||
bool serialize(CellBuilder& cb) const {
|
||||
return value.not_null() && cb.store_int256_bool(*value, bits, S);
|
||||
}
|
||||
};
|
||||
|
||||
inline ConstRefInt<true> i(int l, const td::RefInt256& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
inline ConstRefIntVal<true> i(int l, td::RefInt256&& val) {
|
||||
return {l, std::move(val)};
|
||||
}
|
||||
|
||||
inline ConstBigInt<true> i(int l, const td::BigInt256& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
inline RefInt<true> i(int l, td::RefInt256& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
inline ConstRefInt<false> u(int l, const td::RefInt256& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
inline ConstRefIntVal<false> u(int l, td::RefInt256&& val) {
|
||||
return {l, std::move(val)};
|
||||
}
|
||||
|
||||
inline ConstBigInt<false> u(int l, const td::BigInt256& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
inline RefInt<false> u(int l, td::RefInt256& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const ConstInt<true, T> i(int l, const T& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<true, T> i(int l, T& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
PrefetchInt<true, T> pi(int l, T& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const ConstInt<false, T> u(int l, const T& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<false, T> u(int l, T& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
PrefetchInt<false, T> pu(int l, T& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const ConstInt<true, T, false> iw(int l, const T& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<true, T, false> iw(int l, T& val) {
|
||||
return {l, val};
|
||||
}
|
||||
|
||||
inline ConstInt<true, bool> ib(bool flag) {
|
||||
return {1, flag};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<true, T> ib(T& val) {
|
||||
return {1, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
PrefetchInt<true, T> pib(T& val) {
|
||||
return {1, val};
|
||||
}
|
||||
|
||||
inline ConstInt<false, bool> ub(bool flag) {
|
||||
return {1, flag};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<false, T> ub(T& val) {
|
||||
return {1, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
PrefetchInt<false, T> pub(T& val) {
|
||||
return {1, val};
|
||||
}
|
||||
|
||||
inline ConstInt<true, signed char> i8(signed char val) {
|
||||
return {8, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<true, T> i8(T& val) {
|
||||
return {8, val};
|
||||
}
|
||||
|
||||
inline ConstInt<false, unsigned char> u8(unsigned char val) {
|
||||
return {8, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<false, T> u8(T& val) {
|
||||
return {8, val};
|
||||
}
|
||||
|
||||
inline ConstInt<true, short> i16(short val) {
|
||||
return {16, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<true, T> i16(T& val) {
|
||||
static_assert(sizeof(T) >= 2, "i16 needs at least 16-bit integer variable as a result");
|
||||
return {16, val};
|
||||
}
|
||||
|
||||
inline ConstInt<false, unsigned short> u16(unsigned short val) {
|
||||
return {16, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<false, T> u16(T& val) {
|
||||
static_assert(sizeof(T) >= 2, "u16 needs at least 16-bit integer variable as a result");
|
||||
return {16, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const ConstInt<true, T> i32(const T& val) {
|
||||
return {32, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<true, T> i32(T& val) {
|
||||
static_assert(sizeof(T) >= 4, "i32 needs at least 32-bit integer variable as a result");
|
||||
return {32, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
PrefetchInt<true, T> pi32(T& val) {
|
||||
static_assert(sizeof(T) >= 4, "pi32 needs at least 32-bit integer variable as a result");
|
||||
return {32, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const ConstInt<false, unsigned> u32(const T& val) {
|
||||
return {32, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<false, T> u32(T& val) {
|
||||
static_assert(sizeof(T) >= 4, "u32 needs at least 32-bit integer variable as a result");
|
||||
return {32, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
PrefetchInt<false, T> pu32(T& val) {
|
||||
static_assert(sizeof(T) >= 4, "pu32 needs at least 32-bit integer variable as a result");
|
||||
return {32, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const ConstInt<true, T> i64(const T& val) {
|
||||
return {64, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<true, T> i64(T& val) {
|
||||
static_assert(sizeof(T) >= 8, "i64 needs 64-bit integer variable as a result");
|
||||
return {64, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
const ConstInt<false, T> u64(const T& val) {
|
||||
return {64, val};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Int<false, T> u64(T& val) {
|
||||
static_assert(sizeof(T) >= 8, "u64 needs 64-bit integer variable as a result");
|
||||
return {64, val};
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* non-integer types
|
||||
*
|
||||
*/
|
||||
|
||||
// cr(Ref<Cell>& cell_ref) for (de)serializing cell references
|
||||
|
||||
class ConstCellRef {
|
||||
const td::Ref<vm::Cell>& value;
|
||||
|
||||
public:
|
||||
ConstCellRef(const td::Ref<vm::Cell>& _val) : value(_val) {
|
||||
}
|
||||
bool serialize(CellBuilder& cb) const {
|
||||
return cb.store_ref_bool(value);
|
||||
}
|
||||
};
|
||||
|
||||
class ConstCellRefVal {
|
||||
td::Ref<vm::Cell> value;
|
||||
|
||||
public:
|
||||
ConstCellRefVal(const td::Ref<vm::Cell>& _val) : value(_val) {
|
||||
}
|
||||
ConstCellRefVal(td::Ref<vm::Cell>&& _val) : value(std::move(_val)) {
|
||||
}
|
||||
bool serialize(CellBuilder& cb) const {
|
||||
return cb.store_ref_bool(std::move(value));
|
||||
}
|
||||
};
|
||||
|
||||
class CellRefFmt {
|
||||
td::Ref<vm::Cell>& value;
|
||||
|
||||
public:
|
||||
CellRefFmt(td::Ref<vm::Cell>& _val) : value(_val) {
|
||||
}
|
||||
bool deserialize(CellSlice& cs) const {
|
||||
value = cs.fetch_ref();
|
||||
return value.not_null();
|
||||
}
|
||||
};
|
||||
|
||||
inline ConstCellRef cr(const td::Ref<vm::Cell>& val) {
|
||||
return {val};
|
||||
}
|
||||
|
||||
inline ConstCellRefVal cr(td::Ref<vm::Cell>&& val) {
|
||||
return {std::move(val)};
|
||||
}
|
||||
|
||||
inline CellRefFmt cr(td::Ref<vm::Cell>& val) {
|
||||
return {val};
|
||||
}
|
||||
|
||||
// skip(n) will skip n bits
|
||||
|
||||
class SkipFmt {
|
||||
int bits;
|
||||
|
||||
public:
|
||||
explicit SkipFmt(int _bits) : bits(_bits) {
|
||||
}
|
||||
bool deserialize(CellSlice& cs) const {
|
||||
return cs.advance(bits);
|
||||
}
|
||||
};
|
||||
|
||||
inline SkipFmt skip(int bits) {
|
||||
return SkipFmt{bits};
|
||||
}
|
||||
|
||||
// end will throw an exception if any bits or references remain, or if a previous operation failed
|
||||
// ends similar, but checks only bits
|
||||
|
||||
class ChkEnd {
|
||||
public:
|
||||
explicit ChkEnd() = default;
|
||||
bool deserialize(CellSlice& cs) const {
|
||||
return (cs.empty() && !cs.size_refs());
|
||||
}
|
||||
};
|
||||
|
||||
class ChkEndS {
|
||||
public:
|
||||
explicit ChkEndS() = default;
|
||||
bool deserialize(CellSlice& cs) const {
|
||||
return cs.empty();
|
||||
}
|
||||
};
|
||||
|
||||
template <class Cond>
|
||||
class Chk {
|
||||
Cond cond;
|
||||
|
||||
public:
|
||||
template <typename... Args>
|
||||
explicit constexpr Chk(Args... args) : cond(args...){};
|
||||
bool deserialize_ext(CellSlice& cs, bool state) const {
|
||||
if (!state || !cond.deserialize(cs)) {
|
||||
cs.error();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class ChkOk {
|
||||
public:
|
||||
explicit ChkOk() = default;
|
||||
bool deserialize_ext(CellSlice& cs, bool state) const {
|
||||
if (!state) {
|
||||
cs.error();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr ChkEnd end = ChkEnd{};
|
||||
constexpr ChkEndS ends = ChkEndS{};
|
||||
constexpr Chk<ChkEnd> okend = Chk<ChkEnd>{};
|
||||
constexpr Chk<ChkEndS> okends = Chk<ChkEndS>{};
|
||||
constexpr Chk<ChkEndS> oke = Chk<ChkEndS>{};
|
||||
constexpr ChkOk ok = ChkOk{};
|
||||
|
||||
inline ::vm::CellParser parser(CellSlice& cs) {
|
||||
return ::vm::CellParser{cs};
|
||||
}
|
||||
|
||||
} // namespace fmt
|
||||
|
||||
} // namespace vm
|
||||
53
crypto/vm/log.h
Normal file
53
crypto/vm/log.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "td/utils/logging.h"
|
||||
|
||||
#define VM_LOG_IMPL(st, mask) \
|
||||
LOG_IMPL_FULL(get_log_interface(st), get_log_options(st), DEBUG, VERBOSITY_NAME(DEBUG), \
|
||||
(get_log_mask(st) & mask) != 0, "")
|
||||
|
||||
#define VM_LOG(st) VM_LOG_IMPL(st, 1)
|
||||
#define VM_LOG_MASK(st, mask) VM_LOG_IMPL(st, mask)
|
||||
|
||||
namespace vm {
|
||||
struct VmLog {
|
||||
td::LogInterface *log_interface{td::log_interface};
|
||||
td::LogOptions log_options{td::log_options};
|
||||
enum { DumpStack = 2 };
|
||||
int log_mask{1};
|
||||
};
|
||||
|
||||
template <class State>
|
||||
td::LogInterface &get_log_interface(State *st) {
|
||||
return st ? *st->get_log().log_interface : *::td::log_interface;
|
||||
}
|
||||
|
||||
template <class State>
|
||||
auto get_log_options(State *st) {
|
||||
return st ? st->get_log().log_options : ::td::log_options;
|
||||
}
|
||||
|
||||
template <class State>
|
||||
auto get_log_mask(State *st) {
|
||||
return st ? st->get_log().log_mask : 1;
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
450
crypto/vm/opctable.cpp
Normal file
450
crypto/vm/opctable.cpp
Normal file
|
|
@ -0,0 +1,450 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <functional>
|
||||
|
||||
#include "td/utils/format.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
DispatchTable* OpcodeTable::finalize() {
|
||||
if (final) {
|
||||
return this;
|
||||
}
|
||||
instruction_list.clear();
|
||||
instruction_list.reserve(instructions.size() * 2 + 1);
|
||||
|
||||
unsigned upto = 0;
|
||||
for (const auto& x : instructions) {
|
||||
auto range = x.second->get_opcode_range();
|
||||
assert(range.first == x.first);
|
||||
assert(range.first < range.second);
|
||||
assert(range.first >= upto);
|
||||
assert(range.second <= top_opcode);
|
||||
if (range.first > upto) {
|
||||
instruction_list.emplace_back(upto, new OpcodeInstrDummy{upto, range.first});
|
||||
}
|
||||
instruction_list.emplace_back(x);
|
||||
upto = range.second;
|
||||
}
|
||||
|
||||
if (upto < top_opcode) {
|
||||
instruction_list.emplace_back(upto, new OpcodeInstrDummy{upto, top_opcode});
|
||||
}
|
||||
|
||||
instruction_list.shrink_to_fit();
|
||||
final = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
OpcodeTable& OpcodeTable::insert(const OpcodeInstr* instr) {
|
||||
LOG_IF(FATAL, !insert_bool(instr)) << td::format::lambda([&](auto& sb) {
|
||||
sb << "cannot insert instruction into table " << name << ": ";
|
||||
if (!instr) {
|
||||
sb << "instruction is null";
|
||||
} else if (final) {
|
||||
sb << "instruction table already finalized";
|
||||
} else {
|
||||
auto range = instr->get_opcode_range();
|
||||
sb << "opcode range " << td::format::as_hex(range.first) << ".." << td::format::as_hex(range.second - 1)
|
||||
<< " already occupied or invalid";
|
||||
}
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool OpcodeTable::insert_bool(const OpcodeInstr* instr) {
|
||||
if (!instr || final) {
|
||||
return false;
|
||||
}
|
||||
auto range = instr->get_opcode_range();
|
||||
assert(range.first < range.second);
|
||||
assert(range.second <= top_opcode);
|
||||
auto it = instructions.lower_bound(range.first);
|
||||
if (it != instructions.end() && it->first < range.second) {
|
||||
return false;
|
||||
}
|
||||
if (it != instructions.begin()) {
|
||||
auto prev_range = std::prev(it)->second->get_opcode_range();
|
||||
assert(prev_range.first < prev_range.second);
|
||||
assert(prev_range.first == std::prev(it)->first);
|
||||
if (prev_range.second > range.first) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
instructions.emplace_hint(it, range.first, instr);
|
||||
return true;
|
||||
}
|
||||
|
||||
const OpcodeInstr* OpcodeTable::lookup_instr(unsigned opcode, unsigned bits) const {
|
||||
std::size_t i = 0, j = instruction_list.size();
|
||||
assert(j);
|
||||
while (j - i > 1) {
|
||||
auto k = ((j + i) >> 1);
|
||||
if (instruction_list[k].first <= opcode) {
|
||||
i = k;
|
||||
} else {
|
||||
j = k;
|
||||
}
|
||||
}
|
||||
return instruction_list[i].second;
|
||||
}
|
||||
|
||||
const OpcodeInstr* OpcodeTable::lookup_instr(const CellSlice& cs, unsigned& opcode, unsigned& bits) const {
|
||||
bits = max_opcode_bits;
|
||||
unsigned long long prefetch = cs.prefetch_ulong_top(bits);
|
||||
opcode = (unsigned)(prefetch >> (64 - max_opcode_bits));
|
||||
opcode &= (static_cast<int32_t>(static_cast<td::uint32>(-1) << max_opcode_bits) >> bits);
|
||||
return lookup_instr(opcode, bits);
|
||||
}
|
||||
|
||||
int OpcodeTable::dispatch(VmState* st, CellSlice& cs) const {
|
||||
assert(final);
|
||||
unsigned bits, opcode;
|
||||
auto instr = lookup_instr(cs, opcode, bits);
|
||||
//std::cerr << "lookup_instr: cs.size()=" << cs.size() << "; bits=" << bits << "; opcode=" << std::setw(6) << std::setfill('0') << std::hex << opcode << std::dec << std::endl;
|
||||
return instr->dispatch(st, cs, opcode, bits);
|
||||
}
|
||||
|
||||
std::string OpcodeTable::dump_instr(CellSlice& cs) const {
|
||||
assert(final);
|
||||
unsigned bits, opcode;
|
||||
auto instr = lookup_instr(cs, opcode, bits);
|
||||
return instr->dump(cs, opcode, bits);
|
||||
}
|
||||
|
||||
int OpcodeTable::instr_len(const CellSlice& cs) const {
|
||||
assert(final);
|
||||
unsigned bits, opcode;
|
||||
auto instr = lookup_instr(cs, opcode, bits);
|
||||
return instr->instr_len(cs, opcode, bits);
|
||||
}
|
||||
|
||||
OpcodeInstr::OpcodeInstr(unsigned _opcode, unsigned _bits, bool)
|
||||
: min_opcode(_opcode << (max_opcode_bits - _bits)), max_opcode((_opcode + 1) << (max_opcode_bits - _bits)) {
|
||||
assert(_opcode < (1U << _bits) && _bits <= max_opcode_bits);
|
||||
}
|
||||
|
||||
int OpcodeInstrDummy::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
st->consume_gas(gas_per_instr);
|
||||
throw VmError{Excno::inv_opcode, "invalid opcode", opcode};
|
||||
}
|
||||
|
||||
std::string OpcodeInstr::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
return "";
|
||||
}
|
||||
|
||||
int OpcodeInstr::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
OpcodeInstrSimple::OpcodeInstrSimple(unsigned opcode, unsigned _opc_bits, std::string _name, exec_instr_func_t exec)
|
||||
: OpcodeInstr(opcode, _opc_bits, false)
|
||||
, opc_bits(static_cast<unsigned char>(_opc_bits))
|
||||
, name(_name)
|
||||
, exec_instr(exec) {
|
||||
}
|
||||
|
||||
int OpcodeInstrSimple::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
st->consume_gas(gas_per_instr + opc_bits * gas_per_bit);
|
||||
if (bits < opc_bits) {
|
||||
throw VmError{Excno::inv_opcode, "invalid or too short opcode", opcode + (bits << max_opcode_bits)};
|
||||
}
|
||||
cs.advance(opc_bits);
|
||||
return exec_instr(st, cs, opcode >> (max_opcode_bits - opc_bits), opc_bits);
|
||||
}
|
||||
|
||||
std::string OpcodeInstrSimple::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
if (bits < opc_bits) {
|
||||
return "";
|
||||
}
|
||||
cs.advance(opc_bits);
|
||||
return name;
|
||||
}
|
||||
|
||||
int OpcodeInstrSimple::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
if (bits < opc_bits) {
|
||||
return 0;
|
||||
} else {
|
||||
return opc_bits;
|
||||
}
|
||||
}
|
||||
|
||||
OpcodeInstrSimplest::OpcodeInstrSimplest(unsigned opcode, unsigned _opc_bits, std::string _name,
|
||||
exec_simple_instr_func_t exec)
|
||||
: OpcodeInstr(opcode, _opc_bits, false)
|
||||
, opc_bits(static_cast<unsigned char>(_opc_bits))
|
||||
, name(_name)
|
||||
, exec_instr(exec) {
|
||||
}
|
||||
|
||||
int OpcodeInstrSimplest::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
st->consume_gas(gas_per_instr + opc_bits * gas_per_bit);
|
||||
if (bits < opc_bits) {
|
||||
throw VmError{Excno::inv_opcode, "invalid or too short opcode", opcode + (bits << max_opcode_bits)};
|
||||
}
|
||||
cs.advance(opc_bits);
|
||||
return exec_instr(st);
|
||||
}
|
||||
|
||||
std::string OpcodeInstrSimplest::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
if (bits < opc_bits) {
|
||||
return "";
|
||||
}
|
||||
cs.advance(opc_bits);
|
||||
return name;
|
||||
}
|
||||
|
||||
int OpcodeInstrSimplest::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
if (bits < opc_bits) {
|
||||
return 0;
|
||||
} else {
|
||||
return opc_bits;
|
||||
}
|
||||
}
|
||||
|
||||
OpcodeInstrFixed::OpcodeInstrFixed(unsigned opcode, unsigned _opc_bits, unsigned _arg_bits, dump_arg_instr_func_t dump,
|
||||
exec_arg_instr_func_t exec)
|
||||
: OpcodeInstr(opcode, _opc_bits, false)
|
||||
, opc_bits(static_cast<unsigned char>(_opc_bits))
|
||||
, tot_bits(static_cast<unsigned char>(_opc_bits + _arg_bits))
|
||||
, dump_instr(dump)
|
||||
, exec_instr(exec) {
|
||||
assert(_arg_bits <= max_opcode_bits && _opc_bits <= max_opcode_bits && _arg_bits + _opc_bits <= max_opcode_bits);
|
||||
}
|
||||
|
||||
OpcodeInstrFixed::OpcodeInstrFixed(unsigned opcode_min, unsigned opcode_max, unsigned _tot_bits, unsigned _arg_bits,
|
||||
dump_arg_instr_func_t dump, exec_arg_instr_func_t exec)
|
||||
: OpcodeInstr(opcode_min << (max_opcode_bits - _tot_bits), opcode_max << (max_opcode_bits - _tot_bits))
|
||||
, opc_bits(static_cast<unsigned char>(_tot_bits - _arg_bits))
|
||||
, tot_bits(static_cast<unsigned char>(_tot_bits))
|
||||
, dump_instr(dump)
|
||||
, exec_instr(exec) {
|
||||
assert(_arg_bits <= _tot_bits && _tot_bits <= max_opcode_bits);
|
||||
assert(opcode_min < opcode_max && opcode_max <= (1U << _tot_bits));
|
||||
}
|
||||
|
||||
int OpcodeInstrFixed::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
st->consume_gas(gas_per_instr + tot_bits * gas_per_bit);
|
||||
if (bits < tot_bits) {
|
||||
throw VmError{Excno::inv_opcode, "invalid or too short opcode", opcode + (bits << max_opcode_bits)};
|
||||
}
|
||||
cs.advance(tot_bits);
|
||||
return exec_instr(st, opcode >> (max_opcode_bits - tot_bits));
|
||||
}
|
||||
|
||||
std::string OpcodeInstrFixed::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
if (bits < tot_bits) {
|
||||
return "";
|
||||
}
|
||||
cs.advance(tot_bits);
|
||||
return dump_instr(cs, opcode >> (max_opcode_bits - tot_bits));
|
||||
}
|
||||
|
||||
int OpcodeInstrFixed::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
if (bits < tot_bits) {
|
||||
return 0;
|
||||
} else {
|
||||
return tot_bits;
|
||||
}
|
||||
}
|
||||
|
||||
OpcodeInstrExt::OpcodeInstrExt(unsigned opcode, unsigned _opc_bits, unsigned _arg_bits, dump_instr_func_t dump,
|
||||
exec_instr_func_t exec, compute_instr_len_func_t comp_len)
|
||||
: OpcodeInstr(opcode, _opc_bits, false)
|
||||
, opc_bits(static_cast<unsigned char>(_opc_bits))
|
||||
, tot_bits(static_cast<unsigned char>(_opc_bits + _arg_bits))
|
||||
, dump_instr(dump)
|
||||
, exec_instr(exec)
|
||||
, compute_instr_len(comp_len) {
|
||||
assert(_arg_bits <= max_opcode_bits && _opc_bits <= max_opcode_bits && _arg_bits + _opc_bits <= max_opcode_bits);
|
||||
}
|
||||
|
||||
OpcodeInstrExt::OpcodeInstrExt(unsigned opcode_min, unsigned opcode_max, unsigned _tot_bits, unsigned _arg_bits,
|
||||
dump_instr_func_t dump, exec_instr_func_t exec, compute_instr_len_func_t comp_len)
|
||||
: OpcodeInstr(opcode_min << (max_opcode_bits - _tot_bits), opcode_max << (max_opcode_bits - _tot_bits))
|
||||
, opc_bits(static_cast<unsigned char>(_tot_bits - _arg_bits))
|
||||
, tot_bits(static_cast<unsigned char>(_tot_bits))
|
||||
, dump_instr(dump)
|
||||
, exec_instr(exec)
|
||||
, compute_instr_len(comp_len) {
|
||||
assert(_arg_bits <= _tot_bits && _tot_bits <= max_opcode_bits);
|
||||
assert(opcode_min < opcode_max && opcode_max <= (1U << _tot_bits));
|
||||
}
|
||||
|
||||
int OpcodeInstrExt::dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
st->consume_gas(gas_per_instr + tot_bits * gas_per_bit);
|
||||
if (bits < tot_bits) {
|
||||
throw VmError{Excno::inv_opcode, "invalid or too short opcode", opcode + (bits << max_opcode_bits)};
|
||||
}
|
||||
return exec_instr(st, cs, opcode >> (max_opcode_bits - tot_bits), tot_bits);
|
||||
}
|
||||
|
||||
std::string OpcodeInstrExt::dump(CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
if (bits < tot_bits) {
|
||||
return "";
|
||||
}
|
||||
return dump_instr(cs, opcode >> (max_opcode_bits - tot_bits), (int)tot_bits);
|
||||
}
|
||||
|
||||
int OpcodeInstrExt::instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const {
|
||||
if (bits < tot_bits) {
|
||||
return 0;
|
||||
} else {
|
||||
return compute_instr_len(cs, opcode >> (max_opcode_bits - tot_bits), (int)tot_bits);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
OpcodeInstr* OpcodeInstr::mksimple(unsigned opcode, unsigned opc_bits, std::string _name, exec_instr_func_t exec) {
|
||||
return new OpcodeInstrSimple(opcode, opc_bits, _name, exec);
|
||||
}
|
||||
*/
|
||||
|
||||
OpcodeInstr* OpcodeInstr::mksimple(unsigned opcode, unsigned opc_bits, std::string _name,
|
||||
exec_simple_instr_func_t exec) {
|
||||
return new OpcodeInstrSimplest(opcode, opc_bits, _name, exec);
|
||||
}
|
||||
|
||||
OpcodeInstr* OpcodeInstr::mkfixed(unsigned opcode, unsigned opc_bits, unsigned arg_bits, dump_arg_instr_func_t dump,
|
||||
exec_arg_instr_func_t exec) {
|
||||
return new OpcodeInstrFixed(opcode, opc_bits, arg_bits, dump, exec);
|
||||
}
|
||||
|
||||
OpcodeInstr* OpcodeInstr::mkfixedrange(unsigned opcode_min, unsigned opcode_max, unsigned tot_bits, unsigned arg_bits,
|
||||
dump_arg_instr_func_t dump, exec_arg_instr_func_t exec) {
|
||||
return new OpcodeInstrFixed(opcode_min, opcode_max, tot_bits, arg_bits, dump, exec);
|
||||
}
|
||||
|
||||
OpcodeInstr* OpcodeInstr::mkext(unsigned opcode, unsigned opc_bits, unsigned arg_bits, dump_instr_func_t dump,
|
||||
exec_instr_func_t exec, compute_instr_len_func_t comp_len) {
|
||||
return new OpcodeInstrExt(opcode, opc_bits, arg_bits, dump, exec, comp_len);
|
||||
}
|
||||
|
||||
OpcodeInstr* OpcodeInstr::mkextrange(unsigned opcode_min, unsigned opcode_max, unsigned tot_bits, unsigned arg_bits,
|
||||
dump_instr_func_t dump, exec_instr_func_t exec,
|
||||
compute_instr_len_func_t comp_len) {
|
||||
return new OpcodeInstrExt(opcode_min, opcode_max, tot_bits, arg_bits, dump, exec, comp_len);
|
||||
}
|
||||
|
||||
namespace instr {
|
||||
|
||||
using namespace std::placeholders;
|
||||
|
||||
dump_arg_instr_func_t dump_1sr(std::string prefix, std::string suffix) {
|
||||
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << 's' << (args & 15) << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_1sr_l(std::string prefix, std::string suffix) {
|
||||
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << 's' << (args & 255) << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_2sr(std::string prefix, std::string suffix) {
|
||||
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << 's' << ((args >> 4) & 15) << ",s" << (args & 15) << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_2sr_adj(unsigned adj, std::string prefix, std::string suffix) {
|
||||
return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << 's' << (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15)
|
||||
<< suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_3sr(std::string prefix, std::string suffix) {
|
||||
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << 's' << ((args >> 8) & 15) << ",s" << ((args >> 4) & 15) << ",s" << (args & 15) << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_3sr_adj(unsigned adj, std::string prefix, std::string suffix) {
|
||||
return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << 's' << (int)((args >> 8) & 15) - (int)((adj >> 8) & 15) << ",s"
|
||||
<< (int)((args >> 4) & 15) - (int)((adj >> 4) & 15) << ",s" << (int)(args & 15) - (int)(adj & 15) << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_1c(std::string prefix, std::string suffix) {
|
||||
return [prefix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << (args & 15) << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_1c_l_add(int adj, std::string prefix, std::string suffix) {
|
||||
return [adj, prefix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << (int)(args & 255) + adj << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_1c_and(unsigned mask, std::string prefix, std::string suffix) {
|
||||
return [mask, prefix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << (args & mask) << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_2c(std::string prefix, std::string interfix, std::string suffix) {
|
||||
return [prefix, interfix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << ((args >> 4) & 15) << interfix << (args & 15) << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
dump_arg_instr_func_t dump_2c_add(unsigned add, std::string prefix, std::string interfix, std::string suffix) {
|
||||
return [add, prefix, interfix, suffix](CellSlice&, unsigned args) -> std::string {
|
||||
std::ostringstream os{prefix};
|
||||
os << ((args >> 4) & 15) + ((add >> 4) & 15) << interfix << (args & 15) + (add & 15) << suffix;
|
||||
return os.str();
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace instr
|
||||
|
||||
} // namespace vm
|
||||
191
crypto/vm/opctable.h
Normal file
191
crypto/vm/opctable.h
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/dispatch.h"
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace vm {
|
||||
|
||||
typedef std::function<int(const CellSlice&, unsigned, int)> compute_instr_len_func_t;
|
||||
//typedef std::function<int(unsigned, int)> compute_arg_instr_len_func_t;
|
||||
typedef std::function<std::string(CellSlice&, unsigned, int)> dump_instr_func_t;
|
||||
typedef std::function<std::string(CellSlice&, unsigned)> dump_arg_instr_func_t;
|
||||
typedef std::function<int(VmState* st, CellSlice&, unsigned, int)> exec_instr_func_t;
|
||||
typedef std::function<int(VmState* st, unsigned)> exec_arg_instr_func_t;
|
||||
typedef std::function<int(VmState* st)> exec_simple_instr_func_t;
|
||||
|
||||
enum { max_opcode_bits = 24 };
|
||||
const unsigned top_opcode = (1U << max_opcode_bits);
|
||||
|
||||
class OpcodeInstr {
|
||||
unsigned min_opcode, max_opcode;
|
||||
|
||||
public:
|
||||
static constexpr unsigned gas_per_instr = 10, gas_per_bit = 1;
|
||||
virtual ~OpcodeInstr() = default;
|
||||
virtual int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const = 0;
|
||||
virtual std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const;
|
||||
virtual int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const;
|
||||
OpcodeInstr(unsigned _min, unsigned _max) : min_opcode(_min), max_opcode(_max) {
|
||||
}
|
||||
OpcodeInstr(unsigned _opcode, unsigned _bits, bool);
|
||||
unsigned get_opcode_min() const {
|
||||
return min_opcode;
|
||||
}
|
||||
unsigned get_opcode_max() const {
|
||||
return max_opcode;
|
||||
}
|
||||
std::pair<unsigned, unsigned> get_opcode_range() const {
|
||||
return {min_opcode, max_opcode};
|
||||
}
|
||||
//static OpcodeInstr* mksimple(unsigned opcode, unsigned opc_bits, std::string _name, exec_instr_func_t exec);
|
||||
static OpcodeInstr* mksimple(unsigned opcode, unsigned opc_bits, std::string _name, exec_simple_instr_func_t exec);
|
||||
static OpcodeInstr* mkfixed(unsigned opcode, unsigned opc_bits, unsigned arg_bits, dump_arg_instr_func_t dump,
|
||||
exec_arg_instr_func_t exec);
|
||||
static OpcodeInstr* mkfixedrange(unsigned opcode_min, unsigned opcode_max, unsigned tot_bits, unsigned arg_bits,
|
||||
dump_arg_instr_func_t dump, exec_arg_instr_func_t exec);
|
||||
static OpcodeInstr* mkext(unsigned opcode, unsigned opc_bits, unsigned arg_bits, dump_instr_func_t dump,
|
||||
exec_instr_func_t exec, compute_instr_len_func_t comp_len);
|
||||
static OpcodeInstr* mkextrange(unsigned opcode_min, unsigned opcode_max, unsigned tot_bits, unsigned arg_bits,
|
||||
dump_instr_func_t dump, exec_instr_func_t exec, compute_instr_len_func_t comp_len);
|
||||
};
|
||||
|
||||
namespace instr {
|
||||
|
||||
dump_arg_instr_func_t dump_1sr(std::string prefix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_1sr_l(std::string prefix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_2sr(std::string prefix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_2sr_adj(unsigned adj, std::string prefix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_3sr(std::string prefix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_3sr_adj(unsigned adj, std::string prefix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_1c(std::string prefix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_1c_l_add(int adj, std::string prefix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_1c_and(unsigned mask, std::string prefix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_2c(std::string prefix, std::string interfix, std::string suffix = "");
|
||||
dump_arg_instr_func_t dump_2c_add(unsigned add, std::string prefix, std::string interfix, std::string suffix = "");
|
||||
|
||||
} // namespace instr
|
||||
|
||||
class OpcodeTable : public DispatchTable {
|
||||
std::map<unsigned, const OpcodeInstr*> instructions;
|
||||
std::vector<std::pair<unsigned, const OpcodeInstr*>> instruction_list;
|
||||
std::string name;
|
||||
Codepage codepage;
|
||||
bool final;
|
||||
|
||||
public:
|
||||
OpcodeTable(std::string _name, Codepage cp) : name(_name), codepage(cp), final(false) {
|
||||
}
|
||||
OpcodeTable(const OpcodeTable&) = delete;
|
||||
OpcodeTable(OpcodeTable&&) = delete;
|
||||
OpcodeTable& operator=(const OpcodeTable&) = delete;
|
||||
OpcodeTable& operator=(OpcodeTable&&) = delete;
|
||||
~OpcodeTable() override = default;
|
||||
DispatchTable* finalize() override;
|
||||
bool is_final() const override {
|
||||
return final;
|
||||
}
|
||||
int dispatch(VmState* st, CellSlice& cs) const override;
|
||||
std::string dump_instr(CellSlice& cs) const override;
|
||||
int instr_len(const CellSlice& cs) const override;
|
||||
bool insert_bool(const OpcodeInstr*);
|
||||
OpcodeTable& insert(const OpcodeInstr*);
|
||||
|
||||
private:
|
||||
const OpcodeInstr* lookup_instr(unsigned opcode, unsigned bits) const;
|
||||
const OpcodeInstr* lookup_instr(const CellSlice& cs, unsigned& opcode, unsigned& bits) const;
|
||||
};
|
||||
|
||||
class OpcodeInstrDummy : public OpcodeInstr {
|
||||
public:
|
||||
OpcodeInstrDummy() = delete;
|
||||
OpcodeInstrDummy(unsigned _minopc, unsigned _maxopc) : OpcodeInstr(_minopc, _maxopc) {
|
||||
}
|
||||
~OpcodeInstrDummy() override = default;
|
||||
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
};
|
||||
|
||||
class OpcodeInstrSimple : public OpcodeInstr {
|
||||
unsigned char opc_bits;
|
||||
std::string name;
|
||||
exec_instr_func_t exec_instr;
|
||||
|
||||
public:
|
||||
OpcodeInstrSimple() = delete;
|
||||
OpcodeInstrSimple(unsigned opcode, unsigned _opc_bits, std::string _name, exec_instr_func_t exec);
|
||||
~OpcodeInstrSimple() override = default;
|
||||
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
};
|
||||
|
||||
class OpcodeInstrSimplest : public OpcodeInstr {
|
||||
unsigned char opc_bits;
|
||||
std::string name;
|
||||
exec_simple_instr_func_t exec_instr;
|
||||
|
||||
public:
|
||||
OpcodeInstrSimplest() = delete;
|
||||
OpcodeInstrSimplest(unsigned opcode, unsigned _opc_bits, std::string _name, exec_simple_instr_func_t exec);
|
||||
~OpcodeInstrSimplest() override = default;
|
||||
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
};
|
||||
|
||||
class OpcodeInstrFixed : public OpcodeInstr {
|
||||
unsigned char opc_bits, tot_bits;
|
||||
std::string name;
|
||||
dump_arg_instr_func_t dump_instr;
|
||||
exec_arg_instr_func_t exec_instr;
|
||||
|
||||
public:
|
||||
OpcodeInstrFixed() = delete;
|
||||
OpcodeInstrFixed(unsigned opcode, unsigned _opc_bits, unsigned _arg_bits, dump_arg_instr_func_t dump,
|
||||
exec_arg_instr_func_t exec);
|
||||
OpcodeInstrFixed(unsigned opcode_min, unsigned opcode_max, unsigned _tot_bits, unsigned _arg_bits,
|
||||
dump_arg_instr_func_t dump, exec_arg_instr_func_t exec);
|
||||
~OpcodeInstrFixed() override = default;
|
||||
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
};
|
||||
|
||||
class OpcodeInstrExt : public OpcodeInstr {
|
||||
unsigned char opc_bits, tot_bits;
|
||||
dump_instr_func_t dump_instr;
|
||||
exec_instr_func_t exec_instr;
|
||||
compute_instr_len_func_t compute_instr_len;
|
||||
|
||||
public:
|
||||
OpcodeInstrExt() = delete;
|
||||
OpcodeInstrExt(unsigned opcode, unsigned _opc_bits, unsigned _arg_bits, dump_instr_func_t dump,
|
||||
exec_instr_func_t exec, compute_instr_len_func_t comp_len);
|
||||
OpcodeInstrExt(unsigned opcode_min, unsigned opcode_max, unsigned _tot_bits, unsigned _arg_bits,
|
||||
dump_instr_func_t dump, exec_instr_func_t exec, compute_instr_len_func_t comp_len);
|
||||
~OpcodeInstrExt() override = default;
|
||||
int dispatch(VmState* st, CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
std::string dump(CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
int instr_len(const CellSlice& cs, unsigned opcode, unsigned bits) const override;
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
632
crypto/vm/stack.cpp
Normal file
632
crypto/vm/stack.cpp
Normal file
|
|
@ -0,0 +1,632 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/box.hpp"
|
||||
#include "vm/atom.h"
|
||||
|
||||
namespace td {
|
||||
template class td::Cnt<std::string>;
|
||||
template class td::Ref<td::Cnt<std::string>>;
|
||||
template class td::Cnt<std::vector<vm::StackEntry>>;
|
||||
template class td::Ref<td::Cnt<std::vector<vm::StackEntry>>>;
|
||||
} // namespace td
|
||||
|
||||
namespace vm {
|
||||
|
||||
// from_object_t from_object{};
|
||||
|
||||
const char* exception_messages[(int)(Excno::total)] = {
|
||||
"normal termination", "alternative termination", "stack underflow", "stack overflow", "integer overflow",
|
||||
"integer out of range", "invalid opcode", "type check error", "cell overflow", "cell underflow",
|
||||
"dictionary error", "unknown error", "fatal error"};
|
||||
|
||||
const char* get_exception_msg(Excno exc_no) {
|
||||
if (exc_no >= Excno::none && exc_no < Excno::total) {
|
||||
return exception_messages[static_cast<int>(exc_no)];
|
||||
} else {
|
||||
return "unknown vm exception";
|
||||
}
|
||||
}
|
||||
|
||||
static const char HEX_digits[] = "0123456789ABCDEF";
|
||||
|
||||
std::string str_to_hex(std::string data, std::string prefix) {
|
||||
prefix.reserve(prefix.size() + data.size() * 2);
|
||||
for (char c : data) {
|
||||
prefix += HEX_digits[(c >> 4) & 15];
|
||||
prefix += HEX_digits[c & 15];
|
||||
}
|
||||
return prefix;
|
||||
}
|
||||
|
||||
std::string StackEntry::to_string() const {
|
||||
std::ostringstream os;
|
||||
dump(os);
|
||||
return std::move(os).str();
|
||||
}
|
||||
|
||||
void StackEntry::dump(std::ostream& os) const {
|
||||
switch (tp) {
|
||||
case t_null:
|
||||
os << "(null)";
|
||||
break;
|
||||
case t_int:
|
||||
os << dec_string(as_int());
|
||||
break;
|
||||
case t_cell:
|
||||
os << "C{" << static_cast<Ref<Cell>>(ref)->get_hash().to_hex() << "}";
|
||||
break;
|
||||
case t_builder:
|
||||
os << "BC{" << static_cast<Ref<CellBuilder>>(ref)->to_hex() << "}";
|
||||
break;
|
||||
case t_slice: {
|
||||
os << "CS{";
|
||||
static_cast<Ref<CellSlice>>(ref)->dump(os, 1, false);
|
||||
os << '}';
|
||||
break;
|
||||
}
|
||||
case t_string:
|
||||
os << "\"" << as_string() << "\"";
|
||||
break;
|
||||
case t_bytes:
|
||||
os << "BYTES:" << str_to_hex(as_bytes());
|
||||
break;
|
||||
case t_box: {
|
||||
os << "Box{" << (const void*)&*ref << "}";
|
||||
break;
|
||||
}
|
||||
case t_atom:
|
||||
os << as_atom();
|
||||
break;
|
||||
case t_tuple: {
|
||||
const auto& tuple = *static_cast<Ref<Tuple>>(ref);
|
||||
auto n = tuple.size();
|
||||
if (!n) {
|
||||
os << "[]";
|
||||
} else if (n == 1) {
|
||||
os << "[ ";
|
||||
tuple[0].dump(os);
|
||||
os << " ]";
|
||||
} else {
|
||||
os << "[ ";
|
||||
for (const auto& entry : tuple) {
|
||||
entry.dump(os);
|
||||
os << ' ';
|
||||
}
|
||||
os << ']';
|
||||
}
|
||||
break;
|
||||
}
|
||||
case t_object: {
|
||||
os << "Object{" << (const void*)&*ref << "}";
|
||||
break;
|
||||
}
|
||||
default:
|
||||
os << "???";
|
||||
}
|
||||
}
|
||||
|
||||
void StackEntry::print_list(std::ostream& os) const {
|
||||
switch (tp) {
|
||||
case t_null:
|
||||
os << "()";
|
||||
break;
|
||||
case t_tuple: {
|
||||
const auto& tuple = *static_cast<Ref<Tuple>>(ref);
|
||||
auto n = tuple.size();
|
||||
if (!n) {
|
||||
os << "[]";
|
||||
} else if (n == 1) {
|
||||
os << "[";
|
||||
tuple[0].print_list(os);
|
||||
os << "]";
|
||||
} else if (n != 2) {
|
||||
os << "[";
|
||||
unsigned c = 0;
|
||||
for (const auto& entry : tuple) {
|
||||
if (c++) {
|
||||
os << " ";
|
||||
}
|
||||
entry.print_list(os);
|
||||
}
|
||||
os << ']';
|
||||
} else {
|
||||
os << '(';
|
||||
tuple[0].print_list(os);
|
||||
tuple[1].print_list_tail(os);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
dump(os);
|
||||
}
|
||||
}
|
||||
|
||||
void StackEntry::print_list_tail(std::ostream& os) const {
|
||||
switch (tp) {
|
||||
case t_null:
|
||||
os << ')';
|
||||
break;
|
||||
case t_tuple: {
|
||||
const auto& tuple = *static_cast<Ref<Tuple>>(ref);
|
||||
if (tuple.size() == 2) {
|
||||
os << ' ';
|
||||
tuple[0].print_list(os);
|
||||
tuple[1].print_list_tail(os);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// fall through
|
||||
default:
|
||||
os << " . ";
|
||||
print_list(os);
|
||||
os << ')';
|
||||
}
|
||||
}
|
||||
|
||||
StackEntry::StackEntry(Ref<Stack> stack_ref) : ref(std::move(stack_ref)), tp(t_stack) {
|
||||
}
|
||||
|
||||
StackEntry::StackEntry(Ref<Continuation> cont_ref) : ref(std::move(cont_ref)), tp(t_vmcont) {
|
||||
}
|
||||
|
||||
Ref<Continuation> StackEntry::as_cont() const & {
|
||||
return as<Continuation, t_vmcont>();
|
||||
}
|
||||
|
||||
Ref<Continuation> StackEntry::as_cont() && {
|
||||
return move_as<Continuation, t_vmcont>();
|
||||
}
|
||||
|
||||
StackEntry::StackEntry(Ref<Box> box_ref) : ref(std::move(box_ref)), tp(t_box) {
|
||||
}
|
||||
|
||||
Ref<Box> StackEntry::as_box() const & {
|
||||
return as<Box, t_box>();
|
||||
}
|
||||
|
||||
Ref<Box> StackEntry::as_box() && {
|
||||
return move_as<Box, t_box>();
|
||||
}
|
||||
|
||||
StackEntry::StackEntry(Ref<Tuple> tuple_ref) : ref(std::move(tuple_ref)), tp(t_tuple) {
|
||||
}
|
||||
|
||||
StackEntry::StackEntry(const std::vector<StackEntry>& tuple_components)
|
||||
: ref(Ref<Tuple>{true, tuple_components}), tp(t_tuple) {
|
||||
}
|
||||
|
||||
StackEntry::StackEntry(std::vector<StackEntry>&& tuple_components)
|
||||
: ref(Ref<Tuple>{true, std::move(tuple_components)}), tp(t_tuple) {
|
||||
}
|
||||
|
||||
Ref<Tuple> StackEntry::as_tuple() const & {
|
||||
return as<Tuple, t_tuple>();
|
||||
}
|
||||
|
||||
Ref<Tuple> StackEntry::as_tuple() && {
|
||||
return move_as<Tuple, t_tuple>();
|
||||
}
|
||||
|
||||
Ref<Tuple> StackEntry::as_tuple_range(unsigned max_len, unsigned min_len) const & {
|
||||
auto t = as<Tuple, t_tuple>();
|
||||
if (t.not_null() && t->size() <= max_len && t->size() >= min_len) {
|
||||
return t;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Tuple> StackEntry::as_tuple_range(unsigned max_len, unsigned min_len) && {
|
||||
auto t = move_as<Tuple, t_tuple>();
|
||||
if (t.not_null() && t->size() <= max_len && t->size() >= min_len) {
|
||||
return t;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
StackEntry::StackEntry(Ref<Atom> atom_ref) : ref(std::move(atom_ref)), tp(t_atom) {
|
||||
}
|
||||
|
||||
Ref<Atom> StackEntry::as_atom() const & {
|
||||
return as<Atom, t_atom>();
|
||||
}
|
||||
|
||||
Ref<Atom> StackEntry::as_atom() && {
|
||||
return move_as<Atom, t_atom>();
|
||||
}
|
||||
|
||||
const StackEntry& tuple_index(const Tuple& tup, unsigned idx) {
|
||||
if (idx >= tup->size()) {
|
||||
throw VmError{Excno::range_chk, "tuple index out of range"};
|
||||
}
|
||||
return (*tup)[idx];
|
||||
}
|
||||
|
||||
StackEntry tuple_extend_index(const Ref<Tuple>& tup, unsigned idx) {
|
||||
if (tup.is_null() || idx >= tup->size()) {
|
||||
return {};
|
||||
} else {
|
||||
return tup->at(idx);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned tuple_extend_set_index(Ref<Tuple>& tup, unsigned idx, StackEntry&& value, bool force) {
|
||||
if (tup.is_null()) {
|
||||
if (value.empty() && !force) {
|
||||
return 0;
|
||||
}
|
||||
tup = Ref<Tuple>{true, idx + 1};
|
||||
tup.unique_write().at(idx) = std::move(value);
|
||||
return idx + 1;
|
||||
}
|
||||
if (tup->size() <= idx) {
|
||||
if (value.empty() && !force) {
|
||||
return 0;
|
||||
}
|
||||
auto& tuple = tup.write();
|
||||
tuple.resize(idx + 1);
|
||||
tuple.at(idx) = std::move(value);
|
||||
return idx + 1;
|
||||
} else {
|
||||
tup.write().at(idx) = std::move(value);
|
||||
return (unsigned)tup->size();
|
||||
}
|
||||
}
|
||||
|
||||
Stack::Stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top) {
|
||||
push_from_stack(old_stack, copy_elem, skip_top);
|
||||
}
|
||||
|
||||
Stack::Stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top) {
|
||||
push_from_stack(old_stack, copy_elem, skip_top);
|
||||
}
|
||||
|
||||
void Stack::push_from_stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top) {
|
||||
unsigned n = old_stack.depth();
|
||||
if (skip_top > n || copy_elem > n - skip_top) {
|
||||
throw VmError{Excno::stk_und, "cannot construct stack from another one: not enough elements"};
|
||||
}
|
||||
stack.reserve(stack.size() + copy_elem);
|
||||
auto it = old_stack.stack.cend() - skip_top;
|
||||
std::copy(it - copy_elem, it, std::back_inserter(stack));
|
||||
}
|
||||
|
||||
void Stack::push_from_stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top) {
|
||||
unsigned n = old_stack.depth();
|
||||
if (skip_top > n || copy_elem > n - skip_top) {
|
||||
throw VmError{Excno::stk_und, "cannot construct stack from another one: not enough elements"};
|
||||
}
|
||||
stack.reserve(stack.size() + copy_elem);
|
||||
auto it = old_stack.stack.cend() - skip_top;
|
||||
std::move(it - copy_elem, it, std::back_inserter(stack));
|
||||
}
|
||||
|
||||
void Stack::move_from_stack(Stack& old_stack, unsigned copy_elem) {
|
||||
unsigned n = old_stack.depth();
|
||||
if (copy_elem > n) {
|
||||
throw VmError{Excno::stk_und, "cannot construct stack from another one: not enough elements"};
|
||||
}
|
||||
LOG(DEBUG) << "moving " << copy_elem << " top elements to another stack\n";
|
||||
stack.reserve(stack.size() + copy_elem);
|
||||
auto it = old_stack.stack.cend();
|
||||
std::move(it - copy_elem, it, std::back_inserter(stack));
|
||||
old_stack.pop_many(copy_elem);
|
||||
}
|
||||
|
||||
void Stack::pop_null() {
|
||||
check_underflow(1);
|
||||
if (!pop().empty()) {
|
||||
throw VmError{Excno::type_chk, "not an null"};
|
||||
}
|
||||
}
|
||||
|
||||
td::RefInt256 Stack::pop_int() {
|
||||
check_underflow(1);
|
||||
td::RefInt256 res = pop().as_int();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not an integer"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
td::RefInt256 Stack::pop_int_finite() {
|
||||
auto res = pop_int();
|
||||
if (!res->is_valid()) {
|
||||
throw VmError{Excno::int_ov};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool Stack::pop_bool() {
|
||||
return sgn(pop_int_finite()) != 0;
|
||||
}
|
||||
|
||||
long long Stack::pop_long() {
|
||||
return pop_int()->to_long();
|
||||
}
|
||||
|
||||
long long Stack::pop_long_range(long long max, long long min) {
|
||||
auto res = pop_long();
|
||||
if (res > max || res < min) {
|
||||
throw VmError{Excno::range_chk};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int Stack::pop_smallint_range(int max, int min) {
|
||||
return (int)pop_long_range(max, min);
|
||||
}
|
||||
|
||||
Ref<Cell> Stack::pop_cell() {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_cell();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a cell"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Cell> Stack::pop_maybe_cell() {
|
||||
check_underflow(1);
|
||||
auto tmp = pop();
|
||||
if (tmp.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto res = std::move(tmp).as_cell();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a cell"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<CellBuilder> Stack::pop_builder() {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_builder();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a cell builder"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<CellSlice> Stack::pop_cellslice() {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_slice();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a cell slice"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string Stack::pop_string() {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_string_ref();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a string"};
|
||||
}
|
||||
return *res;
|
||||
}
|
||||
|
||||
std::string Stack::pop_bytes() {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_bytes_ref();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a bytes chunk"};
|
||||
}
|
||||
return *res;
|
||||
}
|
||||
|
||||
Ref<Continuation> Stack::pop_cont() {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_cont();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a continuation"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Box> Stack::pop_box() {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_box();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a box"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Tuple> Stack::pop_tuple() {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_tuple();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a tuple"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Tuple> Stack::pop_tuple_range(unsigned max_len, unsigned min_len) {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_tuple();
|
||||
if (res.is_null() || res->size() > max_len || res->size() < min_len) {
|
||||
throw VmError{Excno::type_chk, "not a tuple of valid size"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Tuple> Stack::pop_maybe_tuple() {
|
||||
check_underflow(1);
|
||||
auto val = pop();
|
||||
if (val.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto res = std::move(val).as_tuple();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not a tuple"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Tuple> Stack::pop_maybe_tuple_range(unsigned max_len) {
|
||||
check_underflow(1);
|
||||
auto val = pop();
|
||||
if (val.empty()) {
|
||||
return {};
|
||||
}
|
||||
auto res = std::move(val).as_tuple();
|
||||
if (res.is_null() || res->size() > max_len) {
|
||||
throw VmError{Excno::type_chk, "not a tuple of valid size"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Ref<Atom> Stack::pop_atom() {
|
||||
check_underflow(1);
|
||||
auto res = pop().as_atom();
|
||||
if (res.is_null()) {
|
||||
throw VmError{Excno::type_chk, "not an atom"};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void Stack::push_null() {
|
||||
push({});
|
||||
}
|
||||
|
||||
void Stack::push_int(td::RefInt256 val) {
|
||||
if (!val->signed_fits_bits(257)) {
|
||||
throw VmError{Excno::int_ov};
|
||||
}
|
||||
push(std::move(val));
|
||||
}
|
||||
|
||||
void Stack::push_int_quiet(td::RefInt256 val, bool quiet) {
|
||||
if (!val->signed_fits_bits(257)) {
|
||||
if (!quiet) {
|
||||
throw VmError{Excno::int_ov};
|
||||
} else if (val->is_valid()) {
|
||||
push(td::RefInt256{true});
|
||||
return;
|
||||
}
|
||||
}
|
||||
push(std::move(val));
|
||||
}
|
||||
|
||||
void Stack::push_string(std::string str) {
|
||||
push(std::move(str));
|
||||
}
|
||||
|
||||
void Stack::push_string(td::Slice slice) {
|
||||
push(slice.str());
|
||||
}
|
||||
|
||||
void Stack::push_bytes(std::string str) {
|
||||
push(std::move(str), true);
|
||||
}
|
||||
|
||||
void Stack::push_bytes(td::Slice slice) {
|
||||
push(slice.str(), true);
|
||||
}
|
||||
|
||||
void Stack::push_cell(Ref<Cell> cell) {
|
||||
push(std::move(cell));
|
||||
}
|
||||
|
||||
void Stack::push_maybe_cell(Ref<Cell> cell) {
|
||||
push_maybe(std::move(cell));
|
||||
}
|
||||
|
||||
void Stack::push_builder(Ref<CellBuilder> cb) {
|
||||
push(std::move(cb));
|
||||
}
|
||||
|
||||
void Stack::push_smallint(long long val) {
|
||||
push(td::RefInt256{true, val});
|
||||
}
|
||||
|
||||
void Stack::push_bool(bool val) {
|
||||
push_smallint(val ? -1 : 0);
|
||||
}
|
||||
|
||||
void Stack::push_cont(Ref<Continuation> cont) {
|
||||
push(std::move(cont));
|
||||
}
|
||||
|
||||
void Stack::push_box(Ref<Box> box) {
|
||||
push(std::move(box));
|
||||
}
|
||||
|
||||
void Stack::push_tuple(Ref<Tuple> tuple) {
|
||||
push(std::move(tuple));
|
||||
}
|
||||
|
||||
void Stack::push_maybe_tuple(Ref<Tuple> tuple) {
|
||||
if (tuple.not_null()) {
|
||||
push(std::move(tuple));
|
||||
} else {
|
||||
push_null();
|
||||
}
|
||||
}
|
||||
|
||||
void Stack::push_tuple(const std::vector<StackEntry>& components) {
|
||||
push(components);
|
||||
}
|
||||
|
||||
void Stack::push_tuple(std::vector<StackEntry>&& components) {
|
||||
push(std::move(components));
|
||||
}
|
||||
|
||||
void Stack::push_atom(Ref<Atom> atom) {
|
||||
push(std::move(atom));
|
||||
}
|
||||
|
||||
Ref<Stack> Stack::split_top(unsigned top_cnt, unsigned drop_cnt) {
|
||||
unsigned n = depth();
|
||||
if (top_cnt > n || drop_cnt > n - top_cnt) {
|
||||
return Ref<Stack>{};
|
||||
}
|
||||
Ref<Stack> new_stk = Ref<Stack>{true};
|
||||
if (top_cnt) {
|
||||
new_stk.unique_write().move_from_stack(*this, top_cnt);
|
||||
}
|
||||
if (drop_cnt) {
|
||||
pop_many(drop_cnt);
|
||||
}
|
||||
return new_stk;
|
||||
}
|
||||
|
||||
void Stack::dump(std::ostream& os, bool cr) const {
|
||||
os << " [ ";
|
||||
for (const auto& x : stack) {
|
||||
os << x.to_string() << ' ';
|
||||
}
|
||||
os << "] ";
|
||||
if (cr) {
|
||||
os << std::endl;
|
||||
}
|
||||
}
|
||||
void Stack::push_cellslice(Ref<CellSlice> cs) {
|
||||
push(std::move(cs));
|
||||
}
|
||||
|
||||
void Stack::push_maybe_cellslice(Ref<CellSlice> cs) {
|
||||
push_maybe(std::move(cs));
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
496
crypto/vm/stack.hpp
Normal file
496
crypto/vm/stack.hpp
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include "common/refcnt.hpp"
|
||||
#include "common/bigint.hpp"
|
||||
#include "common/refint.h"
|
||||
#include "common/bitstring.h"
|
||||
#include "vm/cells.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "vm/excno.hpp"
|
||||
|
||||
namespace td {
|
||||
extern template class td::Cnt<std::string>;
|
||||
extern template class td::Ref<td::Cnt<std::string>>;
|
||||
} // namespace td
|
||||
|
||||
namespace vm {
|
||||
|
||||
using td::Cnt;
|
||||
using td::Ref;
|
||||
using td::RefAny;
|
||||
|
||||
const char* get_exception_msg(Excno exc_no);
|
||||
std::string str_to_hex(std::string data, std::string prefix = "");
|
||||
|
||||
class StackEntry;
|
||||
class Stack;
|
||||
class Continuation;
|
||||
class Box;
|
||||
class Atom;
|
||||
|
||||
using Tuple = td::Cnt<std::vector<StackEntry>>;
|
||||
|
||||
struct from_object_t {};
|
||||
constexpr from_object_t from_object{};
|
||||
|
||||
class StackEntry {
|
||||
public:
|
||||
enum Type {
|
||||
t_null,
|
||||
t_int,
|
||||
t_cell,
|
||||
t_builder,
|
||||
t_slice,
|
||||
t_vmcont,
|
||||
t_tuple,
|
||||
t_stack,
|
||||
t_string,
|
||||
t_bytes,
|
||||
t_bitstring,
|
||||
t_box,
|
||||
t_atom,
|
||||
t_object
|
||||
};
|
||||
|
||||
private:
|
||||
RefAny ref;
|
||||
Type tp;
|
||||
|
||||
public:
|
||||
StackEntry() : ref(), tp(t_null) {
|
||||
}
|
||||
~StackEntry() {
|
||||
}
|
||||
StackEntry(Ref<Cell> cell_ref) : ref(std::move(cell_ref)), tp(t_cell) {
|
||||
}
|
||||
StackEntry(Ref<CellBuilder> cb_ref) : ref(std::move(cb_ref)), tp(t_builder) {
|
||||
}
|
||||
StackEntry(Ref<CellSlice> cs_ref) : ref(std::move(cs_ref)), tp(t_slice) {
|
||||
}
|
||||
StackEntry(td::RefInt256 int_ref) : ref(std::move(int_ref)), tp(t_int) {
|
||||
}
|
||||
StackEntry(std::string str, bool bytes = false) : ref(), tp(bytes ? t_bytes : t_string) {
|
||||
ref = Ref<Cnt<std::string>>{true, std::move(str)};
|
||||
}
|
||||
StackEntry(Ref<Stack> stack_ref);
|
||||
StackEntry(Ref<Continuation> cont_ref);
|
||||
StackEntry(Ref<Box> box_ref);
|
||||
StackEntry(Ref<Tuple> tuple_ref);
|
||||
StackEntry(const std::vector<StackEntry>& tuple_components);
|
||||
StackEntry(std::vector<StackEntry>&& tuple_components);
|
||||
StackEntry(Ref<Atom> atom_ref);
|
||||
StackEntry(const StackEntry& se) : ref(se.ref), tp(se.tp) {
|
||||
}
|
||||
StackEntry(StackEntry&& se) noexcept : ref(std::move(se.ref)), tp(se.tp) {
|
||||
se.tp = t_null;
|
||||
}
|
||||
template <class T>
|
||||
StackEntry(from_object_t, Ref<T> obj_ref) : ref(std::move(obj_ref)), tp(t_object) {
|
||||
}
|
||||
StackEntry& operator=(const StackEntry& se) {
|
||||
ref = se.ref;
|
||||
tp = se.tp;
|
||||
return *this;
|
||||
}
|
||||
StackEntry& operator=(StackEntry&& se) {
|
||||
ref = std::move(se.ref);
|
||||
tp = se.tp;
|
||||
se.tp = t_null;
|
||||
return *this;
|
||||
}
|
||||
StackEntry& clear() {
|
||||
ref.clear();
|
||||
tp = t_null;
|
||||
return *this;
|
||||
}
|
||||
bool empty() const {
|
||||
return tp == t_null;
|
||||
}
|
||||
bool is_tuple() const {
|
||||
return tp == t_tuple;
|
||||
}
|
||||
bool is_atom() const {
|
||||
return tp == t_atom;
|
||||
}
|
||||
bool is(int wanted) const {
|
||||
return tp == wanted;
|
||||
}
|
||||
void swap(StackEntry& se) {
|
||||
ref.swap(se.ref);
|
||||
std::swap(tp, se.tp);
|
||||
}
|
||||
bool operator==(const StackEntry& other) const {
|
||||
return tp == other.tp && ref == other.ref;
|
||||
}
|
||||
bool operator!=(const StackEntry& other) const {
|
||||
return !(tp == other.tp && ref == other.ref);
|
||||
}
|
||||
Type type() const {
|
||||
return tp;
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T, Type tag>
|
||||
Ref<T> dynamic_as() const & {
|
||||
return tp == tag ? static_cast<Ref<T>>(ref) : td::Ref<T>{};
|
||||
}
|
||||
template <typename T, Type tag>
|
||||
Ref<T> dynamic_as() && {
|
||||
return tp == tag ? static_cast<Ref<T>>(std::move(ref)) : td::Ref<T>{};
|
||||
}
|
||||
template <typename T, Type tag>
|
||||
Ref<T> dynamic_move_as() & {
|
||||
return tp == tag ? static_cast<Ref<T>>(std::move(ref)) : td::Ref<T>{};
|
||||
}
|
||||
template <typename T, Type tag>
|
||||
Ref<T> as() const & {
|
||||
return tp == tag ? Ref<T>{td::static_cast_ref(), ref} : td::Ref<T>{};
|
||||
}
|
||||
template <typename T, Type tag>
|
||||
Ref<T> as() && {
|
||||
return tp == tag ? Ref<T>{td::static_cast_ref(), std::move(ref)} : td::Ref<T>{};
|
||||
}
|
||||
template <typename T, Type tag>
|
||||
Ref<T> move_as() & {
|
||||
return tp == tag ? Ref<T>{td::static_cast_ref(), std::move(ref)} : td::Ref<T>{};
|
||||
}
|
||||
|
||||
public:
|
||||
template <typename T>
|
||||
static StackEntry maybe(Ref<T> ref) {
|
||||
if (ref.is_null()) {
|
||||
return {};
|
||||
} else {
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
td::RefInt256 as_int() const & {
|
||||
return as<td::CntInt256, t_int>();
|
||||
}
|
||||
td::RefInt256 as_int() && {
|
||||
return move_as<td::CntInt256, t_int>();
|
||||
}
|
||||
Ref<Cell> as_cell() const & {
|
||||
return as<Cell, t_cell>();
|
||||
}
|
||||
Ref<Cell> as_cell() && {
|
||||
return move_as<Cell, t_cell>();
|
||||
}
|
||||
Ref<CellBuilder> as_builder() const & {
|
||||
return as<CellBuilder, t_builder>();
|
||||
}
|
||||
Ref<CellBuilder> as_builder() && {
|
||||
return move_as<CellBuilder, t_builder>();
|
||||
}
|
||||
Ref<CellSlice> as_slice() const & {
|
||||
return as<CellSlice, t_slice>();
|
||||
}
|
||||
Ref<CellSlice> as_slice() && {
|
||||
return move_as<CellSlice, t_slice>();
|
||||
}
|
||||
Ref<Continuation> as_cont() const &;
|
||||
Ref<Continuation> as_cont() &&;
|
||||
Ref<Cnt<std::string>> as_string_ref() const {
|
||||
return as<Cnt<std::string>, t_string>();
|
||||
}
|
||||
Ref<Cnt<std::string>> as_bytes_ref() const {
|
||||
return as<Cnt<std::string>, t_bytes>();
|
||||
}
|
||||
std::string as_string() const {
|
||||
//assert(!as_string_ref().is_null());
|
||||
return tp == t_string ? *as_string_ref() : "";
|
||||
}
|
||||
std::string as_bytes() const {
|
||||
return tp == t_bytes ? *as_bytes_ref() : "";
|
||||
}
|
||||
Ref<Box> as_box() const &;
|
||||
Ref<Box> as_box() &&;
|
||||
Ref<Tuple> as_tuple() const &;
|
||||
Ref<Tuple> as_tuple() &&;
|
||||
Ref<Tuple> as_tuple_range(unsigned max_len = 255, unsigned min_len = 0) const &;
|
||||
Ref<Tuple> as_tuple_range(unsigned max_len = 255, unsigned min_len = 0) &&;
|
||||
Ref<Atom> as_atom() const &;
|
||||
Ref<Atom> as_atom() &&;
|
||||
template <class T>
|
||||
Ref<T> as_object() const & {
|
||||
return dynamic_as<T, t_object>();
|
||||
}
|
||||
template <class T>
|
||||
Ref<T> as_object() && {
|
||||
return dynamic_move_as<T, t_object>();
|
||||
}
|
||||
void dump(std::ostream& os) const;
|
||||
void print_list(std::ostream& os) const;
|
||||
void print_list_tail(std::ostream& os) const;
|
||||
std::string to_string() const;
|
||||
};
|
||||
|
||||
inline void swap(StackEntry& se1, StackEntry& se2) {
|
||||
se1.swap(se2);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
Ref<Tuple> make_tuple_ref(Args&&... args) {
|
||||
return td::make_cnt_ref<std::vector<vm::StackEntry>>(std::vector<vm::StackEntry>{std::forward<Args>(args)...});
|
||||
}
|
||||
|
||||
const StackEntry& tuple_index(const Tuple& tup, unsigned idx);
|
||||
StackEntry tuple_extend_index(const Ref<Tuple>& tup, unsigned idx);
|
||||
unsigned tuple_extend_set_index(Ref<Tuple>& tup, unsigned idx, StackEntry&& value, bool force = false);
|
||||
|
||||
class Stack : public td::CntObject {
|
||||
std::vector<StackEntry> stack;
|
||||
|
||||
public:
|
||||
Stack() {
|
||||
}
|
||||
~Stack() override = default;
|
||||
Stack(const std::vector<StackEntry>& _stack) : stack(_stack) {
|
||||
}
|
||||
Stack(std::vector<StackEntry>&& _stack) : stack(std::move(_stack)) {
|
||||
}
|
||||
Stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top);
|
||||
Stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top);
|
||||
td::CntObject* make_copy() const override {
|
||||
std::cerr << "copy stack at " << (const void*)this << " (" << depth() << " entries)\n";
|
||||
return new Stack{stack};
|
||||
}
|
||||
void push_from_stack(const Stack& old_stack, unsigned copy_elem, unsigned skip_top = 0);
|
||||
void push_from_stack(Stack&& old_stack, unsigned copy_elem, unsigned skip_top = 0);
|
||||
void move_from_stack(Stack& old_stack, unsigned copy_elem);
|
||||
Ref<Stack> split_top(unsigned top_cnt, unsigned drop_cnt = 0);
|
||||
|
||||
StackEntry& push() {
|
||||
stack.emplace_back();
|
||||
return stack.back();
|
||||
}
|
||||
template <typename... Args>
|
||||
StackEntry& push(Args&&... args) {
|
||||
stack.emplace_back(args...);
|
||||
return stack.back();
|
||||
}
|
||||
StackEntry& push(const StackEntry& se) {
|
||||
stack.push_back(se);
|
||||
return stack.back();
|
||||
}
|
||||
StackEntry& push(StackEntry&& se) {
|
||||
stack.emplace_back(std::move(se));
|
||||
return stack.back();
|
||||
}
|
||||
void pop(StackEntry& se) {
|
||||
stack.back().swap(se);
|
||||
stack.pop_back();
|
||||
}
|
||||
StackEntry pop() {
|
||||
StackEntry res = std::move(stack.back());
|
||||
stack.pop_back();
|
||||
return res;
|
||||
}
|
||||
StackEntry pop_chk() {
|
||||
check_underflow(1);
|
||||
return pop();
|
||||
}
|
||||
void pop_many(int count) {
|
||||
stack.resize(stack.size() - count);
|
||||
}
|
||||
void drop_bottom(int count) {
|
||||
std::move(stack.cbegin() + count, stack.cend(), stack.begin());
|
||||
pop_many(count);
|
||||
}
|
||||
StackEntry& operator[](int idx) { // NB: we sometimes use idx=-1
|
||||
return stack[stack.size() - idx - 1];
|
||||
}
|
||||
const StackEntry& operator[](int idx) const {
|
||||
return stack[stack.size() - idx - 1];
|
||||
}
|
||||
StackEntry& at(int idx) {
|
||||
return stack.at(stack.size() - idx - 1);
|
||||
}
|
||||
const StackEntry& at(int idx) const {
|
||||
return stack.at(stack.size() - idx - 1);
|
||||
}
|
||||
StackEntry fetch(int idx) const {
|
||||
return stack[stack.size() - idx - 1];
|
||||
}
|
||||
StackEntry& tos() {
|
||||
return stack.back();
|
||||
}
|
||||
const StackEntry& tos() const {
|
||||
return stack.back();
|
||||
}
|
||||
bool is_empty() const {
|
||||
return stack.empty();
|
||||
}
|
||||
int depth() const {
|
||||
return (int)stack.size();
|
||||
}
|
||||
std::vector<StackEntry>::iterator top() {
|
||||
return stack.end();
|
||||
}
|
||||
std::vector<StackEntry>::const_iterator top() const {
|
||||
return stack.cend();
|
||||
}
|
||||
std::vector<StackEntry>::iterator from_top(int offs) {
|
||||
return stack.end() - offs;
|
||||
}
|
||||
std::vector<StackEntry>::const_iterator from_top(int offs) const {
|
||||
return stack.cend() - offs;
|
||||
}
|
||||
bool at_least(int req) const {
|
||||
return depth() >= req;
|
||||
}
|
||||
template <typename... Args>
|
||||
bool at_least(int req, Args... args) const {
|
||||
return at_least(req) && at_least(args...);
|
||||
}
|
||||
bool more_than(int req) const {
|
||||
return depth() > req;
|
||||
}
|
||||
template <typename... Args>
|
||||
bool more_than(int req, Args... args) const {
|
||||
return more_than(req) && more_than(args...);
|
||||
}
|
||||
void clear() {
|
||||
stack.clear();
|
||||
}
|
||||
Stack& set_contents(const Stack& other_stack) {
|
||||
stack = other_stack.stack;
|
||||
return *this;
|
||||
}
|
||||
Stack& set_contents(Stack&& other_stack) {
|
||||
stack = std::move(other_stack.stack);
|
||||
return *this;
|
||||
}
|
||||
Stack& set_contents(Ref<Stack> ref) {
|
||||
if (ref.is_null()) {
|
||||
clear();
|
||||
} else if (ref->is_unique()) {
|
||||
set_contents(std::move(ref.unique_write()));
|
||||
} else {
|
||||
set_contents(*ref);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template <typename... Args>
|
||||
const Stack& check_underflow(Args... args) const {
|
||||
if (!at_least(args...)) {
|
||||
throw VmError{Excno::stk_und};
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template <typename... Args>
|
||||
Stack& check_underflow(Args... args) {
|
||||
if (!at_least(args...)) {
|
||||
throw VmError{Excno::stk_und};
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template <typename... Args>
|
||||
const Stack& check_underflow_p(Args... args) const {
|
||||
if (!more_than(args...)) {
|
||||
throw VmError{Excno::stk_und};
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
template <typename... Args>
|
||||
Stack& check_underflow_p(Args... args) {
|
||||
if (!more_than(args...)) {
|
||||
throw VmError{Excno::stk_und};
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
Stack& reserve(int cnt) {
|
||||
stack.reserve(cnt);
|
||||
return *this;
|
||||
}
|
||||
void pop_null();
|
||||
td::RefInt256 pop_int();
|
||||
td::RefInt256 pop_int_finite();
|
||||
bool pop_bool();
|
||||
long long pop_long();
|
||||
long long pop_long_range(long long max, long long min = 0);
|
||||
int pop_smallint_range(int max, int min = 0);
|
||||
Ref<Cell> pop_cell();
|
||||
Ref<Cell> pop_maybe_cell();
|
||||
Ref<CellBuilder> pop_builder();
|
||||
Ref<CellSlice> pop_cellslice();
|
||||
Ref<Continuation> pop_cont();
|
||||
Ref<Box> pop_box();
|
||||
Ref<Tuple> pop_tuple();
|
||||
Ref<Tuple> pop_tuple_range(unsigned max_len = 255, unsigned min_len = 0);
|
||||
Ref<Tuple> pop_maybe_tuple();
|
||||
Ref<Tuple> pop_maybe_tuple_range(unsigned max_len = 255);
|
||||
Ref<Atom> pop_atom();
|
||||
std::string pop_string();
|
||||
std::string pop_bytes();
|
||||
void push_null();
|
||||
void push_int(td::RefInt256 val);
|
||||
void push_int_quiet(td::RefInt256 val, bool quiet = true);
|
||||
void push_smallint(long long val);
|
||||
void push_bool(bool val);
|
||||
void push_string(std::string str);
|
||||
void push_string(td::Slice slice);
|
||||
void push_bytes(std::string str);
|
||||
void push_bytes(td::Slice slice);
|
||||
void push_cell(Ref<Cell> cell);
|
||||
void push_maybe_cell(Ref<Cell> cell);
|
||||
void push_maybe_cellslice(Ref<CellSlice> cs);
|
||||
void push_builder(Ref<CellBuilder> cb);
|
||||
void push_cellslice(Ref<CellSlice> cs);
|
||||
void push_cont(Ref<Continuation> cont);
|
||||
void push_box(Ref<Box> box);
|
||||
void push_tuple(Ref<Tuple> tuple);
|
||||
void push_tuple(const std::vector<StackEntry>& components);
|
||||
void push_tuple(std::vector<StackEntry>&& components);
|
||||
void push_maybe_tuple(Ref<Tuple> tuple);
|
||||
void push_atom(Ref<Atom> atom);
|
||||
template <typename T>
|
||||
void push_object(Ref<T> obj) {
|
||||
push({vm::from_object, std::move(obj)});
|
||||
}
|
||||
template <typename T, typename... Args>
|
||||
void push_make_object(Args&&... args) {
|
||||
push_object<T>(td::make_ref<T>(std::forward<Args>(args)...));
|
||||
}
|
||||
template <typename T>
|
||||
void push_maybe(Ref<T> val) {
|
||||
if (val.is_null()) {
|
||||
push({});
|
||||
} else {
|
||||
push(std::move(val));
|
||||
}
|
||||
}
|
||||
void dump(std::ostream& os, bool cr = true) const;
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
|
||||
namespace td {
|
||||
extern template class td::Cnt<std::vector<vm::StackEntry>>;
|
||||
extern template class td::Ref<td::Cnt<std::vector<vm::StackEntry>>>;
|
||||
} // namespace td
|
||||
576
crypto/vm/stackops.cpp
Normal file
576
crypto/vm/stackops.cpp
Normal file
|
|
@ -0,0 +1,576 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/log.h"
|
||||
#include "vm/stackops.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
|
||||
namespace vm {
|
||||
|
||||
int exec_nop(VmState* st) {
|
||||
VM_LOG(st) << "execute NOP\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// basic stack manipulation primitives
|
||||
|
||||
int exec_swap(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute SWAP\n";
|
||||
stack.check_underflow(2);
|
||||
swap(stack[0], stack[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xchg0(VmState* st, unsigned args) {
|
||||
int x = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XCHG s" << x;
|
||||
stack.check_underflow_p(x);
|
||||
swap(stack[0], stack[x]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xchg0_l(VmState* st, unsigned args) {
|
||||
int x = args & 255;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XCHG s" << x;
|
||||
stack.check_underflow_p(x);
|
||||
swap(stack[0], stack[x]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xchg(VmState* st, unsigned args) {
|
||||
int x = (args >> 4) & 15, y = args & 15;
|
||||
if (!x || x >= y) {
|
||||
throw VmError{Excno::inv_opcode, "invalid XCHG arguments"};
|
||||
}
|
||||
VM_LOG(st) << "execute XCHG s" << x << ",s" << y;
|
||||
Stack& stack = st->get_stack();
|
||||
stack.check_underflow_p(y);
|
||||
swap(stack[x], stack[y]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_xchg(CellSlice&, unsigned args) {
|
||||
int x = (args >> 4) & 15, y = args & 15;
|
||||
if (!x || x >= y) {
|
||||
return "";
|
||||
}
|
||||
std::ostringstream os{"XCHG s"};
|
||||
os << x << ",s" << y;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int exec_xchg1(VmState* st, unsigned args) {
|
||||
int x = args & 15;
|
||||
assert(x >= 2);
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XCHG s1,s" << x;
|
||||
stack.check_underflow_p(x);
|
||||
swap(stack[1], stack[x]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_dup(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DUP\n";
|
||||
stack.check_underflow(1);
|
||||
stack.push(stack.fetch(0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_over(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute OVER\n";
|
||||
stack.check_underflow(2);
|
||||
stack.push(stack.fetch(1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_push(VmState* st, unsigned args) {
|
||||
int x = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSH s" << x;
|
||||
stack.check_underflow_p(x);
|
||||
stack.push(stack.fetch(x));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_push_l(VmState* st, unsigned args) {
|
||||
int x = args & 255;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSH s" << x;
|
||||
stack.check_underflow_p(x);
|
||||
stack.push(stack.fetch(x));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_drop(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DROP\n";
|
||||
stack.check_underflow(1);
|
||||
stack.pop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_nip(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute NIP\n";
|
||||
stack.check_underflow(2);
|
||||
stack.pop(stack[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_pop(VmState* st, unsigned args) {
|
||||
int x = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute POP s" << x;
|
||||
stack.check_underflow_p(x);
|
||||
stack.pop(stack[x]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_pop_l(VmState* st, unsigned args) {
|
||||
int x = args & 255;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute POP s" << x;
|
||||
stack.check_underflow_p(x);
|
||||
stack.pop(stack[x]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// compound stack manipulation primitives
|
||||
|
||||
int exec_xchg2(VmState* st, unsigned args) {
|
||||
int x = (args >> 4) & 15, y = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XCHG2 s" << x << ",s" << y;
|
||||
stack.check_underflow_p(x, y, 1);
|
||||
swap(stack[1], stack[x]);
|
||||
swap(stack[0], stack[y]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xcpu(VmState* st, unsigned args) {
|
||||
int x = (args >> 4) & 15, y = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XCPU s" << x << ",s" << y;
|
||||
stack.check_underflow_p(x, y);
|
||||
swap(stack[0], stack[x]);
|
||||
stack.push(stack.fetch(y));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_puxc(VmState* st, unsigned args) {
|
||||
int x = (args >> 4) & 15, y = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUXC s" << x << ",s" << y - 1;
|
||||
stack.check_underflow_p(x).check_underflow(y);
|
||||
stack.push(stack.fetch(x));
|
||||
swap(stack[0], stack[1]);
|
||||
swap(stack[0], stack[y]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_push2(VmState* st, unsigned args) {
|
||||
int x = (args >> 4) & 15, y = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSH2 s" << x << ",s" << y;
|
||||
stack.check_underflow_p(x, y);
|
||||
stack.push(stack.fetch(x));
|
||||
stack.push(stack.fetch(y + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xchg3(VmState* st, unsigned args) {
|
||||
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XCHG3 s" << x << ",s" << y << ",s" << z;
|
||||
stack.check_underflow_p(x, y, z, 2);
|
||||
swap(stack[2], stack[x]);
|
||||
swap(stack[1], stack[y]);
|
||||
swap(stack[0], stack[z]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xc2pu(VmState* st, unsigned args) {
|
||||
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XC2PU s" << x << ",s" << y << ",s" << z;
|
||||
stack.check_underflow_p(x, y, z, 1);
|
||||
swap(stack[1], stack[x]);
|
||||
swap(stack[0], stack[y]);
|
||||
stack.push(stack.fetch(z));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xcpuxc(VmState* st, unsigned args) {
|
||||
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XCPUXC s" << x << ",s" << y << ",s" << z - 1;
|
||||
stack.check_underflow_p(x, y, 1).check_underflow(z);
|
||||
swap(stack[1], stack[x]);
|
||||
stack.push(stack.fetch(y));
|
||||
swap(stack[0], stack[1]);
|
||||
swap(stack[0], stack[z]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xcpu2(VmState* st, unsigned args) {
|
||||
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XCPU2 s" << x << ",s" << y << ",s" << z;
|
||||
stack.check_underflow_p(x, y, z);
|
||||
swap(stack[0], stack[x]);
|
||||
stack.push(stack.fetch(y));
|
||||
stack.push(stack.fetch(z + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_puxc2(VmState* st, unsigned args) {
|
||||
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUXC2 s" << x << ",s" << y - 1 << ",s" << z - 1;
|
||||
stack.check_underflow_p(x, 1).check_underflow(y, z);
|
||||
stack.push(stack.fetch(x));
|
||||
swap(stack[2], stack[0]);
|
||||
swap(stack[1], stack[y]);
|
||||
swap(stack[0], stack[z]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_puxcpu(VmState* st, unsigned args) {
|
||||
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUXCPU s" << x << ",s" << y - 1 << ",s" << z - 1;
|
||||
stack.check_underflow_p(x).check_underflow(y, z);
|
||||
stack.push(stack.fetch(x));
|
||||
swap(stack[0], stack[1]);
|
||||
swap(stack[0], stack[y]);
|
||||
stack.push(stack.fetch(z));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_pu2xc(VmState* st, unsigned args) {
|
||||
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PU2XC s" << x << ",s" << y - 1 << ",s" << z - 2;
|
||||
stack.check_underflow_p(x).check_underflow(y, z - 1);
|
||||
stack.push(stack.fetch(x));
|
||||
swap(stack[1], stack[0]);
|
||||
stack.push(stack.fetch(y));
|
||||
swap(stack[1], stack[0]);
|
||||
swap(stack[0], stack[z]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_push3(VmState* st, unsigned args) {
|
||||
int x = (args >> 8) & 15, y = (args >> 4) & 15, z = args & 15;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PUSH3 s" << x << ",s" << y << ",s" << z;
|
||||
stack.check_underflow_p(x, y, z);
|
||||
stack.push(stack.fetch(x));
|
||||
stack.push(stack.fetch(y + 1));
|
||||
stack.push(stack.fetch(z + 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// exotic stack manipulation primitives
|
||||
|
||||
int exec_blkswap(VmState* st, unsigned args) {
|
||||
int x = ((args >> 4) & 15) + 1, y = (args & 15) + 1;
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute BLKSWAP " << x << ',' << y;
|
||||
stack.check_underflow(x + y);
|
||||
std::reverse(stack.from_top(x + y), stack.from_top(y));
|
||||
std::reverse(stack.from_top(y), stack.top());
|
||||
std::reverse(stack.from_top(x + y), stack.top());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_rot(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ROT\n";
|
||||
stack.check_underflow(3);
|
||||
swap(stack[1], stack[2]);
|
||||
swap(stack[0], stack[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_rotrev(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ROTREV\n";
|
||||
stack.check_underflow(3);
|
||||
swap(stack[0], stack[1]);
|
||||
swap(stack[1], stack[2]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_2swap(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute 2SWAP\n";
|
||||
stack.check_underflow(4);
|
||||
swap(stack[1], stack[3]);
|
||||
swap(stack[0], stack[2]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_2drop(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute 2DROP\n";
|
||||
stack.check_underflow(2);
|
||||
stack.pop();
|
||||
stack.pop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_2dup(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute 2DUP\n";
|
||||
stack.check_underflow(2);
|
||||
stack.push(stack.fetch(1));
|
||||
stack.push(stack.fetch(1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_2over(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute 2OVER\n";
|
||||
stack.check_underflow(4);
|
||||
stack.push(stack.fetch(3));
|
||||
stack.push(stack.fetch(3));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_reverse(VmState* st, unsigned args) {
|
||||
int x = ((args >> 4) & 15) + 2, y = (args & 15);
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute REVERSE " << x << ',' << y;
|
||||
stack.check_underflow(x + y);
|
||||
std::reverse(stack.from_top(x + y), stack.from_top(y));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_blkdrop(VmState* st, unsigned args) {
|
||||
int x = (args & 15);
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute BLKDROP " << x;
|
||||
stack.check_underflow(x);
|
||||
stack.pop_many(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_blkpush(VmState* st, unsigned args) {
|
||||
int x = ((args >> 4) & 15), y = (args & 15);
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute BLKPUSH " << x << ',' << y;
|
||||
stack.check_underflow_p(y);
|
||||
while (--x >= 0) {
|
||||
stack.push(stack.fetch(y));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_pick(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute PICK\n";
|
||||
stack.check_underflow(1);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow_p(x);
|
||||
stack.push(stack.fetch(x));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_roll(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ROLL\n";
|
||||
stack.check_underflow(1);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow_p(x);
|
||||
while (--x >= 0) {
|
||||
swap(stack[x], stack[x + 1]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_rollrev(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ROLLREV\n";
|
||||
stack.check_underflow(1);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow_p(x);
|
||||
for (int i = 0; i < x; i++) {
|
||||
swap(stack[i], stack[i + 1]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_blkswap_x(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute BLKSWX\n";
|
||||
stack.check_underflow(2);
|
||||
int y = stack.pop_smallint_range(255);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow(x + y);
|
||||
if (x > 0 && y > 0) {
|
||||
std::reverse(stack.from_top(x + y), stack.from_top(y));
|
||||
std::reverse(stack.from_top(y), stack.top());
|
||||
std::reverse(stack.from_top(x + y), stack.top());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_reverse_x(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute REVX\n";
|
||||
stack.check_underflow(2);
|
||||
int y = stack.pop_smallint_range(255);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow(x + y);
|
||||
std::reverse(stack.from_top(x + y), stack.from_top(y));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_drop_x(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DROPX\n";
|
||||
stack.check_underflow(1);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow(x);
|
||||
stack.pop_many(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuck(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute TUCK\n";
|
||||
stack.check_underflow(2);
|
||||
swap(stack[0], stack[1]);
|
||||
stack.push(stack.fetch(1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_xchg_x(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute XCHGX\n";
|
||||
stack.check_underflow(1);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow_p(x);
|
||||
swap(stack[0], stack[x]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_depth(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute DEPTH\n";
|
||||
stack.push_smallint(stack.depth());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_chkdepth(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute CHKDEPTH\n";
|
||||
stack.check_underflow(1);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_onlytop_x(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ONLYTOPX\n";
|
||||
stack.check_underflow(1);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow(x);
|
||||
int n = stack.depth(), d = n - x;
|
||||
if (d > 0) {
|
||||
for (int i = n - 1; i >= d; i--) {
|
||||
stack[i] = std::move(stack[i - d]);
|
||||
}
|
||||
}
|
||||
stack.pop_many(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_only_x(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ONLYX\n";
|
||||
stack.check_underflow(1);
|
||||
int x = stack.pop_smallint_range(255);
|
||||
stack.check_underflow(x);
|
||||
stack.pop_many(stack.depth() - x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_stack_ops(OpcodeTable& cp0) {
|
||||
cp0.insert(OpcodeInstr::mksimple(0x00, 8, "NOP", exec_nop))
|
||||
.insert(OpcodeInstr::mksimple(0x01, 8, "SWAP", exec_swap))
|
||||
.insert(OpcodeInstr::mkfixedrange(0x02, 0x10, 8, 4, instr::dump_1sr("XCHG "), exec_xchg0))
|
||||
.insert(OpcodeInstr::mkfixed(0x10, 8, 8, dump_xchg, exec_xchg))
|
||||
.insert(OpcodeInstr::mkfixed(0x11, 8, 8, instr::dump_1sr_l("XCHG "), exec_xchg0_l))
|
||||
.insert(OpcodeInstr::mkfixedrange(0x12, 0x20, 8, 4, instr::dump_1sr("XCHG s1,"), exec_xchg1))
|
||||
.insert(OpcodeInstr::mksimple(0x20, 8, "DUP", exec_dup))
|
||||
.insert(OpcodeInstr::mksimple(0x21, 8, "OVER", exec_over))
|
||||
.insert(OpcodeInstr::mkfixedrange(0x22, 0x30, 8, 4, instr::dump_1sr("PUSH "), exec_push))
|
||||
.insert(OpcodeInstr::mksimple(0x30, 8, "DROP", exec_drop))
|
||||
.insert(OpcodeInstr::mksimple(0x31, 8, "NIP", exec_nip))
|
||||
.insert(OpcodeInstr::mkfixedrange(0x32, 0x40, 8, 4, instr::dump_1sr("POP "), exec_pop))
|
||||
.insert(OpcodeInstr::mkfixed(0x4, 4, 12, instr::dump_3sr("XCHG3 "), exec_xchg3))
|
||||
.insert(OpcodeInstr::mkfixed(0x50, 8, 8, instr::dump_2sr("XCHG2 "), exec_xchg2))
|
||||
.insert(OpcodeInstr::mkfixed(0x51, 8, 8, instr::dump_2sr("XCPU "), exec_xcpu))
|
||||
.insert(OpcodeInstr::mkfixed(0x52, 8, 8, instr::dump_2sr_adj(1, "PUXC "), exec_puxc))
|
||||
.insert(OpcodeInstr::mkfixed(0x53, 8, 8, instr::dump_2sr("PUSH2 "), exec_push2))
|
||||
.insert(OpcodeInstr::mkfixed(0x540, 12, 12, instr::dump_3sr("XCHG3 "), exec_xchg3))
|
||||
.insert(OpcodeInstr::mkfixed(0x541, 12, 12, instr::dump_3sr("XC2PU "), exec_xc2pu))
|
||||
.insert(OpcodeInstr::mkfixed(0x542, 12, 12, instr::dump_3sr_adj(1, "XCPUXC "), exec_xcpuxc))
|
||||
.insert(OpcodeInstr::mkfixed(0x543, 12, 12, instr::dump_3sr("XCPU2 "), exec_xcpu2))
|
||||
.insert(OpcodeInstr::mkfixed(0x544, 12, 12, instr::dump_3sr_adj(0x11, "PUXC2 "), exec_puxc2))
|
||||
.insert(OpcodeInstr::mkfixed(0x545, 12, 12, instr::dump_3sr_adj(0x11, "PUXCPU "), exec_puxcpu))
|
||||
.insert(OpcodeInstr::mkfixed(0x546, 12, 12, instr::dump_3sr_adj(0x12, "PU2XC "), exec_pu2xc))
|
||||
.insert(OpcodeInstr::mkfixed(0x547, 12, 12, instr::dump_3sr("PUSH3 "), exec_push3))
|
||||
.insert(OpcodeInstr::mkfixed(0x55, 8, 8, instr::dump_2c_add(0x11, "BLKSWAP ", ","), exec_blkswap))
|
||||
.insert(OpcodeInstr::mkfixed(0x56, 8, 8, instr::dump_1sr_l("PUSH "), exec_push_l))
|
||||
.insert(OpcodeInstr::mkfixed(0x57, 8, 8, instr::dump_1sr_l("POP "), exec_pop_l))
|
||||
.insert(OpcodeInstr::mksimple(0x58, 8, "ROT", exec_rot))
|
||||
.insert(OpcodeInstr::mksimple(0x59, 8, "ROTREV", exec_rotrev))
|
||||
.insert(OpcodeInstr::mksimple(0x5a, 8, "2SWAP", exec_2swap))
|
||||
.insert(OpcodeInstr::mksimple(0x5b, 8, "2DROP", exec_2drop))
|
||||
.insert(OpcodeInstr::mksimple(0x5c, 8, "2DUP", exec_2dup))
|
||||
.insert(OpcodeInstr::mksimple(0x5d, 8, "2OVER", exec_2over))
|
||||
.insert(OpcodeInstr::mkfixed(0x5e, 8, 8, instr::dump_2c_add(0x20, "REVERSE ", ","), exec_reverse))
|
||||
.insert(OpcodeInstr::mkfixed(0x5f0, 12, 4, instr::dump_1c("BLKDROP "), exec_blkdrop))
|
||||
.insert(OpcodeInstr::mkfixedrange(0x5f10, 0x6000, 16, 8, instr::dump_2c("BLKPUSH ", ","), exec_blkpush))
|
||||
.insert(OpcodeInstr::mksimple(0x60, 8, "PICK", exec_pick))
|
||||
.insert(OpcodeInstr::mksimple(0x61, 8, "ROLL", exec_roll))
|
||||
.insert(OpcodeInstr::mksimple(0x62, 8, "ROLLREV", exec_rollrev))
|
||||
.insert(OpcodeInstr::mksimple(0x63, 8, "BLKSWX", exec_blkswap_x))
|
||||
.insert(OpcodeInstr::mksimple(0x64, 8, "REVX", exec_reverse_x))
|
||||
.insert(OpcodeInstr::mksimple(0x65, 8, "DROPX", exec_drop_x))
|
||||
.insert(OpcodeInstr::mksimple(0x66, 8, "TUCK", exec_tuck))
|
||||
.insert(OpcodeInstr::mksimple(0x67, 8, "XCHGX", exec_xchg_x))
|
||||
.insert(OpcodeInstr::mksimple(0x68, 8, "DEPTH", exec_depth))
|
||||
.insert(OpcodeInstr::mksimple(0x69, 8, "CHKDEPTH", exec_chkdepth))
|
||||
.insert(OpcodeInstr::mksimple(0x6a, 8, "ONLYTOPX", exec_onlytop_x))
|
||||
.insert(OpcodeInstr::mksimple(0x6b, 8, "ONLYX", exec_only_x));
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
27
crypto/vm/stackops.h
Normal file
27
crypto/vm/stackops.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace vm {
|
||||
|
||||
class OpcodeTable;
|
||||
|
||||
void register_stack_ops(OpcodeTable& cp0);
|
||||
|
||||
} // namespace vm
|
||||
675
crypto/vm/tonops.cpp
Normal file
675
crypto/vm/tonops.cpp
Normal file
|
|
@ -0,0 +1,675 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include <functional>
|
||||
#include "vm/tonops.h"
|
||||
#include "vm/log.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "vm/dict.h"
|
||||
#include "Ed25519.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
namespace {
|
||||
|
||||
bool debug(const char* str) TD_UNUSED;
|
||||
bool debug(const char* str) {
|
||||
std::cerr << str;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool debug(int x) TD_UNUSED;
|
||||
bool debug(int x) {
|
||||
if (x < 100) {
|
||||
std::cerr << '[' << (char)(64 + x) << ']';
|
||||
} else {
|
||||
std::cerr << '[' << (char)(64 + x / 100) << x % 100 << ']';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#define DBG_START int dbg = 0;
|
||||
#define DBG debug(++dbg)&&
|
||||
#define DEB_START DBG_START
|
||||
#define DEB DBG
|
||||
|
||||
int exec_set_gas_generic(VmState* st, long long new_gas_limit) {
|
||||
if (new_gas_limit < st->gas_consumed()) {
|
||||
throw VmNoGas{};
|
||||
}
|
||||
st->change_gas_limit(new_gas_limit);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_accept(VmState* st) {
|
||||
VM_LOG(st) << "execute ACCEPT";
|
||||
return exec_set_gas_generic(st, GasLimits::infty);
|
||||
}
|
||||
|
||||
int exec_set_gas_limit(VmState* st) {
|
||||
VM_LOG(st) << "execute SETGASLIMIT";
|
||||
td::RefInt256 x = st->get_stack().pop_int_finite();
|
||||
long long gas = 0;
|
||||
if (x->sgn() > 0) {
|
||||
gas = x->unsigned_fits_bits(63) ? x->to_long() : GasLimits::infty;
|
||||
}
|
||||
return exec_set_gas_generic(st, gas);
|
||||
}
|
||||
|
||||
void register_basic_gas_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xf800, 16, "ACCEPT", exec_accept))
|
||||
.insert(OpcodeInstr::mksimple(0xf801, 16, "SETGASLIMIT", exec_set_gas_limit));
|
||||
}
|
||||
|
||||
void register_ton_gas_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
}
|
||||
|
||||
int exec_get_param(VmState* st, unsigned idx, const char* name) {
|
||||
if (name) {
|
||||
VM_LOG(st) << "execute " << name;
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
auto tuple = st->get_c7();
|
||||
auto t1 = tuple_index(*tuple, 0).as_tuple_range(255);
|
||||
if (t1.is_null()) {
|
||||
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
|
||||
}
|
||||
stack.push(tuple_index(*t1, idx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_get_var_param(VmState* st, unsigned idx) {
|
||||
idx &= 15;
|
||||
VM_LOG(st) << "execute GETPARAM " << idx;
|
||||
return exec_get_param(st, idx, nullptr);
|
||||
}
|
||||
|
||||
int exec_get_config_dict(VmState* st) {
|
||||
exec_get_param(st, 9, "CONFIGDICT");
|
||||
st->get_stack().push_smallint(32);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_get_config_param(VmState* st, bool opt) {
|
||||
VM_LOG(st) << "execute CONFIG" << (opt ? "OPTPARAM" : "PARAM");
|
||||
Stack& stack = st->get_stack();
|
||||
auto idx = stack.pop_int();
|
||||
exec_get_param(st, 9, nullptr);
|
||||
Dictionary dict{stack.pop_maybe_cell(), 32};
|
||||
td::BitArray<32> key;
|
||||
Ref<vm::Cell> value;
|
||||
if (idx->export_bits(key.bits(), key.size(), true)) {
|
||||
value = dict.lookup_ref(key);
|
||||
}
|
||||
if (opt) {
|
||||
stack.push_maybe_cell(std::move(value));
|
||||
} else if (value.not_null()) {
|
||||
stack.push_cell(std::move(value));
|
||||
stack.push_bool(true);
|
||||
} else {
|
||||
stack.push_bool(false);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_get_global_common(VmState* st, unsigned n) {
|
||||
st->get_stack().push(tuple_extend_index(st->get_c7(), n));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_get_global(VmState* st, unsigned args) {
|
||||
args &= 31;
|
||||
VM_LOG(st) << "execute GETGLOB " << args;
|
||||
return exec_get_global_common(st, args);
|
||||
}
|
||||
|
||||
int exec_get_global_var(VmState* st) {
|
||||
VM_LOG(st) << "execute GETGLOBVAR";
|
||||
st->check_underflow(1);
|
||||
unsigned args = st->get_stack().pop_smallint_range(254);
|
||||
return exec_get_global_common(st, args);
|
||||
}
|
||||
|
||||
int exec_set_global_common(VmState* st, unsigned idx) {
|
||||
Stack& stack = st->get_stack();
|
||||
auto x = stack.pop();
|
||||
auto tuple = st->get_c7();
|
||||
if (idx >= 255) {
|
||||
throw VmError{Excno::range_chk, "tuple index out of range"};
|
||||
}
|
||||
auto tpay = tuple_extend_set_index(tuple, idx, std::move(x));
|
||||
if (tpay > 0) {
|
||||
st->consume_tuple_gas(tpay);
|
||||
}
|
||||
st->set_c7(std::move(tuple));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_set_global(VmState* st, unsigned args) {
|
||||
args &= 31;
|
||||
VM_LOG(st) << "execute SETGLOB " << args;
|
||||
st->check_underflow(1);
|
||||
return exec_set_global_common(st, args);
|
||||
}
|
||||
|
||||
int exec_set_global_var(VmState* st) {
|
||||
VM_LOG(st) << "execute SETGLOBVAR";
|
||||
st->check_underflow(2);
|
||||
unsigned args = st->get_stack().pop_smallint_range(254);
|
||||
return exec_set_global_common(st, args);
|
||||
}
|
||||
|
||||
void register_ton_config_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mkfixedrange(0xf820, 0xf823, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param))
|
||||
.insert(OpcodeInstr::mksimple(0xf823, 16, "NOW", std::bind(exec_get_param, _1, 3, "NOW")))
|
||||
.insert(OpcodeInstr::mksimple(0xf824, 16, "BLOCKLT", std::bind(exec_get_param, _1, 4, "BLOCKLT")))
|
||||
.insert(OpcodeInstr::mksimple(0xf825, 16, "LTIME", std::bind(exec_get_param, _1, 5, "LTIME")))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf826, 0xf828, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param))
|
||||
.insert(OpcodeInstr::mksimple(0xf828, 16, "MYADDR", std::bind(exec_get_param, _1, 8, "MYADDR")))
|
||||
.insert(OpcodeInstr::mksimple(0xf829, 16, "CONFIGROOT", std::bind(exec_get_param, _1, 9, "CONFIGROOT")))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf82a, 0xf830, 16, 4, instr::dump_1c("GETPARAM "), exec_get_var_param))
|
||||
.insert(OpcodeInstr::mksimple(0xf830, 16, "CONFIGDICT", exec_get_config_dict))
|
||||
.insert(OpcodeInstr::mksimple(0xf832, 16, "CONFIGPARAM", std::bind(exec_get_config_param, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xf833, 16, "CONFIGOPTPARAM", std::bind(exec_get_config_param, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xf840, 16, "GETGLOBVAR", exec_get_global_var))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf841, 0xf860, 16, 5, instr::dump_1c_and(31, "GETGLOB "), exec_get_global))
|
||||
.insert(OpcodeInstr::mksimple(0xf860, 16, "SETGLOBVAR", exec_set_global_var))
|
||||
.insert(OpcodeInstr::mkfixedrange(0xf861, 0xf880, 16, 5, instr::dump_1c_and(31, "SETGLOB "), exec_set_global));
|
||||
}
|
||||
|
||||
int exec_compute_hash(VmState* st, int mode) {
|
||||
VM_LOG(st) << "execute HASH" << (mode & 1 ? 'S' : 'C') << 'U';
|
||||
Stack& stack = st->get_stack();
|
||||
std::array<unsigned char, 32> hash;
|
||||
if (!(mode & 1)) {
|
||||
auto cell = stack.pop_cell();
|
||||
hash = cell->get_hash().as_array();
|
||||
} else {
|
||||
auto cs = stack.pop_cellslice();
|
||||
vm::CellBuilder cb;
|
||||
CHECK(cb.append_cellslice_bool(std::move(cs)));
|
||||
// TODO: use cb.get_hash() instead
|
||||
hash = cb.finalize()->get_hash().as_array();
|
||||
}
|
||||
td::RefInt256 res{true};
|
||||
CHECK(res.write().import_bytes(hash.data(), hash.size(), false));
|
||||
stack.push_int(std::move(res));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_compute_sha256(VmState* st) {
|
||||
VM_LOG(st) << "execute SHA256U";
|
||||
Stack& stack = st->get_stack();
|
||||
auto cs = stack.pop_cellslice();
|
||||
if (cs->size() & 7) {
|
||||
throw VmError{Excno::cell_und, "Slice does not consist of an integer number of bytes"};
|
||||
}
|
||||
auto len = (cs->size() >> 3);
|
||||
unsigned char data[128], hash[32];
|
||||
CHECK(len <= sizeof(data));
|
||||
CHECK(cs->prefetch_bytes(data, len));
|
||||
digest::hash_str<digest::SHA256>(hash, data, len);
|
||||
td::RefInt256 res{true};
|
||||
CHECK(res.write().import_bytes(hash, 32, false));
|
||||
stack.push_int(std::move(res));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_ed25519_check_signature(VmState* st, bool from_slice) {
|
||||
VM_LOG(st) << "execute CHKSIGN" << (from_slice ? 'S' : 'U');
|
||||
Stack& stack = st->get_stack();
|
||||
stack.check_underflow(3);
|
||||
auto key_int = stack.pop_int();
|
||||
auto signature_cs = stack.pop_cellslice();
|
||||
unsigned char data[128], key[32], signature[64];
|
||||
unsigned data_len;
|
||||
if (from_slice) {
|
||||
auto cs = stack.pop_cellslice();
|
||||
if (cs->size() & 7) {
|
||||
throw VmError{Excno::cell_und, "Slice does not consist of an integer number of bytes"};
|
||||
}
|
||||
data_len = (cs->size() >> 3);
|
||||
CHECK(data_len <= sizeof(data));
|
||||
CHECK(cs->prefetch_bytes(data, data_len));
|
||||
} else {
|
||||
auto hash_int = stack.pop_int();
|
||||
data_len = 32;
|
||||
if (!hash_int->export_bytes(data, data_len, false)) {
|
||||
throw VmError{Excno::range_chk, "data hash must fit in an unsigned 256-bit integer"};
|
||||
}
|
||||
}
|
||||
if (!signature_cs->prefetch_bytes(signature, 64)) {
|
||||
throw VmError{Excno::cell_und, "Ed25519 signature must contain at least 512 data bits"};
|
||||
}
|
||||
if (!key_int->export_bytes(key, 32, false)) {
|
||||
throw VmError{Excno::range_chk, "Ed25519 public key must fit in an unsigned 256-bit integer"};
|
||||
}
|
||||
td::Ed25519::PublicKey pub_key{td::SecureString(td::Slice{key, 32})};
|
||||
auto res = pub_key.verify_signature(td::Slice{data, data_len}, td::Slice{signature, 64});
|
||||
stack.push_bool(res.is_ok());
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_ton_crypto_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xf900, 16, "HASHCU", std::bind(exec_compute_hash, _1, 0)))
|
||||
.insert(OpcodeInstr::mksimple(0xf901, 16, "HASHSU", std::bind(exec_compute_hash, _1, 1)))
|
||||
.insert(OpcodeInstr::mksimple(0xf902, 16, "SHA256U", exec_compute_sha256))
|
||||
.insert(OpcodeInstr::mksimple(0xf910, 16, "CHKSIGNU", std::bind(exec_ed25519_check_signature, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xf911, 16, "CHKSIGNS", std::bind(exec_ed25519_check_signature, _1, true)));
|
||||
}
|
||||
|
||||
int exec_load_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) {
|
||||
if (len_bits == 4 && !sgnd) {
|
||||
VM_LOG(st) << "execute LDGRAMS" << (quiet ? "Q" : "");
|
||||
} else {
|
||||
VM_LOG(st) << "execute LDVAR" << (sgnd ? "" : "U") << "INT" << (1 << len_bits) << (quiet ? "Q" : "");
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
auto csr = stack.pop_cellslice();
|
||||
td::RefInt256 x;
|
||||
int len;
|
||||
if (!(csr.write().fetch_uint_to(len_bits, len) && csr.unique_write().fetch_int256_to(len * 8, x, sgnd))) {
|
||||
if (quiet) {
|
||||
stack.push_bool(false);
|
||||
} else {
|
||||
throw VmError{Excno::cell_und, "cannot deserialize a variable-length integer"};
|
||||
}
|
||||
} else {
|
||||
stack.push_int(std::move(x));
|
||||
stack.push_cellslice(std::move(csr));
|
||||
if (quiet) {
|
||||
stack.push_bool(true);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_store_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) {
|
||||
if (len_bits == 4 && !sgnd) {
|
||||
VM_LOG(st) << "execute STGRAMS" << (quiet ? "Q" : "");
|
||||
} else {
|
||||
VM_LOG(st) << "execute STVAR" << (sgnd ? "" : "U") << "INT" << (1 << len_bits) << (quiet ? "Q" : "");
|
||||
}
|
||||
Stack& stack = st->get_stack();
|
||||
stack.check_underflow(2);
|
||||
auto x = stack.pop_int();
|
||||
auto cbr = stack.pop_builder();
|
||||
unsigned len = ((x->bit_size(sgnd) + 7) >> 3);
|
||||
if (len >= (1u << len_bits)) {
|
||||
throw VmError{Excno::range_chk};
|
||||
}
|
||||
if (!(cbr.write().store_long_bool(len, len_bits) && cbr.unique_write().store_int256_bool(*x, len * 8, sgnd))) {
|
||||
if (quiet) {
|
||||
stack.push_bool(false);
|
||||
} else {
|
||||
throw VmError{Excno::cell_ov, "cannot serialize a variable-length integer"};
|
||||
}
|
||||
} else {
|
||||
stack.push_builder(std::move(cbr));
|
||||
if (quiet) {
|
||||
stack.push_bool(true);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool skip_maybe_anycast(CellSlice& cs) {
|
||||
if (cs.prefetch_ulong(1) != 1) {
|
||||
return cs.advance(1);
|
||||
}
|
||||
unsigned depth;
|
||||
return cs.advance(1) // just$1
|
||||
&& cs.fetch_uint_leq(30, depth) // anycast_info$_ depth:(#<= 30)
|
||||
&& depth >= 1 // { depth >= 1 }
|
||||
&& cs.advance(depth); // rewrite_pfx:(bits depth) = Anycast;
|
||||
}
|
||||
|
||||
bool skip_message_addr(CellSlice& cs) {
|
||||
switch ((unsigned)cs.fetch_ulong(2)) {
|
||||
case 0: // addr_none$00 = MsgAddressExt;
|
||||
return true;
|
||||
case 1: { // addr_extern$01
|
||||
unsigned len;
|
||||
return cs.fetch_uint_to(9, len) // len:(## 9)
|
||||
&& cs.advance(len); // external_address:(bits len) = MsgAddressExt;
|
||||
}
|
||||
case 2: { // addr_std$10
|
||||
return skip_maybe_anycast(cs) // anycast:(Maybe Anycast)
|
||||
&& cs.advance(8 + 256); // workchain_id:int8 address:bits256 = MsgAddressInt;
|
||||
}
|
||||
case 3: { // addr_var$11
|
||||
unsigned len;
|
||||
return skip_maybe_anycast(cs) // anycast:(Maybe Anycast)
|
||||
&& cs.fetch_uint_to(9, len) // addr_len:(## 9)
|
||||
&& cs.advance(32 + len); // workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int exec_load_message_addr(VmState* st, bool quiet) {
|
||||
VM_LOG(st) << "execute LDMSGADDR" << (quiet ? "Q" : "");
|
||||
Stack& stack = st->get_stack();
|
||||
auto csr = stack.pop_cellslice(), csr_copy = csr;
|
||||
auto& cs = csr.write();
|
||||
if (!(skip_message_addr(cs) && csr_copy.write().cut_tail(cs))) {
|
||||
csr.clear();
|
||||
if (quiet) {
|
||||
stack.push_cellslice(std::move(csr_copy));
|
||||
stack.push_bool(false);
|
||||
} else {
|
||||
throw VmError{Excno::cell_und, "cannot load a MsgAddress"};
|
||||
}
|
||||
} else {
|
||||
stack.push_cellslice(std::move(csr_copy));
|
||||
stack.push_cellslice(std::move(csr));
|
||||
if (quiet) {
|
||||
stack.push_bool(true);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool parse_maybe_anycast(CellSlice& cs, StackEntry& res) {
|
||||
res = StackEntry{};
|
||||
if (cs.prefetch_ulong(1) != 1) {
|
||||
return cs.advance(1);
|
||||
}
|
||||
unsigned depth;
|
||||
Ref<CellSlice> pfx;
|
||||
if (cs.advance(1) // just$1
|
||||
&& cs.fetch_uint_leq(30, depth) // anycast_info$_ depth:(#<= 30)
|
||||
&& depth >= 1 // { depth >= 1 }
|
||||
&& cs.fetch_subslice_to(depth, pfx)) { // rewrite_pfx:(bits depth) = Anycast;
|
||||
res = std::move(pfx);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool parse_message_addr(CellSlice& cs, std::vector<StackEntry>& res) {
|
||||
res.clear();
|
||||
switch ((unsigned)cs.fetch_ulong(2)) {
|
||||
case 0: // addr_none$00 = MsgAddressExt;
|
||||
res.emplace_back(td::RefInt256{true, 0}); // -> (0)
|
||||
return true;
|
||||
case 1: { // addr_extern$01
|
||||
unsigned len;
|
||||
Ref<CellSlice> addr;
|
||||
if (cs.fetch_uint_to(9, len) // len:(## 9)
|
||||
&& cs.fetch_subslice_to(len, addr)) { // external_address:(bits len) = MsgAddressExt;
|
||||
res.emplace_back(td::RefInt256{true, 1});
|
||||
res.emplace_back(std::move(addr));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: { // addr_std$10
|
||||
StackEntry v;
|
||||
int workchain;
|
||||
Ref<CellSlice> addr;
|
||||
if (parse_maybe_anycast(cs, v) // anycast:(Maybe Anycast)
|
||||
&& cs.fetch_int_to(8, workchain) // workchain_id:int8
|
||||
&& cs.fetch_subslice_to(256, addr)) { // address:bits256 = MsgAddressInt;
|
||||
res.emplace_back(td::RefInt256{true, 2});
|
||||
res.emplace_back(std::move(v));
|
||||
res.emplace_back(td::RefInt256{true, workchain});
|
||||
res.emplace_back(std::move(addr));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3: { // addr_var$11
|
||||
StackEntry v;
|
||||
int len, workchain;
|
||||
Ref<CellSlice> addr;
|
||||
if (parse_maybe_anycast(cs, v) // anycast:(Maybe Anycast)
|
||||
&& cs.fetch_uint_to(9, len) // addr_len:(## 9)
|
||||
&& cs.fetch_int_to(32, workchain) // workchain_id:int32
|
||||
&& cs.fetch_subslice_to(len, addr)) { // address:(bits addr_len) = MsgAddressInt;
|
||||
res.emplace_back(td::RefInt256{true, 3});
|
||||
res.emplace_back(std::move(v));
|
||||
res.emplace_back(td::RefInt256{true, workchain});
|
||||
res.emplace_back(std::move(addr));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int exec_parse_message_addr(VmState* st, bool quiet) {
|
||||
VM_LOG(st) << "execute PARSEMSGADDR" << (quiet ? "Q" : "");
|
||||
Stack& stack = st->get_stack();
|
||||
auto csr = stack.pop_cellslice();
|
||||
auto& cs = csr.write();
|
||||
std::vector<StackEntry> res;
|
||||
if (!(parse_message_addr(cs, res) && cs.empty_ext())) {
|
||||
if (quiet) {
|
||||
stack.push_bool(false);
|
||||
} else {
|
||||
throw VmError{Excno::cell_und, "cannot parse a MsgAddress"};
|
||||
}
|
||||
} else {
|
||||
stack.push_tuple(std::move(res));
|
||||
if (quiet) {
|
||||
stack.push_bool(true);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// replaces first bits of `addr` with those of `prefix`
|
||||
Ref<CellSlice> do_rewrite_addr(Ref<CellSlice> addr, Ref<CellSlice> prefix) {
|
||||
if (prefix.is_null() || !prefix->size()) {
|
||||
return std::move(addr);
|
||||
}
|
||||
if (prefix->size() > addr->size()) {
|
||||
return {};
|
||||
}
|
||||
if (prefix->size() == addr->size()) {
|
||||
return std::move(prefix);
|
||||
}
|
||||
vm::CellBuilder cb;
|
||||
if (!(addr.write().advance(prefix->size()) && cb.append_cellslice_bool(std::move(prefix)) &&
|
||||
cb.append_cellslice_bool(std::move(addr)))) {
|
||||
return {};
|
||||
}
|
||||
return vm::load_cell_slice_ref(cb.finalize());
|
||||
}
|
||||
|
||||
int exec_rewrite_message_addr(VmState* st, bool allow_var_addr, bool quiet) {
|
||||
VM_LOG(st) << "execute REWRITE" << (allow_var_addr ? "VAR" : "STD") << "ADDR" << (quiet ? "Q" : "");
|
||||
Stack& stack = st->get_stack();
|
||||
auto csr = stack.pop_cellslice();
|
||||
auto& cs = csr.write();
|
||||
std::vector<StackEntry> tuple;
|
||||
if (!(parse_message_addr(cs, tuple) && cs.empty_ext())) {
|
||||
if (quiet) {
|
||||
stack.push_bool(false);
|
||||
return 0;
|
||||
}
|
||||
throw VmError{Excno::cell_und, "cannot parse a MsgAddress"};
|
||||
}
|
||||
int t = (int)std::move(tuple[0]).as_int()->to_long();
|
||||
if (t != 2 && t != 3) {
|
||||
if (quiet) {
|
||||
stack.push_bool(false);
|
||||
return 0;
|
||||
}
|
||||
throw VmError{Excno::cell_und, "cannot parse a MsgAddressInt"};
|
||||
}
|
||||
auto addr = std::move(tuple[3]).as_slice();
|
||||
auto prefix = std::move(tuple[1]).as_slice();
|
||||
if (!allow_var_addr) {
|
||||
if (addr->size() != 256) {
|
||||
if (quiet) {
|
||||
stack.push_bool(false);
|
||||
return 0;
|
||||
}
|
||||
throw VmError{Excno::cell_und, "MsgAddressInt is not a standard 256-bit address"};
|
||||
}
|
||||
td::Bits256 rw_addr;
|
||||
td::RefInt256 int_addr{true};
|
||||
CHECK(addr->prefetch_bits_to(rw_addr) &&
|
||||
(prefix.is_null() || prefix->prefetch_bits_to(rw_addr.bits(), prefix->size())) &&
|
||||
int_addr.unique_write().import_bits(rw_addr, false));
|
||||
stack.push(std::move(tuple[2]));
|
||||
stack.push(std::move(int_addr));
|
||||
} else {
|
||||
addr = do_rewrite_addr(std::move(addr), std::move(prefix));
|
||||
if (addr.is_null()) {
|
||||
if (quiet) {
|
||||
stack.push_bool(false);
|
||||
return 0;
|
||||
}
|
||||
throw VmError{Excno::cell_und, "cannot rewrite address in a MsgAddressInt"};
|
||||
}
|
||||
stack.push(std::move(tuple[2]));
|
||||
stack.push(std::move(addr));
|
||||
}
|
||||
if (quiet) {
|
||||
stack.push_bool(true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_ton_currency_address_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xfa00, 16, "LDGRAMS", std::bind(exec_load_var_integer, _1, 4, false, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa01, 16, "LDVARINT16", std::bind(exec_load_var_integer, _1, 4, true, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa02, 16, "STGRAMS", std::bind(exec_store_var_integer, _1, 4, false, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa03, 16, "STVARINT16", std::bind(exec_store_var_integer, _1, 4, true, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa04, 16, "LDVARUINT32", std::bind(exec_load_var_integer, _1, 5, false, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa05, 16, "LDVARINT32", std::bind(exec_load_var_integer, _1, 5, true, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa06, 16, "STVARUINT32", std::bind(exec_store_var_integer, _1, 5, false, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa07, 16, "STVARINT32", std::bind(exec_store_var_integer, _1, 5, true, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa40, 16, "LDMSGADDR", std::bind(exec_load_message_addr, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa41, 16, "LDMSGADDRQ", std::bind(exec_load_message_addr, _1, true)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa42, 16, "PARSEMSGADDR", std::bind(exec_parse_message_addr, _1, false)))
|
||||
.insert(OpcodeInstr::mksimple(0xfa43, 16, "PARSEMSGADDRQ", std::bind(exec_parse_message_addr, _1, true)))
|
||||
.insert(
|
||||
OpcodeInstr::mksimple(0xfa44, 16, "REWRITESTDADDR", std::bind(exec_rewrite_message_addr, _1, false, false)))
|
||||
.insert(
|
||||
OpcodeInstr::mksimple(0xfa45, 16, "REWRITESTDADDRQ", std::bind(exec_rewrite_message_addr, _1, false, true)))
|
||||
.insert(
|
||||
OpcodeInstr::mksimple(0xfa46, 16, "REWRITEVARADDR", std::bind(exec_rewrite_message_addr, _1, true, false)))
|
||||
.insert(
|
||||
OpcodeInstr::mksimple(0xfa47, 16, "REWRITEVARADDRQ", std::bind(exec_rewrite_message_addr, _1, true, true)));
|
||||
}
|
||||
|
||||
static constexpr int output_actions_idx = 5;
|
||||
|
||||
int install_output_action(VmState* st, Ref<Cell> new_action_head) {
|
||||
// TODO: increase actions:uint16 and msgs_sent:uint16 in SmartContractInfo at first reference of c5
|
||||
VM_LOG(st) << "installing an output action";
|
||||
st->set_d(output_actions_idx, std::move(new_action_head));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline Ref<Cell> get_actions(VmState* st) {
|
||||
return st->get_d(output_actions_idx);
|
||||
}
|
||||
|
||||
int exec_send_raw_message(VmState* st) {
|
||||
VM_LOG(st) << "execute SENDRAWMSG";
|
||||
Stack& stack = st->get_stack();
|
||||
stack.check_underflow(2);
|
||||
int f = stack.pop_smallint_range(255);
|
||||
Ref<Cell> msg_cell = stack.pop_cell();
|
||||
CellBuilder cb;
|
||||
if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n)
|
||||
&& cb.store_long_bool(0x0ec3c86d, 32) // action_send_msg#0ec3c86d
|
||||
&& cb.store_long_bool(f, 8) // mode:(## 8)
|
||||
&& cb.store_ref_bool(std::move(msg_cell)))) {
|
||||
throw VmError{Excno::cell_ov, "cannot serialize raw output message into an output action cell"};
|
||||
}
|
||||
return install_output_action(st, cb.finalize());
|
||||
}
|
||||
|
||||
bool store_grams(CellBuilder& cb, td::RefInt256 value) {
|
||||
int k = value->bit_size(false);
|
||||
return k <= 15 * 8 && cb.store_long_bool((k + 7) >> 3, 4) && cb.store_int256_bool(*value, (k + 7) & -8, false);
|
||||
}
|
||||
|
||||
int exec_reserve_raw(VmState* st, int mode) {
|
||||
VM_LOG(st) << "execute RESERVERAW" << (mode & 1 ? "X" : "");
|
||||
Stack& stack = st->get_stack();
|
||||
stack.check_underflow(2);
|
||||
int f = stack.pop_smallint_range(3);
|
||||
td::RefInt256 x;
|
||||
Ref<CellSlice> csr;
|
||||
if (mode & 1) {
|
||||
csr = stack.pop_cellslice();
|
||||
} else {
|
||||
x = stack.pop_int_finite();
|
||||
if (td::sgn(x) < 0) {
|
||||
throw VmError{Excno::range_chk, "amount of nanograms must be non-negative"};
|
||||
}
|
||||
}
|
||||
CellBuilder cb;
|
||||
if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n)
|
||||
&& cb.store_long_bool(0x36e6b809, 32) // action_reserve_currency#36e6b809
|
||||
&& cb.store_long_bool(f, 8) // mode:(## 8)
|
||||
&& (mode & 1 ? cb.append_cellslice_bool(std::move(csr))
|
||||
: (store_grams(cb, std::move(x)) && cb.store_bool_bool(false))))) {
|
||||
throw VmError{Excno::cell_ov, "cannot serialize raw reserved currency amount into an output action cell"};
|
||||
}
|
||||
return install_output_action(st, cb.finalize());
|
||||
}
|
||||
|
||||
int exec_set_code(VmState* st) {
|
||||
VM_LOG(st) << "execute SETCODE";
|
||||
auto code = st->get_stack().pop_cell();
|
||||
CellBuilder cb;
|
||||
if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n)
|
||||
&& cb.store_long_bool(0xad4de08e, 32) // action_set_code#ad4de08e
|
||||
&& cb.store_ref_bool(std::move(code)))) { // new_code:^Cell = OutAction;
|
||||
throw VmError{Excno::cell_ov, "cannot serialize new smart contract code into an output action cell"};
|
||||
}
|
||||
return install_output_action(st, cb.finalize());
|
||||
}
|
||||
|
||||
void register_ton_message_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0xfb00, 16, "SENDRAWMSG", exec_send_raw_message))
|
||||
.insert(OpcodeInstr::mksimple(0xfb02, 16, "RESERVERAW", std::bind(exec_reserve_raw, _1, 0)))
|
||||
.insert(OpcodeInstr::mksimple(0xfb03, 16, "RESERVERAWX", std::bind(exec_reserve_raw, _1, 1)))
|
||||
.insert(OpcodeInstr::mksimple(0xfb04, 16, "SETCODE", exec_set_code));
|
||||
}
|
||||
|
||||
void register_ton_ops(OpcodeTable& cp0) {
|
||||
register_basic_gas_ops(cp0);
|
||||
register_ton_gas_ops(cp0);
|
||||
register_ton_config_ops(cp0);
|
||||
register_ton_crypto_ops(cp0);
|
||||
register_ton_currency_address_ops(cp0);
|
||||
register_ton_message_ops(cp0);
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
27
crypto/vm/tonops.h
Normal file
27
crypto/vm/tonops.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace vm {
|
||||
|
||||
class OpcodeTable;
|
||||
|
||||
void register_ton_ops(OpcodeTable& cp0);
|
||||
|
||||
} // namespace vm
|
||||
381
crypto/vm/tupleops.cpp
Normal file
381
crypto/vm/tupleops.cpp
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "vm/log.h"
|
||||
#include "vm/stackops.h"
|
||||
#include "vm/opctable.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "vm/continuation.h"
|
||||
#include "vm/excno.hpp"
|
||||
|
||||
namespace vm {
|
||||
|
||||
int exec_push_null(VmState* st) {
|
||||
VM_LOG(st) << "execute PUSHNULL";
|
||||
st->get_stack().push({});
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_is_null(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ISNULL";
|
||||
stack.push_bool(stack.pop_chk().empty());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_null_swap_if(VmState* st, bool cond, int depth) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute NULL" << (depth ? "ROTR" : "SWAP") << (cond ? "IF" : "IFNOT");
|
||||
stack.check_underflow(depth + 1);
|
||||
auto x = stack.pop_int_finite();
|
||||
if (!x->sgn() != cond) {
|
||||
stack.push({});
|
||||
for (int i = 0; i < depth; i++) {
|
||||
swap(stack[i], stack[i + 1]);
|
||||
}
|
||||
}
|
||||
stack.push_int(std::move(x));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_mktuple_common(VmState* st, unsigned n) {
|
||||
Stack& stack = st->get_stack();
|
||||
stack.check_underflow(n);
|
||||
Ref<Tuple> ref{true};
|
||||
auto& tuple = ref.unique_write();
|
||||
tuple.reserve(n);
|
||||
for (int i = n - 1; i >= 0; i--) {
|
||||
tuple.push_back(std::move(stack[i]));
|
||||
}
|
||||
stack.pop_many(n);
|
||||
st->consume_tuple_gas(n);
|
||||
stack.push_tuple(std::move(ref));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_mktuple(VmState* st, unsigned args) {
|
||||
args &= 15;
|
||||
VM_LOG(st) << "execute TUPLE " << args;
|
||||
return exec_mktuple_common(st, args);
|
||||
}
|
||||
|
||||
int exec_mktuple_var(VmState* st) {
|
||||
VM_LOG(st) << "execute TUPLEVAR";
|
||||
unsigned args = st->get_stack().pop_smallint_range(255);
|
||||
return exec_mktuple_common(st, args);
|
||||
}
|
||||
|
||||
int exec_tuple_index_common(Stack& stack, unsigned n) {
|
||||
auto tuple = stack.pop_tuple_range(255);
|
||||
stack.push(tuple_index(*tuple, n));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuple_index(VmState* st, unsigned args) {
|
||||
args &= 15;
|
||||
VM_LOG(st) << "execute INDEX " << args;
|
||||
return exec_tuple_index_common(st->get_stack(), args);
|
||||
}
|
||||
|
||||
int exec_tuple_index_var(VmState* st) {
|
||||
VM_LOG(st) << "execute INDEXVAR";
|
||||
st->check_underflow(2);
|
||||
unsigned args = st->get_stack().pop_smallint_range(254);
|
||||
return exec_tuple_index_common(st->get_stack(), args);
|
||||
}
|
||||
|
||||
int exec_tuple_quiet_index_common(Stack& stack, unsigned n) {
|
||||
stack.push(tuple_extend_index(stack.pop_maybe_tuple_range(255), n));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuple_quiet_index(VmState* st, unsigned args) {
|
||||
args &= 15;
|
||||
VM_LOG(st) << "execute INDEXQ " << args;
|
||||
return exec_tuple_quiet_index_common(st->get_stack(), args);
|
||||
}
|
||||
|
||||
int exec_tuple_quiet_index_var(VmState* st) {
|
||||
VM_LOG(st) << "execute INDEXVARQ";
|
||||
st->check_underflow(2);
|
||||
unsigned args = st->get_stack().pop_smallint_range(254);
|
||||
return exec_tuple_quiet_index_common(st->get_stack(), args);
|
||||
}
|
||||
|
||||
int do_explode_tuple(VmState* st, Ref<Tuple> tuple, unsigned n) {
|
||||
auto& stack = st->get_stack();
|
||||
if (tuple.is_unique()) {
|
||||
auto& tw = tuple.unique_write();
|
||||
for (unsigned i = 0; i < n; i++) {
|
||||
stack.push(std::move(tw[i]));
|
||||
}
|
||||
} else {
|
||||
const auto& t = *tuple;
|
||||
for (unsigned i = 0; i < n; i++) {
|
||||
stack.push(t[i]);
|
||||
}
|
||||
}
|
||||
st->consume_tuple_gas(n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_untuple_common(VmState* st, unsigned n) {
|
||||
return do_explode_tuple(st, st->get_stack().pop_tuple_range(n, n), n);
|
||||
}
|
||||
|
||||
int exec_untuple(VmState* st, unsigned args) {
|
||||
args &= 15;
|
||||
VM_LOG(st) << "execute UNTUPLE " << args;
|
||||
return exec_untuple_common(st, args);
|
||||
}
|
||||
|
||||
int exec_untuple_var(VmState* st) {
|
||||
VM_LOG(st) << "execute UNTUPLEVAR";
|
||||
st->check_underflow(2);
|
||||
unsigned args = st->get_stack().pop_smallint_range(255);
|
||||
return exec_untuple_common(st, args);
|
||||
}
|
||||
|
||||
int exec_untuple_first_common(VmState* st, unsigned n) {
|
||||
return do_explode_tuple(st, st->get_stack().pop_tuple_range(255, n), n);
|
||||
}
|
||||
|
||||
int exec_untuple_first(VmState* st, unsigned args) {
|
||||
args &= 15;
|
||||
VM_LOG(st) << "execute UNPACKFIRST " << args;
|
||||
return exec_untuple_first_common(st, args);
|
||||
}
|
||||
|
||||
int exec_untuple_first_var(VmState* st) {
|
||||
VM_LOG(st) << "execute UNPACKFIRSTVAR";
|
||||
st->check_underflow(2);
|
||||
unsigned args = st->get_stack().pop_smallint_range(255);
|
||||
return exec_untuple_first_common(st, args);
|
||||
}
|
||||
|
||||
int exec_explode_tuple_common(VmState* st, unsigned n) {
|
||||
auto t = st->get_stack().pop_tuple_range(n);
|
||||
unsigned l = (unsigned)(t->size());
|
||||
do_explode_tuple(st, std::move(t), l);
|
||||
st->get_stack().push_smallint(l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_explode_tuple(VmState* st, unsigned args) {
|
||||
args &= 15;
|
||||
VM_LOG(st) << "execute EXPLODE " << args;
|
||||
return exec_explode_tuple_common(st, args);
|
||||
}
|
||||
|
||||
int exec_explode_tuple_var(VmState* st) {
|
||||
VM_LOG(st) << "execute EXPLODEVAR";
|
||||
st->check_underflow(2);
|
||||
unsigned args = st->get_stack().pop_smallint_range(255);
|
||||
return exec_explode_tuple_common(st, args);
|
||||
}
|
||||
|
||||
int exec_tuple_set_index_common(VmState* st, unsigned idx) {
|
||||
Stack& stack = st->get_stack();
|
||||
auto x = stack.pop();
|
||||
auto tuple = stack.pop_tuple_range(255);
|
||||
if (idx >= tuple->size()) {
|
||||
throw VmError{Excno::range_chk, "tuple index out of range"};
|
||||
}
|
||||
tuple.write()[idx] = std::move(x);
|
||||
st->consume_tuple_gas(tuple);
|
||||
stack.push(std::move(tuple));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuple_set_index(VmState* st, unsigned args) {
|
||||
args &= 15;
|
||||
VM_LOG(st) << "execute SETINDEX " << args;
|
||||
st->check_underflow(2);
|
||||
return exec_tuple_set_index_common(st, args);
|
||||
}
|
||||
|
||||
int exec_tuple_set_index_var(VmState* st) {
|
||||
VM_LOG(st) << "execute SETINDEXVAR";
|
||||
st->check_underflow(3);
|
||||
unsigned args = st->get_stack().pop_smallint_range(254);
|
||||
return exec_tuple_set_index_common(st, args);
|
||||
}
|
||||
|
||||
int exec_tuple_quiet_set_index_common(VmState* st, unsigned idx) {
|
||||
Stack& stack = st->get_stack();
|
||||
auto x = stack.pop();
|
||||
auto tuple = stack.pop_maybe_tuple_range(255);
|
||||
if (idx >= 255) {
|
||||
throw VmError{Excno::range_chk, "tuple index out of range"};
|
||||
}
|
||||
auto tpay = tuple_extend_set_index(tuple, idx, std::move(x));
|
||||
if (tpay > 0) {
|
||||
st->consume_tuple_gas(tpay);
|
||||
}
|
||||
stack.push_maybe_tuple(std::move(tuple));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuple_quiet_set_index(VmState* st, unsigned args) {
|
||||
args &= 15;
|
||||
VM_LOG(st) << "execute SETINDEXQ " << args;
|
||||
st->check_underflow(2);
|
||||
return exec_tuple_quiet_set_index_common(st, args);
|
||||
}
|
||||
|
||||
int exec_tuple_quiet_set_index_var(VmState* st) {
|
||||
VM_LOG(st) << "execute SETINDEXVARQ";
|
||||
st->check_underflow(3);
|
||||
unsigned args = st->get_stack().pop_smallint_range(254);
|
||||
return exec_tuple_quiet_set_index_common(st, args);
|
||||
}
|
||||
|
||||
int exec_tuple_length(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute TLEN";
|
||||
auto t = stack.pop_tuple_range(255);
|
||||
stack.push_smallint((long long)(t->size()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuple_length_quiet(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute QTLEN";
|
||||
auto t = stack.pop_chk();
|
||||
stack.push_smallint(t.is_tuple() ? (long long)(t.as_tuple()->size()) : -1LL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_is_tuple(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute ISTUPLE";
|
||||
stack.push_bool(stack.pop_chk().is_tuple());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuple_last(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute LAST";
|
||||
auto t = stack.pop_tuple_range(255, 1);
|
||||
stack.push(t->back());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuple_push(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute TPUSH";
|
||||
stack.check_underflow(2);
|
||||
auto x = stack.pop();
|
||||
auto t = stack.pop_tuple_range(254);
|
||||
t.write().push_back(std::move(x));
|
||||
st->consume_tuple_gas(t);
|
||||
stack.push(std::move(t));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuple_pop(VmState* st) {
|
||||
Stack& stack = st->get_stack();
|
||||
VM_LOG(st) << "execute TPOP";
|
||||
auto t = stack.pop_tuple_range(255, 1);
|
||||
auto x = std::move(t.write().back());
|
||||
t.write().pop_back();
|
||||
st->consume_tuple_gas(t);
|
||||
stack.push(std::move(t));
|
||||
stack.push(std::move(x));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int exec_tuple_index2(VmState* st, unsigned args) {
|
||||
unsigned i = (args >> 2) & 3, j = args & 3;
|
||||
VM_LOG(st) << "execute INDEX2 " << i << "," << j;
|
||||
Stack& stack = st->get_stack();
|
||||
auto tuple = stack.pop_tuple_range(255);
|
||||
auto t1 = tuple_index(*tuple, i).as_tuple_range(255);
|
||||
if (t1.is_null()) {
|
||||
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
|
||||
}
|
||||
stack.push(tuple_index(*t1, j));
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_tuple_index2(CellSlice& cs, unsigned args) {
|
||||
unsigned i = (args >> 2) & 3, j = args & 3;
|
||||
std::ostringstream os;
|
||||
os << "INDEX2 " << i << ',' << j;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
int exec_tuple_index3(VmState* st, unsigned args) {
|
||||
unsigned i = (args >> 4) & 3, j = (args >> 2) & 3, k = args & 3;
|
||||
VM_LOG(st) << "execute INDEX3 " << i << "," << j << "," << k;
|
||||
Stack& stack = st->get_stack();
|
||||
auto tuple = stack.pop_tuple_range(255);
|
||||
auto t1 = tuple_index(*tuple, i).as_tuple_range(255);
|
||||
if (t1.is_null()) {
|
||||
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
|
||||
}
|
||||
auto t2 = tuple_index(*t1, j).as_tuple_range(255);
|
||||
if (t2.is_null()) {
|
||||
throw VmError{Excno::type_chk, "intermediate value is not a tuple"};
|
||||
}
|
||||
stack.push(tuple_index(*t2, k));
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string dump_tuple_index3(CellSlice& cs, unsigned args) {
|
||||
unsigned i = (args >> 4) & 3, j = (args >> 2) & 3, k = args & 3;
|
||||
std::ostringstream os;
|
||||
os << "INDEX3 " << i << ',' << j << ',' << k;
|
||||
return os.str();
|
||||
}
|
||||
|
||||
void register_tuple_ops(OpcodeTable& cp0) {
|
||||
using namespace std::placeholders;
|
||||
cp0.insert(OpcodeInstr::mksimple(0x6d, 8, "PUSHNULL", exec_push_null))
|
||||
.insert(OpcodeInstr::mksimple(0x6e, 8, "ISNULL", exec_is_null))
|
||||
.insert(OpcodeInstr::mkfixed(0x6f0, 12, 4, instr::dump_1c("TUPLE "), exec_mktuple))
|
||||
.insert(OpcodeInstr::mkfixed(0x6f1, 12, 4, instr::dump_1c("INDEX "), exec_tuple_index))
|
||||
.insert(OpcodeInstr::mkfixed(0x6f2, 12, 4, instr::dump_1c("UNTUPLE "), exec_untuple))
|
||||
.insert(OpcodeInstr::mkfixed(0x6f3, 12, 4, instr::dump_1c("UNPACKFIRST "), exec_untuple_first))
|
||||
.insert(OpcodeInstr::mkfixed(0x6f4, 12, 4, instr::dump_1c("EXPLODE "), exec_explode_tuple))
|
||||
.insert(OpcodeInstr::mkfixed(0x6f5, 12, 4, instr::dump_1c("SETINDEX "), exec_tuple_set_index))
|
||||
.insert(OpcodeInstr::mkfixed(0x6f6, 12, 4, instr::dump_1c("INDEXQ "), exec_tuple_quiet_index))
|
||||
.insert(OpcodeInstr::mkfixed(0x6f7, 12, 4, instr::dump_1c("SETINDEXQ "), exec_tuple_quiet_set_index))
|
||||
.insert(OpcodeInstr::mksimple(0x6f80, 16, "TUPLEVAR", exec_mktuple_var))
|
||||
.insert(OpcodeInstr::mksimple(0x6f81, 16, "INDEXVAR", exec_tuple_index_var))
|
||||
.insert(OpcodeInstr::mksimple(0x6f82, 16, "UNTUPLEVAR", exec_untuple_var))
|
||||
.insert(OpcodeInstr::mksimple(0x6f83, 16, "UNPACKFIRSTVAR", exec_untuple_first_var))
|
||||
.insert(OpcodeInstr::mksimple(0x6f84, 16, "EXPLODEVAR", exec_explode_tuple_var))
|
||||
.insert(OpcodeInstr::mksimple(0x6f85, 16, "SETINDEXVAR", exec_tuple_set_index_var))
|
||||
.insert(OpcodeInstr::mksimple(0x6f86, 16, "INDEXVARQ", exec_tuple_quiet_index_var))
|
||||
.insert(OpcodeInstr::mksimple(0x6f87, 16, "SETINDEXVARQ", exec_tuple_quiet_set_index_var))
|
||||
.insert(OpcodeInstr::mksimple(0x6f88, 16, "TLEN", exec_tuple_length))
|
||||
.insert(OpcodeInstr::mksimple(0x6f89, 16, "QTLEN", exec_tuple_length_quiet))
|
||||
.insert(OpcodeInstr::mksimple(0x6f8a, 16, "ISTUPLE", exec_is_tuple))
|
||||
.insert(OpcodeInstr::mksimple(0x6f8b, 16, "LAST", exec_tuple_last))
|
||||
.insert(OpcodeInstr::mksimple(0x6f8c, 16, "TPUSH", exec_tuple_push))
|
||||
.insert(OpcodeInstr::mksimple(0x6f8d, 16, "TPOP", exec_tuple_pop))
|
||||
.insert(OpcodeInstr::mksimple(0x6fa0, 16, "NULLSWAPIF", std::bind(exec_null_swap_if, _1, true, 0)))
|
||||
.insert(OpcodeInstr::mksimple(0x6fa1, 16, "NULLSWAPIFNOT", std::bind(exec_null_swap_if, _1, false, 0)))
|
||||
.insert(OpcodeInstr::mksimple(0x6fa2, 16, "NULLROTRIF", std::bind(exec_null_swap_if, _1, true, 1)))
|
||||
.insert(OpcodeInstr::mksimple(0x6fa3, 16, "NULLROTRIFNOT", std::bind(exec_null_swap_if, _1, false, 1)))
|
||||
.insert(OpcodeInstr::mkfixed(0x6fb, 12, 4, dump_tuple_index2, exec_tuple_index2))
|
||||
.insert(OpcodeInstr::mkfixed(0x6fc >> 2, 10, 6, dump_tuple_index3, exec_tuple_index3));
|
||||
}
|
||||
|
||||
} // namespace vm
|
||||
27
crypto/vm/tupleops.h
Normal file
27
crypto/vm/tupleops.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace vm {
|
||||
|
||||
class OpcodeTable;
|
||||
|
||||
void register_tuple_ops(OpcodeTable& cp0);
|
||||
|
||||
} // namespace vm
|
||||
39
crypto/vm/vmstate.h
Normal file
39
crypto/vm/vmstate.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "common/refcnt.hpp"
|
||||
#include "vm/cells.h"
|
||||
|
||||
#include "td/utils/Context.h"
|
||||
|
||||
namespace vm {
|
||||
using td::Ref;
|
||||
|
||||
class VmStateInterface : public td::Context<VmStateInterface> {
|
||||
public:
|
||||
virtual ~VmStateInterface() = default;
|
||||
virtual Ref<vm::Cell> load_library(
|
||||
td::ConstBitPtr hash) { // may throw a dictionary exception; returns nullptr if library is not found
|
||||
return {};
|
||||
}
|
||||
virtual void register_cell_load(){};
|
||||
virtual void register_cell_create(){};
|
||||
};
|
||||
|
||||
} // namespace vm
|
||||
Loading…
Add table
Add a link
Reference in a new issue