Skip to content

simd: branch-free mask_gather_u32 + gated mask_gather_u32_under - #342

Merged
AdaWorldAPI merged 1 commit into
masterfrom
ccr-a86d1f2f-015t11
Oct 7, 2026
Merged

AdaWorldAPI merged 1 commit into
masterfrom
ccr-a86d1f2f-015t11

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

What

  • mask_gather_u32 is branch-free per row. Each row's bit is shifted in unconditionally. The out-of-range test is a select on the address plus an AND on the bit (gather_bit). The loads and the contract are unchanged.
  • New mask_gather_u32_under(src, src_rows, index, under, out). It returns gather & under, but reads index only at set gate bits. It skips zero gate words and walks set bits with trailing_zeros, so the cost follows popcount(under), not index.len(). It is exported through the ndarray::simd facade.

Why

lance-graph D-GATED-GATHER-0 found two costs in the semijoin gate ∧ gather(fk, foreign):

  1. The kernel's per-row test. Whether if idx < rows && bit == 1 compiles to a branch depends on the call site.
  2. Gathering rows the gate has already rejected.

Measured

I re-ran lance-graph's gated_gather_probe against this commit: 1M rows, a 1024-row foreign table with half its rows set, release build, debug info off, median of 15. Every route's count is asserted equal to an oracle.

layout gate density prod before prod now hand branch-free bit-gated schedule
uniform 100 % 5295 1554 1505 1453
uniform 1 % 5298 1549 1358 78
uniform 0.01 % 5491 1731 2427 6.8
clustered 10 % 5323 1750 1309 141
clustered 0.01 % 5297 1646 1497 4.8

All times are µs.

  • "prod" is mask-risc Gather calling this kernel. It went from about 5.3 ms to about 1.5 ms, level with the hand-written branch-free loop.
  • The bit-gated column is the schedule that mask_gather_u32_under implements. Mask-risc does not call it yet; that is a follow-up.
  • The uniform 50 % and 10 % rows are not shown. A host slowdown hit every route on those rows in this run.

This depends on the call site. In a standalone ndarray example, with the old loop inlined, the compiler had already removed the branch. There the old and new forms time the same (ratio 1.00–1.04), and a predictable all-set source did not speed up the old form either. The branch-free form removes the dependence on that compiler choice; it does not speed up every caller. The doc comment says so.

Tests

  • Parity: against a naive reference, and against gather & gate. Covered: lengths 0/1/63/64/65/67/130, dirty output buffers, and gates (empty, full, random) with their tail bits past n set.
  • Edge cases: src_rows == 0 with an empty src gives all-false and no panic, for both functions. A short gate buffer panics.
  • simd-masking-parity: new check 0xD01. All 14 groups are bit-identical on this AVX-512 host.
  • Disable runs, each red, then restored from the commit:
    • without the gate-tail clamp, an out-of-bounds index read panics;
    • without the gated form's src_rows == 0 return, an empty src panics.
  • cargo clippy -p ndarray --all-targets -- -D warnings and cargo fmt are clean. The 165 simd_masking_ops lib tests and the 2 doctests pass.

Not in this PR

  • mask-risc MaskOp::Gather { under }, which would route the semijoin through the gated kernel. That change belongs in lance-graph.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HdJxpkHATkdNL2veorKao2


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added a gated bit-gather operation that selects rows only when enabled by a gate, while keeping output fully overwritten.
    • Made the gated operation available through the standard-feature SIMD interface.
  • Bug Fixes
    • Gather operations now safely handle empty sources and out-of-range indices, producing false for invalid selections.

mask_gather_u32 tested `idx < src_rows && bit == 1` per row. Whether
that compiles to a branch depends on the call site. Called through
lance-graph's mask-risc Gather on a 1M-row lane with an unpredictable
source, it ran 5.3 ms. The branch-free form runs 1.5 ms, level with a
hand-written branch-free loop (lance-graph D-GATED-GATHER-0, re-run
against this commit). Inlined into a plain loop, the compiler had
already removed the branch, and the two forms time the same.

The new form shifts each row's bit in unconditionally; the out-of-range
test is a select on the address plus an AND on the bit (gather_bit).
Same loads, same contract.

mask_gather_u32_under(src, src_rows, index, under, out) returns
gather & under, but reads index only at set gate bits. It skips zero gate
words and walks set bits with trailing_zeros, so the cost tracks
popcount(under). The same probe measured this schedule fastest, or within
noise of fastest, at every gate density from 0.01% to 100%.

Both gathers return early on src_rows == 0, because src may then be
empty and gather_bit's fallback read of src[0] would not exist. Gate
bits past index.len() are cleared before they can address index.

Tests: parity against a naive reference and against gather & gate,
across word tails, with dirty output buffers and dirty gate tails. The
simd-masking-parity crate gains check 0xD01 for the gated form.
Disable runs, each red and then restored: dropping the gate-tail clamp
panics on an out-of-bounds index read; dropping the gated form's
src_rows == 0 return panics on an empty src.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HdJxpkHATkdNL2veorKao2
@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: f7d0b5fe-6a98-4e3e-a3e2-07ae5c5a0eb3
📥 Commits

Reviewing files that changed from the base of the PR and between 11089f6 and d57ede4.

📒 Files selected for processing (3)
  • crates/simd-masking-parity/src/lib.rs
  • src/simd.rs
  • src/simd_masking_ops.rs

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.


📝 Walkthrough

Walkthrough

mask_gather_u32 now uses branch-free per-row bit construction and handles empty sources. The change adds and exports mask_gather_u32_under, with tests and a parity check for gated gathering.

Changes

SIMD Masked Gather

Layer / File(s) Summary
Full gather semantics
src/simd_masking_ops.rs
mask_gather_u32 clears the output when the source has zero rows. Otherwise, it uses gather_bit to construct result bits and return false for out-of-range indices.
Gated gather and validation
src/simd_masking_ops.rs, src/simd.rs, crates/simd-masking-parity/src/lib.rs
Adds mask_gather_u32_under, which validates lengths, clears the full output, and gathers selected rows. Tests and the parity check cover empty sources, gate selection, tail bits, and short-gate rejection.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Suggested reviewers: claude

Merge Risk: ⚪ Minimal · up to d57ed

No confirmed issue prevents merging after normal checks.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes both main changes: branch-free mask_gather_u32 and the addition of gated mask_gather_u32_under.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 3 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


A rabbit checks each gathered bit,
And gates the rows that pass.
The empty source leaves output clear,
Tail bits do not slip past.
Tests hop through the word boundaries,
Then settle in the grass.

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

@cursor

cursor Bot commented Oct 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 2501f148-2933-436d-b351-043194abb7cc)

@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review October 7, 2026 15:03
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 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-07T15:06:38.642324Z d57ede4 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.

@cursor

cursor Bot commented Oct 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 09d42580-55b8-4e52-a227-bd7a940ae996)

@AdaWorldAPI
AdaWorldAPI merged commit 30ce119 into master Oct 7, 2026
27 checks 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