Skip to content

Commit d37e776

Browse files
authored
Merge pull request #690 from frstrtr/dgb/msvc-arith256-portable
Fix MSVC: portable 128-bit primitives in dgb_arith256.hpp (unblocks AUX_DOGE Windows)
2 parents 99ca772 + 8c5d48e commit d37e776

4 files changed

Lines changed: 411 additions & 14 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ jobs:
114114
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test nmc_host_dualpath_test nmc_fallback_path_conformance_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \
115115
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test dgb_aux_broadcast_path_election_test dgb_aux_doge_submit_test dgb_aux_doge_embed_livewire_test dgb_aux_doge_dc_layout_verifier_test \
116116
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
117-
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_share_target_retarget_test dgb_share_bits_oracle_pin_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_auto_ratchet_sim_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_min_protocol_ratchet_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test dgb_emergency_decay_saturation_test v37_test \
117+
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_share_target_retarget_test dgb_share_bits_oracle_pin_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_auto_ratchet_sim_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_min_protocol_ratchet_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test dgb_emergency_decay_saturation_test dgb_arith256_muldiv_kat_test v37_test \
118118
-j8
119119
120120
- name: Run tests
@@ -263,7 +263,7 @@ jobs:
263263
dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test nmc_host_dualpath_test nmc_fallback_path_conformance_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \
264264
dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_mm_commitment_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test dgb_aux_dual_target_select_test dgb_aux_broadcast_path_election_test dgb_aux_doge_submit_test dgb_aux_doge_embed_livewire_test dgb_aux_doge_dc_layout_verifier_test \
265265
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
266-
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_share_target_retarget_test dgb_share_bits_oracle_pin_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_auto_ratchet_sim_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_min_protocol_ratchet_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test dgb_emergency_decay_saturation_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
266+
dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_share_target_retarget_test dgb_share_bits_oracle_pin_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_auto_ratchet_sim_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_min_protocol_ratchet_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test dgb_hash_format_parity_test dgb_emergency_decay_saturation_test dgb_arith256_muldiv_kat_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \
267267
v37_test \
268268
-j8
269269

src/impl/dgb/coin/dgb_arith256.hpp

Lines changed: 185 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,137 @@
2828

2929
#include <cstdint>
3030

31+
#ifdef _MSC_VER
32+
#include <intrin.h> // _umul128 / _udiv128 -- MSVC has no __int128 keyword
33+
#endif
34+
3135
namespace dgb::coin {
3236

37+
// ---------------------------------------------------------------------------
38+
// Portable 128-bit primitives. GCC/Clang express the 64x64->128 multiply,
39+
// 128/64 divide and 64+64 add through unsigned __int128; MSVC has no such
40+
// type, so it uses the x64 intrinsics (_umul128 / _udiv128) that lower to the
41+
// SAME mul/div/add-with-carry instructions. Both branches are bit-identical,
42+
// so the DigiShield boundary vectors are unchanged. Mirrors the portable
43+
// mul128_shift precedent in dgb/share_tracker.hpp.
44+
// ---------------------------------------------------------------------------
45+
namespace detail {
46+
47+
// low 64 of (a*b + add); high 64 written to hi. a*b+add always fits 128 bits.
48+
inline uint64_t mul_add_u128(uint64_t a, uint64_t b, uint64_t add, uint64_t& hi) {
49+
#ifdef _MSC_VER
50+
uint64_t lo = _umul128(a, b, &hi);
51+
lo += add;
52+
hi += (lo < add) ? 1u : 0u; // carry out of the low limb
53+
return lo;
54+
#else
55+
unsigned __int128 p = (unsigned __int128)a * b + add;
56+
hi = (uint64_t)(p >> 64);
57+
return (uint64_t)p;
58+
#endif
59+
}
60+
61+
// quotient of (hi:lo)/d; remainder written to rem. Requires hi < d (holds for
62+
// schoolbook long division where hi is the running remainder), so the 64-bit
63+
// quotient never overflows.
64+
inline uint64_t div_u128(uint64_t hi, uint64_t lo, uint64_t d, uint64_t& rem) {
65+
#ifdef _MSC_VER
66+
return _udiv128(hi, lo, d, &rem);
67+
#else
68+
unsigned __int128 cur = ((unsigned __int128)hi << 64) | lo;
69+
rem = (uint64_t)(cur % d);
70+
return (uint64_t)(cur / d);
71+
#endif
72+
}
73+
74+
// low 64 of (a + b + carry_in); carry out (0/1) written to carry_out.
75+
inline uint64_t add_u128(uint64_t a, uint64_t b, uint64_t carry_in, uint64_t& carry_out) {
76+
#ifdef _MSC_VER
77+
uint64_t s = a + b;
78+
uint64_t c = (s < a) ? 1u : 0u;
79+
s += carry_in;
80+
c += (s < carry_in) ? 1u : 0u;
81+
carry_out = c;
82+
return s;
83+
#else
84+
unsigned __int128 s = (unsigned __int128)a + b + carry_in;
85+
carry_out = (uint64_t)(s >> 64);
86+
return (uint64_t)s;
87+
#endif
88+
}
89+
90+
91+
// ---------------------------------------------------------------------------
92+
// PORTABLE-FORCED reference primitives (the #690 native==portable KAT oracle).
93+
//
94+
// The MSVC branches above route through _umul128 / _udiv128 -- x64 hardware
95+
// instructions that CANNOT be compiled or run on the Linux/macOS CI, so the
96+
// "both branches bit-identical" claim can't be diffed directly there. These
97+
// twins reproduce the SAME two operations (64x64->128 multiply, 128/64 divide)
98+
// in pure 32-bit-limb software -- NO __int128, NO intrinsic -- a genuinely
99+
// INDEPENDENT computation. If the software path equals the native __int128
100+
// path across the DigiShield operand domain on Linux, and the x64
101+
// _umul128/_udiv128 are (by ISA definition) exactly the 64x64->128 product and
102+
// 128/64 quotient this software reproduces, then the MSVC path we cannot run
103+
// here is equal too. Compiled unconditionally on every platform; NEVER
104+
// referenced by the shipping u256 / mul_div_u256 (those stay byte-identical).
105+
// ---------------------------------------------------------------------------
106+
107+
// software 64x64 -> 128 multiply (what _umul128 does in hardware).
108+
inline uint64_t umul128_soft(uint64_t a, uint64_t b, uint64_t& hi) {
109+
const uint64_t a_lo = (uint32_t)a, a_hi = a >> 32;
110+
const uint64_t b_lo = (uint32_t)b, b_hi = b >> 32;
111+
const uint64_t p0 = a_lo * b_lo;
112+
const uint64_t p1 = a_lo * b_hi;
113+
const uint64_t p2 = a_hi * b_lo;
114+
const uint64_t p3 = a_hi * b_hi;
115+
const uint64_t mid = (p0 >> 32) + (uint32_t)p1 + (uint32_t)p2;
116+
hi = p3 + (p1 >> 32) + (p2 >> 32) + (mid >> 32);
117+
return (p0 & 0xffffffffULL) | (mid << 32);
118+
}
119+
120+
// software 128/64 -> 64 divide (what _udiv128 does in hardware). Requires
121+
// hi < d so the 64-bit quotient never overflows -- the SAME precondition the
122+
// _udiv128 intrinsic imposes. Binary long division from the top bit down; the
123+
// running remainder r is kept < d, and (top:r) is the conceptual 65-bit value
124+
// so d up to 2^64-1 is handled without a wider type.
125+
inline uint64_t udiv128_soft(uint64_t hi, uint64_t lo, uint64_t d, uint64_t& rem) {
126+
uint64_t q = 0, r = hi; // r < d on entry (precondition)
127+
for (int i = 63; i >= 0; --i) {
128+
const uint64_t top = r >> 63; // bit shifted out of the 64-bit r
129+
r = (r << 1) | ((lo >> i) & 1ULL); // conceptual 65-bit value == (top:r)
130+
if (top || r >= d) { // (top:r) >= d -> subtract, set bit
131+
r -= d; // uint64 wrap yields the true low64
132+
q |= 1ULL << i;
133+
}
134+
}
135+
rem = r;
136+
return q;
137+
}
138+
139+
// Portable-forced twins of the three primitives the shipping u256 uses; each
140+
// mirrors the corresponding MSVC branch above with the intrinsic swapped for
141+
// the software op (add_u128's MSVC branch is already intrinsic-free).
142+
inline uint64_t mul_add_u128_portable(uint64_t a, uint64_t b, uint64_t add, uint64_t& hi) {
143+
uint64_t lo = umul128_soft(a, b, hi);
144+
lo += add;
145+
hi += (lo < add) ? 1u : 0u;
146+
return lo;
147+
}
148+
inline uint64_t div_u128_portable(uint64_t hi, uint64_t lo, uint64_t d, uint64_t& rem) {
149+
return udiv128_soft(hi, lo, d, rem);
150+
}
151+
inline uint64_t add_u128_portable(uint64_t a, uint64_t b, uint64_t carry_in, uint64_t& carry_out) {
152+
uint64_t s = a + b;
153+
uint64_t c = (s < a) ? 1u : 0u;
154+
s += carry_in;
155+
c += (s < carry_in) ? 1u : 0u;
156+
carry_out = c;
157+
return s;
158+
}
159+
160+
} // namespace detail
161+
33162
// 256-bit unsigned, little-endian limbs (limb[0] least significant). Only the
34163
// operations the DigiShield damped multiply needs: scale-by-u64 (truncating at
35164
// 256 bits exactly as arith_uint256::operator*=), divide-by-u64, and compare.
@@ -75,11 +204,11 @@ struct u256 {
75204
// does. This is the divergence point vs a 128/wider proxy.
76205
u256 mul_u64(uint64_t m) const {
77206
u256 r;
78-
unsigned __int128 carry = 0;
207+
uint64_t carry = 0;
79208
for (int i = 0; i < 4; ++i) {
80-
unsigned __int128 prod = (unsigned __int128)limb[i] * m + carry;
81-
r.limb[i] = (uint64_t)prod;
82-
carry = prod >> 64;
209+
uint64_t hi;
210+
r.limb[i] = detail::mul_add_u128(limb[i], m, carry, hi);
211+
carry = hi;
83212
}
84213
// carry beyond limb[3] dropped -> 256-bit truncation (consensus).
85214
return r;
@@ -89,11 +218,9 @@ struct u256 {
89218
// Schoolbook long division from the most-significant limb down.
90219
u256 div_u64(uint64_t d) const {
91220
u256 r;
92-
unsigned __int128 rem = 0;
221+
uint64_t rem = 0;
93222
for (int i = 3; i >= 0; --i) {
94-
unsigned __int128 cur = (rem << 64) | limb[i];
95-
r.limb[i] = (uint64_t)(cur / d);
96-
rem = cur % d;
223+
r.limb[i] = detail::div_u128(rem, limb[i], d, rem);
97224
}
98225
return r;
99226
}
@@ -102,11 +229,9 @@ struct u256 {
102229
// same width discipline as mul_u64). Accumulates the retarget window's
103230
// target sum before the divide-by-count average.
104231
u256& operator+=(const u256& o) {
105-
unsigned __int128 carry = 0;
232+
uint64_t carry = 0;
106233
for (int i = 0; i < 4; ++i) {
107-
unsigned __int128 s = (unsigned __int128)limb[i] + o.limb[i] + carry;
108-
limb[i] = (uint64_t)s;
109-
carry = s >> 64;
234+
limb[i] = detail::add_u128(limb[i], o.limb[i], carry, carry);
110235
}
111236
return *this;
112237
}
@@ -130,4 +255,52 @@ inline u256 mul_div_u256(const u256& avg, uint64_t mul, uint64_t div) {
130255
return avg.mul_u64(mul).div_u64(div);
131256
}
132257

258+
259+
// ---------------------------------------------------------------------------
260+
// PORTABLE-FORCED twin of u256 -- the #690 KAT oracle. Same little-endian limb
261+
// layout and 256-bit truncation discipline, but mul_u64 / div_u64 / operator+=
262+
// route through the software detail::*_portable primitives instead of the
263+
// native __int128 (or MSVC intrinsic) ones. The native==portable guard diffs
264+
// this against u256 on Linux. NOT used by any shipping code path.
265+
// ---------------------------------------------------------------------------
266+
struct u256_portable {
267+
uint64_t limb[4] = {0, 0, 0, 0};
268+
269+
u256_portable() = default;
270+
explicit u256_portable(const u256& s) { for (int i = 0; i < 4; ++i) limb[i] = s.limb[i]; }
271+
272+
u256_portable mul_u64(uint64_t m) const {
273+
u256_portable r;
274+
uint64_t carry = 0;
275+
for (int i = 0; i < 4; ++i) {
276+
uint64_t hi;
277+
r.limb[i] = detail::mul_add_u128_portable(limb[i], m, carry, hi);
278+
carry = hi;
279+
}
280+
return r;
281+
}
282+
u256_portable div_u64(uint64_t d) const {
283+
u256_portable r;
284+
uint64_t rem = 0;
285+
for (int i = 3; i >= 0; --i)
286+
r.limb[i] = detail::div_u128_portable(rem, limb[i], d, rem);
287+
return r;
288+
}
289+
u256_portable& operator+=(const u256_portable& o) {
290+
uint64_t carry = 0;
291+
for (int i = 0; i < 4; ++i)
292+
limb[i] = detail::add_u128_portable(limb[i], o.limb[i], carry, carry);
293+
return *this;
294+
}
295+
// BIT-IDENTICAL check against a native u256 (all four limbs).
296+
bool equals(const u256& s) const {
297+
return limb[0] == s.limb[0] && limb[1] == s.limb[1]
298+
&& limb[2] == s.limb[2] && limb[3] == s.limb[3];
299+
}
300+
};
301+
302+
inline u256_portable mul_div_u256_portable(const u256_portable& avg, uint64_t mul, uint64_t div) {
303+
return avg.mul_u64(mul).div_u64(div);
304+
}
305+
133306
} // namespace dgb::coin

src/impl/dgb/test/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,20 @@ if (BUILD_TESTING AND GTest_FOUND)
357357
GTest::gtest_main GTest::gtest nlohmann_json::nlohmann_json)
358358
gtest_add_tests(${dgb_guard} "" AUTO)
359359
endforeach()
360+
361+
# dgb_arith256_muldiv_kat_test: FENCED, additive KAT for #690 -- PROVES the
362+
# DigiShield 256-bit retarget primitives (detail mul_add_u128 / div_u128 /
363+
# add_u128, and u256 mul_u64 / div_u64 / operator+=) are BIT-IDENTICAL on the
364+
# native __int128 path vs the portable-forced software path
365+
# (detail::*_portable / u256_portable) that certifies the MSVC
366+
# _umul128/_udiv128 branch we cannot compile or run on Linux. Special
367+
# coverage of the _udiv128 hi<d running-remainder boundary. Pure header math
368+
# (<cstdint> only) -> links only GTest, like the foreach guards above. MUST
369+
# also be in BOTH build.yml --target allowlists (#143 NOT_BUILT trap).
370+
add_executable(dgb_arith256_muldiv_kat_test arith256_muldiv_kat_test.cpp)
371+
target_link_libraries(dgb_arith256_muldiv_kat_test PRIVATE
372+
GTest::gtest_main GTest::gtest)
373+
gtest_add_tests(dgb_arith256_muldiv_kat_test "" AUTO)
360374
# --- #82 broadcaster-gate: CoinNode submit/seam contract -----------------
361375
# Links the dgb OBJECT lib (coin_node.cpp) like dgb_share_test so the live
362376
# CoinNode::submit_block_hex !m_rpc guard + embedded WorkView slice are

0 commit comments

Comments
 (0)