diff --git a/crypto/block/block-parse.cpp b/crypto/block/block-parse.cpp index 29d6ebea..ae1df343 100644 --- a/crypto/block/block-parse.cpp +++ b/crypto/block/block-parse.cpp @@ -241,6 +241,28 @@ bool MsgAddressInt::extract_std_address(vm::CellSlice& cs, ton::WorkchainId& wor return false; } +bool MsgAddressInt::store_std_address(vm::CellBuilder& cb, ton::WorkchainId workchain, + const ton::StdSmcAddress& addr) const { + if (workchain >= -128 && workchain < 128) { + return cb.store_long_bool(4, 3) // addr_std$10 anycast:(Maybe Anycast) + && cb.store_long_bool(workchain, 8) // workchain_id:int8 + && cb.store_bits_bool(addr); // address:bits256 = MsgAddressInt; + } else { + return cb.store_long_bool(0xd00, 12) // addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9) + && cb.store_long_bool(workchain, 32) // workchain_id:int32 + && cb.store_bits_bool(addr); // address:(bits addr_len) = MsgAddressInt; + } +} + +Ref MsgAddressInt::pack_std_address(ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const { + vm::CellBuilder cb; + if (store_std_address(cb, workchain, addr)) { + return vm::load_cell_slice_ref(cb.finalize()); + } else { + return {}; + } +} + const MsgAddressInt t_MsgAddressInt; bool MsgAddress::validate_skip(vm::CellSlice& cs, bool weak) const { diff --git a/crypto/block/block-parse.h b/crypto/block/block-parse.h index 410a8ce5..8ea3325e 100644 --- a/crypto/block/block-parse.h +++ b/crypto/block/block-parse.h @@ -285,6 +285,8 @@ struct MsgAddressInt final : TLB_Complex { bool rewrite = true) const; bool extract_std_address(vm::CellSlice& cs, ton::WorkchainId& workchain, ton::StdSmcAddress& addr, bool rewrite = true) const; + bool store_std_address(vm::CellBuilder& cb, ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const; + Ref pack_std_address(ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const; }; extern const MsgAddressInt t_MsgAddressInt; diff --git a/crypto/block/block.tlb b/crypto/block/block.tlb index 882e0d7b..a30cd89a 100644 --- a/crypto/block/block.tlb +++ b/crypto/block/block.tlb @@ -727,3 +727,33 @@ after:^VmCont = VmCont; vmc_while_body$110011 cond:^VmCont body:^VmCont after:^VmCont = VmCont; vmc_pushint$1111 value:int32 next:^VmCont = VmCont; + +// +// DNS RECORDS +// +_ (HashmapE 16 ^DNSRecord) = DNS_RecordSet; + +chunk_ref$_ {n:#} ref:^(TextChunks (n + 1)) = TextChunkRef (n + 1); +chunk_ref_empty$_ = TextChunkRef 0; +text_chunk$_ {n:#} len:(## 8) data:(bits (len * 8)) next:(TextChunkRef n) = TextChunks (n + 1); +text_chunk_empty$_ = TextChunks 0; +text$_ chunks:(## 8) rest:(TextChunks chunks) = Text; +dns_text#1eda _:Text = DNSRecord; + +dns_next_resolver#ba93 resolver:MsgAddressInt = DNSRecord; // usually in record #-1 + +dns_adnl_address#ad01 adnl_addr:bits256 flags:(## 8) { flags <= 1 } + proto_list:flags . 0?ProtoList = DNSRecord; // often in record #2 +proto_list_nil$0 = ProtoList; +proto_list_next$1 head:Protocol tail:ProtoList = ProtoList; +proto_http#4854 = Protocol; + +dns_smc_address#9fd3 smc_addr:MsgAddressInt flags:(## 8) { flags <= 1 } + cap_list:flags . 0?SmcCapList = DNSRecord; // often in record #1 +cap_list_nil$0 = SmcCapList; +cap_list_next$1 head:SmcCapability tail:SmcCapList = SmcCapList; +cap_method_seqno#5371 = SmcCapability; +cap_method_pubkey#71f4 = SmcCapability; +cap_is_wallet#2177 = SmcCapability; +cap_name#ff name:Text = SmcCapability; + diff --git a/crypto/block/create-state.cpp b/crypto/block/create-state.cpp index 8b92a24f..02896e7b 100644 --- a/crypto/block/create-state.cpp +++ b/crypto/block/create-state.cpp @@ -633,6 +633,88 @@ void init_words_custom(fift::Dictionary& d) { d.def_stack_word("isWorkchainDescr? ", interpret_is_workchain_descr); } +tlb::TypenameLookup tlb_dict; + +// ( S -- T -1 or 0 ) Looks up TLB type by name +void interpret_tlb_type_lookup(vm::Stack& stack) { + auto ptr = tlb_dict.lookup(stack.pop_string()); + if (ptr) { + stack.push_make_object(ptr); + } + stack.push_bool(ptr); +} + +td::Ref pop_tlb_type(vm::Stack& stack) { + auto res = stack.pop_object(); + if (res.is_null()) { + throw vm::VmError{vm::Excno::type_chk, "not a TLB type"}; + } + return res; +} + +// ( T -- S ) Gets TLB type name +void interpret_tlb_type_name(vm::Stack& stack) { + stack.push_string((*pop_tlb_type(stack))->get_type_name()); +} + +// ( T -- ) Prints TLB type name +void interpret_print_tlb_type(vm::Stack& stack) { + std::cout << (*pop_tlb_type(stack))->get_type_name(); +} + +// ( s T -- ) Dumps (part of) slice s as a value of TLB type T +void interpret_tlb_dump_as(vm::Stack& stack) { + auto tp = pop_tlb_type(stack); + (*tp)->print(std::cout, stack.pop_cellslice()); +} + +// ( s T -- s' S -1 or 0 ) +// Detects prefix of slice s that is a value of TLB type T, returns the remainder as s', and prints the value into String S. +void interpret_tlb_dump_to_str(vm::Stack& stack) { + auto tp = pop_tlb_type(stack); + auto cs = stack.pop_cellslice(); + std::ostringstream os; + bool ok = (*tp)->print_skip(os, cs.write()); + if (ok) { + stack.push(std::move(cs)); + stack.push_string(os.str()); + } + stack.push_bool(ok); +} + +// ( s T -- s' -1 or 0 ) Skips the only prefix of slice s that can be a value of TLB type T +void interpret_tlb_skip(vm::Stack& stack) { + auto tp = pop_tlb_type(stack); + auto cs = stack.pop_cellslice(); + bool ok = (*tp)->skip(cs.write()); + if (ok) { + stack.push(std::move(cs)); + } + stack.push_bool(ok); +} + +// ( s T -- s' -1 or 0 ) Checks whether a prefix of slice s is a valid value of TLB type T, and skips it +void interpret_tlb_validate_skip(vm::Stack& stack) { + auto tp = pop_tlb_type(stack); + auto cs = stack.pop_cellslice(); + bool ok = (*tp)->validate_skip(cs.write()); + if (ok) { + stack.push(std::move(cs)); + } + stack.push_bool(ok); +} + +void init_words_tlb(fift::Dictionary& d) { + tlb_dict.register_types(block::gen::register_simple_types); + d.def_stack_word("tlb-type-lookup ", interpret_tlb_type_lookup); + d.def_stack_word("tlb-type-name ", interpret_tlb_type_name); + d.def_stack_word("tlb. ", interpret_print_tlb_type); + d.def_stack_word("tlb-dump-as ", interpret_tlb_dump_as); + d.def_stack_word("(tlb-dump-str?) ", interpret_tlb_dump_to_str); + d.def_stack_word("tlb-skip ", interpret_tlb_skip); + d.def_stack_word("tlb-validate-skip ", interpret_tlb_validate_skip); +} + void usage(const char* progname) { std::cerr << "Creates initial state for a TON blockchain, using configuration defined by Fift-language source files\n"; @@ -739,6 +821,7 @@ int main(int argc, char* const argv[]) { fift::init_words_vm(config.dictionary); fift::init_words_ton(config.dictionary); init_words_custom(config.dictionary); + init_words_tlb(config.dictionary); if (script_mode) { fift::import_cmdline_args(config.dictionary, source_list.empty() ? "" : source_list[0], argc - optind, diff --git a/crypto/block/dump-block.cpp b/crypto/block/dump-block.cpp index 86465bf6..25eb0eed 100644 --- a/crypto/block/dump-block.cpp +++ b/crypto/block/dump-block.cpp @@ -191,7 +191,8 @@ void test2(vm::CellSlice& cs) { } void usage() { - std::cout << "usage: dump-block [-S][]\n\tor dump-block -h\n\tDumps specified blockchain block or state " + std::cout << "usage: dump-block [-t][-S][]\n\tor dump-block -h\n\tDumps specified blockchain " + "block or state " "from , or runs some tests\n\t-S\tDump a blockchain state instead of a block\n"; std::exit(2); } @@ -199,15 +200,20 @@ void usage() { int main(int argc, char* const argv[]) { int i; int new_verbosity_level = VERBOSITY_NAME(INFO); - bool dump_state = false, dump_vmcont = false; + const char* tname = nullptr; + const tlb::TLB* type = &block::gen::t_Block; auto zerostate = std::make_unique(); - while ((i = getopt(argc, argv, "CShv:")) != -1) { + while ((i = getopt(argc, argv, "CSt:hv:")) != -1) { switch (i) { case 'C': - dump_vmcont = true; + type = &block::gen::t_VmCont; break; case 'S': - dump_state = true; + type = &block::gen::t_ShardStateUnsplit; + break; + case 't': + tname = optarg; + type = nullptr; break; case 'v': new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer(td::Slice(optarg))); @@ -233,13 +239,18 @@ int main(int argc, char* const argv[]) { vm::CellSlice cs{vm::NoVm(), boc}; cs.print_rec(std::cout); std::cout << std::endl; - auto& type = !dump_vmcont - ? (dump_state ? (const tlb::TLB&)block::gen::t_ShardStateUnsplit : block::gen::t_Block) - : block::gen::t_VmCont; - type.print_ref(std::cout, boc); + if (!type) { + tlb::TypenameLookup dict(block::gen::register_simple_types); + type = dict.lookup(tname); + if (!type) { + std::cerr << "unknown TL-B type " << tname << std::endl; + std::exit(3); + } + } + type->print_ref(std::cout, boc); std::cout << std::endl; - bool ok = type.validate_ref(boc); - std::cout << "(" << (ok ? "" : "in") << "valid " << type << ")" << std::endl; + bool ok = type->validate_ref(boc); + std::cout << "(" << (ok ? "" : "in") << "valid " << *type << ")" << std::endl; } } if (!done) { diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 997e06a3..f0035a00 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -1064,19 +1064,23 @@ x{FFF0} @Defop SETCPX variable @proccnt variable @proclist variable @procdict +variable @gvarcnt 19 constant @procdictkeylen { @proclist @ cons @proclist ! } : @proclistadd { dup @procdictkeylen fits not abort"procedure index out of range" 1 'nop does swap dup @proclistadd 0 (create) } : @declproc +{ 1 'nop does swap 0 (create) } : @declglobvar { @proccnt @ 1+ dup @proccnt ! @declproc } : @newproc -{ 0 =: main @proclist null! @proccnt 0! +{ @gvarcnt @ 1+ dup @gvarcnt ! @declglobvar } : @newglobvar +{ 0 =: main @proclist null! @proccnt 0! @gvarcnt 0! { bl word @newproc } : NEWPROC { bl word dup (def?) ' drop ' @newproc cond } : DECLPROC { bl word dup find { nip execute <> abort"method redefined with different id" } { swap @declproc } cond } : DECLMETHOD + { bl word @newglobvar } : DECLGLOBVAR "main" @proclistadd dictnew @procdict ! } : PROGRAM{ diff --git a/crypto/fift/lib/Fift.fif b/crypto/fift/lib/Fift.fif index 4a16aca6..edb4ad92 100644 --- a/crypto/fift/lib/Fift.fif +++ b/crypto/fift/lib/Fift.fif @@ -116,3 +116,15 @@ variable base { 9 hold } : +tab { "" swap { 0 word 2dup $cmp } { rot swap $+ +cr swap } while 2drop } : scan-until-word { 0 word -trailing scan-until-word 1 'nop } ::_ $<< +{ 0x40 runvmx } : runvmcode +{ 0x48 runvmx } : gasrunvmcode +{ 0x43 runvmx } : runvmdict +{ 0x4b runvmx } : gasrunvmdict +{ 0x45 runvmx } : runvm +{ 0x4d runvmx } : gasrunvm +{ 0x55 runvmx } : runvmctx +{ 0x5d runvmx } : gasrunvmctx +{ 0x75 runvmx } : runvmctxact +{ 0x7d runvmx } : gasrunvmctxact +{ 0x35 runvmx } : runvmctxactq +{ 0x3d runvmx } : gasrunvmctxactq diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp index 043fc17f..1fc12a2c 100644 --- a/crypto/fift/words.cpp +++ b/crypto/fift/words.cpp @@ -1512,11 +1512,12 @@ void interpret_store_dict(vm::Stack& stack) { } // val key dict keylen -- dict' ? -void interpret_dict_add_u(vm::Stack& stack, vm::Dictionary::SetMode mode, bool add_builder, bool sgnd) { +void interpret_dict_add(vm::Stack& stack, vm::Dictionary::SetMode mode, bool add_builder, int sgnd) { int n = stack.pop_smallint_range(vm::Dictionary::max_key_bits); vm::Dictionary dict{stack.pop_maybe_cell(), n}; unsigned char buffer[vm::Dictionary::max_key_bytes]; - vm::BitSlice key = dict.integer_key(stack.pop_int(), n, sgnd, buffer); + vm::BitSlice key = + (sgnd >= 0) ? dict.integer_key(stack.pop_int(), n, sgnd, buffer) : stack.pop_cellslice()->prefetch_bits(n); if (!key.is_valid()) { throw IntError{"not enough bits for a dictionary key"}; } @@ -1530,11 +1531,12 @@ void interpret_dict_add_u(vm::Stack& stack, vm::Dictionary::SetMode mode, bool a stack.push_bool(res); } -void interpret_dict_get_u(vm::Stack& stack, bool sgnd) { +void interpret_dict_get(vm::Stack& stack, int sgnd) { int n = stack.pop_smallint_range(vm::Dictionary::max_key_bits); vm::Dictionary dict{stack.pop_maybe_cell(), n}; unsigned char buffer[vm::Dictionary::max_key_bytes]; - vm::BitSlice key = dict.integer_key(stack.pop_int(), n, sgnd, buffer); + vm::BitSlice key = + (sgnd >= 0) ? dict.integer_key(stack.pop_int(), n, sgnd, buffer) : stack.pop_cellslice()->prefetch_bits(n); if (!key.is_valid()) { throw IntError{"not enough bits for a dictionary key"}; } @@ -2169,6 +2171,7 @@ class StringLogger : public td::LogInterface { } std::string res; }; + class OstreamLogger : public td::LogInterface { public: explicit OstreamLogger(std::ostream* stream) : stream_(stream) { @@ -2191,59 +2194,42 @@ std::vector> get_vm_libraries() { } } -void interpret_run_vm_code(IntCtx& ctx, bool with_gas) { - long long gas_limit = with_gas ? ctx.stack.pop_long_range(vm::GasLimits::infty) : vm::GasLimits::infty; - auto cs = ctx.stack.pop_cellslice(); - OstreamLogger ostream_logger(ctx.error_stream); - auto log = create_vm_log(ctx.error_stream ? &ostream_logger : nullptr); - vm::GasLimits gas{gas_limit}; - int res = vm::run_vm_code(cs, ctx.stack, 0, nullptr, log, nullptr, &gas, get_vm_libraries()); - ctx.stack.push_smallint(res); - if (with_gas) { - ctx.stack.push_smallint(gas.gas_consumed()); +// mode: -1 = pop from stack +// +1 = same_c3 (set c3 to code) +// +2 = push_0 (push an implicit 0 before running the code) +// +4 = load c4 (persistent data) from stack and return its final value +// +8 = load gas limit from stack and return consumed gas +// +16 = load c7 (smart-contract context) +// +32 = return c5 (actions) +// +64 = log vm ops to stderr +void interpret_run_vm(IntCtx& ctx, int mode) { + if (mode < 0) { + mode = ctx.stack.pop_smallint_range(0xff); } -} - -void interpret_run_vm_dict(IntCtx& ctx, bool with_gas) { - long long gas_limit = with_gas ? ctx.stack.pop_long_range(vm::GasLimits::infty) : vm::GasLimits::infty; - auto cs = ctx.stack.pop_cellslice(); - OstreamLogger ostream_logger(ctx.error_stream); - auto log = create_vm_log(ctx.error_stream ? &ostream_logger : nullptr); - vm::GasLimits gas{gas_limit}; - int res = vm::run_vm_code(cs, ctx.stack, 3, nullptr, log, nullptr, &gas, get_vm_libraries()); - ctx.stack.push_smallint(res); - if (with_gas) { - ctx.stack.push_smallint(gas.gas_consumed()); + bool with_data = mode & 4; + Ref c7; + Ref data, actions; + long long gas_limit = (mode & 8) ? ctx.stack.pop_long_range(vm::GasLimits::infty) : vm::GasLimits::infty; + if (mode & 16) { + c7 = ctx.stack.pop_tuple(); } -} - -void interpret_run_vm(IntCtx& ctx, bool with_gas) { - long long gas_limit = with_gas ? ctx.stack.pop_long_range(vm::GasLimits::infty) : vm::GasLimits::infty; - auto data = ctx.stack.pop_cell(); - auto cs = ctx.stack.pop_cellslice(); - OstreamLogger ostream_logger(ctx.error_stream); - auto log = create_vm_log(ctx.error_stream ? &ostream_logger : nullptr); - vm::GasLimits gas{gas_limit}; - int res = vm::run_vm_code(cs, ctx.stack, 1, &data, log, nullptr, &gas, get_vm_libraries()); - ctx.stack.push_smallint(res); - ctx.stack.push_cell(std::move(data)); - if (with_gas) { - ctx.stack.push_smallint(gas.gas_consumed()); + if (with_data) { + data = ctx.stack.pop_cell(); } -} - -void interpret_run_vm_c7(IntCtx& ctx, bool with_gas) { - long long gas_limit = with_gas ? ctx.stack.pop_long_range(vm::GasLimits::infty) : vm::GasLimits::infty; - auto c7 = ctx.stack.pop_tuple(); - auto data = ctx.stack.pop_cell(); auto cs = ctx.stack.pop_cellslice(); OstreamLogger ostream_logger(ctx.error_stream); - auto log = create_vm_log(ctx.error_stream ? &ostream_logger : nullptr); + auto log = create_vm_log((mode & 64) && ctx.error_stream ? &ostream_logger : nullptr); vm::GasLimits gas{gas_limit}; - int res = vm::run_vm_code(cs, ctx.stack, 1, &data, log, nullptr, &gas, get_vm_libraries(), std::move(c7)); + int res = + vm::run_vm_code(cs, ctx.stack, mode & 3, &data, log, nullptr, &gas, get_vm_libraries(), std::move(c7), &actions); ctx.stack.push_smallint(res); - ctx.stack.push_cell(std::move(data)); - if (with_gas) { + if (with_data) { + ctx.stack.push_cell(std::move(data)); + } + if (mode & 32) { + ctx.stack.push_cell(std::move(actions)); + } + if (mode & 8) { ctx.stack.push_smallint(gas.gas_consumed()); } } @@ -2759,16 +2745,21 @@ void init_words_common(Dictionary& d) { d.def_stack_word("dict, ", interpret_store_dict); d.def_stack_word("dict@ ", std::bind(interpret_load_dict, _1, false)); d.def_stack_word("dict@+ ", std::bind(interpret_load_dict, _1, true)); - d.def_stack_word("udict!+ ", std::bind(interpret_dict_add_u, _1, vm::Dictionary::SetMode::Add, false, false)); - d.def_stack_word("udict! ", std::bind(interpret_dict_add_u, _1, vm::Dictionary::SetMode::Set, false, false)); - d.def_stack_word("b>udict!+ ", std::bind(interpret_dict_add_u, _1, vm::Dictionary::SetMode::Add, true, false)); - d.def_stack_word("b>udict! ", std::bind(interpret_dict_add_u, _1, vm::Dictionary::SetMode::Set, true, false)); - d.def_stack_word("udict@ ", std::bind(interpret_dict_get_u, _1, false)); - d.def_stack_word("idict!+ ", std::bind(interpret_dict_add_u, _1, vm::Dictionary::SetMode::Add, false, true)); - d.def_stack_word("idict! ", std::bind(interpret_dict_add_u, _1, vm::Dictionary::SetMode::Set, false, true)); - d.def_stack_word("b>idict!+ ", std::bind(interpret_dict_add_u, _1, vm::Dictionary::SetMode::Add, true, true)); - d.def_stack_word("b>idict! ", std::bind(interpret_dict_add_u, _1, vm::Dictionary::SetMode::Set, true, true)); - d.def_stack_word("idict@ ", std::bind(interpret_dict_get_u, _1, true)); + d.def_stack_word("sdict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, false, -1)); + d.def_stack_word("sdict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, -1)); + d.def_stack_word("b>sdict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, -1)); + d.def_stack_word("b>sdict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, -1)); + d.def_stack_word("sdict@ ", std::bind(interpret_dict_get, _1, -1)); + d.def_stack_word("udict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, false, 0)); + d.def_stack_word("udict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, 0)); + d.def_stack_word("b>udict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, 0)); + d.def_stack_word("b>udict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, 0)); + d.def_stack_word("udict@ ", std::bind(interpret_dict_get, _1, 0)); + d.def_stack_word("idict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, false, 1)); + d.def_stack_word("idict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, 1)); + d.def_stack_word("b>idict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, 1)); + d.def_stack_word("b>idict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, 1)); + d.def_stack_word("idict@ ", std::bind(interpret_dict_get, _1, 1)); d.def_stack_word("pfxdict!+ ", std::bind(interpret_pfx_dict_add, _1, vm::Dictionary::SetMode::Add, false)); d.def_stack_word("pfxdict! ", std::bind(interpret_pfx_dict_add, _1, vm::Dictionary::SetMode::Set, false)); d.def_stack_word("pfxdict@ ", interpret_pfx_dict_get); @@ -2867,14 +2858,9 @@ void init_words_vm(Dictionary& d) { vm::init_op_cp0(); // vm run d.def_stack_word("vmlibs ", std::bind(interpret_literal, _1, vm::StackEntry{vm_libraries})); - d.def_ctx_word("runvmcode ", std::bind(interpret_run_vm_code, _1, false)); - d.def_ctx_word("gasrunvmcode ", std::bind(interpret_run_vm_code, _1, true)); - d.def_ctx_word("runvmdict ", std::bind(interpret_run_vm_dict, _1, false)); - d.def_ctx_word("gasrunvmdict ", std::bind(interpret_run_vm_dict, _1, true)); - d.def_ctx_word("runvm ", std::bind(interpret_run_vm, _1, false)); - d.def_ctx_word("gasrunvm ", std::bind(interpret_run_vm, _1, true)); - d.def_ctx_word("runvmctx ", std::bind(interpret_run_vm_c7, _1, false)); - d.def_ctx_word("gasrunvmctx ", std::bind(interpret_run_vm_c7, _1, true)); + // d.def_ctx_word("runvmcode ", std::bind(interpret_run_vm, _1, 0x40)); + // d.def_ctx_word("runvm ", std::bind(interpret_run_vm, _1, 0x45)); + d.def_ctx_word("runvmx ", std::bind(interpret_run_vm, _1, -1)); d.def_ctx_word("dbrunvm ", interpret_db_run_vm); d.def_ctx_word("dbrunvm-parallel ", interpret_db_run_vm_parallel); d.def_stack_word("vmcont, ", interpret_store_vm_cont); diff --git a/crypto/func/abscode.cpp b/crypto/func/abscode.cpp index 8ff204f1..3d3348a1 100644 --- a/crypto/func/abscode.cpp +++ b/crypto/func/abscode.cpp @@ -343,6 +343,12 @@ void Op::show(std::ostream& os, const std::vector& vars, std::string pfx show_var_list(os, left, vars); os << " := " << (fun_ref ? fun_ref->name() : "(null)") << std::endl; break; + case _SetGlob: + os << pfx << dis << "SETGLOB "; + os << (fun_ref ? fun_ref->name() : "(null)") << " := "; + show_var_list(os, right, vars); + os << std::endl; + break; case _Repeat: os << pfx << dis << "REPEAT "; show_var_list(os, left, vars); diff --git a/crypto/func/analyzer.cpp b/crypto/func/analyzer.cpp index 354486cf..14d0a084 100644 --- a/crypto/func/analyzer.cpp +++ b/crypto/func/analyzer.cpp @@ -365,6 +365,13 @@ bool Op::compute_used_vars(const CodeBlob& code, bool edit) { } return std_compute_used_vars(); } + case _SetGlob: { + // GLOB = right + if (right.empty() && edit) { + disable(); + } + return std_compute_used_vars(right.empty()); + } case _Let: { // left = right std::size_t cnt = next_var_info.count_used(left); @@ -531,6 +538,7 @@ bool prune_unreachable(std::unique_ptr& ops) { switch (op.cl) { case Op::_IntConst: case Op::_GlobVar: + case Op::_SetGlob: case Op::_Call: case Op::_CallInd: case Op::_Import: @@ -694,7 +702,6 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { values.add_newval(left[0]).set_const(int_const); break; } - case _GlobVar: case _Call: { prepare_args(values); auto func = dynamic_cast(fun_ref->value); @@ -717,12 +724,15 @@ VarDescrList Op::fwd_analyze(VarDescrList values) { } break; } + case _GlobVar: case _CallInd: { for (var_idx_t i : left) { values.add_newval(i); } break; } + case _SetGlob: + break; case _Let: { std::vector old_val; assert(left.size() == right.size()); @@ -832,6 +842,7 @@ bool Op::mark_noreturn() { case _Import: case _IntConst: case _Let: + case _SetGlob: case _GlobVar: case _CallInd: case _Call: diff --git a/crypto/func/builtins.cpp b/crypto/func/builtins.cpp index 30beb7c8..41951a53 100644 --- a/crypto/func/builtins.cpp +++ b/crypto/func/builtins.cpp @@ -27,8 +27,8 @@ using namespace std::literals::string_literals; * */ -int glob_func_cnt, undef_func_cnt; -std::vector glob_func; +int glob_func_cnt, undef_func_cnt, glob_var_cnt; +std::vector glob_func, glob_vars; SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) { sym_idx_t name_idx = sym::symbols.lookup(name, 1); @@ -44,30 +44,49 @@ SymDef* predefine_builtin_func(std::string name, TypeExpr* func_type) { } template -void define_builtin_func(std::string name, TypeExpr* func_type, const T& func, bool impure = false) { +SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, bool impure = false) { SymDef* def = predefine_builtin_func(name, func_type); def->value = new SymValAsmFunc{func_type, func, impure}; + return def; } template -void define_builtin_func(std::string name, TypeExpr* func_type, const T& func, std::initializer_list arg_order, - std::initializer_list ret_order = {}, bool impure = false) { +SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const T& func, std::initializer_list arg_order, + std::initializer_list ret_order = {}, bool impure = false) { SymDef* def = predefine_builtin_func(name, func_type); def->value = new SymValAsmFunc{func_type, func, arg_order, ret_order, impure}; + return def; } -void define_builtin_func(std::string name, TypeExpr* func_type, const AsmOp& macro, - std::initializer_list arg_order, std::initializer_list ret_order = {}, - bool impure = false) { +SymDef* define_builtin_func(std::string name, TypeExpr* func_type, const AsmOp& macro, + std::initializer_list arg_order, std::initializer_list ret_order = {}, + bool impure = false) { SymDef* def = predefine_builtin_func(name, func_type); def->value = new SymValAsmFunc{func_type, make_simple_compile(macro), arg_order, ret_order, impure}; + return def; } -bool SymValAsmFunc::compile(AsmOpList& dest, std::vector& in, std::vector& out) const { +SymDef* force_autoapply(SymDef* def) { + if (def) { + auto val = dynamic_cast(def->value); + if (val) { + val->auto_apply = true; + } + } + return def; +} + +template +SymDef* define_builtin_const(std::string name, TypeExpr* const_type, Args&&... args) { + return force_autoapply( + define_builtin_func(name, TypeExpr::new_map(TypeExpr::new_unit(), const_type), std::forward(args)...)); +} + +bool SymValAsmFunc::compile(AsmOpList& dest, std::vector& out, std::vector& in) const { if (simple_compile) { - return dest.append(simple_compile(in, out)); + return dest.append(simple_compile(out, in)); } else if (ext_compile) { - return ext_compile(dest, in, out); + return ext_compile(dest, out, in); } else { return false; } @@ -317,6 +336,12 @@ AsmOp exec_arg_op(std::string op, td::RefInt256 arg, int args, int retv) { return AsmOp::Custom(os.str(), args, retv); } +AsmOp exec_arg2_op(std::string op, long long imm1, long long imm2, int args, int retv) { + std::ostringstream os; + os << imm1 << ' ' << imm2 << ' ' << op; + return AsmOp::Custom(os.str(), args, retv); +} + AsmOp push_const(td::RefInt256 x) { return AsmOp::IntConst(std::move(x)); } @@ -918,11 +943,11 @@ void define_builtins() { define_builtin_func("_<=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 6)); define_builtin_func("_>=_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 3)); define_builtin_func("_<=>_", arith_bin_op, std::bind(compile_cmp_int, _1, _2, 7)); - define_builtin_func("true", Int, /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true)); - define_builtin_func("false", Int, /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false)); + define_builtin_const("true", Int, /* AsmOp::Const("TRUE") */ std::bind(compile_bool_const, _1, _2, true)); + define_builtin_const("false", Int, /* AsmOp::Const("FALSE") */ std::bind(compile_bool_const, _1, _2, false)); // define_builtin_func("null", Null, AsmOp::Const("PUSHNULL")); - define_builtin_func("nil", Tuple, AsmOp::Const("PUSHNULL")); - define_builtin_func("Nil", Tuple, AsmOp::Const("NIL")); + define_builtin_const("nil", Tuple, AsmOp::Const("PUSHNULL")); + define_builtin_const("Nil", Tuple, AsmOp::Const("NIL")); define_builtin_func("null?", TypeExpr::new_forall({X}, TypeExpr::new_map(X, Int)), compile_is_null); define_builtin_func("throw", impure_un_op, compile_throw, true); define_builtin_func("throw_if", impure_bin_op, std::bind(compile_cond_throw, _1, _2, true), true); diff --git a/crypto/func/codegen.cpp b/crypto/func/codegen.cpp index 70ae929d..91bb0be9 100644 --- a/crypto/func/codegen.cpp +++ b/crypto/func/codegen.cpp @@ -84,7 +84,9 @@ void Stack::forget_const() { void Stack::issue_pop(int i) { validate(i); - o << AsmOp::Pop(i); + if (output_enabled()) { + o << AsmOp::Pop(i); + } at(i) = get(0); s.pop_back(); modified(); @@ -92,7 +94,9 @@ void Stack::issue_pop(int i) { void Stack::issue_push(int i) { validate(i); - o << AsmOp::Push(i); + if (output_enabled()) { + o << AsmOp::Push(i); + } s.push_back(get(i)); modified(); } @@ -101,7 +105,9 @@ void Stack::issue_xchg(int i, int j) { validate(i); validate(j); if (i != j && get(i) != get(j)) { - o << AsmOp::Xchg(i, j); + if (output_enabled()) { + o << AsmOp::Xchg(i, j); + } std::swap(at(i), at(j)); modified(); } @@ -183,6 +189,10 @@ void Stack::enforce_state(const StackLayout& req_stack) { if (i < depth() && s[i].first == x) { continue; } + while (depth() > 0 && std::find(req_stack.cbegin(), req_stack.cend(), get(0).first) == req_stack.cend()) { + // current TOS entry is unused in req_stack, drop it + issue_pop(0); + } int j = find(x); if (j >= depth() - i) { issue_push(j); @@ -292,27 +302,61 @@ bool Op::generate_code_step(Stack& stack) { } return true; } - case _GlobVar: { - assert(left.size() == 1); - auto p = next_var_info[left[0]]; - if (!p || p->is_unused() || disabled()) { + case _GlobVar: + if (dynamic_cast(fun_ref->value)) { + bool used = false; + for (auto i : left) { + auto p = next_var_info[i]; + if (p && !p->is_unused()) { + used = true; + } + } + if (!used || disabled()) { + return true; + } + std::string name = sym::symbols.get_name(fun_ref->sym_idx); + stack.o << AsmOp::Custom(name + " GETGLOB", 0, 1); + if (left.size() != 1) { + assert(left.size() <= 15); + stack.o << exec_arg_op("UNTUPLE", (int)left.size(), 1, (int)left.size()); + } + for (auto i : left) { + stack.push_new_var(i); + } + return true; + } else { + assert(left.size() == 1); + auto p = next_var_info[left[0]]; + if (!p || p->is_unused() || disabled()) { + return true; + } + stack.o << "CONT:<{"; + stack.o.indent(); + auto func = dynamic_cast(fun_ref->value); + if (func) { + // TODO: create and compile a true lambda instead of this (so that arg_order and ret_order would work correctly) + std::vector args0, res; + TypeExpr::remove_indirect(func->sym_type); + assert(func->get_type()->is_map()); + auto wr = func->get_type()->args.at(0)->get_width(); + auto wl = func->get_type()->args.at(1)->get_width(); + assert(wl >= 0 && wr >= 0); + for (int i = 0; i < wl; i++) { + res.emplace_back(0); + } + for (int i = 0; i < wr; i++) { + args0.emplace_back(0); + } + func->compile(stack.o, res, args0); // compile res := f (args0) + } else { + std::string name = sym::symbols.get_name(fun_ref->sym_idx); + stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size()); + } + stack.o.undent(); + stack.o << "}>"; + stack.push_new_var(left.at(0)); return true; } - auto func = dynamic_cast(fun_ref->value); - if (func) { - std::vector res; - res.reserve(left.size()); - for (var_idx_t i : left) { - res.emplace_back(i); - } - func->compile(stack.o, res, args); // compile res := f (args) - } else { - std::string name = sym::symbols.get_name(fun_ref->sym_idx); - stack.o << AsmOp::Custom(name + " CALLDICT", (int)right.size(), (int)left.size()); - } - stack.push_new_var(left[0]); - return true; - } case _Let: { assert(left.size() == right.size()); int i = 0; @@ -395,7 +439,9 @@ bool Op::generate_code_step(Stack& stack) { assert(stack.s[k + i].first == right1[i]); } if (cl == _CallInd) { - stack.o << exec_arg_op("CALLARGS", (int)right.size() - 1, (int)right.size(), (int)left.size()); + // TODO: replace with exec_arg2_op() + stack.o << exec_arg2_op("CALLXARGS", (int)right.size() - 1, (int)left.size(), (int)right.size(), + (int)left.size()); } else { auto func = dynamic_cast(fun_ref->value); if (func) { @@ -420,6 +466,32 @@ bool Op::generate_code_step(Stack& stack) { } return true; } + case _SetGlob: { + assert(fun_ref && dynamic_cast(fun_ref->value)); + std::vector last; + for (var_idx_t x : right) { + last.push_back(var_info[x] && var_info[x]->is_last()); + } + stack.rearrange_top(right, std::move(last)); + stack.opt_show(); + int k = (int)stack.depth() - (int)right.size(); + assert(k >= 0); + for (int i = 0; i < (int)right.size(); i++) { + if (stack.s[k + i].first != right[i]) { + std::cerr << stack.o; + } + assert(stack.s[k + i].first == right[i]); + } + if (right.size() > 1) { + stack.o << exec_arg_op("TUPLE", (int)right.size(), (int)right.size(), 1); + } + if (!right.empty()) { + std::string name = sym::symbols.get_name(fun_ref->sym_idx); + stack.o << AsmOp::Custom(name + " SETGLOB", 1, 0); + } + stack.s.resize(k); + return true; + } case _If: { if (block0->is_empty() && block1->is_empty()) { return true; @@ -448,7 +520,9 @@ bool Op::generate_code_step(Stack& stack) { } stack.o << "IF:<{"; stack.o.indent(); - Stack stack_copy{stack}; + Stack stack_copy{stack}, stack_target{stack}; + stack_target.disable_output(); + stack_target.drop_vars_except(next->var_info); block0->generate_code_all(stack_copy); stack_copy.drop_vars_except(var_info); stack_copy.opt_show(); @@ -457,7 +531,8 @@ bool Op::generate_code_step(Stack& stack) { stack.o << "}>"; return true; } - stack_copy.drop_vars_except(next->var_info); + // stack_copy.drop_vars_except(next->var_info); + stack_copy.enforce_state(stack_target.vars()); stack_copy.opt_show(); if (stack_copy.vars() == stack.vars()) { stack.o.undent(); @@ -487,7 +562,9 @@ bool Op::generate_code_step(Stack& stack) { } stack.o << "IFNOT:<{"; stack.o.indent(); - Stack stack_copy{stack}; + Stack stack_copy{stack}, stack_target{stack}; + stack_target.disable_output(); + stack_target.drop_vars_except(next->var_info); block1->generate_code_all(stack_copy); stack_copy.drop_vars_except(var_info); stack_copy.opt_show(); @@ -497,7 +574,8 @@ bool Op::generate_code_step(Stack& stack) { stack.merge_const(stack_copy); return true; } - stack_copy.drop_vars_except(next->var_info); + // stack_copy.drop_vars_except(next->var_info); + stack_copy.enforce_state(stack_target.vars()); stack_copy.opt_show(); if (stack_copy.vars() == stack.vars()) { stack.o.undent(); diff --git a/crypto/func/func.cpp b/crypto/func/func.cpp index 2b98c332..5b937c83 100644 --- a/crypto/func/func.cpp +++ b/crypto/func/func.cpp @@ -130,6 +130,11 @@ int generate_output() { *outs << func_val->method_id << " DECLMETHOD " << name << "\n"; } } + for (SymDef* gvar_sym : glob_vars) { + assert(dynamic_cast(gvar_sym->value)); + std::string name = sym::symbols.get_name(gvar_sym->sym_idx); + *outs << std::string(indent * 2, ' ') << "DECLGLOBVAR " << name << "\n"; + } int errors = 0; for (SymDef* func_sym : glob_func) { try { diff --git a/crypto/func/func.h b/crypto/func/func.h index d1481299..789319c5 100644 --- a/crypto/func/func.h +++ b/crypto/func/func.h @@ -97,6 +97,7 @@ enum Keyword { _Forall, _Asm, _Impure, + _Global, _Extern, _Inline, _InlineRef, @@ -181,6 +182,9 @@ struct TypeExpr { bool is_var() const { return constr == te_Var; } + bool is_map() const { + return constr == te_Map; + } bool has_fixed_width() const { return minw == maxw; } @@ -498,6 +502,7 @@ struct Op { _Let, _IntConst, _GlobVar, + _SetGlob, _Import, _Return, _If, @@ -694,6 +699,7 @@ struct SymVal : sym::SymValBase { TypeExpr* sym_type; td::RefInt256 method_id; bool impure; + bool auto_apply{false}; short flags; // +1 = inline, +2 = inline_ref SymVal(int _type, int _idx, TypeExpr* _stype = nullptr, bool _impure = false) : sym::SymValBase(_type, _idx), sym_type(_stype), impure(_impure), flags(0) { @@ -745,8 +751,20 @@ struct SymValType : sym::SymValBase { } }; -extern int glob_func_cnt, undef_func_cnt; -extern std::vector glob_func; +struct SymValGlobVar : sym::SymValBase { + TypeExpr* sym_type; + int out_idx{0}; + SymValGlobVar(int val, TypeExpr* gvtype, int oidx = 0) + : sym::SymValBase(_GlobVar, val), sym_type(gvtype), out_idx(oidx) { + } + ~SymValGlobVar() override = default; + TypeExpr* get_type() const { + return sym_type; + } +}; + +extern int glob_func_cnt, undef_func_cnt, glob_var_cnt; +extern std::vector glob_func, glob_vars; /* * @@ -775,6 +793,7 @@ struct Expr { _Const, _Var, _Glob, + _GlobVar, _Letop, _LetFirst, _Hole, @@ -1155,6 +1174,7 @@ struct StackTransform { bool apply_push(int i); bool apply_pop(int i = 0); bool apply_push_newconst(); + bool apply_blkpop(int k); bool apply(const StackTransform& other); // this = this * other bool preapply(const StackTransform& other); // this = other * this // c := a * b @@ -1246,6 +1266,11 @@ struct StackTransform { bool is_nip_seq(int i, int j = 0) const; bool is_nip_seq(int* i) const; bool is_nip_seq(int* i, int* j) const; + bool is_pop_blkdrop(int i, int k) const; + bool is_pop_blkdrop(int* i, int* k) const; + bool is_2pop_blkdrop(int i, int j, int k) const; + bool is_2pop_blkdrop(int* i, int* j, int* k) const; + bool is_const_rot() const; void show(std::ostream& os, int mode = 0) const; @@ -1306,14 +1331,20 @@ struct Optimizer { bool rewrite_const_push_swap(); bool is_const_push_xchgs(); bool rewrite_const_push_xchgs(); + bool is_const_rot() const; + bool rewrite_const_rot(); bool simple_rewrite(int p, AsmOp&& new_op); bool simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2); + bool simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& new_op3); bool simple_rewrite(AsmOp&& new_op) { return simple_rewrite(p_, std::move(new_op)); } bool simple_rewrite(AsmOp&& new_op1, AsmOp&& new_op2) { return simple_rewrite(p_, std::move(new_op1), std::move(new_op2)); } + bool simple_rewrite(AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& new_op3) { + return simple_rewrite(p_, std::move(new_op1), std::move(new_op2), std::move(new_op3)); + } bool simple_rewrite_nop(); bool is_pred(const std::function& pred, int min_p = 2); bool is_same_as(const StackTransform& trans, int min_p = 2); @@ -1345,6 +1376,8 @@ struct Optimizer { bool is_blkdrop(int* i); bool is_reverse(int* i, int* j); bool is_nip_seq(int* i, int* j); + bool is_pop_blkdrop(int* i, int* k); + bool is_2pop_blkdrop(int* i, int* j, int* k); AsmOpConsList extract_code(); }; @@ -1355,7 +1388,7 @@ void optimize_code(AsmOpList& ops); struct Stack { StackLayoutExt s; AsmOpList& o; - enum { _StkCmt = 1, _CptStkCmt = 2, _DisableOpt = 4, _Shown = 256, _Garbage = -0x10000 }; + enum { _StkCmt = 1, _CptStkCmt = 2, _DisableOpt = 4, _DisableOut = 128, _Shown = 256, _Garbage = -0x10000 }; int mode; Stack(AsmOpList& _o, int _mode = 0) : o(_o), mode(_mode) { } @@ -1381,6 +1414,15 @@ struct Stack { var_const_idx_t get(int i) const { return at(i); } + bool output_disabled() const { + return mode & _DisableOut; + } + bool output_enabled() const { + return !output_disabled(); + } + void disable_output() { + mode |= _DisableOut; + } StackLayout vars() const; int find(var_idx_t var, int from = 0) const; int find(var_idx_t var, int from, int to) const; @@ -1470,7 +1512,7 @@ struct SymValAsmFunc : SymValFunc { std::initializer_list ret_order = {}, bool impure = false) : SymValFunc(-1, ft, arg_order, ret_order, impure), ext_compile(std::move(_compile)) { } - bool compile(AsmOpList& dest, std::vector& in, std::vector& out) const; + bool compile(AsmOpList& dest, std::vector& out, std::vector& in) const; }; // defined in builtins.cpp @@ -1478,6 +1520,7 @@ AsmOp exec_arg_op(std::string op, long long arg); AsmOp exec_arg_op(std::string op, long long arg, int args, int retv = 1); AsmOp exec_arg_op(std::string op, td::RefInt256 arg); AsmOp exec_arg_op(std::string op, td::RefInt256 arg, int args, int retv = 1); +AsmOp exec_arg2_op(std::string op, long long imm1, long long imm2, int args, int retv = 1); AsmOp push_const(td::RefInt256 x); void define_builtins(); diff --git a/crypto/func/gen-abscode.cpp b/crypto/func/gen-abscode.cpp index a3cc5204..87a4e2de 100644 --- a/crypto/func/gen-abscode.cpp +++ b/crypto/func/gen-abscode.cpp @@ -282,15 +282,22 @@ std::vector Expr::pre_compile(CodeBlob& code) const { code.emplace_back(here, Op::_IntConst, rvect, intval); return rvect; } - case _Glob: { + case _Glob: + case _GlobVar: { auto rvect = new_tmp_vect(code); code.emplace_back(here, Op::_GlobVar, rvect, std::vector{}, sym); return rvect; } case _Letop: { auto right = args[1]->pre_compile(code); - auto left = args[0]->pre_compile(code); - code.emplace_back(here, Op::_Let, std::move(left), right); + if (args[0]->cls == Expr::_GlobVar) { + assert(args[0]->sym); + auto& op = code.emplace_back(here, Op::_SetGlob, std::vector{}, right, args[0]->sym); + op.flags |= Op::_Impure; + } else { + auto left = args[0]->pre_compile(code); + code.emplace_back(here, Op::_Let, std::move(left), right); + } return right; } case _LetFirst: { diff --git a/crypto/func/keywords.cpp b/crypto/func/keywords.cpp index c640f4fe..91559f96 100644 --- a/crypto/func/keywords.cpp +++ b/crypto/func/keywords.cpp @@ -113,6 +113,7 @@ void define_keywords() { .add_keyword("forall", Kw::_Forall); sym::symbols.add_keyword("extern", Kw::_Extern) + .add_keyword("global", Kw::_Global) .add_keyword("asm", Kw::_Asm) .add_keyword("impure", Kw::_Impure) .add_keyword("inline", Kw::_Inline) diff --git a/crypto/func/optimize.cpp b/crypto/func/optimize.cpp index 95b4403c..cd27150c 100644 --- a/crypto/func/optimize.cpp +++ b/crypto/func/optimize.cpp @@ -150,6 +150,21 @@ bool Optimizer::rewrite_const_push_swap() { return true; } +bool Optimizer::is_const_rot() const { + return pb_ >= 3 && pb_ <= l2_ && op_[0]->is_gconst() && tr_[pb_ - 1].is_const_rot(); +} + +bool Optimizer::rewrite_const_rot() { + p_ = pb_; + q_ = 2; + show_left(); + oq_[0] = std::move(op_[0]); + oq_[1] = std::move(op_[1]); + *oq_[1] = AsmOp::Custom("ROT", 3, 3); + show_right(); + return true; +} + bool Optimizer::is_const_push_xchgs() { if (!(pb_ >= 2 && pb_ <= l2_ && op_[0]->is_gconst())) { return false; @@ -260,6 +275,21 @@ bool Optimizer::simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2) { return true; } +bool Optimizer::simple_rewrite(int p, AsmOp&& new_op1, AsmOp&& new_op2, AsmOp&& new_op3) { + assert(p > 2 && p <= l_); + p_ = p; + q_ = 3; + show_left(); + oq_[0] = std::move(op_[0]); + *oq_[0] = new_op1; + oq_[1] = std::move(op_[1]); + *oq_[1] = new_op2; + oq_[2] = std::move(op_[2]); + *oq_[2] = new_op3; + show_right(); + return true; +} + bool Optimizer::simple_rewrite_nop() { assert(p_ > 0 && p_ <= l_); q_ = 0; @@ -402,6 +432,16 @@ bool Optimizer::is_nip_seq(int* i, int* j) { return is_pred([i, j](const auto& t) { return t.is_nip_seq(i, j) && *i >= 3 && *i <= 15; }); } +bool Optimizer::is_pop_blkdrop(int* i, int* k) { + return is_pred([i, k](const auto& t) { return t.is_pop_blkdrop(i, k) && *i >= *k && *k >= 2 && *k <= 15; }, 3); +} + +bool Optimizer::is_2pop_blkdrop(int* i, int* j, int* k) { + return is_pred( + [i, j, k](const auto& t) { return t.is_2pop_blkdrop(i, j, k) && *i >= *k && *j >= *k && *k >= 2 && *k <= 15; }, + 3); +} + bool Optimizer::compute_stack_transforms() { StackTransform trans; for (int i = 0; i < l_; i++) { @@ -450,7 +490,7 @@ bool Optimizer::find_at_least(int pb) { // show_stack_transforms(); int i = -100, j = -100, k = -100; return (is_const_push_swap() && 3 >= pb && rewrite_const_push_swap()) || (is_nop() && simple_rewrite_nop()) || - (is_const_push_xchgs() && rewrite_const_push_xchgs()) || + (is_const_rot() && rewrite_const_rot()) || (is_const_push_xchgs() && rewrite_const_push_xchgs()) || (is_xchg(&i, &j) && simple_rewrite(AsmOp::Xchg(i, j))) || (is_push(&i) && simple_rewrite(AsmOp::Push(i))) || (is_pop(&i) && simple_rewrite(AsmOp::Pop(i))) || (is_rot() && simple_rewrite(AsmOp::Custom("ROT", 3, 3))) || (is_rotrev() && simple_rewrite(AsmOp::Custom("-ROT", 3, 3))) || @@ -468,6 +508,10 @@ bool Optimizer::find_at_least(int pb) { (is_blkdrop(&i) && simple_rewrite(AsmOp::BlkDrop(i))) || (is_reverse(&i, &j) && simple_rewrite(AsmOp::BlkReverse(i, j))) || (is_nip_seq(&i, &j) && simple_rewrite(AsmOp::Xchg(i, j), AsmOp::BlkDrop(i))) || + (is_pop_blkdrop(&i, &k) && simple_rewrite(AsmOp::Pop(i), AsmOp::BlkDrop(k))) || + (is_2pop_blkdrop(&i, &j, &k) && (k >= 3 && k <= 13 && i != j + 1 && i <= 15 && j <= 14 + ? simple_rewrite(AsmOp::Xchg2(j + 1, i), AsmOp::BlkDrop(k + 2)) + : simple_rewrite(AsmOp::Pop(i), AsmOp::Pop(j), AsmOp::BlkDrop(k)))) || (is_xchg3(&i, &j, &k) && simple_rewrite(AsmOp::Xchg3(i, j, k))) || (is_xc2pu(&i, &j, &k) && simple_rewrite(AsmOp::Xc2Pu(i, j, k))) || (is_xcpuxc(&i, &j, &k) && simple_rewrite(AsmOp::XcPuXc(i, j, k))) || diff --git a/crypto/func/parse-func.cpp b/crypto/func/parse-func.cpp index e0407791..01a43f63 100644 --- a/crypto/func/parse-func.cpp +++ b/crypto/func/parse-func.cpp @@ -174,6 +174,53 @@ FormalArg parse_formal_arg(Lexer& lex, int fa_idx) { return std::make_tuple(arg_type, new_sym_def, loc); } +void parse_global_var_decl(Lexer& lex) { + TypeExpr* var_type = 0; + SrcLocation loc = lex.cur().loc; + if (lex.tp() == '_') { + lex.next(); + var_type = TypeExpr::new_hole(); + loc = lex.cur().loc; + } else if (lex.tp() != _Ident) { + var_type = parse_type(lex); + } else { + auto sym = sym::lookup_symbol(lex.cur().val); + if (sym && dynamic_cast(sym->value)) { + auto val = dynamic_cast(sym->value); + lex.next(); + var_type = val->get_type(); + } else { + var_type = TypeExpr::new_hole(); + } + } + if (lex.tp() != _Ident) { + lex.expect(_Ident, "global variable name"); + } + loc = lex.cur().loc; + SymDef* sym_def = sym::define_global_symbol(lex.cur().val, false, loc); + if (!sym_def) { + lex.cur().error_at("cannot define global symbol `", "`"); + } + if (sym_def->value) { + auto val = dynamic_cast(sym_def->value); + if (!val) { + lex.cur().error_at("symbol `", "` cannot be redefined as a global variable"); + } + try { + unify(var_type, val->sym_type); + } catch (UnifyError& ue) { + std::ostringstream os; + os << "cannot unify new type " << var_type << " of global variable `" << sym_def->name() + << "` with its previous type " << val->sym_type << ": " << ue; + lex.cur().error(os.str()); + } + } else { + sym_def->value = new SymValGlobVar{glob_var_cnt++, var_type}; + glob_vars.push_back(sym_def); + } + lex.next(); +} + FormalArgList parse_formal_args(Lexer& lex) { FormalArgList args; lex.expect('(', "formal argument list"); @@ -205,6 +252,18 @@ TypeExpr* extract_total_arg_type(const FormalArgList& arg_list) { return TypeExpr::new_tensor(std::move(type_list)); } +void parse_global_var_decls(Lexer& lex) { + lex.expect(_Global); + while (true) { + parse_global_var_decl(lex); + if (lex.tp() != ',') { + break; + } + lex.expect(','); + } + lex.expect(';'); +} + SymValCodeFunc* make_new_glob_func(SymDef* func_sym, TypeExpr* func_type, bool impure = false) { SymValCodeFunc* res = new SymValCodeFunc{glob_func_cnt, func_type, impure}; func_sym->value = res; @@ -239,6 +298,22 @@ bool check_global_func(const Lexem& cur, sym_idx_t func_name = 0) { } } +Expr* make_func_apply(Expr* fun, Expr* x) { + Expr* res; + if (fun->cls == Expr::_Glob) { + if (x->cls == Expr::_Tuple) { + res = new Expr{Expr::_Apply, fun->sym, x->args}; + } else { + res = new Expr{Expr::_Apply, fun->sym, {x}}; + } + res->flags = Expr::_IsRvalue | (fun->flags & Expr::_IsImpure); + } else { + res = new Expr{Expr::_VarApply, {fun, x}}; + res->flags = Expr::_IsRvalue; + } + return res; +} + Expr* parse_expr(Lexer& lex, CodeBlob& code, bool nv = false); // parse ( E { , E } ) | () | id | num | _ @@ -323,6 +398,16 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { lex.next(); return res; } + if (sym && dynamic_cast(sym->value)) { + auto val = dynamic_cast(sym->value); + Expr* res = new Expr{Expr::_GlobVar, lex.cur().loc}; + res->e_type = val->get_type(); + res->sym = sym; + res->flags = Expr::_IsLvalue | Expr::_IsRvalue | Expr::_IsImpure; + lex.next(); + return res; + } + bool auto_apply = false; Expr* res = new Expr{Expr::_Var, lex.cur().loc}; if (nv) { res->val = ~lex.cur().val; @@ -344,6 +429,7 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { } else if (val->type == SymVal::_Func) { res->e_type = val->get_type(); res->cls = Expr::_Glob; + auto_apply = val->auto_apply; } else if (val->idx < 0) { lex.cur().error_at("accessing variable `", "` being defined"); } else { @@ -354,6 +440,12 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { // std::cerr << "accessing symbol " << lex.cur().str << " : " << res->e_type << (val->impure ? " (impure)" : " (pure)") << std::endl; res->flags = Expr::_IsLvalue | Expr::_IsRvalue | (val->impure ? Expr::_IsImpure : 0); } + if (auto_apply) { + int impure = res->flags & Expr::_IsImpure; + delete res; + res = new Expr{Expr::_Apply, sym, {}}; + res->flags = Expr::_IsRvalue | impure; + } res->deduce_type(lex.cur()); lex.next(); return res; @@ -362,22 +454,6 @@ Expr* parse_expr100(Lexer& lex, CodeBlob& code, bool nv) { return nullptr; } -Expr* make_func_apply(Expr* fun, Expr* x) { - Expr* res; - if (fun->cls == Expr::_Glob) { - if (x->cls == Expr::_Tuple) { - res = new Expr{Expr::_Apply, fun->sym, x->args}; - } else { - res = new Expr{Expr::_Apply, fun->sym, {x}}; - } - res->flags = Expr::_IsRvalue | (fun->flags & Expr::_IsImpure); - } else { - res = new Expr{Expr::_VarApply, {fun, x}}; - res->flags = Expr::_IsRvalue; - } - return res; -} - // parse E { E } Expr* parse_expr90(Lexer& lex, CodeBlob& code, bool nv) { Expr* res = parse_expr100(lex, code, nv); @@ -1193,7 +1269,11 @@ bool parse_source(std::istream* is, const src::FileDescr* fdescr) { src::SourceReader reader{is, fdescr}; Lexer lex{reader, true}; while (lex.tp() != _Eof) { - parse_func_def(lex); + if (lex.tp() == _Global) { + parse_global_var_decls(lex); + } else { + parse_func_def(lex); + } } return true; } diff --git a/crypto/func/stack-transform.cpp b/crypto/func/stack-transform.cpp index f682b490..069324dc 100644 --- a/crypto/func/stack-transform.cpp +++ b/crypto/func/stack-transform.cpp @@ -334,6 +334,13 @@ bool StackTransform::apply_pop(int i) { } } +bool StackTransform::apply_blkpop(int k) { + if (!is_valid() || k < 0) { + return invalidate(); + } + return !k || (touch(k - 1) && shift(k)); +} + bool StackTransform::equal(const StackTransform &other, bool relaxed) const { if (!is_valid() || !other.is_valid()) { return false; @@ -800,6 +807,49 @@ bool StackTransform::is_nip_seq(int *i, int *j) const { } } +// POP s(i); BLKDROP k (usually for i >= k >= 0) +bool StackTransform::is_pop_blkdrop(int i, int k) const { + StackTransform t; + return is_valid() && d == k + 1 && t.apply_pop(i) && t.apply_blkpop(k) && t <= *this; +} + +// POP s(i); BLKDROP k == XCHG s0,s(i); BLKDROP k+1 for i >= k >= 0 +// k+1 k+2 .. i-1 0 i+1 .. +bool StackTransform::is_pop_blkdrop(int *i, int *k) const { + if (is_valid() && n == 1 && d > 0 && !A[0].second) { + *k = d - 1; + *i = A[0].first; + return is_pop_blkdrop(*i, *k); + } else { + return false; + } +} + +// POP s(i); POP s(j); BLKDROP k (usually for i<>j >= k >= 0) +bool StackTransform::is_2pop_blkdrop(int i, int j, int k) const { + StackTransform t; + return is_valid() && d == k + 2 && t.apply_pop(i) && t.apply_pop(j) && t.apply_blkpop(k) && t <= *this; +} + +// POP s(i); POP s(j); BLKDROP k == XCHG s0,s(i); XCHG s1,s(j+1); BLKDROP k+2 (usually for i<>j >= k >= 2) +// k+2 k+3 .. i-1 0 i+1 ... j 1 j+2 ... +bool StackTransform::is_2pop_blkdrop(int *i, int *j, int *k) const { + if (is_valid() && n == 2 && d >= 2 && A[0].second + A[1].second == 1) { + *k = d - 2; + int t = (A[0].second > 0); + *i = A[t].first; + *j = A[1 - t].first - 1; + return is_2pop_blkdrop(*i, *j, *k); + } else { + return false; + } +} + +// PUSHCONST c ; ROT == 1 -1000 0 2 3 +bool StackTransform::is_const_rot() const { + return is_valid() && d == -1 && is_trivial_after(3) && get(0) == 1 && get(1) <= c_start && get(2) == 0; +} + void StackTransform::show(std::ostream &os, int mode) const { if (!is_valid()) { os << ""; diff --git a/crypto/func/test/a11.fc b/crypto/func/test/a11.fc new file mode 100644 index 00000000..45bdf1f3 --- /dev/null +++ b/crypto/func/test/a11.fc @@ -0,0 +1,28 @@ +_ f(slice ds, int total_weight, int elect_at, cell frozen, int total_stakes, int cnt, cell credits, cell vdict, int elect) { + return (ds, elect, credits); +} + +_ g(slice ds, int total_weight, int elect_at, cell frozen, int total_stakes, int cnt, cell credits, int elect) { + return (ds, elect, credits); +} + +_ h(slice ds, int total_weight, int elect_at, cell frozen, int total_stakes, cell credits, int elect) { + return (ds, elect, credits); +} + +_ h2(slice ds, int total_weight, int elect_at, cell frozen, int total_stakes, cell credits, int elect) { + return (ds, credits, elect); +} + +_ h3(int pubkey, int adnl_addr, int stake, int tot_weight, tuple vinfo) { + return (pubkey, tot_weight, stake, vinfo); +} + +_ z(int a, int b) { + return (b, 0, a); +} + +_ z2(int a, int b) { + return (0, a, b); +} + diff --git a/crypto/func/test/a6_5.fc b/crypto/func/test/a6_5.fc new file mode 100644 index 00000000..ece7cd38 --- /dev/null +++ b/crypto/func/test/a6_5.fc @@ -0,0 +1,20 @@ +var twice(f, x) { + return f (f x); +} + +_ sqr(x) { + return x * x; +} + +var main(x) { + var f = sqr; + return twice(f, x) * f(x); +} + +var pow6(x) { + return twice(sqr, x) * sqr(x); +} + +_ q() { + return false; +} diff --git a/crypto/func/test/c1.fc b/crypto/func/test/c1.fc new file mode 100644 index 00000000..1a07baca --- /dev/null +++ b/crypto/func/test/c1.fc @@ -0,0 +1,37 @@ +global int x, y, z; +global (cell, slice) y; +global ((int, int) -> int) op; + +_ get() { + var t = z + 1; + return x; +} + +_ pre_next() { + return x + 1; +} + +() init() impure { + ;; global x; + x = 0; +} + +int next() impure { + ;; global x; + return x += 1; +} + +_ set_y(x, w) { + y = (w, x); +} + +_ get_y() impure { + return y; +} + +int main(int c) { + init(); + c += next(); + return c + pre_next(); +} + diff --git a/crypto/func/test/c2.fc b/crypto/func/test/c2.fc new file mode 100644 index 00000000..277c7053 --- /dev/null +++ b/crypto/func/test/c2.fc @@ -0,0 +1,10 @@ +global ((int, int) -> int) op; + +int check_assoc(int a, int b, int c) { + return op(op(a, b), c) == op(a, op(b, c)); +} + +int main() { + op = _+_; + return check_assoc(2, 3, 9); +} diff --git a/crypto/func/test/c2_1.fc b/crypto/func/test/c2_1.fc new file mode 100644 index 00000000..3bf863a6 --- /dev/null +++ b/crypto/func/test/c2_1.fc @@ -0,0 +1,7 @@ +_ check_assoc(op, a, b, c) { + return op(op(a, b), c) == op(a, op(b, c)); +} + +int main() { + return check_assoc(_+_, 2, 3, 9); +} diff --git a/crypto/parser/symtable.h b/crypto/parser/symtable.h index 6c77ca7b..88f683bd 100644 --- a/crypto/parser/symtable.h +++ b/crypto/parser/symtable.h @@ -32,7 +32,7 @@ namespace sym { typedef int var_idx_t; struct SymValBase { - enum { _Param, _Var, _Func, _Typename }; + enum { _Param, _Var, _Func, _Typename, _GlobVar }; int type; int idx; SymValBase(int _type, int _idx) : type(_type), idx(_idx) { diff --git a/crypto/smartcont/CreateState.fif b/crypto/smartcont/CreateState.fif index ed82499b..1330a446 100644 --- a/crypto/smartcont/CreateState.fif +++ b/crypto/smartcont/CreateState.fif @@ -242,3 +242,7 @@ dictnew constant special-dict register_smc Masterchain swap 6 .Addr cr } : create-wallet0 + +{ dup tlb-type-lookup { nip } { "unknown TLB type " swap $+ abort } cond } : $>tlb +{ bl word $>tlb 1 'nop } ::_ tlb: +{ proof_union; { - proof_union = MerkleProof::combine(proof1, proof2); + auto check = [&](auto proof_union) { + auto virtualized_proof = MerkleProof::virtualize(proof_union, 1); + auto exploration_a = CellExplorer::explore(virtualized_proof, exploration1.ops); + auto exploration_b = CellExplorer::explore(virtualized_proof, exploration2.ops); + ASSERT_EQ(exploration_a.log, exploration1.log); + ASSERT_EQ(exploration_b.log, exploration2.log); + }; + auto proof_union = MerkleProof::combine(proof1, proof2); ASSERT_EQ(proof_union->get_hash(), proof12->get_hash()); + check(proof_union); - auto virtualized_proof = MerkleProof::virtualize(proof_union, 1); - auto exploration_a = CellExplorer::explore(virtualized_proof, exploration1.ops); - auto exploration_b = CellExplorer::explore(virtualized_proof, exploration2.ops); - ASSERT_EQ(exploration_a.log, exploration1.log); - ASSERT_EQ(exploration_b.log, exploration2.log); + auto proof_union_fast = MerkleProof::combine_fast(proof1, proof2); + check(proof_union_fast); } { auto cell = MerkleProof::virtualize(proof12, 1); @@ -1194,6 +1199,88 @@ TEST(Cell, MerkleProofArrayHands) { test_boc_deserializer_full(proof).ensure(); test_boc_deserializer_full(CellBuilder::create_merkle_proof(proof)).ensure(); } + +TEST(Cell, MerkleProofCombineArray) { + size_t n = 1 << 15; + std::vector data; + for (size_t i = 0; i < n; i++) { + data.push_back(i / 3); + } + CompactArray arr(data); + + td::Ref root = vm::CellBuilder::create_merkle_proof(arr.merkle_proof({})); + td::Timer timer; + for (size_t i = 0; i < n; i++) { + auto new_root = vm::CellBuilder::create_merkle_proof(arr.merkle_proof({i})); + root = vm::MerkleProof::combine_fast(root, new_root); + if ((i - 1) % 100 == 0) { + LOG(ERROR) << timer; + timer = {}; + } + } + + CompactArray arr2(n, vm::MerkleProof::virtualize(root, 1)); + for (size_t i = 0; i < n; i++) { + CHECK(arr.get(i) == arr2.get(i)); + } +} + +TEST(Cell, MerkleProofCombineArray2) { + auto a = vm::CellBuilder().store_long(1, 8).finalize(); + auto b = vm::CellBuilder().store_long(2, 8).finalize(); + auto c = vm::CellBuilder().store_long(3, 8).finalize(); + auto d = vm::CellBuilder().store_long(4, 8).finalize(); + auto left = vm::CellBuilder().store_ref(a).store_ref(b).finalize(); + auto right = vm::CellBuilder().store_ref(c).store_ref(d).finalize(); + auto x = vm::CellBuilder().store_ref(left).store_ref(right).finalize(); + size_t n = 18; + //TODO: n = 100, currently TL + for (size_t i = 0; i < n; i++) { + x = vm::CellBuilder().store_ref(x).store_ref(x).finalize(); + } + + td::Ref root; + auto apply_op = [&](auto op) { + auto usage_tree = std::make_shared(); + auto usage_cell = UsageCell::create(x, usage_tree->root_ptr()); + root = usage_cell; + op(); + return MerkleProof::generate(root, usage_tree.get()); + }; + + auto first = apply_op([&] { + auto x = root; + while (true) { + auto cs = vm::load_cell_slice(x); + if (cs.size_refs() == 0) { + break; + } + x = cs.prefetch_ref(0); + } + }); + auto second = apply_op([&] { + auto x = root; + while (true) { + auto cs = vm::load_cell_slice(x); + if (cs.size_refs() == 0) { + break; + } + x = cs.prefetch_ref(1); + } + }); + + { + td::Timer t; + auto x = vm::MerkleProof::combine(first, second); + LOG(ERROR) << "slow " << t; + } + { + td::Timer t; + auto x = vm::MerkleProof::combine_fast(first, second); + LOG(ERROR) << "fast " << t; + } +} + TEST(Cell, MerkleUpdateHands) { auto data = CellBuilder{}.store_bytes("pruned data").store_ref(CellBuilder{}.finalize()).finalize(); auto node = CellBuilder{}.store_bytes("protected data").store_ref(data).finalize(); @@ -1965,6 +2052,222 @@ TEST(Ref, AtomicRef) { LOG(ERROR) << String::total_strings.sum(); } +class FileMerkleTree { + public: + FileMerkleTree(size_t chunks_count, td::Ref root = {}) { + log_n_ = 0; + while ((size_t(1) << log_n_) < chunks_count) { + log_n_++; + } + n_ = size_t(1) << log_n_; + mark_.resize(n_ * 2); + proof_.resize(n_ * 2); + + CHECK(n_ == chunks_count); // TODO: support other chunks_count + //auto x = vm::CellBuilder().finalize(); + root_ = std::move(root); + } + + struct Chunk { + td::size_t index{0}; + td::Slice hash; + }; + + void remove_chunk(td::size_t index) { + CHECK(index < n_); + index += n_; + while (proof_[index].not_null()) { + proof_[index] = {}; + index /= 2; + } + } + + bool has_chunk(td::size_t index) const { + CHECK(index < n_); + index += n_; + return proof_[index].not_null(); + } + + void add_chunk(td::size_t index, td::Slice hash) { + CHECK(hash.size() == 32); + CHECK(index < n_); + index += n_; + auto cell = vm::CellBuilder().store_bytes(hash).finalize(); + CHECK(proof_[index].is_null()); + proof_[index] = std::move(cell); + for (index /= 2; index != 0; index /= 2) { + CHECK(proof_[index].is_null()); + auto &left = proof_[index * 2]; + auto &right = proof_[index * 2 + 1]; + if (left.not_null() && right.not_null()) { + proof_[index] = vm::CellBuilder().store_ref(left).store_ref(right).finalize(); + } else { + mark_[index] = mark_id_; + } + } + } + + td::Status validate_proof(td::Ref new_root) { + // TODO: check structure + return td::Status::OK(); + } + + td::Status add_proof(td::Ref new_root) { + TRY_STATUS(validate_proof(new_root)); + auto combined = vm::MerkleProof::combine_fast_raw(root_, new_root); + if (combined.is_null()) { + return td::Status::Error("Can't combine proofs"); + } + root_ = std::move(combined); + return td::Status::OK(); + } + + td::Status try_add_chunks(td::Span chunks) { + for (auto chunk : chunks) { + if (has_chunk(chunk.index)) { + return td::Status::Error("Already has chunk"); + } + } + mark_id_++; + for (auto chunk : chunks) { + add_chunk(chunk.index, chunk.hash); + } + auto r_new_root = merge(root_, 1); + if (r_new_root.is_error()) { + for (auto chunk : chunks) { + remove_chunk(chunk.index); + } + return r_new_root.move_as_error(); + } + root_ = r_new_root.move_as_ok(); + return td::Status::OK(); + } + + td::Result> merge(td::Ref root, size_t index) { + const auto &down = proof_[index]; + if (down.not_null()) { + if (down->get_hash() != root->get_hash(0)) { + return td::Status::Error("Hash mismatch"); + } + return down; + } + + if (mark_[index] != mark_id_) { + return root; + } + + vm::CellSlice cs(vm::NoVm(), root); + if (cs.is_special()) { + return td::Status::Error("Proof is not enough to validate chunks"); + } + + CHECK(cs.size_refs() == 2); + vm::CellBuilder cb; + cb.store_bits(cs.fetch_bits(cs.size())); + TRY_RESULT(left, merge(cs.fetch_ref(), index * 2)); + TRY_RESULT(right, merge(cs.fetch_ref(), index * 2 + 1)); + cb.store_ref(std::move(left)).store_ref(std::move(right)); + return cb.finalize(); + } + + void init_proof() { + CHECK(proof_[1].not_null()); + root_ = proof_[1]; + } + + td::Result> gen_proof(size_t l, size_t r) { + auto usage_tree = std::make_shared(); + auto usage_cell = vm::UsageCell::create(root_, usage_tree->root_ptr()); + TRY_STATUS(do_gen_proof(std::move(usage_cell), 0, n_ - 1, l, r)); + auto res = vm::MerkleProof::generate_raw(root_, usage_tree.get()); + CHECK(res.not_null()); + return res; + } + + private: + td::size_t n_; // n = 2^log_n + td::size_t log_n_; + td::size_t mark_id_{0}; + std::vector mark_; // n_ * 2 + std::vector> proof_; // n_ * 2 + td::Ref root_; + + td::Status do_gen_proof(td::Ref node, size_t il, size_t ir, size_t l, size_t r) { + if (ir < l || il > r) { + return td::Status::OK(); + } + if (l <= il && ir <= r) { + return td::Status::OK(); + } + vm::CellSlice cs(vm::NoVm(), std::move(node)); + if (cs.is_special()) { + return td::Status::Error("Can't generate a proof"); + } + CHECK(cs.size_refs() == 2); + auto ic = (il + ir) / 2; + TRY_STATUS(do_gen_proof(cs.fetch_ref(), il, ic, l, r)); + TRY_STATUS(do_gen_proof(cs.fetch_ref(), ic + 1, ir, l, r)); + return td::Status::OK(); + } +}; + +TEST(FileMerkleTree, Manual) { + // create big random file + size_t chunk_size = 768; + // for simplicity numer of chunks in a file is a power of two + size_t chunks_count = 1 << 16; + size_t file_size = chunk_size * chunks_count; + td::Timer timer; + LOG(INFO) << "Generate random string"; + const auto file = td::rand_string('a', 'z', td::narrow_cast(file_size)); + LOG(INFO) << timer; + + timer = {}; + LOG(INFO) << "Calculate all hashes"; + std::vector hashes(chunks_count); + for (size_t i = 0; i < chunks_count; i++) { + td::sha256(td::Slice(file).substr(i * chunk_size, chunk_size), hashes[i].as_slice()); + } + LOG(INFO) << timer; + + timer = {}; + LOG(INFO) << "Init merkle tree"; + FileMerkleTree tree(chunks_count); + for (size_t i = 0; i < chunks_count; i++) { + tree.add_chunk(i, hashes[i].as_slice()); + } + tree.init_proof(); + LOG(INFO) << timer; + + auto root_proof = tree.gen_proof(0, chunks_count - 1).move_as_ok(); + + // first download each chunk one by one + + for (size_t stride : {1 << 6, 1}) { + timer = {}; + LOG(INFO) << "Gen all proofs, stride = " << stride; + for (size_t i = 0; i < chunks_count; i += stride) { + tree.gen_proof(i, i + stride - 1).move_as_ok(); + } + LOG(INFO) << timer; + timer = {}; + LOG(INFO) << "Proof size: " << vm::std_boc_serialize(tree.gen_proof(0, stride - 1).move_as_ok()).ok().size(); + LOG(INFO) << "Download file, stride = " << stride; + { + FileMerkleTree new_tree(chunks_count, root_proof); + for (size_t i = 0; i < chunks_count; i += stride) { + new_tree.add_proof(tree.gen_proof(i, i + stride - 1).move_as_ok()).ensure(); + std::vector chunks; + for (size_t j = 0; j < stride; j++) { + chunks.push_back({i + j, hashes[i + j].as_slice()}); + } + new_tree.try_add_chunks(chunks).ensure(); + } + } + LOG(INFO) << timer; + } +} + //TEST(Tmp, Boc) { //LOG(ERROR) << "A"; //auto data = td::read_file("boc"); diff --git a/crypto/tl/tlbc-gen-cpp.cpp b/crypto/tl/tlbc-gen-cpp.cpp index 8dfbbb1f..3de92bd5 100644 --- a/crypto/tl/tlbc-gen-cpp.cpp +++ b/crypto/tl/tlbc-gen-cpp.cpp @@ -3257,6 +3257,31 @@ void generate_type_constants(std::ostream& os, int mode) { } } +void generate_register_function(std::ostream& os, int mode) { + os << "\n// " << (mode ? "definition" : "declaration") << " of type name registration function\n"; + if (!mode) { + os << "extern bool register_simple_types(std::function func);\n"; + return; + } + os << "bool register_simple_types(std::function func) {\n"; + os << " return "; + int k = 0; + for (int i = builtin_types_num; i < types_num; i++) { + Type& type = types[i]; + CppTypeCode& cc = *cpp_type[i]; + if (!cc.cpp_type_var_name.empty() && type.type_name) { + if (k++) { + os << "\n && "; + } + os << "func(\"" << type.get_name() << "\", &" << cc.cpp_type_var_name << ")"; + } + } + if (!k) { + os << "true"; + } + os << ";\n}\n\n"; +} + void assign_const_type_cpp_idents() { const_type_expr_cpp_idents.resize(const_type_expr_num + 1, ""); const_type_expr_simple.resize(const_type_expr_num + 1, false); @@ -3389,6 +3414,7 @@ void generate_cpp_output_to(std::ostream& os, int options = 0, std::vectorsecond : nullptr; +} + +} // namespace tlb diff --git a/crypto/tl/tlblib.hpp b/crypto/tl/tlblib.hpp index d90bfcc4..2f6794ec 100644 --- a/crypto/tl/tlblib.hpp +++ b/crypto/tl/tlblib.hpp @@ -18,6 +18,7 @@ */ #pragma once #include +#include #include "vm/cellslice.h" namespace tlb { @@ -268,6 +269,30 @@ struct TLB_Complex : TLB { } }; +class TlbTypeHolder : public td::CntObject { + const TLB* type{nullptr}; + char* data{nullptr}; + + public: + TlbTypeHolder() = default; + TlbTypeHolder(const TLB* _type) : type(_type), data(nullptr) { + } + TlbTypeHolder(const TLB* _type, char* _data) : type(_type), data(_data) { + } + ~TlbTypeHolder() override { + free(data); + } + const TLB* get() const { + return type; + } + const TLB& operator*() const { + return *type; + } + const TLB* operator->() const { + return type; + } +}; + static inline bool add_chk(int x, int y, int z) { return x + y == z && z >= 0; } @@ -508,6 +533,25 @@ struct PrettyPrinter { namespace tlb { +class TypenameLookup { + std::map types; + + public: + typedef std::function simple_register_func_t; + typedef std::function register_func_t; + TypenameLookup() = default; + TypenameLookup(register_func_t func) { + register_types(func); + } + bool register_type(const char* name, const TLB* tp); + bool register_types(register_func_t func); + const TLB* lookup(std::string str) const; +}; + +} // namespace tlb + +namespace tlb { + struct False final : TLB { int get_size(const vm::CellSlice& cs) const override { return -1; diff --git a/crypto/vm/cells/MerkleProof.cpp b/crypto/vm/cells/MerkleProof.cpp index a373e804..105c74c1 100644 --- a/crypto/vm/cells/MerkleProof.cpp +++ b/crypto/vm/cells/MerkleProof.cpp @@ -143,19 +143,80 @@ Ref MerkleProof::virtualize(Ref cell, int virtualization) { return virtualize_raw(r_raw.move_as_ok(), {0 /*level*/, static_cast(virtualization)}); } +class MerkleProofCombineFast { + public: + MerkleProofCombineFast(Ref a, Ref b) : a_(std::move(a)), b_(std::move(b)) { + } + td::Result> run() { + TRY_RESULT_ASSIGN(a_, unpack_proof(a_)); + TRY_RESULT_ASSIGN(b_, unpack_proof(b_)); + TRY_RESULT(res, run_raw()); + return CellBuilder::create_merkle_proof(std::move(res)); + } + + td::Result> run_raw() { + if (a_->get_hash(0) != b_->get_hash(0)) { + return td::Status::Error("Can't combine MerkleProofs with different roots"); + } + return merge(a_, b_, 0); + } + + private: + Ref a_; + Ref b_; + + Ref merge(Ref a, Ref b, td::uint32 merkle_depth) { + if (a->get_hash() == b->get_hash()) { + return a; + } + if (a->get_level() == merkle_depth) { + return a; + } + if (b->get_level() == merkle_depth) { + return b; + } + + CellSlice csa(NoVm(), a); + CellSlice csb(NoVm(), b); + + if (csa.is_special() && csa.special_type() == vm::Cell::SpecialType::PrunnedBranch) { + return b; + } + if (csb.is_special() && csb.special_type() == vm::Cell::SpecialType::PrunnedBranch) { + return a; + } + + CHECK(csa.size_refs() != 0); + + auto child_merkle_depth = csa.child_merkle_depth(merkle_depth); + + CellBuilder cb; + cb.store_bits(csa.fetch_bits(csa.size())); + for (unsigned i = 0; i < csa.size_refs(); i++) { + cb.store_ref(merge(csa.prefetch_ref(i), csb.prefetch_ref(i), child_merkle_depth)); + } + return cb.finalize(csa.is_special()); + } +}; + class MerkleProofCombine { public: MerkleProofCombine(Ref a, Ref b) : a_(std::move(a)), b_(std::move(b)) { } td::Result> run() { - TRY_RESULT(a, unpack_proof(a_)); - TRY_RESULT(b, unpack_proof(b_)); - if (a->get_hash(0) != b->get_hash(0)) { + TRY_RESULT_ASSIGN(a_, unpack_proof(a_)); + TRY_RESULT_ASSIGN(b_, unpack_proof(b_)); + TRY_RESULT(res, run_raw()); + return CellBuilder::create_merkle_proof(std::move(res)); + } + + td::Result> run_raw() { + 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)); + dfs(a_, 0); + dfs(b_, 0); + return create_A(a_, 0, 0); } private: @@ -262,6 +323,30 @@ Ref MerkleProof::combine(Ref a, Ref b) { return res.move_as_ok(); } +Ref MerkleProof::combine_fast(Ref a, Ref b) { + auto res = MerkleProofCombineFast(std::move(a), std::move(b)).run(); + if (res.is_error()) { + return {}; + } + return res.move_as_ok(); +} + +Ref MerkleProof::combine_raw(Ref a, Ref b) { + auto res = MerkleProofCombine(std::move(a), std::move(b)).run_raw(); + if (res.is_error()) { + return {}; + } + return res.move_as_ok(); +} + +Ref MerkleProof::combine_fast_raw(Ref a, Ref b) { + auto res = MerkleProofCombineFast(std::move(a), std::move(b)).run_raw(); + if (res.is_error()) { + return {}; + } + return res.move_as_ok(); +} + MerkleProofBuilder::MerkleProofBuilder(Ref root) : usage_tree(std::make_shared()), orig_root(std::move(root)) { usage_root = UsageCell::create(orig_root, usage_tree->root_ptr()); diff --git a/crypto/vm/cells/MerkleProof.h b/crypto/vm/cells/MerkleProof.h index 5b3b8ebc..41788c5f 100644 --- a/crypto/vm/cells/MerkleProof.h +++ b/crypto/vm/cells/MerkleProof.h @@ -38,12 +38,15 @@ class MerkleProof { static Ref virtualize(Ref cell, int virtualization); static Ref combine(Ref a, Ref b); + static Ref combine_fast(Ref a, Ref 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 generate_raw(Ref cell, IsPrunnedFunction is_prunned); static Ref generate_raw(Ref cell, CellUsageTree *usage_tree); static Ref virtualize_raw(Ref cell, Cell::VirtualizationParameters virt); + static Ref combine_raw(Ref a, Ref b); + static Ref combine_fast_raw(Ref a, Ref b); }; class MerkleProofBuilder { diff --git a/crypto/vm/cells/MerkleUpdate.cpp b/crypto/vm/cells/MerkleUpdate.cpp index 30b5116e..d75f1667 100644 --- a/crypto/vm/cells/MerkleUpdate.cpp +++ b/crypto/vm/cells/MerkleUpdate.cpp @@ -268,7 +268,7 @@ class MerkleCombine { // 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 + // 2. Max(A) and Max(D) should be merkle update already. But we 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. diff --git a/crypto/vm/continuation.cpp b/crypto/vm/continuation.cpp index 09d2be89..f75aff75 100644 --- a/crypto/vm/continuation.cpp +++ b/crypto/vm/continuation.cpp @@ -1055,7 +1055,7 @@ ControlRegs* force_cregs(Ref& cont) { } int run_vm_code(Ref code, Ref& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, - GasLimits* gas_limits, std::vector> libraries, Ref init_c7) { + GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { VmState vm{code, std::move(stack), gas_limits ? *gas_limits : GasLimits{}, @@ -1066,8 +1066,11 @@ int run_vm_code(Ref code, Ref& stack, int flags, Ref* da std::move(init_c7)}; int res = vm.run(); stack = vm.get_stack_ref(); - if (res == -1 && data_ptr) { - *data_ptr = vm.get_c4(); + if (vm.committed() && data_ptr) { + *data_ptr = vm.get_committed_state().c4; + } + if (vm.committed() && actions_ptr) { + *actions_ptr = vm.get_committed_state().c5; } if (steps) { *steps = vm.get_steps_count(); @@ -1090,11 +1093,12 @@ int run_vm_code(Ref code, Ref& stack, int flags, Ref* da } int run_vm_code(Ref code, Stack& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, - GasLimits* gas_limits, std::vector> libraries, Ref init_c7) { + GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { Ref 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), std::move(init_c7)); + int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries), std::move(init_c7), + actions_ptr); CHECK(stack.is_unique()); if (stk.is_null()) { stack.clear(); diff --git a/crypto/vm/continuation.h b/crypto/vm/continuation.h index 61cbc2d6..1c24c4cd 100644 --- a/crypto/vm/continuation.h +++ b/crypto/vm/continuation.h @@ -644,12 +644,12 @@ class VmState final : public VmStateInterface { void init_cregs(bool same_c3 = false, bool push_0 = true); }; -int run_vm_code(Ref _code, Ref& _stack, int flags = 0, Ref* data_ptr = 0, VmLog log = {}, +int run_vm_code(Ref _code, Ref& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, - Ref init_c7 = {}); -int run_vm_code(Ref _code, Stack& _stack, int flags = 0, Ref* data_ptr = 0, VmLog log = {}, + Ref init_c7 = {}, Ref* actions_ptr = nullptr); +int run_vm_code(Ref _code, Stack& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, - Ref init_c7 = {}); + Ref init_c7 = {}, Ref* actions_ptr = nullptr); ControlData* force_cdata(Ref& cont); ControlRegs* force_cregs(Ref& cont); diff --git a/crypto/vm/stack.hpp b/crypto/vm/stack.hpp index 830f4340..5174ad7f 100644 --- a/crypto/vm/stack.hpp +++ b/crypto/vm/stack.hpp @@ -481,6 +481,18 @@ class Stack : public td::CntObject { Ref pop_atom(); std::string pop_string(); std::string pop_bytes(); + template + Ref pop_object() { + return pop_chk().as_object(); + } + template + Ref pop_object_type_chk() { + auto res = pop_object(); + if (!res) { + throw VmError{Excno::type_chk, "not an object of required type"}; + } + return res; + } void push_null(); void push_int(td::RefInt256 val); void push_int_quiet(td::RefInt256 val, bool quiet = true); diff --git a/doc/fiftbase.tex b/doc/fiftbase.tex index d39bf3fc..162f6784 100644 --- a/doc/fiftbase.tex +++ b/doc/fiftbase.tex @@ -1338,8 +1338,10 @@ Fift has several words for {\em hashmap\/} or {\em (TVM) dictionary\/} manipulat \item {\tt idict!+} ($v$ $x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new key-value pair $(x,v)$ into dictionary $D$ similarly to {\tt idict!}, but fails if the key already exists by returning the unchanged dictionary $D$ and $0$. \item {\tt b>idict!}, {\tt b>idict!+}, variants of {\tt idict!} and {\tt idict!+} accepting the new value $v$ in a {\em Builder\/} instead of a {\em Slice}. \item {\tt udict!}, {\tt udict!+}, {\tt b>udict!}, {\tt b>udict!+}, variants of {\tt idict!}, {\tt idict!+}, {\tt b>idict!}, {\tt b>idict!+}, but with an unsigned $n$-bit integer $x$ used as a key. +\item {\tt sdict!}, {\tt sdict!+}, {\tt b>sdict!}, {\tt b>sdict!+}, variants of {\tt idict!}, {\tt idict!+}, {\tt b>idict!}, {\tt b>idict!+}, but with the first $n$ data bits of {\em Slice\/}~$x$ used as a key. \item {\tt idict@} ($x$ $D$ $n$ -- $v$ $-1$ or $0$), looks up the key represented by signed big-endian $n$-bit {\em Integer\/}~$x$ in the dictionary represented by {\em Cell\/}~$D$. If the key is found, returns the corresponding value as a {\em Slice\/}~$v$ and $-1$. Otherwise returns $0$. \item {\tt udict@} ($x$ $D$ $n$ -- $v$ $-1$ or $0$), similar to {\tt idict@}, but with an {\em un}signed big-endian $n$-bit {\em Integer\/}~$x$ used as a key. +\item {\tt sdict@} ($k$ $D$ $n$ -- $v$ $-1$ or $0$), similar to {\tt idict@}, but with the key provided in the first $n$ bits of {\em Slice\/}~$k$. \item {\tt dictmap} ($D$ $n$ $e$ -- $s'$), applies execution token $e$ (i.e., an anonymous function) to each of the key-value pairs stored in a dictionary $D$ with $n$-bit keys. The execution token is executed once for each key-value pair, with a {\em Builder\/} $b$ and a {\em Slice\/} $v$ (containing the value) pushed into the stack before executing $e$. After the execution $e$ must leave in the stack either a modified {\em Builder\/} $b'$ (containing all data from~$b$ along with the new value $v'$) and $-1$, or $0$ indicating failure. In the latter case, the corresponding key is omitted from the new dictionary. \item {\tt dictmerge} ($D$ $D'$ $n$ $e$ -- $D''$), combines two dictionaries $D$ and $D'$ with $n$-bit keys into one dictionary $D''$ with the same keys. If a key is present in only one of the dictionaries $D$ and $D'$, this key and the corresponding value are copied verbatim to the new dictionary $D''$. Otherwise the execution token (anonymous function) $e$ is invoked to merge the two values $v$ and $v'$ corresponding to the same key $k$ in $D$ and $D'$, respectively. Before $e$ is invoked, a {\em Builder\/}~$b$ and two {\em Slice}s $v$ and $v'$ representing the two values to be merged are pushed. After the execution $e$ leaves either a modified {\em Builder\/}~$b'$ (containing the original data from $b$ along with the combined value) and $-1$, or $0$ on failure. In the latter case, the corresponding key is omitted from the new dictionary. \end{itemize} @@ -2001,10 +2003,12 @@ For example, the active prefix word {\tt B\{}, used for defining {\em Bytes\/} l \item {\tt b.} ($x$ -- ), prints the binary representation of an {\em Integer\/}~$x$, followed by a single space. Equivalent to {\tt b.\_ space}. \item {\tt b.\_} ($x$ -- ), prints the binary representation of an {\em Integer\/}~$x$ without any spaces. Equivalent to {\tt (b.)~type}. \item {\tt b>} ($b$ -- $c$), transforms a {\em Builder\/}~$b$ into a new {\em Cell\/}~$c$ containing the same data as~$b$, cf.~\ptref{p:builder.ops}. -\item {\tt b>idict!} ($v$ $x$ $s$ $n$ -- $s'$ $-1$ or $s$ $0$), adds a new value $v$ (represented by a {\em Builder}) with key given by signed big-endian $n$-bit integer $x$ into dictionary $s$ with $n$-bit keys, and returns the new dictionary $s'$ and $-1$ on success, cf.~\ptref{p:hashmap.ops}. Otherwise the unchanged dictionary $s$ and $0$ are returned. -\item {\tt b>idict!+} ($v$ $x$ $s$ $n$ -- $s'$ $-1$ or $s$ $0$), adds a new key-value pair $(x,v)$ into dictionary $s$ similarly to {\tt b>idict!}, but fails if the key already exists by returning the unchanged dictionary $s$ and $0$, cf.~\ptref{p:hashmap.ops}. -\item {\tt b>udict!} ($v$ $x$ $s$ $n$ -- $s'$ $-1$ or $s$ $0$), adds a new value $v$ (represented by a {\em Builder}) with key given by unsigned big-endian $n$-bit integer $x$ into dictionary $s$ with $n$-bit keys, and returns the new dictionary $s'$ and $-1$ on success, cf.~\ptref{p:hashmap.ops}. Otherwise the unchanged dictionary $s$ and $0$ are returned. -\item {\tt b>udict!+} ($v$ $x$ $s$ $n$ -- $s'$ $-1$ or $s$ $0$), adds a new key-value pair $(x,v)$ into dictionary $s$ similarly to {\tt b>udict!}, but fails if the key already exists by returning the unchanged dictionary $s$ and $0$, cf.~\ptref{p:hashmap.ops}. +\item {\tt b>idict!} ($v$ $x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new value $v$ (represented by a {\em Builder}) with key given by signed big-endian $n$-bit integer $x$ into dictionary $D$ with $n$-bit keys, and returns the new dictionary $D'$ and $-1$ on success, cf.~\ptref{p:hashmap.ops}. Otherwise the unchanged dictionary $D$ and $0$ are returned. +\item {\tt b>idict!+} ($v$ $x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new key-value pair $(x,v)$ into dictionary $D$ similarly to {\tt b>idict!}, but fails if the key already exists by returning the unchanged dictionary $D$ and $0$, cf.~\ptref{p:hashmap.ops}. +\item {\tt b>sdict!} ($v$ $k$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new value $v$ (represented by a {\em Builder}) with key given by the first $n$ bits of {\em Slice\/}~$k$ into dictionary $D$ with $n$-bit keys, and returns the new dictionary $D'$ and $-1$ on success, cf.~\ptref{p:hashmap.ops}. Otherwise the unchanged dictionary $D$ and $0$ are returned. +\item {\tt b>sdict!+} ($v$ $k$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new key-value pair $(k,v)$ into dictionary $D$ similarly to {\tt b>sdict!}, but fails if the key already exists by returning the unchanged dictionary $D$ and $0$, cf.~\ptref{p:hashmap.ops}. +\item {\tt b>udict!} ($v$ $x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new value $v$ (represented by a {\em Builder}) with key given by unsigned big-endian $n$-bit integer $x$ into dictionary $D$ with $n$-bit keys, and returns the new dictionary $D'$ and $-1$ on success, cf.~\ptref{p:hashmap.ops}. Otherwise the unchanged dictionary $D$ and $0$ are returned. +\item {\tt b>udict!+} ($v$ $x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new key-value pair $(x,v)$ into dictionary $D$ similarly to {\tt b>udict!}, but fails if the key already exists by returning the unchanged dictionary $D$ and $0$, cf.~\ptref{p:hashmap.ops}. \item {\tt bbitrefs} ($b$ -- $x$ $y$), returns both the number of data bits $x$ and the number of references $y$ already stored in {\em Builder\/}~$b$, cf.~\ptref{p:builder.ops}. \item {\tt bbits} ($b$ -- $x$), returns the number of data bits already stored in {\em Builder\/}~$b$. The result $x$ is an {\em Integer\/} in the range $0\dots1023$, cf.~\ptref{p:builder.ops}. \item {\tt bl} ( -- $x$), pushes the Unicode codepoint of a space, i.e., 32, cf.~\ptref{p:string.ops}. @@ -2129,6 +2133,9 @@ Typical values of $x$ are $x=0$ or $x=2$ for very small bags of cells (e.g., TON \item {\tt s>c} ($s$ -- $c$), creates a {\em Cell}~$c$ directly from a {\em Slice}~$s$, cf.~\ptref{p:slice.ops}. Equivalent to {\tt }. \item {\tt sbitrefs} ($s$ -- $x$ $y$), returns both the number of data bits $x$ and the number of cell references $y$ remaining in {\em Slice}~$s$, cf.~\ptref{p:slice.ops}. Equivalent to {\tt remaining}. \item {\tt sbits} ($s$ -- $x$), returns the number of data bits $x$ remaining in {\em Slice}~$s$, cf.~\ptref{p:slice.ops}. +\item {\tt sdict!} ($v$ $k$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new value $v$ (represented by a {\em Slice\/}) with key given by the first $n$ bits of {\em Slice\/}~$k$ into dictionary $D$ with $n$-bit keys, and returns the new dictionary $D'$ and $-1$ on success, cf.~\ptref{p:hashmap.ops}. Otherwise the unchanged dictionary $D$ and $0$ are returned. +\item {\tt sdict!+} ($v$ $k$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new key-value pair $(k,v)$ into dictionary $D$ similarly to {\tt sdict!}, but fails if the key already exists by returning the unchanged dictionary $D$ and $0$, cf.~\ptref{p:hashmap.ops}. +\item {\tt sdict@} ($k$ $D$ $n$ -- $v$ $-1$ or $0$), looks up the key given by the first $n$ data bits of {\em Slice\/}~$x$ in the dictionary represented by {\em Cell\/} or {\em Null\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, returns the corresponding value as a {\em Slice\/}~$v$ and $-1$. Otherwise returns $0$. \item {\tt second} ($t$ -- $x$), returns the second component of a {\em Tuple}, cf.~\ptref{p:tuples}. Equivalent to {\tt 1 []}. \item {\tt sgn} ($x$ -- $y$), computes the sign of an {\em Integer\/} $x$ (i.e., pushes $1$ if $x>0$, $-1$ if $x<0$, and $0$ if $x=0$), cf.~\ptref{p:int.comp}. Equivalent to {\tt 0 cmp}. \item {\tt shash} ($s$ -- $B$), computes the $\Sha$-based representation hash of a {\em Slice\/} by first transforming it into a cell, cf.~\ptref{p:hash.ops}. Equivalent to {\tt s>c hashB}. diff --git a/tdactor/test/actors_core.cpp b/tdactor/test/actors_core.cpp index d426f344..2e44d3bb 100644 --- a/tdactor/test/actors_core.cpp +++ b/tdactor/test/actors_core.cpp @@ -101,10 +101,10 @@ TEST(Actor2, locker) { kill_signal.add_signal(ActorSignals::Kill); ActorSignals wakeup_signal; - kill_signal.add_signal(ActorSignals::Wakeup); + wakeup_signal.add_signal(ActorSignals::Wakeup); ActorSignals cpu_signal; - kill_signal.add_signal(ActorSignals::Cpu); + cpu_signal.add_signal(ActorSignals::Cpu); { ActorLocker lockerA(&state); diff --git a/tl/generate/scheme/lite_api.tl b/tl/generate/scheme/lite_api.tl index 0dd3fa22..aa9f1c08 100644 --- a/tl/generate/scheme/lite_api.tl +++ b/tl/generate/scheme/lite_api.tl @@ -35,6 +35,7 @@ liteServer.blockState id:tonNode.blockIdExt root_hash:int256 file_hash:int256 da liteServer.blockHeader id:tonNode.blockIdExt mode:# header_proof:bytes = liteServer.BlockHeader; liteServer.sendMsgStatus status:int = liteServer.SendMsgStatus; liteServer.accountState id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:bytes proof:bytes state:bytes = liteServer.AccountState; +liteServer.runMethodResult mode:# id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:bytes proof:bytes state_proof:bytes init_c7:bytes lib_extras:bytes exit_code:int result:bytes = liteServer.RunMethodResult; liteServer.shardInfo id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:bytes shard_descr:bytes = liteServer.ShardInfo; liteServer.allShardsInfo id:tonNode.blockIdExt proof:bytes data:bytes = liteServer.AllShardsInfo; liteServer.transactionInfo id:tonNode.blockIdExt proof:bytes transaction:bytes = liteServer.TransactionInfo; @@ -63,6 +64,7 @@ liteServer.getState id:tonNode.blockIdExt = liteServer.BlockState; liteServer.getBlockHeader id:tonNode.blockIdExt mode:# = liteServer.BlockHeader; liteServer.sendMessage body:bytes = liteServer.SendMsgStatus; liteServer.getAccountState id:tonNode.blockIdExt account:liteServer.accountId = liteServer.AccountState; +liteServer.runSmcMethod mode:# id:tonNode.blockIdExt account:liteServer.accountId method_id:int params:bytes = liteServer.RunMethodResult; liteServer.getShardInfo id:tonNode.blockIdExt workchain:int shard:long exact:Bool = liteServer.ShardInfo; liteServer.getAllShardsInfo id:tonNode.blockIdExt = liteServer.AllShardsInfo; liteServer.getOneTransaction id:tonNode.blockIdExt account:liteServer.accountId lt:long = liteServer.TransactionInfo; diff --git a/tl/generate/scheme/lite_api.tlo b/tl/generate/scheme/lite_api.tlo index 02b507f4..c813541e 100644 Binary files a/tl/generate/scheme/lite_api.tlo and b/tl/generate/scheme/lite_api.tlo differ diff --git a/tl/generate/scheme/tonlib_api.tl b/tl/generate/scheme/tonlib_api.tl index 9235ab35..da23bcdc 100644 --- a/tl/generate/scheme/tonlib_api.tl +++ b/tl/generate/scheme/tonlib_api.tl @@ -79,8 +79,8 @@ fees in_fwd_fee:int53 storage_fee:int53 gas_fee:int53 fwd_fee:int53= Fees; query.fees source_fees:fees destination_fees:fees = query.Fees; query.info id:int53 valid_until:int53 body_hash:bytes = query.Info; -tvm.slice bytes:string = tvm.Slice; -tvm.cell bytes:string = tvm.Cell; +tvm.slice bytes:bytes = tvm.Slice; +tvm.cell bytes:bytes = tvm.Cell; tvm.numberDecimal number:string = tvm.Number; tvm.tuple elements:vector = tvm.Tuple; tvm.list elements:vector = tvm.List; diff --git a/tl/generate/scheme/tonlib_api.tlo b/tl/generate/scheme/tonlib_api.tlo index 82702148..5c9d506e 100644 Binary files a/tl/generate/scheme/tonlib_api.tlo and b/tl/generate/scheme/tonlib_api.tlo differ diff --git a/validator-engine-console/validator-engine-console.h b/validator-engine-console/validator-engine-console.h index 8c4596dd..5722c1e7 100644 --- a/validator-engine-console/validator-engine-console.h +++ b/validator-engine-console/validator-engine-console.h @@ -85,6 +85,7 @@ class ValidatorEngineConsole : public td::actor::Actor { void add_cmd(td::BufferSlice data) { ex_mode_ = true; ex_queries_.push_back(std::move(data)); + set_readline_enabled(false); } void set_fail_timeout(td::Timestamp ts) { fail_timeout_ = ts; diff --git a/validator/impl/liteserver.cpp b/validator/impl/liteserver.cpp index b9f89f0c..8a11af71 100644 --- a/validator/impl/liteserver.cpp +++ b/validator/impl/liteserver.cpp @@ -129,7 +129,7 @@ void LiteQuery::start_up() { [&](lite_api::liteServer_getState& q) { this->perform_getState(ton::create_block_id(q.id_)); }, [&](lite_api::liteServer_getAccountState& q) { this->perform_getAccountState(ton::create_block_id(q.id_), static_cast(q.account_->workchain_), - q.account_->id_); + q.account_->id_, 0); }, [&](lite_api::liteServer_getOneTransaction& q) { this->perform_getOneTransaction(ton::create_block_id(q.id_), @@ -172,6 +172,10 @@ void LiteQuery::start_up() { q.mode_ & 1 ? q.start_after_ : td::Bits256::zero(), q.mode_ & 4 ? q.modified_after_ : 0); }, + [&](lite_api::liteServer_runSmcMethod& q) { + this->perform_runSmcMethod(ton::create_block_id(q.id_), static_cast(q.account_->workchain_), + q.account_->id_, q.mode_, q.method_id_, std::move(q.params_)); + }, [&](auto& obj) { this->abort_query(td::Status::Error(ErrorCode::protoviolation, "unknown query")); })); } @@ -604,9 +608,9 @@ bool LiteQuery::request_zero_state(BlockIdExt blkid) { return true; } -void LiteQuery::perform_getAccountState(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr) { - LOG(INFO) << "started a getAccountState(" << blkid.to_str() << ", " << workchain << ", " << addr.to_hex() - << ") liteserver query"; +void LiteQuery::perform_getAccountState(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr, int mode) { + LOG(INFO) << "started a getAccountState(" << blkid.to_str() << ", " << workchain << ", " << addr.to_hex() << ", " + << mode << ") liteserver query"; if (blkid.id.workchain != masterchainId && blkid.id.workchain != workchain) { fatal_error("reference block for a getAccountState() must belong to the masterchain"); return; @@ -626,6 +630,7 @@ void LiteQuery::perform_getAccountState(BlockIdExt blkid, WorkchainId workchain, } acc_workchain_ = workchain; acc_addr_ = addr; + mode_ = mode; if (blkid.id.workchain != masterchainId) { base_blk_id_ = blkid; set_continuation([&]() -> void { finish_getAccountState({}); }); @@ -659,6 +664,45 @@ void LiteQuery::continue_getAccountState_0(Ref request_mc_block_data(blkid); } +void LiteQuery::perform_runSmcMethod(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr, int mode, + int method_id, td::BufferSlice params) { + LOG(INFO) << "started a runSmcMethod(" << blkid.to_str() << ", " << workchain << ", " << addr.to_hex() << ", " + << method_id << ", " << mode << ") liteserver query with " << params.size() << " parameter bytes"; + if (params.size() >= 65536) { + fatal_error("more than 64k parameter bytes passed"); + return; + } + if (mode & ~0xf) { + fatal_error("unsupported mode in runSmcMethod"); + return; + } + stack_.clear(); + try { + if (params.size()) { + auto res = vm::std_boc_deserialize(std::move(params)); + if (res.is_error()) { + fatal_error("cannot deserialize parameter list boc: "s + res.move_as_error().to_string()); + return; + } + auto cs = vm::load_cell_slice(res.move_as_ok()); + if (!(vm::Stack::deserialize_to(cs, stack_, 0) && cs.empty_ext())) { + fatal_error("parameter list boc cannot be deserialized as a VmStack"); + return; + } + } else { + stack_ = td::make_ref(); + } + stack_.write().push_smallint(method_id); + } catch (vm::VmError& vme) { + fatal_error("error deserializing parameter list: "s + vme.get_msg()); + return; + } catch (vm::VmVirtError& vme) { + fatal_error("virtualization error while deserializing parameter list: "s + vme.get_msg()); + return; + } + perform_getAccountState(blkid, workchain, addr, mode | 0x10000); +} + void LiteQuery::perform_getOneTransaction(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr, LogicalTime lt) { LOG(INFO) << "started a getOneTransaction(" << blkid.to_str() << ", " << workchain << ", " << addr.to_hex() << "," << lt << ") liteserver query"; @@ -934,10 +978,15 @@ void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) { acc_root = acc_csr->prefetch_ref(); } auto proof = vm::std_boc_serialize_multi({std::move(proof1), pb.extract_proof()}); + pb.clear(); if (proof.is_error()) { fatal_error(proof.move_as_error()); return; } + if (mode_ & 0x10000) { + finish_runSmcMethod(std::move(shard_proof), proof.move_as_ok(), std::move(acc_root)); + return; + } td::BufferSlice data; if (acc_root.not_null()) { auto res = vm::std_boc_serialize(std::move(acc_root)); @@ -954,6 +1003,13 @@ void LiteQuery::finish_getAccountState(td::BufferSlice shard_proof) { finish_query(std::move(b)); } +void LiteQuery::finish_runSmcMethod(td::BufferSlice shard_proof, td::BufferSlice state_proof, Ref acc_root) { + LOG(INFO) << "completing runSmcMethod() query"; + // ... TODO ... + fatal_error("runSmcMethod not implemented"); + return; +} + void LiteQuery::continue_getOneTransaction() { LOG(INFO) << "completing getOneTransaction() query"; CHECK(block_.not_null()); diff --git a/validator/impl/liteserver.hpp b/validator/impl/liteserver.hpp index 36fc9a80..4ebcfeb2 100644 --- a/validator/impl/liteserver.hpp +++ b/validator/impl/liteserver.hpp @@ -37,6 +37,7 @@ class LiteQuery : public td::actor::Actor { td::Timestamp timeout_; td::Promise promise_; int pending_{0}; + int mode_{0}; WorkchainId acc_workchain_; StdSmcAddress acc_addr_; LogicalTime trans_lt_; @@ -55,6 +56,7 @@ class LiteQuery : public td::actor::Actor { std::vector> aux_objs_; std::vector blk_ids_; std::unique_ptr chain_; + Ref stack_; public: enum { @@ -91,10 +93,13 @@ class LiteQuery : public td::actor::Actor { void continue_getState(BlockIdExt blkid, Ref state); void continue_getZeroState(BlockIdExt blkid, td::BufferSlice state); void perform_sendMessage(td::BufferSlice ext_msg); - void perform_getAccountState(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr); + void perform_getAccountState(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr, int mode); void continue_getAccountState_0(Ref mc_state, BlockIdExt blkid); void continue_getAccountState(); void finish_getAccountState(td::BufferSlice shard_proof); + void perform_runSmcMethod(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr, int mode, int method_id, + td::BufferSlice params); + void finish_runSmcMethod(td::BufferSlice shard_proof, td::BufferSlice state_proof, Ref acc_root); void perform_getOneTransaction(BlockIdExt blkid, WorkchainId workchain, StdSmcAddress addr, LogicalTime lt); void continue_getOneTransaction(); void perform_getTransactions(WorkchainId workchain, StdSmcAddress addr, LogicalTime lt, Bits256 hash, unsigned count);