// SIMD Adler-32 — wasm target only.
//
// Inline-WAT v128 implementation ported from mizchi/simd's
// `src/simd_wasm_bytes.mbt`. Each 16-byte chunk runs:
//
// v = v128.load
// sum_a += i16x8.extadd_pairwise_i8x16_u(v) folded to i32 (sums all bytes)
// sum_b += i32x4 [a1*16 + a2*15 + ... + a16*1] via i16x8.extmul + add
//
// Then we apply zlib's NMAX=5552 mod-cycling. ~7.7× scalar at 256 KB.
//
// Two implementations because `Bytes` and `FixedArray[Byte]` are distinct
// FFI types in MoonBit's inline-WAT — bodies are identical, parameter
// types differ.
///|
/// Adler-32 weight vector [16, 15, ..., 1] for one 16-byte SIMD chunk.
let adler_weights : FixedArray[Byte] = {
let w = FixedArray::make(16, b'\x00')
for j in 0..<16 {
w[j] = (16 - j).to_byte()
}
w
}
///|
/// Process `count16` 16-byte chunks starting at `data + offset`, returning
/// the updated `(a, b)` packed as `((b as u64) << 32) | (a as u64)`.
/// Bytes-direct variant.
#borrow(data, weights)
fn adler32_chunks_bytes(
data : Bytes,
offset : Int,
count16 : Int,
weights : FixedArray[Byte],
a : Int,
b : Int,
) -> Int64 =
#|(func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i64) (local i32) (local i32) (local i32) (local i32) (local v128) (local v128) (local v128) local.get 3 v128.load local.set 10 local.get 1 local.get 2 i32.const 4 i32.shl i32.add local.set 7 local.get 1 local.set 6 block loop local.get 6 local.get 7 i32.ge_s br_if 1 local.get 0 local.get 6 i32.add v128.load local.set 11 local.get 11 i16x8.extadd_pairwise_i8x16_u i32x4.extadd_pairwise_i16x8_u local.set 12 local.get 12 i32x4.extract_lane 0 local.get 12 i32x4.extract_lane 1 i32.add local.get 12 i32x4.extract_lane 2 i32.add local.get 12 i32x4.extract_lane 3 i32.add local.set 8 local.get 11 local.get 10 i16x8.extmul_low_i8x16_u i32x4.extadd_pairwise_i16x8_u local.get 11 local.get 10 i16x8.extmul_high_i8x16_u i32x4.extadd_pairwise_i16x8_u i32x4.add local.set 12 local.get 12 i32x4.extract_lane 0 local.get 12 i32x4.extract_lane 1 i32.add local.get 12 i32x4.extract_lane 2 i32.add local.get 12 i32x4.extract_lane 3 i32.add local.set 9 local.get 5 local.get 4 i32.const 4 i32.shl i32.add local.get 9 i32.add local.set 5 local.get 4 local.get 8 i32.add local.set 4 local.get 6 i32.const 16 i32.add local.set 6 br 0 end end local.get 5 i64.extend_i32_u i64.const 32 i64.shl local.get 4 i64.extend_i32_u i64.or)
///|
/// FixedArray-direct variant. Same WAT body, different param type.
#borrow(data, weights)
fn adler32_chunks_fixed(
data : FixedArray[Byte],
offset : Int,
count16 : Int,
weights : FixedArray[Byte],
a : Int,
b : Int,
) -> Int64 =
#|(func (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i64) (local i32) (local i32) (local i32) (local i32) (local v128) (local v128) (local v128) local.get 3 v128.load local.set 10 local.get 1 local.get 2 i32.const 4 i32.shl i32.add local.set 7 local.get 1 local.set 6 block loop local.get 6 local.get 7 i32.ge_s br_if 1 local.get 0 local.get 6 i32.add v128.load local.set 11 local.get 11 i16x8.extadd_pairwise_i8x16_u i32x4.extadd_pairwise_i16x8_u local.set 12 local.get 12 i32x4.extract_lane 0 local.get 12 i32x4.extract_lane 1 i32.add local.get 12 i32x4.extract_lane 2 i32.add local.get 12 i32x4.extract_lane 3 i32.add local.set 8 local.get 11 local.get 10 i16x8.extmul_low_i8x16_u i32x4.extadd_pairwise_i16x8_u local.get 11 local.get 10 i16x8.extmul_high_i8x16_u i32x4.extadd_pairwise_i16x8_u i32x4.add local.set 12 local.get 12 i32x4.extract_lane 0 local.get 12 i32x4.extract_lane 1 i32.add local.get 12 i32x4.extract_lane 2 i32.add local.get 12 i32x4.extract_lane 3 i32.add local.set 9 local.get 5 local.get 4 i32.const 4 i32.shl i32.add local.get 9 i32.add local.set 5 local.get 4 local.get 8 i32.add local.set 4 local.get 6 i32.const 16 i32.add local.set 6 br 0 end end local.get 5 i64.extend_i32_u i64.const 32 i64.shl local.get 4 i64.extend_i32_u i64.or)
///|
fn adler32_impl(data : Bytes) -> Int {
let len = data.length()
let mut a : UInt = 1
let mut b : UInt = 0
let mut i = 0
while i < len {
let remaining = len - i
let block = if remaining > adler_nmax { adler_nmax } else { remaining }
let block_end = i + block
let simd_chunks = block / 16
if simd_chunks > 0 {
let packed = adler32_chunks_bytes(
data,
i,
simd_chunks,
adler_weights,
a.reinterpret_as_int(),
b.reinterpret_as_int(),
)
a = (packed & 0xFFFFFFFFL).to_int().reinterpret_as_uint()
b = (packed.reinterpret_as_uint64() >> 32).to_int().reinterpret_as_uint()
i = i + simd_chunks * 16
}
while i < block_end {
a = a + data[i].to_int().reinterpret_as_uint()
b = b + a
i = i + 1
}
a = a % adler_mod
b = b % adler_mod
}
UInt::reinterpret_as_int((b << 16) | a)
}
///|
fn adler32_fixed_impl(data : FixedArray[Byte]) -> Int {
let len = data.length()
let mut a : UInt = 1
let mut b : UInt = 0
let mut i = 0
while i < len {
let remaining = len - i
let block = if remaining > adler_nmax { adler_nmax } else { remaining }
let block_end = i + block
let simd_chunks = block / 16
if simd_chunks > 0 {
let packed = adler32_chunks_fixed(
data,
i,
simd_chunks,
adler_weights,
a.reinterpret_as_int(),
b.reinterpret_as_int(),
)
a = (packed & 0xFFFFFFFFL).to_int().reinterpret_as_uint()
b = (packed.reinterpret_as_uint64() >> 32).to_int().reinterpret_as_uint()
i = i + simd_chunks * 16
}
while i < block_end {
a = a + data[i].to_int().reinterpret_as_uint()
b = b + a
i = i + 1
}
a = a % adler_mod
b = b % adler_mod
}
UInt::reinterpret_as_int((b << 16) | a)
}