Generic single-threaded implementation
fn ktSingleThreaded(comptime Variant: type, view: *const MultiSliceView, total_len: usize, output: []u8) void
fn ktSingleThreaded(comptime Variant: type, view: *const MultiSliceView, total_len: usize, output: []u8) void {
const cv_size = Variant.cv_size;
const StateType = Variant.StateType;
// Initialize streaming TurboSHAKE state for final node (delimiter 0x06 is set in the type)
var final_state = StateType.init(.{});
// Absorb first B bytes from input
var first_b_buffer: [chunk_size]u8 = undefined;
if (view.tryGetSlice(0, chunk_size)) |first_chunk| {
final_state.update(first_chunk);
} else {
view.copyRange(0, chunk_size, &first_b_buffer);
final_state.update(&first_b_buffer);
}
// Absorb padding bytes (8 bytes: 0x03 followed by 7 zeros)
const padding = [_]u8{ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
final_state.update(&padding);
var j: usize = chunk_size;
var n: usize = 0;
// Temporary buffers for boundary-spanning leaves and CV computation
var leaf_buffer: [chunk_size * 8]u8 align(cache_line_size) = undefined;
var cv_buffer: [64]u8 = undefined; // Max CV size is 64 bytes
// Process leaves in SIMD batches (8x, 4x, 2x)
inline for ([_]usize{ 8, 4, 2 }) |batch_size| {
while (optimal_vector_len >= batch_size and j + batch_size * chunk_size <= total_len) {
processAndAbsorbNLeaves(Variant, batch_size, view, j, &leaf_buffer, &final_state);
j += batch_size * chunk_size;
n += batch_size;
}
}
// Process remaining leaves one at a time
while (j < total_len) {
const chunk_len = @min(chunk_size, total_len - j);
if (view.tryGetSlice(j, j + chunk_len)) |leaf_data| {
const cv_slice = MultiSliceView.init(leaf_data, &[_]u8{}, &[_]u8{});
Variant.turboShakeToBuffer(&cv_slice, 0x0B, cv_buffer[0..cv_size]);
final_state.update(cv_buffer[0..cv_size]); // Absorb CV immediately
} else {
view.copyRange(j, j + chunk_len, leaf_buffer[0..chunk_len]);
const cv_slice = MultiSliceView.init(leaf_buffer[0..chunk_len], &[_]u8{}, &[_]u8{});
Variant.turboShakeToBuffer(&cv_slice, 0x0B, cv_buffer[0..cv_size]);
final_state.update(cv_buffer[0..cv_size]);
}
j += chunk_size;
n += 1;
}
// Absorb right_encode(n) and terminator
const n_enc = rightEncode(n);
final_state.update(n_enc.slice());
const terminator = [_]u8{ 0xFF, 0xFF };
final_state.update(&terminator);
// Finalize and squeeze output
final_state.final(output);
}