Skip to content

D-BINARY-ABACUS-POLYFILL-0 PR A: half/full adder mask words; vectorized pow2n_from_int - #402

Merged
AdaWorldAPI merged 4 commits into
masterfrom
ccr-45a41fb4-0j3i0l
Oct 30, 2026
Merged

AdaWorldAPI merged 4 commits into
masterfrom
ccr-45a41fb4-0j3i0l

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

PR A of D-BINARY-ABACUS-POLYFILL-0, plus the cost-gate lab work that decides PR B and PR C. Four commits:

  • two library commits (the adder words, and the exponent change that was already in flight in this session);
  • two commits that touch only the research fixture and docs.

1. 9aea2fc: half_add_mask, full_add_mask

pub const fn half_add_mask(a: u64, carry: u64) -> (u64, u64); // (a ^ carry, a & carry)
pub const fn full_add_mask(a: u64, b: u64, c: u64) -> (u64, u64); // (a ^ b ^ c, majority)

Tests (4 unit tests, 2 doctests):

  • both truth tables, with every row in every lane position;
  • lanes 0, 31, 32 and 63 alone, each staying in its lane;
  • 2,000 random words, including zero, all-ones and alternating masks, against per-lane integer addition;
  • a ripple counter built from the words keeps lanes apart over chains of up to 7 columns, and moving the carry to the neighbouring bit corrupts it (negative test).

2. 02f9018: vectorized pow2n_from_int; U32x16 variable shifts on every backend

simd_exp_f32 built 2^n one lane at a time through to_array, and vsexp, vstanh and vserf all go through it. The new version stays in registers:

let biased = n.simd_clamp(F32x16::splat(-126.0), F32x16::splat(127.0)) + F32x16::splat(8_388_608.0 + 127.0);
F32x16::from_bits(biased.to_bits() << U32x16::splat(23))

For integral n, after the clamp, 2^23 + 127 + n is an exact f32 whose mantissa field holds the biased exponent. Shifting left by 23 drops the 0x4B00_0000 exponent and leaves k << 23. The function stays private: simd_exp_f32 still does its own high-exponent, subnormal and NaN handling around it, unchanged.

This needed U32x16 << U32x16, which only the AVX-512 and scalar backends had. Shl/Shr<Self> for U32x16 now follow each backend's U64x8 shifts:

backend realization
AVX2 VPSLLVD / VPSRLVD per 256-bit half
AVX without AVX2 element-wise (AVX1 has no variable shift)
NEON vshlq_u32
wasm simd128 lane extracts (simd128 shifts are uniform)
nightly core::simd << / >>

Lowering. simd_exp_f32 compiled out of line, new against master:

tier instructions scalar float↔int conversions extract/insert
AVX-512 159 → 44 16 → 0 12 → 0
AVX2 589 → 471 16 → 0 54 → 42
NEON 635 → 633 0 → 0 —
  • The shift lowers to one vpslld $23 on both x86 tiers.
  • The 42 extract/inserts left on AVX2 are vpextrw/vpinsrw packing the simd_gt/select masks; that code predates this change.
  • On NEON, LLVM had already vectorized the old loop, so nothing changes. That path also contains about 47 scalar fcmp/csel, which predate this change and are noted rather than fixed here.

Timing. ns per element, n = 65,536, best of 15, three runs each:

master this PR
AVX-512 vsexp / vstanh / vserf 1.93–2.07 / 2.28–2.36 / 2.29–2.43 0.48–0.50 / 0.76–0.82 / 0.89–1.06
AVX2 vsexp / vstanh / vserf 4.65–4.74 / 7.02–7.38 / 8.72–8.84 3.26–3.28 / 5.39–5.74 / 7.17–7.50

Tests.

  • pow2n_from_int, bit for bit against the scalar exponent rule over n ∈ [−200, 200], across both clamp ends.
  • U32x16 shifts against per-lane shifts, with a distinct count in every lane.
  • All existing simd_exp_f32 and vml tests (2 ULP, bit-for-bit unfused sequence, infinities, NaN per lane, overflow and subnormals).
  • The wasm parity harness gains a U32x16 shift check (return codes 14 and 15).

Passed:

  • 75/75 on AVX-512, AVX2 (v3), AVX (qemu SandyBridge), v2 (qemu Nehalem), NEON (qemu) and nightly-simd;
  • scripts/wasm-parity.sh OK;
  • full lib suite 2,702 passed.

3. a70f336, de03001: cost gate (lab only, examples/u9_domino)

Candidate C, Abacus<BITS>, is F's eager ripple built from half_add_mask:

  • 1 <= BITS <= 122, so a lane value and the 64-lane total are both exact in u128.
  • try_add_at is transactional. On overflow, the touched columns are restored by recomputing the carry chain forward from the input mask (old_k = new_k ^ m_k, m_{k+1} = old_k & m_k), so the success path keeps no undo log.
  • A significance at or past BITS is refused without any change.
  • The existing eager F is not transactional: on overflow, its lower columns stay updated.

Tests (6, in tests/u9_domino.rs):

  • lanes 0, 31, 32 and 63 alone;
  • carry chains of 1, 2, 8, 16, 32 and 64 columns;
  • repeated carries into one column add up;
  • refusal leaves the counter unchanged;
  • 24,000 random steps against 64 u128 counters, with both outcomes common;
  • reversed traversal.

Disables, each red: no rollback; rollback stops after one column; no range check; carry moved to the neighbouring lane.

ns per add, range over 3 processes:

tier workload native per-lane F eager C
AVX-512 VNNI dense / random / sparse 8.79–9.08 / 8.78–8.80 / 8.02–9.08 2.46–2.66 / 7.79–8.05 / 2.14–2.44 2.27–2.35 / 8.34–8.80 / 2.21–3.90
AVX-512, no VNNI dense / random / sparse 8.01–8.78 / 8.02–9.39 / 8.78–8.79 2.39–2.45 / 7.56–8.50 / 2.30–2.35 2.21–2.22 / 6.88–7.84 / 3.72–5.26
AVX2 dense / random / sparse 9.04–9.92 / 9.69–10.23 / 9.91–10.33 2.25–2.52 / 7.22–8.32 / 2.09–2.36 2.33–2.43 / 8.53–10.57 / 2.29–3.42
  • Both bit-plane counters beat 64 per-lane adds by about 4× on dense and single-lane masks, and are roughly even with them on random masks.
  • C matches F on dense masks. On sparse masks it is slower and noisier, even though the rollback never runs in these rows; this is not diagnosed further.
  • AVX, SSE2, NEON and wasm were not timed (emulator only). The exactness tests run on every tier.

Verdicts (full report in .claude/knowledge/binary-abacus-polyfill-probe.md):

  • PR B, a public BinaryAbacus64: deferred. The semantics are settled and the cost matches F, but no consumer counts 64-lane masks at chosen significances yet. C stays in the fixture as the reference.
  • PR C, a facet bit-plane helper: deferred, not built.
  • The counters carry no presence; a consumer that must tell never-touched from zero keeps its own presence plane, as lut9 and Codec9 do.

Disable runs for the library commits (each red, then restored)

disable caught by
P1 pow2n shifts by 22 pow2n_… + 5 simd_exp_f32 tests
P2 AVX2 shl uses the low half's counts for the high half u32x16_variable_shifts_… (v3)
N1 NEON shr without negated counts u32x16_variable_shifts_… (aarch64 qemu)
W1 wasm shl does shr wasm parity rc=14
V1 AVX shl uses lane 0's count u32x16_variable_shifts_… (SandyBridge qemu)
A1 half adder carry uses OR 4 tests
A2 full adder drops the t & c carry term 3 tests
A3 full adder carry moved one lane 3 tests

Checks

  • clippy -D warnings clean on native and v3, including the fixture example and test; fmt clean.
  • The facet-stride guard is clean on the diff.

Not merged automatically.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY

simd_exp_f32 (and with it vsexp, vstanh, vserf) built 2^n one lane at a
time through to_array. It now stays in registers through the facade's
typed words: clamp n, add 2^23 + 127 so the biased exponent sits in the
mantissa field, and shift the bits left by 23.

That needs U32x16 << U32x16, which only the AVX-512 and scalar backends
had. Added Shl/Shr<Self> for U32x16 on AVX2 (VPSLLVD/VPSRLVD per 256-bit
half), AVX without AVX2 (per lane; no AVX1 form exists), NEON
(vshlq_u32), wasm simd128 (lane extracts; simd128 shifts are uniform)
and the nightly core::simd backend, following each backend's U64x8
shifts.

Tests: pow2n_from_int bit for bit against the scalar exponent rule over
n in [-200, 200]; U32x16 shifts against per-lane shifts with distinct
counts per lane.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
…y for U32x16 shifts

half_add_mask(a, carry) = (a ^ carry, a & carry) and
full_add_mask(a, b, c) = (a ^ b ^ c, majority) are the flip and
carry_save3 laws proven in #391 and #389, now in simd_masking_ops and
re-exported through ndarray::simd. Each u64 is one significance column of
64 independent counters; the carry belongs one column up in the same lane.

Tests: both truth tables in every lane position, lanes 0/31/32/63 alone,
2000 random words (with zero, all-ones and alternating masks) against
per-lane addition, and a ripple counter that keeps lanes apart while
moving the carry to a neighbouring bit corrupts it.

The wasm parity harness now also checks U32x16 << and >> with a distinct
count per lane.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0ba4ed35-ce45-49d2-986c-0f8ca2e2291c
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…sal (lab)

D-BINARY-ABACUS-POLYFILL-0 cost-gate candidate, kept in the research
fixture. F's eager ripple built from ndarray::simd::half_add_mask; on
overflow the touched columns are restored by recomputing the carry chain
forward from the input mask, so the success path keeps no undo log.
BITS <= 122 keeps lane values and the 64-lane total exact in u128.

Tests: lanes 0/31/32/63 alone, carry chains of 1..64 columns, repeated
carries, refusal leaves the counter unchanged (overflow and out-of-range
significance), 24,000 random steps against 64 u128 counters with both
outcomes common, and reversed traversal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
Adds candidate C next to native and F in the counter rows, and records
the cost gate: C matches F, both beat 64 per-lane adds ~4x on dense and
single-lane masks. PR B (public BinaryAbacus64) and PR C (facet bit-plane
helper) are deferred: no consumer yet, and the planes already cost one
AND and one cmpeq_mask each on existing facade words.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review October 30, 2026 12:17
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T12:18:41.273555Z de03001 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@AdaWorldAPI
AdaWorldAPI merged commit 6e0a007 into master Oct 30, 2026
30 checks passed
AdaWorldAPI pushed a commit that referenced this pull request Oct 30, 2026
Records #402, plus the rows #400 and #401 moved. New dots N22-N26: the
adders as facade words with no consumer yet, the exp speed-up reaching
every exp-based word, AVX2 select mask packing as a second array-backed
cost, NEON scalar fcmp/csel in simd_exp_f32, and the transactional
Abacus against the non-transactional eager counter. Fruit L20, L21, H12,
H13; PR C not planned.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
AdaWorldAPI pushed a commit that referenced this pull request Oct 30, 2026
AdaWorldAPI added a commit that referenced this pull request Oct 30, 2026
Synergy map R7: #402 (adder words, vectorized exp, Abakus cost gate)
AdaWorldAPI added a commit that referenced this pull request Oct 30, 2026
Synergy map R7.5: #402's late CI jobs all passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants