From 2edd61fb0f63936fda3a6d7b39c85f65cd65201e Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 2 Jul 2024 18:35:59 -0400 Subject: [PATCH 1/5] Initialize accumulating collections to empty. --- include/bitcoin/system/impl/radix/base_58.ipp | 4 ++-- src/radix/base_58.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/bitcoin/system/impl/radix/base_58.ipp b/include/bitcoin/system/impl/radix/base_58.ipp index dd6935a41d..9007a11fd3 100644 --- a/include/bitcoin/system/impl/radix/base_58.ipp +++ b/include/bitcoin/system/impl/radix/base_58.ipp @@ -29,7 +29,7 @@ namespace system { template bool decode_base58(data_array& out, const std::string& in) NOEXCEPT { - data_chunk data; + data_chunk data{}; if (!decode_base58(data, in) || (data.size() != Size)) return false; @@ -42,7 +42,7 @@ template data_array base58_array(const char(&string)[Size]) NOEXCEPT { // log(58) / log(256), rounded up. - data_array out; + data_array out{}; if (!decode_base58(out, string)) out.fill(0); diff --git a/src/radix/base_58.cpp b/src/radix/base_58.cpp index 52aa85367e..14a901d2bb 100644 --- a/src/radix/base_58.cpp +++ b/src/radix/base_58.cpp @@ -96,7 +96,7 @@ std::string encode_base58(const data_slice& unencoded) NOEXCEPT const auto indexes_size = add1(number_nonzero * 138_size / 100_size); // Allocate enough space in big-endian base58 representation. - data_chunk indexes(indexes_size); + data_chunk indexes(indexes_size, 0x00); // Process the bytes. for (size_t index = leading_zeros; index < unencoded.size(); ++index) @@ -166,7 +166,7 @@ bool decode_base58(data_chunk& out, const std::string& in) NOEXCEPT const size_t data_size = add1(in.size() * 733_size / 1000_size); // Allocate enough space in big-endian base256 representation. - data_chunk data(data_size); + data_chunk data(data_size, 0x00); // Process the characters. for (auto it = in.begin() + leading_zeros; it != in.end(); ++it) From ee94dfbb32b12ff5c6f06ed0ddc5e76e857db812 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 2 Jul 2024 18:36:12 -0400 Subject: [PATCH 2/5] Remove unused include. --- include/bitcoin/system/types.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/bitcoin/system/types.hpp b/include/bitcoin/system/types.hpp index 72b3ecb7d2..8d1841e1eb 100644 --- a/include/bitcoin/system/types.hpp +++ b/include/bitcoin/system/types.hpp @@ -20,7 +20,6 @@ #define LIBBITCOIN_SYSTEM_TYPES_HPP #include -#include #include #include #include From 4432add2a14f0487c951b664f4a731a99f5089db Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 2 Jul 2024 18:36:41 -0400 Subject: [PATCH 3/5] Un-alias printer/parameter std::vector. --- test/config/parameter.cpp | 2 +- test/config/printer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/config/parameter.cpp b/test/config/parameter.cpp index ae23132946..7766554dfc 100644 --- a/test/config/parameter.cpp +++ b/test/config/parameter.cpp @@ -55,7 +55,7 @@ static void load_test_options(po::options_description& options) ("toggled", value()->zero_tokens(), "Toggle only bool.") /* The enumerability of the data types does not control multiple, instance behavior but it is necessary to capture multiples. */ - ("VECTOR", value>(), "String vector.") + ("VECTOR", value>(), "String vector.") ("multitoken", value()->multitoken(), "Multi-token int."); } diff --git a/test/config/printer.cpp b/test/config/printer.cpp index 6ad6fd8334..b8f0388c04 100644 --- a/test/config/printer.cpp +++ b/test/config/printer.cpp @@ -305,7 +305,7 @@ BOOST_AUTO_TEST_CASE(printer__format_usage_parameters__unsorted_multiple_paramet ("required", value()->required(), "Required path.") ("untoggled", value()->zero_tokens(), "Zero token but not short.") ("toggled,t", value()->zero_tokens(), "Toggled, zero token and short.") - ("ARRAY", value>(), "String vector.") + ("ARRAY", value>(), "String vector.") ("multy", value()->multitoken(), "Multi-token int."); arguments.add("required", 1); arguments.add("SIMPLE", 1); From f1545539a988f65fedd187a38ca4afa2e17a766c Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 3 Jul 2024 16:31:12 -0400 Subject: [PATCH 4/5] Update /test/machine files. --- Makefile.am | 2 + builds/cmake/CMakeLists.txt | 2 + .../libbitcoin-system-test.vcxproj | 2 + .../libbitcoin-system-test.vcxproj.filters | 6 + test/machine/interpreter.cpp | 5 +- test/machine/number.cpp | 4 +- test/machine/program.cpp | 214 +--------------- test/machine/sizing.cpp | 229 ++++++++++++++++++ test/machine/stack.cpp | 28 +++ 9 files changed, 274 insertions(+), 218 deletions(-) create mode 100644 test/machine/sizing.cpp create mode 100644 test/machine/stack.cpp diff --git a/Makefile.am b/Makefile.am index abe9a0f141..9593fdce45 100755 --- a/Makefile.am +++ b/Makefile.am @@ -311,6 +311,8 @@ test_libbitcoin_system_test_SOURCES = \ test/machine/interpreter.cpp \ test/machine/number.cpp \ test/machine/program.cpp \ + test/machine/sizing.cpp \ + test/machine/stack.cpp \ test/math/addition.cpp \ test/math/bits.cpp \ test/math/bytes.cpp \ diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index db7024a854..debabad30c 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -794,6 +794,8 @@ if (with-tests) "../../test/machine/interpreter.cpp" "../../test/machine/number.cpp" "../../test/machine/program.cpp" + "../../test/machine/sizing.cpp" + "../../test/machine/stack.cpp" "../../test/math/addition.cpp" "../../test/math/bits.cpp" "../../test/math/bytes.cpp" diff --git a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj index 0a7d534bb8..b7a1e266c2 100644 --- a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj @@ -182,6 +182,8 @@ + + diff --git a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters index dd333188f3..967ee2e723 100644 --- a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters @@ -378,6 +378,12 @@ src\machine + + src\machine + + + src\machine + src diff --git a/test/machine/interpreter.cpp b/test/machine/interpreter.cpp index 9fdb390166..b850b299e0 100644 --- a/test/machine/interpreter.cpp +++ b/test/machine/interpreter.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -18,10 +18,9 @@ */ #include "../test.hpp" - BOOST_AUTO_TEST_SUITE(interpreter_tests) -BOOST_AUTO_TEST_CASE(interpreter__construct__todo__todo) +BOOST_AUTO_TEST_CASE(interpreter_test) { BOOST_REQUIRE(true); } diff --git a/test/machine/number.cpp b/test/machine/number.cpp index 5a151fd760..3d7fafa029 100644 --- a/test/machine/number.cpp +++ b/test/machine/number.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -20,8 +20,6 @@ BOOST_AUTO_TEST_SUITE(number_tests) -using namespace bc::system::machine; - BOOST_AUTO_TEST_CASE(number_test) { BOOST_REQUIRE(true); diff --git a/test/machine/program.cpp b/test/machine/program.cpp index f194c9c743..4f2e1ff7b1 100644 --- a/test/machine/program.cpp +++ b/test/machine/program.cpp @@ -1,5 +1,5 @@ /** - * Copyright (c) 2011-2023 libbitcoin developers (see AUTHORS) + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) * * This file is part of libbitcoin. * @@ -20,219 +20,9 @@ BOOST_AUTO_TEST_SUITE(program_tests) -BOOST_AUTO_TEST_CASE(program__construct__todo__todo) +BOOST_AUTO_TEST_CASE(program_test) { BOOST_REQUIRE(true); } BOOST_AUTO_TEST_SUITE_END() - -// Performance considerations. -// ---------------------------------------------------------------------------- - -// stdlib object sizes are subjsct to implementation (including debug builds). -#if defined(HAVE_MSC) && defined(NDEBUG) - -// std_vector requires 3 pointers (front/back/size). -constexpr auto a1_ = sizeof(std_vector*); -constexpr auto b1_ = sizeof(std_vector&); -constexpr auto c1_ = sizeof(std_vector); -static_assert(a1_ == 1 * sizeof(size_t)); -static_assert(b1_ == 3 * sizeof(size_t)); -static_assert(c1_ == 3 * sizeof(size_t)); - -// std_vector> does not increase this (per element) cost. -constexpr auto a2_ = sizeof(std_vector*>::value_type); -constexpr auto c2_ = sizeof(std_vector>::value_type); -constexpr auto d2_ = sizeof(std_vector>>::value_type); -static_assert(a2_ == 1 * sizeof(size_t)); -static_assert(c2_ == 3 * sizeof(size_t)); -static_assert(d2_ == 2 * sizeof(size_t)); - -// std_vector> requires 3 pointers (singular cost). -constexpr auto a3 = sizeof(std_vector*>); -constexpr auto c3 = sizeof(std_vector>); -constexpr auto d3 = sizeof(std_vector>>); -static_assert(a3 == 3 * sizeof(size_t)); -static_assert(c3 == 3 * sizeof(size_t)); -static_assert(d3 == 3 * sizeof(size_t)); - -// ---------------------------------------------------------------------------- - -// std::variant> object reference requires 4 pointers (max element + 1). -constexpr auto a4 = sizeof(std::variant*>); -constexpr auto c4 = sizeof(std::variant>); -static_assert(a4 == 2 * sizeof(int64_t)); -static_assert(c4 == 2 * sizeof(size_t) + 2 * sizeof(int64_t)); - -// std_vector>> does not increase this (per element) cost. -constexpr auto a5 = sizeof(std_vector*>>::value_type); -constexpr auto c5 = sizeof(std_vector>>::value_type); -static_assert(a5 == 2 * sizeof(int64_t)); -static_assert(c5 == 2 * sizeof(size_t) + 2 * sizeof(int64_t)); - -// std_vector>> requires 3 pointers (singular cost). -constexpr auto a6 = sizeof(std_vector*>>); -constexpr auto c6 = sizeof(std_vector>>); -static_assert(a6 == 3 * sizeof(size_t)); -static_assert(c6 == 3 * sizeof(size_t)); - -// ---------------------------------------------------------------------------- - -// std::shared_ptr> requires 2 pointers (ptr/refcount). -constexpr auto a7 = sizeof(std::shared_ptr>::element_type*); -constexpr auto b7 = sizeof(std::shared_ptr>); -static_assert(a7 == 1 * sizeof(size_t)); -static_assert(b7 == 2 * sizeof(size_t)); - -// std::variant>> requires 3 pointers (max element + 1). -constexpr auto a8 = sizeof(std::variant>>); -static_assert(a8 == 2 * sizeof(size_t) + 1 * sizeof(int64_t)); - -// std::variant*> requires 2 pointers (max element + 1). -// This would be possible in the case where all stack data_chunks are externally owned. -// This is the case for all script/witness push data elements (but not for computed hashes). -// The program may need to hold a shared_ptr to the witness and script, though (like the tx) it -// could be made a requirement of the caller to retain the passed-by-reference objects in scope. -constexpr auto a9 = sizeof(std::variant*>); -constexpr auto c9 = sizeof(std::variant>>); -constexpr auto d9 = sizeof(std::variant>); -static_assert(a9 == 2 * sizeof(int64_t)); -static_assert(c9 == 2 * sizeof(size_t) + 1 * sizeof(int64_t)); -static_assert(d9 == 2 * sizeof(size_t) + 2 * sizeof(int64_t)); - -// Computed hashes can be maintained in the variant by std::unique_ptr>. -// This constrols their lifetime without increasing the size of the union. But -// std::unique_ptr> can be used as a safer std_vector*, -// so that's all that is needed. -constexpr auto a10 = sizeof(std::variant>, std_vector*>); -constexpr auto a11 = sizeof(std::variant>>); -static_assert(a10 == 2 * sizeof(int64_t)); -static_assert(a11 == 2 * sizeof(int64_t)); - -// Use of std::weak_ptr vs. std_vector* would expand the -// variant size, and use of std::unique_ptr> with the shared -// pointer's raw pointer would attempt to delete the shared-object's underlying -// data_chunk. So a C ponter is necessary for shared_ptr references, and their -// lifetimes must be externally controlled. -constexpr auto a0 = sizeof(std::weak_ptr); -static_assert(a0 == 2 * sizeof(size_t)); - -#endif // HAVE_MSC && NDEBUG - -// copy/move restrictions of unique_ptr: -////auto foo() -////{ -//// auto unique = std::make_unique(data_chunk{}); -//// auto shared = std::make_shared(data_chunk{}); -//// -//// std::unique_ptr unique_ref_unique(unique.get()); -//// std::unique_ptr unique_ref_shared(shared.get()); -//// ////std::shared_ptr unique_copy_unique(unique); -//// std::shared_ptr unique_copy_shared(shared); -//// std::unique_ptr unique_move_unique(std::move(unique)); -//// ////std::unique_ptr unique_move_shared(std::move(shared)); -//// unique = std::move(unique_move_unique); -//// -//// std::shared_ptr shared_ref_unique(unique.get()); -//// std::shared_ptr shared_ref_shared(shared.get()); -//// std::shared_ptr shared_copy_shared(shared); -//// ////std::shared_ptr shared_copy_unique(unique); -//// std::shared_ptr shared_move_unique(std::move(unique)); -//// std::shared_ptr shared_move_shared(std::move(shared)); -//// shared = std::move(shared_move_unique); -////} - -// Note that operation.data is nullptr (2 pointers vs. 5) for non-push data -// opcodes (and one byte for the overflow boolean). This is additional script -// overhead in order to provide shared pointers to avoid push data copy. If -// average data element byte length is < (2 * sizeof(pointer) + 1), such as -// predominantly non-push data opcodes, this implies an increased average cost. -// -// std_vector => 3 pointers per element. -// std_vector> => 5 pointers per element. -// -// The additional cost of stack storage for std::shared_ptr is 2 pointers per -// element, and shared pointer origination (i.e. by the tx) requires 2 more (4). -// This allows avoidance of a full vector copy of each data chunk pushed to the -// stack, instead copying its shared pointer from script/witness data. The -// vector copy would require 3 pointers for the copy construction of the new -// vector for each stack element, in addition to the element contents (up to 520 -// bytes per element). So the net tx/stack storage cost of avoiding push data -// copy is 5-4=1 pointer storage/copy per element. -// -// std_vector => 3 -// std_vector>> => 6 -// -// This change to variant added a cost of 1 pointer per stack element (only) to -// the previous cost of 2 pointers per element, making the net cost 2 pointers -// per stack element (with no change to the operation stack). This enables -// strong typing of stack data as bool/int64_t (or chunk_cptr), eliminating -// a large number of chunk<->integral conversions, as well as 3 pointer vector -// constructions and value copies for all information pushed to the stack. -// -// std_vector => 3 -// std_vector> => 2 -// -// This eliminates the use of unnecessary shared pointer construction for all -// script/witness push data, limiting it to hash-constructed stack elements. -// This would cost 2 pointers per hash construction (as before) but eliminate -// 4 pointers for all other stack data. So in relation to the simple vactor of -// data_chunk, it is -1 for push data and +1 for hash constructions. There is -// typically far more push data than hash construction, so this is a clear win. - -// pay-to-public-key-hash -// With average signature size and compressed public key, output/input: -// "[72] [33] : dup hash160 [32] equalverify checksig" - -// script constructions (two vectors): -// vector[72 + 33 + 1 + 1 + 32 + 1 + 1] => 2*3 + 141 = 147 bytes. -// vs: -// script constructions (overflow, opcode, shared_pointer): -// op(vector[72]) 1 + 1 + 16 + 3 + 72 -// op(vector[33]) 1 + 1 + 16 + 3 + 33 -// op(nullptr) 1 + 1 + 16 -// op(nullptr) 1 + 1 + 16 -// op(vector[32]) 1 + 1 + 16 + 3 + 32 -// op(nullptr) 1 + 1 + 16 -// op(nullptr) 1 + 1 + 16 -// vector[7*18 + 3*3 + 72 + 33 + 32] => 2*3 + 272 = 278 bytes (278/147 = 189%). - -// Stack vector construction: -// vector[72] 24 + 72 -// vector[33] 24 + 33 -// vector[dup] 24 + 33 -// vector[hash160] 24 + 32 (hash moved to new vector) -// vector[checksig] 24 + 1 (1 byte for success result) -// Stack copy: 5*24 + 72 + 2*33 + 32 + 1 = 291 bytes. -// Inclusive of script constructions: (291 + 278)/(291 + 147) = 130%. -// vs: -// Previous conversion to shared_ptr push data storage: -// Stack vector> construction: -// ptr[72] 16 -// ptr[33] 16 -// ptr[dup] 16 -// ptr[hash160] 16 + 24 + 32 (hash moved to new shared_ptr[vector]) -// ptr[checksig] 16 + 24 + 1 (1 byte for success result) -// Stack copy: 5*16 + 2*24 + 32 + 1 = 161 bytes (161/291 = 55%). -// Inclusive of script constructions: (161 + 278)/(291 + 147) = 100%. -// vs: -// Recent conversion to variant storage using shared_ptr: -// Stack vector>> construction: -// var[ptr[vector[72]]] 24 -// var[ptr[vector[33]]] 24 -// var[ptr[dup]]] 24 -// var[ptr[hash160]]] 24 + 24 + 32 (hash moved to new shared_ptr, vector) -// var[bool[checksig]] 24 (native bool union) -// Stack copy: 5*24 + 2*24 + 32 + 1 = 201 bytes (201/161 = 125%, 201/291 = 69%). -// Inclusive of script constructions: (201 + 278)/(291 + 147) = 109%. -// vs: -// Possible conversion to variant storage using data_chunk&: -// Stack vector> construction: -// var[vector[72]&] 24 -// var[vector[33]&] 24 -// var[[dup]&] 24 -// var[[hash160]&] 24 + 24 + 32 (hash move, heap pointer storage) -// var[bool[checksig]] 24 (native bool union) -// Stack copy: 5*16 + 24 + 32 = 136 bytes (136/201 = 68%, 136/161 = 84%, 136/291 = 47%). -// Inclusive of script constructions: (136 + 278)/(291 + 147) = 95%. diff --git a/test/machine/sizing.cpp b/test/machine/sizing.cpp new file mode 100644 index 0000000000..963f3ab78d --- /dev/null +++ b/test/machine/sizing.cpp @@ -0,0 +1,229 @@ +/** + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "../test.hpp" + +// Size considerations. +// ---------------------------------------------------------------------------- + +// stdlib object sizes are subjsct to implementation (including debug builds). +#if defined(HAVE_MSC) && defined(NDEBUG) + +// std_vector requires 3 pointers (front/back/size). +constexpr auto a1_ = sizeof(std_vector*); +constexpr auto b1_ = sizeof(std_vector&); +constexpr auto c1_ = sizeof(std_vector); +static_assert(a1_ == 1 * sizeof(size_t)); +static_assert(b1_ == 3 * sizeof(size_t)); +static_assert(c1_ == 3 * sizeof(size_t)); + +// std_vector> does not increase this (per element) cost. +constexpr auto a2_ = sizeof(std_vector*>::value_type); +constexpr auto c2_ = sizeof(std_vector>::value_type); +constexpr auto d2_ = sizeof(std_vector>>::value_type); +static_assert(a2_ == 1 * sizeof(size_t)); +static_assert(c2_ == 3 * sizeof(size_t)); +static_assert(d2_ == 2 * sizeof(size_t)); + +// std_vector> requires 3 pointers (singular cost). +constexpr auto a3 = sizeof(std_vector*>); +constexpr auto c3 = sizeof(std_vector>); +constexpr auto d3 = sizeof(std_vector>>); +static_assert(a3 == 3 * sizeof(size_t)); +static_assert(c3 == 3 * sizeof(size_t)); +static_assert(d3 == 3 * sizeof(size_t)); + +// ---------------------------------------------------------------------------- + +// std::variant> object reference requires 4 pointers (max element + 1). +constexpr auto a4 = sizeof(std::variant*>); +constexpr auto c4 = sizeof(std::variant>); +static_assert(a4 == 2 * sizeof(int64_t)); +static_assert(c4 == 2 * sizeof(size_t) + 2 * sizeof(int64_t)); + +// std_vector>> does not increase this (per element) cost. +constexpr auto a5 = sizeof(std_vector*>>::value_type); +constexpr auto c5 = sizeof(std_vector>>::value_type); +static_assert(a5 == 2 * sizeof(int64_t)); +static_assert(c5 == 2 * sizeof(size_t) + 2 * sizeof(int64_t)); + +// std_vector>> requires 3 pointers (singular cost). +constexpr auto a6 = sizeof(std_vector*>>); +constexpr auto c6 = sizeof(std_vector>>); +static_assert(a6 == 3 * sizeof(size_t)); +static_assert(c6 == 3 * sizeof(size_t)); + +// ---------------------------------------------------------------------------- + +// std::shared_ptr> requires 2 pointers (ptr/refcount). +constexpr auto a7 = sizeof(std::shared_ptr>::element_type*); +constexpr auto b7 = sizeof(std::shared_ptr>); +static_assert(a7 == 1 * sizeof(size_t)); +static_assert(b7 == 2 * sizeof(size_t)); + +// std::variant>> requires 3 pointers (max element + 1). +constexpr auto a8 = sizeof(std::variant>>); +static_assert(a8 == 2 * sizeof(size_t) + 1 * sizeof(int64_t)); + +// std::variant*> requires 2 pointers (max element + 1). +// This would be possible in the case where all stack data_chunks are externally owned. +// This is the case for all script/witness push data elements (but not for computed hashes). +// The program may need to hold a shared_ptr to the witness and script, though (like the tx) it +// could be made a requirement of the caller to retain the passed-by-reference objects in scope. +constexpr auto a9 = sizeof(std::variant*>); +constexpr auto c9 = sizeof(std::variant>>); +constexpr auto d9 = sizeof(std::variant>); +static_assert(a9 == 2 * sizeof(int64_t)); +static_assert(c9 == 2 * sizeof(size_t) + 1 * sizeof(int64_t)); +static_assert(d9 == 2 * sizeof(size_t) + 2 * sizeof(int64_t)); + +// Computed hashes can be maintained in the variant by std::unique_ptr>. +// This constrols their lifetime without increasing the size of the union. But +// std::unique_ptr> can be used as a safer std_vector*, +// so that's all that is needed. +constexpr auto a10 = sizeof(std::variant>, std_vector*>); +constexpr auto a11 = sizeof(std::variant>>); +static_assert(a10 == 2 * sizeof(int64_t)); +static_assert(a11 == 2 * sizeof(int64_t)); + +// Use of std::weak_ptr vs. std_vector* would expand the +// variant size, and use of std::unique_ptr> with the shared +// pointer's raw pointer would attempt to delete the shared-object's underlying +// data_chunk. So a C ponter is necessary for shared_ptr references, and their +// lifetimes must be externally controlled. +constexpr auto a0 = sizeof(std::weak_ptr); +static_assert(a0 == 2 * sizeof(size_t)); + +#endif // HAVE_MSC && NDEBUG + +// copy/move restrictions of unique_ptr: +////auto foo() +////{ +//// auto unique = std::make_unique(data_chunk{}); +//// auto shared = std::make_shared(data_chunk{}); +//// +//// std::unique_ptr unique_ref_unique(unique.get()); +//// std::unique_ptr unique_ref_shared(shared.get()); +//// ////std::shared_ptr unique_copy_unique(unique); +//// std::shared_ptr unique_copy_shared(shared); +//// std::unique_ptr unique_move_unique(std::move(unique)); +//// ////std::unique_ptr unique_move_shared(std::move(shared)); +//// unique = std::move(unique_move_unique); +//// +//// std::shared_ptr shared_ref_unique(unique.get()); +//// std::shared_ptr shared_ref_shared(shared.get()); +//// std::shared_ptr shared_copy_shared(shared); +//// ////std::shared_ptr shared_copy_unique(unique); +//// std::shared_ptr shared_move_unique(std::move(unique)); +//// std::shared_ptr shared_move_shared(std::move(shared)); +//// shared = std::move(shared_move_unique); +////} + +// Note that operation.data is nullptr (2 pointers vs. 5) for non-push data +// opcodes (and one byte for the overflow boolean). This is additional script +// overhead in order to provide shared pointers to avoid push data copy. If +// average data element byte length is < (2 * sizeof(pointer) + 1), such as +// predominantly non-push data opcodes, this implies an increased average cost. +// +// std_vector => 3 pointers per element. +// std_vector> => 5 pointers per element. +// +// The additional cost of stack storage for std::shared_ptr is 2 pointers per +// element, and shared pointer origination (i.e. by the tx) requires 2 more (4). +// This allows avoidance of a full vector copy of each data chunk pushed to the +// stack, instead copying its shared pointer from script/witness data. The +// vector copy would require 3 pointers for the copy construction of the new +// vector for each stack element, in addition to the element contents (up to 520 +// bytes per element). So the net tx/stack storage cost of avoiding push data +// copy is 5-4=1 pointer storage/copy per element. +// +// std_vector => 3 +// std_vector>> => 6 +// +// This change to variant added a cost of 1 pointer per stack element (only) to +// the previous cost of 2 pointers per element, making the net cost 2 pointers +// per stack element (with no change to the operation stack). This enables +// strong typing of stack data as bool/int64_t (or chunk_cptr), eliminating +// a large number of chunk<->integral conversions, as well as 3 pointer vector +// constructions and value copies for all information pushed to the stack. +// +// std_vector => 3 +// std_vector> => 2 +// +// This eliminates the use of unnecessary shared pointer construction for all +// script/witness push data, limiting it to hash-constructed stack elements. +// This would cost 2 pointers per hash construction (as before) but eliminate +// 4 pointers for all other stack data. So in relation to the simple vactor of +// data_chunk, it is -1 for push data and +1 for hash constructions. There is +// typically far more push data than hash construction, so this is a clear win. + +// pay-to-public-key-hash +// With average signature size and compressed public key, output/input: +// "[72] [33] : dup hash160 [32] equalverify checksig" + +// script constructions (two vectors): +// vector[72 + 33 + 1 + 1 + 32 + 1 + 1] => 2*3 + 141 = 147 bytes. +// vs: +// script constructions (overflow, opcode, shared_pointer): +// op(vector[72]) 1 + 1 + 16 + 3 + 72 +// op(vector[33]) 1 + 1 + 16 + 3 + 33 +// op(nullptr) 1 + 1 + 16 +// op(nullptr) 1 + 1 + 16 +// op(vector[32]) 1 + 1 + 16 + 3 + 32 +// op(nullptr) 1 + 1 + 16 +// op(nullptr) 1 + 1 + 16 +// vector[7*18 + 3*3 + 72 + 33 + 32] => 2*3 + 272 = 278 bytes (278/147 = 189%). + +// Stack vector construction: +// vector[72] 24 + 72 +// vector[33] 24 + 33 +// vector[dup] 24 + 33 +// vector[hash160] 24 + 32 (hash moved to new vector) +// vector[checksig] 24 + 1 (1 byte for success result) +// Stack copy: 5*24 + 72 + 2*33 + 32 + 1 = 291 bytes. +// Inclusive of script constructions: (291 + 278)/(291 + 147) = 130%. +// vs: +// Previous conversion to shared_ptr push data storage: +// Stack vector> construction: +// ptr[72] 16 +// ptr[33] 16 +// ptr[dup] 16 +// ptr[hash160] 16 + 24 + 32 (hash moved to new shared_ptr[vector]) +// ptr[checksig] 16 + 24 + 1 (1 byte for success result) +// Stack copy: 5*16 + 2*24 + 32 + 1 = 161 bytes (161/291 = 55%). +// Inclusive of script constructions: (161 + 278)/(291 + 147) = 100%. +// vs: +// Recent conversion to variant storage using shared_ptr: +// Stack vector>> construction: +// var[ptr[vector[72]]] 24 +// var[ptr[vector[33]]] 24 +// var[ptr[dup]]] 24 +// var[ptr[hash160]]] 24 + 24 + 32 (hash moved to new shared_ptr, vector) +// var[bool[checksig]] 24 (native bool union) +// Stack copy: 5*24 + 2*24 + 32 + 1 = 201 bytes (201/161 = 125%, 201/291 = 69%). +// Inclusive of script constructions: (201 + 278)/(291 + 147) = 109%. +// vs: +// Possible conversion to variant storage using data_chunk&: +// Stack vector> construction: +// var[vector[72]&] 24 +// var[vector[33]&] 24 +// var[[dup]&] 24 +// var[[hash160]&] 24 + 24 + 32 (hash move, heap pointer storage) +// var[bool[checksig]] 24 (native bool union) +// Stack copy: 5*16 + 24 + 32 = 136 bytes (136/201 = 68%, 136/161 = 84%, 136/291 = 47%). +// Inclusive of script constructions: (136 + 278)/(291 + 147) = 95%. diff --git a/test/machine/stack.cpp b/test/machine/stack.cpp new file mode 100644 index 0000000000..8cb56a7ef1 --- /dev/null +++ b/test/machine/stack.cpp @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "../test.hpp" + +BOOST_AUTO_TEST_SUITE(stack_tests) + +BOOST_AUTO_TEST_CASE(stack_test) +{ + BOOST_REQUIRE(true); +} + +BOOST_AUTO_TEST_SUITE_END() From 1973f88943e0dc83d1b9f3b347c4ddc726cc20c7 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 3 Jul 2024 18:57:50 -0400 Subject: [PATCH 5/5] Add machine/stack tests. --- test/machine/stack.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/test/machine/stack.cpp b/test/machine/stack.cpp index 8cb56a7ef1..9938f7f01f 100644 --- a/test/machine/stack.cpp +++ b/test/machine/stack.cpp @@ -18,11 +18,33 @@ */ #include "../test.hpp" +using namespace system::machine; + BOOST_AUTO_TEST_SUITE(stack_tests) -BOOST_AUTO_TEST_CASE(stack_test) +// stack_variant is not serializable, so cannot boost compare it. + +BOOST_AUTO_TEST_CASE(stack__pop__pushed_bool__expected) +{ + stack stack{}; + stack.push(true); + BOOST_REQUIRE(stack.pop() == stack_variant{ true }); +} + +BOOST_AUTO_TEST_CASE(stack__pop__pushed_int64__expected) +{ + stack stack{}; + stack.push(42); + BOOST_REQUIRE(stack.pop() == stack_variant{ 42 }); +} + +BOOST_AUTO_TEST_CASE(stack__pop__pushed_chunk__expected) { - BOOST_REQUIRE(true); + const auto expected = data_chunk{ 0x42, 0x43, 0x44, 0x45, 0x46 }; + const chunk_xptr ptr{ expected }; + stack stack{}; + stack.push(data_chunk{ 0x42, 0x43, 0x44, 0x45, 0x46 }); + BOOST_REQUIRE(stack.pop() == stack_variant{ ptr }); } BOOST_AUTO_TEST_SUITE_END()