Skip to content

PR-X2 Worker A: generalize aos_to_soa / soa_to_aos to <T, U, N, F>#168

Merged
AdaWorldAPI merged 1 commit into
masterfrom
claude/pr-x2-generic-soa
May 20, 2026
Merged

PR-X2 Worker A: generalize aos_to_soa / soa_to_aos to <T, U, N, F>#168
AdaWorldAPI merged 1 commit into
masterfrom
claude/pr-x2-generic-soa

Conversation

@AdaWorldAPI
Copy link
Copy Markdown
Owner

Summary

PR-X2 Worker A per .claude/knowledge/pr-x2-design.md § "Worker decomposition" line 458. Lifts the f32-only constraint on aos_to_soa / soa_to_aos that W3-W6 shipped, so downstream consumers with u8 / u16 / u64 / i8 SoA fields (palette indices, BF16 carrier, CausalEdge64 mantissa, quantized weights) can use the public surface instead of rolling their own extract loop.

2 files, +183 / -42 vs master.

Signature change (Option C — change in place)

Per the design's recommended migration path:

// Pre-PR-X2 (W3-W6, f32-hardwired):
pub fn aos_to_soa<T, const N: usize, F>(aos: &[T], extract: F)
    -> SoaVec<f32, N>
where F: Fn(&T) -> [f32; N]

// After this PR:
pub fn aos_to_soa<T, U, const N: usize, F>(aos: &[T], extract: F)
    -> SoaVec<U, N>
where F: Fn(&T) -> [U; N]

soa_to_aos mirrors the same generalisation, adding a U: Copy bound (needed to materialise a per-row [U; N] via core::array::from_fn).

SoaVec<T, N> itself was already generic — the constraint was purely at the closure-helper signature layer.

Caller migration

Callers using return-type inference are unaffected.

Turbofish callers gain one type param at position 2:

// Before:
aos_to_soa::<_, 3, _>(&aos, |it| [it.a, it.b, it.c])
// After (either form works):
aos_to_soa::<_, _, 3, _>(&aos, |it| [it.a, it.b, it.c])        // infer U
aos_to_soa::<_, f32, 3, _>(&aos, |it| [it.a, it.b, it.c])      // explicit U

In-tree turbofish callers updated this PR:

  • src/hpc/soa.rs — 4 inline test bodies
  • src/hpc/bulk.rs — 1 module doctest + 1 inline test body

New tests

  • aos_to_soa_u64_round_trip — 3-field u64, full range incl. u64::MAX
  • aos_to_soa_u8_round_trip — palette/alpha + soa_to_aos round-trip
  • aos_to_soa_u16_round_trip — BF16 carrier + soa_to_aos round-trip
  • aos_to_soa_inference_onlyi8, no turbofish (closure ret-type annotation)

Updated doctests on aos_to_soa cover f32 (back-compat), u64 (CausalEdge64-style), u8 (palette). soa_to_aos doctests cover f32 + u16 (BF16). Module header "Element-type scope" rewritten to record the lift + migration note.

Verified locally

  • cargo test -p ndarray --lib hpc::soa — 33 passed
  • cargo test --doc -p ndarray hpc::soa — 13 passed
  • cargo fmt --check — clean
  • cargo clippy --features approx,serde,rayon -- -D warnings — clean

Out of scope (Worker B of PR-X2)

#[soa(pad_to_lanes=N)] field attribute on soa_struct! — separate follow-up PR per design § "Worker decomposition" (depends on this generalisation landing first since the macro emits aos_to_soa-shaped conversions).

🤖 Generated with Claude Code


Generated by Claude Code

Worker A of PR-X2 (sequential, per .claude/knowledge/pr-x2-design.md
§ "Worker decomposition" line 458). Lifts the f32-only constraint that
W3-W6 shipped, so downstream consumers with u8/u16/u64/i8 SoA fields
(palette indices, BF16 carrier, CausalEdge64 mantissa, quantized
weights) can use the public surface instead of rolling their own
extract loop.

Signature change (Option C per design § "Migration path"):

  // Pre-PR-X2:
  pub fn aos_to_soa<T, const N: usize, F>(aos: &[T], extract: F)
      -> SoaVec<f32, N>
  where F: Fn(&T) -> [f32; N]

  // After PR-X2 (this commit):
  pub fn aos_to_soa<T, U, const N: usize, F>(aos: &[T], extract: F)
      -> SoaVec<U, N>
  where F: Fn(&T) -> [U; N]

`soa_to_aos` mirrors the same generalisation and adds a `U: Copy`
bound (needed to materialise the per-row `[U; N]` via
`core::array::from_fn`). `SoaVec<T, N>` itself was already generic over
`T` so no internal changes were needed — the constraint was
purely at the closure-helper signature layer.

Caller migration (callers using return-type inference are unaffected):

  // Turbofish form gains one type param at position 2:
  aos_to_soa::<_, 3, _>(&aos, …)        // was
  aos_to_soa::<_, _, 3, _>(&aos, …)     // now (or `<_, f32, 3, _>` explicit)

Updated callers:
  - src/hpc/soa.rs       4 inline test bodies (sed-rewrite)
  - src/hpc/bulk.rs      1 module doctest + 1 inline test body

New tests in `src/hpc/soa.rs`:
  - aos_to_soa_u64_round_trip       — 3-field u64, full range incl. u64::MAX
  - aos_to_soa_u8_round_trip        — palette/alpha + soa_to_aos round-trip
  - aos_to_soa_u16_round_trip       — BF16 carrier + soa_to_aos round-trip
  - aos_to_soa_inference_only       — i8, no turbofish (closure ret-type)

Updated doctests on aos_to_soa + soa_to_aos cover f32 (back-compat),
u64 (CausalEdge64-style), and u8 (palette indices); module header
"Element-type scope" rewritten to record the lift + migration note.

Verified:
  cargo test -p ndarray --lib hpc::soa      33 passed
  cargo test --doc -p ndarray hpc::soa      13 passed
  cargo fmt --check                          clean
  cargo clippy --features approx,serde,rayon -- -D warnings  clean

Out of scope (Worker B of PR-X2):
  #[soa(pad_to_lanes=N)] field attribute on soa_struct! — separate
  commit per design § "Worker decomposition".
@AdaWorldAPI AdaWorldAPI merged commit fb95cb3 into master May 20, 2026
15 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