blake3_servil/stream.rs
1//! A stream of messages hashed in place by this crate's threads, in the
2//! program's own memory, each digest written into the program's own memory
3//! and that memory handed back in order ([`stream`]).
4//!
5//! How it runs:
6//! - Each message of up to [`PIECE`] bytes is one entry in a ring of
7//! entries; entries gather into tasks of up to [`MEMBERS`] messages (or
8//! TASK_LEN / 4 bytes), hashed as members of a task: messages of
9//! one length side by side, the rest one at a time.
10//! - A longer message is split into pieces of PIECE bytes, each a whole
11//! subtree at its offset and a task of its own; the program's thread
12//! merges the pieces' chaining values in order as it hands back the
13//! digest.
14//! - The stream is its own source of tasks for the pool's workers
15//! (`lanes::sources`): it writes each task into its source's ring and
16//! publishes it with one store, and workers take tasks in order by
17//! compare-and-swap, with no lock and no shared queue. The open task is
18//! handed over when it fills, or when the program collects or finishes.
19//! - Each submission names where its digests go, in the program's memory;
20//! the threads write them there (a long message's, merged on the
21//! program's thread, too). The stream hands that memory back to the
22//! stream's one handler in submission order, on the program's thread,
23//! from a submission (when the stream is full), `collect` and `finish`.
24//! A program waiting hashes tasks no thread has taken.
25//!
26//! The messages are borrowed for the scope of [`stream`], which waits for
27//! every task before it returns, also when its body panics: no thread
28//! reads a message after the scope ends.
29
30use crate::lanes::{MEMBERS, Member, OwnLine, TASK_LEN, Task};
31use crate::platform::Platform;
32use crate::{CHUNK_LEN, CVBytes, CVWords, Hash, Mode, OUT_LEN};
33use core::cell::UnsafeCell;
34use core::marker::PhantomData;
35use core::sync::atomic::{AtomicUsize, Ordering};
36
37/// The longest message hashed as one entry; a longer one goes in pieces
38/// this long (a task's subtree).
39pub(crate) const PIECE: usize = TASK_LEN;
40
41/// The bytes a stream keeps for messages and batches written in place:
42/// 2 MiB. With the digests in the program's places, 4 MiB made two
43/// programs streaming 4 MiB batches 18% slower (the ring, the places, and
44/// the programs' own buffers past the L3, i7-12700K); 2 MiB is level there
45/// and 2-4 points less fast than 4 MiB on some other cells.
46const RING_BYTES: usize = 2 << 20;
47
48/// The most entries (messages and pieces) in flight in one stream (1024:
49/// 4 KiB to 16 KiB messages 8-9% faster than at 8192, i7-12700K; 256 lost
50/// 26-130% at 64 and 256 B).
51const ENTRIES: usize = 1024;
52
53/// One message, one piece of a long one, or one part of a batch, from its
54/// submission to its delivery: what the program's thread keeps of it. Its
55/// task's threads write its digests, then count down the `left` of the
56/// task's first entry (Release, once a task); the program hands them back
57/// once that is zero (Acquire). Kept apart, so that the program's writes
58/// here share no line with the threads'.
59#[derive(Clone, Copy)]
60struct Entry {
61 len: usize,
62 kind: Kind,
63 /// The index of its task's first entry, whose `left` counts the task.
64 first: usize,
65 /// The stream's ring bytes reserved up to and including this entry
66 /// (its own, for a message written in place): free once it is handed
67 /// back.
68 ring_end: usize,
69}
70
71/// What an entry is. Each `out` is the program's, borrowed for 'env.
72#[derive(Clone, Copy)]
73enum Kind {
74 /// A whole message: its digest at `out`.
75 Message { out: *mut Hash },
76 /// A piece of a long message: its first chunk's index, whether it is
77 /// the message's last piece, and where the message's digest goes.
78 Piece { counter: u64, last: bool, out: *mut Hash },
79 /// A part of a batch of messages of `message_len` bytes laid out as
80 /// hash_many takes them (an empty batch: one part of none), its
81 /// digests written into the batch's space; the batch's last part holds
82 /// that whole space, handed back once it is done.
83 Batch { message_len: usize, whole: Option<(*mut Hash, usize)> },
84 /// The end of a long message whose reader failed after some pieces
85 /// ([`Stream::submit_reader`]): no digest; its pieces' chaining values
86 /// are dropped.
87 Abandoned,
88}
89
90/// What a task's threads write for a piece of a long message, in
91/// `pieces_out`: its chaining value (a piece at chunk 0, of two chunks or
92/// more, gives its two children instead), on a line of its own.
93#[repr(transparent)]
94struct Out<const N: usize>(UnsafeCell<[u8; N]>);
95
96/// Hash a stream of messages on this crate's threads, in place: `body`
97/// submits messages that live at least as long as the call
98/// ([`Stream::submit`]), each with the place its digest goes, in the
99/// program's memory (a batch's, the place for all its digests). The
100/// threads write the digests there, and `done` receives each place back,
101/// in submission order, once its digests are in it: from a submission
102/// (when the stream is full), [`collect`](Stream::collect) and
103/// [`finish`](Stream::finish), on this thread. `body` calls `finish` last
104/// (a scope that ends with places not handed back panics). A message of
105/// any length; this crate's threads read it where it is, and `stream`
106/// returns only after they are done, also when `body` panics.
107///
108/// The program calls [`crate::initialize_multithreaded`] first. A stream
109/// holds up to 1024 messages (a message over 64 KiB counts once per 64 KiB,
110/// a batch once per 64 KiB of messages) in flight; when it is full, a
111/// submission waits for room, handing places back and hashing a waiting
112/// task itself. A message stays unhashed in a task still open until the
113/// task fills or the program calls `collect` or `finish`, so a program
114/// that needs a digest soon calls one of them. State that `done` and
115/// `body` share goes in a [`Cell`](core::cell::Cell) or
116/// [`RefCell`](core::cell::RefCell).
117///
118/// Each message's place comes back alone, about 1-2 ns a message: for
119/// many short messages of one length, [`Stream::submit_batch`] hands back
120/// one place per batch and hashes them side by side (64-byte messages one
121/// at a time ran 4-8% slower than in the stream's earlier design, which
122/// handed back runs of digests from its own memory; batches of them,
123/// 5-13% faster; i7-12700K).
124///
125/// **Streams, then other calls.** While a stream is open, this crate's
126/// threads keep looking for its next message for a few microseconds before
127/// they sleep, which makes streams of messages 3-5% faster. A program that
128/// streams and then soon makes a one-shot multithreaded call pays for it:
129/// on an i7-12700K under Linux, [`crate::hash_many_multithreaded`] on 16384
130/// messages right after streams of 16-64 KiB messages ran about 5% slower.
131/// A program that keeps streaming does not pay it; one that alternates can
132/// stream its other work
133/// too ([`Stream::submit_batch`] for a batch, a stream per round with
134/// [`finish`](Stream::finish) between rounds that depend on each other).
135///
136/// ```
137/// # blake3_servil::initialize_multithreaded(None);
138/// use blake3_servil::{Hash, Mode};
139/// let messages: Vec<Vec<u8>> = (0..1000u32).map(|i| i.to_le_bytes().repeat(16)).collect();
140/// let mut digests = vec![Hash::from_bytes([0; 32]); messages.len()];
141/// let mut handed_back = 0;
142/// blake3_servil::stream(Mode::Hash, |done: &mut [Hash]| handed_back += done.len(), |s| {
143/// for (message, digest) in messages.iter().zip(&mut digests) {
144/// s.submit(message, digest);
145/// }
146/// s.finish();
147/// });
148/// assert_eq!(handed_back, 1000);
149/// assert_eq!(digests[7], blake3_servil::hash(&messages[7]));
150/// ```
151pub fn stream<'env, D: FnMut(&'env mut [Hash]), T>(mode: Mode, done: D, body: impl for<'s> FnOnce(&'s mut Stream<'env, D>) -> T) -> T {
152 let mut stream = Stream::open(mode, done);
153 let result = body(&mut stream);
154 assert_eq!(stream.delivered, stream.next, "a stream's body calls finish last");
155 result
156}
157
158impl<'env, D: FnMut(&'env mut [Hash])> Stream<'env, D> {
159 /// A stream outside [`stream`]'s scope, for the C API (`capi/`): the
160 /// same contract, with the program holding the stream and ending it
161 /// (`finish`, then drop). Dropping a stream waits for its tasks. Rust
162 /// programs call [`stream`], whose scope holds the contract for them.
163 #[doc(hidden)]
164 pub fn open(mode: Mode, done: D) -> Self {
165 crate::lanes::require();
166 let (key, flags) = mode.key_and_flags();
167 let s = Storage::take();
168 // A slot, where some thread beside this one can take tasks (with one
169 // CPU none can: then every task is hashed here as it is handed over,
170 // as when every slot is taken).
171 let slot = if crate::lanes::splits() { s.source.register() } else { None };
172 crate::lanes::sources::opened();
173 Stream {
174 key,
175 flags,
176 s,
177 slot,
178 done,
179 open_first: 0,
180 next: 0,
181 delivered: 0,
182 head: 0,
183 tail: 0,
184 open: None,
185 stack: arrayvec::ArrayVec::new(),
186 pieces: 0,
187 env: PhantomData,
188 }
189 }
190}
191
192/// A stream's entries, and room for their tasks in the pool's list: kept
193/// for the thread's next stream (a stream of one message up to 4 KiB
194/// after a 1 ms pause: 55 us with 248 KiB allocated and faulted in each
195/// time, 4-5 us with the spare; one stream kept open, 2.6-4 us. i7-12700K).
196struct Storage {
197 entries: Box<[Entry]>,
198 /// RING_BYTES for messages and batches written in place
199 /// ([`Stream::submit_with`]), each at a 64-byte boundary, in the order
200 /// of their entries: an allocation of its own, reached by pointer
201 /// alone (the program writes some bytes while threads read others).
202 ring: *mut u8,
203 pieces_out: Box<[OwnLine<Out<64>>]>,
204 /// For a task's first entry, the count of the task's entries still
205 /// unhashed: on lines of their own (128 bytes, as the adjacent-line
206 /// prefetcher pairs lines), so that the program polling one shares no
207 /// line with the threads writing digests. Zero between streams.
208 lefts: Box<[OwnLine<AtomicUsize>]>,
209 /// The workers' view of this storage's tasks: shared with them, so
210 /// apart from the stream's own (`&mut`) storage, and never freed.
211 source: &'static crate::lanes::sources::Source,
212}
213
214/// Streams' storage not in use: recycled, never freed (a worker may read a
215/// source's counters after its stream ended).
216static FREE: std::sync::Mutex<std::vec::Vec<FreeStorage>> = std::sync::Mutex::new(std::vec::Vec::new());
217
218struct FreeStorage(&'static mut Storage);
219
220// Sound: a storage in the free list is no stream's; the one that takes it
221// owns it alone.
222unsafe impl Send for FreeStorage {}
223
224impl Storage {
225 /// A storage from the free list, or a new one (kept for good).
226 fn take() -> &'static mut Storage {
227 match FREE.lock().unwrap().pop() {
228 Some(FreeStorage(s)) => s,
229 None => Box::leak(Box::new(Storage::new())),
230 }
231 }
232
233 fn new() -> Storage {
234 Storage {
235 source: Box::leak(Box::new(crate::lanes::sources::Source::new(ENTRIES))),
236 entries: std::vec![Entry { len: 0, kind: Kind::Abandoned, first: 0, ring_end: 0 }; ENTRIES].into_boxed_slice(),
237 ring: {
238 // Zeroed, untouched pages (only the bytes used are faulted
239 // in: calloc maps them lazily, where an aligned allocation
240 // would write all 2 MiB), starting at the first 64-byte
241 // boundary, as each room's offset is, so every room starts
242 // a cache line (glibc puts a 2 MiB block 16 bytes into
243 // one). Kept for good, as the storage is.
244 let block = Box::leak(std::vec![0u8; RING_BYTES + 64].into_boxed_slice());
245 let start = block.as_ptr().align_offset(64);
246 block[start..].as_mut_ptr()
247 },
248 pieces_out: (0..ENTRIES).map(|_| OwnLine(Out(UnsafeCell::new([0; 64])))).collect(),
249 lefts: (0..ENTRIES).map(|_| OwnLine(AtomicUsize::new(0))).collect(),
250 }
251 }
252
253 /// The ring's byte at `at`.
254 fn ring_at(&self, at: usize) -> *mut u8 {
255 debug_assert!(at <= RING_BYTES);
256 // Sound: `at` is in the ring.
257 unsafe { self.ring.add(at) }
258 }
259}
260
261/// Part `i` of a message in `pieces` parts, its digest at `out`: the
262/// message whole, or a piece.
263fn part_kind(i: usize, pieces: usize, out: *mut Hash) -> Kind {
264 if pieces == 1 { Kind::Message { out } } else { Kind::Piece { counter: (i * PIECE / CHUNK_LEN) as u64, last: i + 1 == pieces, out } }
265}
266
267/// The messages of one [`stream`] call, and the places for their digests,
268/// borrowed for its scope; `D` is the stream's handler.
269pub struct Stream<'env, D> {
270 key: CVWords,
271 flags: u8,
272 /// Its own until the stream ends (then the thread's spare).
273 s: &'static mut Storage,
274 /// The source's registration, or None (this thread hashes every task).
275 slot: Option<usize>,
276 /// The handler that takes each place back.
277 done: D,
278 /// The open task's first entry.
279 open_first: usize,
280 /// Entries submitted, and entries handed back (each counts on).
281 next: usize,
282 delivered: usize,
283 /// Ring bytes reserved, and freed (each counts on; the ring holds
284 /// head - tail).
285 head: usize,
286 tail: usize,
287 /// Short messages gathered, not yet handed to the pool.
288 open: Option<Task>,
289 /// A long message being handed back: its pieces' chaining values so
290 /// far (merged as the Hasher's stack merges), and how many.
291 stack: arrayvec::ArrayVec<CVBytes, { crate::MAX_DEPTH + 1 }>,
292 pieces: u64,
293 /// Invariant in 'env, as std::thread::Scope is: the messages' and the
294 /// places' lifetime.
295 env: PhantomData<&'env mut &'env ()>,
296}
297
298// Sound: the entries are reached by the program's thread through `&mut
299// self`, and by the pool's threads only for entries in tasks in flight,
300// across the Release/Acquire pair on each entry's `left`; the messages are
301// shared borrows and the places exclusive ones, alive for 'env, each place
302// written only by its task's thread before the Release and handed back
303// only after the Acquire.
304unsafe impl<D: Send> Send for Stream<'_, D> {}
305
306impl<'env, D: FnMut(&'env mut [Hash])> Stream<'env, D> {
307 /// Hash `message` where it is, its digest into `out`.
308 pub fn submit(&mut self, message: &'env [u8], out: &'env mut Hash) {
309 let out: *mut Hash = out;
310 let pieces = message.len().div_ceil(PIECE).max(1);
311 for i in 0..pieces {
312 let part = &message[i * PIECE..][..(message.len() - i * PIECE).min(PIECE)];
313 let at = self.head;
314 self.add_part(part.as_ptr(), part.len(), part_kind(i, pieces, out), at, out as *mut u8);
315 }
316 }
317
318 /// Hash a message of `len` bytes that `fill` writes in place, into the
319 /// stream's own bytes, in pieces of up to 64 KiB, its digest into
320 /// `out`: `fill(at, room)` writes the message's bytes from `at` into
321 /// all of `room` (a read from a file or socket straight into the
322 /// stream). Each `room` starts on a 64-byte boundary. The stream
323 /// reuses the bytes once `out` is handed back; a stream keeps 2 MiB of
324 /// them, and `submit_with` waits for room as `submit` does.
325 pub fn submit_with(&mut self, len: usize, mut fill: impl FnMut(usize, &mut [u8]), out: &'env mut Hash) {
326 let out: *mut Hash = out;
327 let pieces = len.div_ceil(PIECE).max(1);
328 for i in 0..pieces {
329 let part_len = (len - i * PIECE).min(PIECE);
330 let at = self.reserve(part_len);
331 // Sound: [at, at + part_len) is reserved and free (no entry in
332 // flight holds it), and `&mut self` keeps every other writer out.
333 let room = unsafe { core::slice::from_raw_parts_mut(self.s.ring_at(at), part_len) };
334 fill(i * PIECE, room);
335 let end = self.head;
336 self.add_part(room.as_ptr(), part_len, part_kind(i, pieces, out), end, out as *mut u8);
337 }
338 }
339
340 /// Hash the message `reader` yields, to its end, read straight into the
341 /// stream's own bytes in pieces of up to 64 KiB (a file or socket read
342 /// into the stream, overlapping the hashing of earlier pieces), its
343 /// digest into `out`: the message's length on success. On a read error
344 /// (other than an interruption, which reads again) the error returns,
345 /// `out` gets no digest and is not handed back, and the stream goes on
346 /// with the next message. The length need not be known: the stream
347 /// reads one piece ahead to tell the last.
348 ///
349 /// `size_hint` is the length the caller expects, where it knows one (a
350 /// file's size, a protocol's length field): a scheduling hint, never
351 /// part of the hash, so a wrong one costs nothing but speed. Today the
352 /// stream does not use it; it is here so that a stream that schedules
353 /// by it later changes no caller.
354 pub fn submit_reader(&mut self, mut reader: impl std::io::Read, size_hint: Option<u64>, out: &'env mut Hash) -> std::io::Result<u64> {
355 let _ = size_hint;
356 let out: *mut Hash = out;
357 let start = self.head;
358 let (mut at, mut end, mut n, mut read) = self.read_piece(&mut reader);
359 let mut i = 0;
360 loop {
361 let ahead = (n == PIECE && read.is_ok()).then(|| self.read_piece(&mut reader));
362 let failed = read.is_err() || ahead.as_ref().is_some_and(|a| a.3.is_err());
363 if failed {
364 let error = match (read, ahead) {
365 (Err(e), _) => e,
366 (_, Some((_, _, _, Err(e)))) => e,
367 _ => unreachable!("a read failed"),
368 };
369 // The bytes not yet handed to the pool go; the pieces that
370 // were end in an entry that drops their chaining values.
371 self.head = if i == 0 { start } else { end - PIECE.div_ceil(64) * 64 };
372 if i > 0 {
373 let head = self.head;
374 self.add_entry(0, Kind::Abandoned, head);
375 }
376 return Err(error);
377 }
378 let last = match &ahead {
379 None => true,
380 // Exactly a piece, then the end: give the read-ahead room back.
381 Some((_, _, 0, _)) => {
382 self.head = end;
383 true
384 }
385 Some(_) => false,
386 };
387 let kind = if i == 0 && last { Kind::Message { out } } else { Kind::Piece { counter: (i * PIECE / CHUNK_LEN) as u64, last, out } };
388 // [at, at + n): this piece's reserved room, written.
389 let input = self.s.ring_at(at);
390 self.add_part(input, n, kind, end, out as *mut u8);
391 if last {
392 return Ok((i * PIECE + n) as u64);
393 }
394 (at, end, n, read) = ahead.unwrap();
395 i += 1;
396 }
397 }
398
399 /// A piece's room (PIECE bytes, reserved), read into from `reader` until
400 /// it is full, the reader ends, or a read fails: its offset, the ring's
401 /// head after its reservation, the bytes read, and the read's result.
402 fn read_piece(&mut self, reader: &mut impl std::io::Read) -> (usize, usize, usize, std::io::Result<()>) {
403 let at = self.reserve(PIECE);
404 let end = self.head;
405 // Sound: [at, at + PIECE) is reserved and free, and `&mut self`
406 // keeps every other writer out.
407 let room = unsafe { core::slice::from_raw_parts_mut(self.s.ring_at(at), PIECE) };
408 let mut n = 0;
409 while n < PIECE {
410 match reader.read(&mut room[n..]) {
411 Ok(0) => break,
412 Ok(k) => n += k,
413 Err(e) if e.kind() == std::io::ErrorKind::Interrupted => {}
414 Err(e) => return (at, end, n, Err(e)),
415 }
416 }
417 (at, end, n, Ok(()))
418 }
419
420 /// Hash a batch of messages of `message_len` bytes where it is, laid
421 /// out as [`hash_many`](crate::hash_many) takes them (each in whole
422 /// blocks, zero after its bytes), a digest per message into `out`, in
423 /// the messages' order: `out` holds one per message. The batch goes in
424 /// parts of up to 64 KiB, each one entry; `out` comes back whole.
425 pub fn submit_batch(&mut self, batch: &'env [u8], message_len: usize, out: &'env mut [Hash]) {
426 let slot = crate::many::slot_len(message_len);
427 assert_eq!(Some(batch.len()), slot.checked_mul(out.len()), "a batch's digest space holds one digest per message");
428 crate::many::assert_slots(batch, message_len, out.len());
429 let whole = (out.as_mut_ptr(), out.len());
430 let per_part = (TASK_LEN / slot).max(1);
431 let parts = out.len().div_ceil(per_part).max(1);
432 for p in 0..parts {
433 let first = p * per_part;
434 let count = (out.len() - first).min(per_part);
435 let at = self.head;
436 let kind = Kind::Batch { message_len, whole: (p + 1 == parts).then_some(whole) };
437 // Sound: `first` is within the batch and its space (count may be 0).
438 let (input, output) = unsafe { (batch.as_ptr().add(first * slot), whole.0.add(first)) };
439 self.add_part(input, count * slot, kind, at, output as *mut u8);
440 }
441 }
442
443 /// Hash a batch of `out.len()` messages of `message_len` bytes that
444 /// `fill` writes in place, laid out as [`hash_many`](crate::hash_many)
445 /// takes them: `fill(at, room)` writes the batch's bytes from `at`
446 /// (padding included: each message in whole blocks, zero after its
447 /// bytes) into all of `room`, a part of up to 64 KiB at a time. As
448 /// [`submit_with`](Stream::submit_with) and [`submit_batch`](Stream::submit_batch).
449 pub fn submit_batch_with(&mut self, message_len: usize, mut fill: impl FnMut(usize, &mut [u8]), out: &'env mut [Hash]) {
450 let slot = crate::many::slot_len(message_len);
451 let whole = (out.as_mut_ptr(), out.len());
452 let per_part = (TASK_LEN / slot).max(1);
453 let parts = out.len().div_ceil(per_part).max(1);
454 for p in 0..parts {
455 let first = p * per_part;
456 let count = (out.len() - first).min(per_part);
457 let at = self.reserve(count * slot);
458 // Sound: as in submit_with.
459 let room = unsafe { core::slice::from_raw_parts_mut(self.s.ring_at(at), count * slot) };
460 fill(first * slot, room);
461 crate::many::assert_slots(room, message_len, count);
462 let end = self.head;
463 let kind = Kind::Batch { message_len, whole: (p + 1 == parts).then_some(whole) };
464 // Sound: `first` is within the batch's space.
465 let output = unsafe { whole.0.add(first) };
466 self.add_part(room.as_ptr(), room.len(), kind, end, output as *mut u8);
467 }
468 }
469
470 /// An entry of `kind`, `len` bytes at `input`, its digests at `out`,
471 /// its ring bytes reserved up to `ring_end`: a whole message, or a
472 /// batch's part, joins the open task (of its kind: one of another kind
473 /// is handed over first); a piece of a long message is a task of its
474 /// own, a whole subtree at its offset; an empty batch's one part is no
475 /// task (done as it is added).
476 fn add_part(&mut self, input: *const u8, len: usize, kind: Kind, ring_end: usize, out: *mut u8) {
477 let entry = self.add_entry(len, kind, ring_end);
478 let batch = match kind {
479 Kind::Message { .. } => None,
480 Kind::Abandoned => unreachable!("an abandoned message adds no part"),
481 Kind::Batch { .. } if len == 0 => return,
482 Kind::Batch { message_len, .. } => Some(message_len),
483 Kind::Piece { counter, .. } => {
484 self.s.lefts[entry].store(1, Ordering::Relaxed);
485 // Sound: the bytes stay in place until the entry is handed back.
486 let bytes = unsafe { core::slice::from_raw_parts(input, len) };
487 let task = Task {
488 key: self.key,
489 flags: self.flags,
490 out: self.s.pieces_out[entry].0.0.get() as *mut u8,
491 left: &*self.s.lefts[entry],
492 ..Task::of(bytes, counter)
493 };
494 self.hand_over(task);
495 return;
496 }
497 };
498 if self.open.as_ref().is_some_and(|open| open.batch != batch) {
499 self.publish();
500 }
501 let (key, flags) = (self.key, self.flags);
502 let open = self.open.get_or_insert_with(|| Task::members(&key, flags, batch));
503 // The task's first member's `left` counts all of its members (set
504 // as the task is handed over).
505 if open.members == 0 {
506 self.open_first = entry;
507 open.left = &*self.s.lefts[entry];
508 }
509 self.s.entries[entry].first = self.open_first;
510 open.member[open.members] = Member { input, len, out };
511 open.members += 1;
512 open.len += len;
513 // Messages longer than a block hash one at a time: a task of them
514 // stops at a quarter of a task's bytes, to spread over more threads
515 // (spread over more threads); batches fill a task's bytes.
516 if open.members == MEMBERS || open.len >= if batch.is_some() { TASK_LEN } else { TASK_LEN / 4 } {
517 self.publish();
518 }
519 }
520
521 /// The ring offset of `len` free bytes at a 64-byte boundary (whole,
522 /// before the ring's end), reserved: waits, handing places back and
523 /// hashing tasks, until entries in flight free them.
524 fn reserve(&mut self, len: usize) -> usize {
525 let slot = len.div_ceil(64) * 64;
526 let mut at = self.head % RING_BYTES;
527 // A slot that would cross the end starts the next lap instead.
528 let skip = if at + slot > RING_BYTES { RING_BYTES - at } else { 0 };
529 while RING_BYTES - (self.head - self.tail) < skip + slot {
530 self.publish();
531 self.help();
532 }
533 if skip > 0 {
534 at = 0;
535 }
536 self.head += skip + slot;
537 at
538 }
539
540 /// Hand back every place whose digests are done, in order, without
541 /// waiting; and hand the open task to the pool, so what it holds
542 /// starts hashing.
543 pub fn collect(&mut self) {
544 self.publish();
545 // Nothing back: a worker for any task still waiting (the caller
546 // takes none here; in steady flow the workers are awake and this
547 // costs a comparison). Workers spin 5 us for a stream's next task
548 // and then park, and publish wakes one only for two waiting tasks,
549 // so without this a caller looping on collect waited forever where
550 // no SME2 thread takes stream tasks (tests/stream_collect_parked_workers).
551 if self.deliver() == 0 && self.slot.is_some() {
552 self.s.source.wake_for_waiting();
553 }
554 }
555
556 /// Hand back every place, in order, once its digests are done: this
557 /// thread hashes any task no thread has taken, and waits for the rest.
558 pub fn finish(&mut self) {
559 self.publish();
560 while self.delivered < self.next {
561 self.help();
562 }
563 }
564
565 /// Hand back what is done; with nothing done, hash a waiting task or
566 /// pause.
567 fn help(&mut self) {
568 if self.deliver() == 0 && !self.run_waiting() {
569 core::hint::spin_loop();
570 }
571 }
572
573 /// Hash one of the stream's tasks no thread has taken: whether there
574 /// was one.
575 fn run_waiting(&mut self) -> bool {
576 match self.s.source.take_task() {
577 Some(task) => {
578 crate::lanes::run_task(task);
579 true
580 }
581 None => false,
582 }
583 }
584
585 /// A new entry of `len` bytes, waiting for room when every entry is in
586 /// flight.
587 #[inline(always)]
588 fn add_entry(&mut self, len: usize, kind: Kind, ring_end: usize) -> usize {
589 if self.next - self.delivered == ENTRIES {
590 self.wait_for_room();
591 }
592 let index = self.next % ENTRIES;
593 self.s.entries[index] = Entry { len, kind, first: index, ring_end };
594 self.next += 1;
595 index
596 }
597
598 /// Hand back places, and hash tasks, until an entry is free (out of
599 /// line: a frame of its own, set up only when the stream is full).
600 #[cold]
601 #[inline(never)]
602 fn wait_for_room(&mut self) {
603 self.publish();
604 while self.next - self.delivered == ENTRIES {
605 self.help();
606 }
607 }
608
609 /// Hand the open task to the pool.
610 fn publish(&mut self) {
611 if let Some(task) = self.open.take() {
612 // Before the push, whose lock orders it before the threads' reads.
613 self.s.lefts[self.open_first].store(task.members, Ordering::Relaxed);
614 self.hand_over(task);
615 }
616 }
617
618 /// Give `task` to the workers through the stream's source; with no
619 /// slot, hash it on this thread now.
620 fn hand_over(&mut self, task: Task) {
621 match self.slot {
622 Some(_) => self.s.source.publish(task),
623 None => crate::lanes::run_task(task),
624 }
625 }
626
627 /// Hand back every finished entry's place from the oldest on (a
628 /// batch's once its last part is done); how many entries.
629 fn deliver(&mut self) -> usize {
630 let mut count = 0;
631 while self.delivered < self.next {
632 let index = self.delivered % ENTRIES;
633 let Entry { len, kind, first, ring_end } = self.s.entries[index];
634 // An entry of no task (an empty batch's part, an abandoned
635 // message) has its own `left`, zero.
636 if self.s.lefts[first].load(Ordering::Acquire) != 0 {
637 break;
638 }
639 self.delivered += 1;
640 self.tail = ring_end;
641 count += 1;
642 // Sound, each place: the program lent it for 'env and the stream
643 // hands it back once, after its task wrote it (the Acquire above).
644 match kind {
645 Kind::Message { out } => (self.done)(core::slice::from_mut(unsafe { &mut *out })),
646 Kind::Batch { whole: Some((out, n)), .. } => (self.done)(unsafe { core::slice::from_raw_parts_mut(out, n) }),
647 Kind::Batch { whole: None, .. } => {}
648 Kind::Abandoned => {
649 self.stack.clear();
650 self.pieces = 0;
651 }
652 Kind::Piece { counter, last, out } => {
653 // Sound: the task wrote its out before counting `left` down.
654 let piece = unsafe { *self.s.pieces_out[index].0.0.get() };
655 let (key, flags, platform) = (self.key, self.flags, Platform::detect());
656 let parent = |left: &CVBytes, right: &CVBytes| crate::hasher::parent_output(left, right, &key, flags, platform);
657 let cv: CVBytes = if counter == 0 && len > CHUNK_LEN {
658 parent(piece[..OUT_LEN].try_into().unwrap(), piece[OUT_LEN..].try_into().unwrap()).chaining_value()
659 } else {
660 piece[..OUT_LEN].try_into().unwrap()
661 };
662 // As the Hasher's stack: before pushing piece n, merge
663 // down to as many values as n has 1 bits.
664 while self.stack.len() > self.pieces.count_ones() as usize {
665 let right = self.stack.pop().unwrap();
666 let left = self.stack.pop().unwrap();
667 self.stack.push(parent(&left, &right).chaining_value());
668 }
669 self.stack.push(cv);
670 self.pieces += 1;
671 if last {
672 let mut right = self.stack.pop().unwrap();
673 while self.stack.len() > 1 {
674 right = parent(&self.stack.pop().unwrap(), &right).chaining_value();
675 }
676 let place = unsafe { &mut *out };
677 *place = parent(&self.stack.pop().unwrap(), &right).root_hash();
678 self.pieces = 0;
679 (self.done)(core::slice::from_mut(place));
680 }
681 }
682 }
683 }
684 count
685 }
686}
687
688impl<D> Drop for Stream<'_, D> {
689 /// Wait for every task still reading the messages and writing the
690 /// places (also when the body panicked), then keep the storage as the
691 /// thread's spare. A stream lives only inside [`stream`], which owns
692 /// it, so this runs before the scope ends.
693 fn drop(&mut self) {
694 if let Some(task) = self.open.take() {
695 // Handed over, so that the wait below ends: as publish.
696 self.s.lefts[self.open_first].store(task.members, Ordering::Relaxed);
697 match self.slot {
698 Some(_) => self.s.source.publish(task),
699 None => crate::lanes::run_task(task),
700 }
701 }
702 for i in self.delivered..self.next {
703 while self.s.lefts[self.s.entries[i % ENTRIES].first].load(Ordering::Acquire) != 0 {
704 let ran = match self.s.source.take_task() {
705 Some(task) => {
706 crate::lanes::run_task(task);
707 true
708 }
709 None => false,
710 };
711 if !ran {
712 core::hint::spin_loop();
713 }
714 }
715 }
716 if let Some(slot) = self.slot {
717 crate::lanes::sources::Source::unregister(slot);
718 }
719 crate::lanes::sources::closed();
720 // Sound: `s` is not used again; the next stream takes it whole.
721 let s: &'static mut Storage = unsafe { &mut *(self.s as *mut Storage) };
722 FREE.lock().unwrap().push(FreeStorage(s));
723 }
724}
725
726#[cfg(test)]
727mod test {
728 use super::*;
729
730 /// Places for `n` digests, each starting as zeros.
731 fn places(n: usize) -> std::vec::Vec<Hash> {
732 std::vec![Hash::from_bytes([0; 32]); n]
733 }
734
735 /// The next `n` places of `rest`, split off its front.
736 fn take<'a>(rest: &mut &'a mut [Hash], n: usize) -> &'a mut [Hash] {
737 let (now, after) = core::mem::take(rest).split_at_mut(n);
738 *rest = after;
739 now
740 }
741
742 /// Where each place handed back starts, and its length.
743 fn record(handed: &mut std::vec::Vec<(usize, usize)>) -> impl FnMut(&mut [Hash]) + '_ {
744 |d: &mut [Hash]| handed.push((d.as_ptr() as usize, d.len()))
745 }
746
747 /// Messages of every kind the stream handles (empty, short, one length
748 /// many times over, a whole piece, a piece and a byte, several pieces
749 /// and a partial one), borrowed and written in place, in each mode,
750 /// against hash_with, in order; far more entries and bytes than a
751 /// stream holds at once, so it fills, wraps its ring, and waits.
752 /// Keep: it detects, by hanging, mutants the clock then stops
753 /// (tools/mutants/timeouts-x86_64.txt: publish, take).
754 #[test]
755 fn test_stream_against_hash_with() {
756 let mut input = std::vec![0u8; (1 << 20) + 200];
757 crate::test::paint_test_input(&mut input);
758 let key = [5u8; 32];
759 let lens: std::vec::Vec<usize> =
760 (0..12000).map(|i| [0, 1, 63, 64, 64, 64, 100, 1024, 1024, 1500, 4096, PIECE, PIECE + 1, 200_000, (1 << 20) + 7, 64][i % 16]).collect();
761 let message = |i: usize| &input[i % 97..][..lens[i]];
762 for (mode, ways) in [(Mode::Hash, 0), (Mode::Keyed(&key), 1), (Mode::DeriveKey("stream test"), 2)] {
763 let mut got = places(lens.len());
764 let want_handed: std::vec::Vec<(usize, usize)> = got.iter().map(|h| (h as *const Hash as usize, 1)).collect();
765 let mut handed = std::vec::Vec::new();
766 stream(mode, record(&mut handed), |s| {
767 // With workers to hand tasks to, the stream has its slot
768 // among the pool's sources (a stream without one hashes
769 // every task on the caller: right, and slower).
770 assert_eq!(s.slot.is_some(), std::thread::available_parallelism().map_or(1, |n| n.get()) > 1);
771 for (i, out) in got.iter_mut().enumerate() {
772 // Borrowed, written in place (in reads of up to 1000
773 // bytes), or each in turn.
774 if ways == 0 || (ways == 2 && i % 3 == 0) {
775 s.submit(message(i), out);
776 } else {
777 s.submit_with(
778 lens[i],
779 |at, room| {
780 for (j, part) in room.chunks_mut(1000).enumerate() {
781 part.copy_from_slice(&message(i)[at + j * 1000..][..part.len()]);
782 }
783 },
784 out,
785 );
786 }
787 if i % 500 == 499 {
788 s.collect();
789 }
790 }
791 s.finish();
792 });
793 assert!(handed == want_handed, "every place handed back once, in order");
794 for i in 0..lens.len() {
795 assert_eq!(got[i], crate::hash_with(mode, message(i)), "message {i}, {} bytes", lens[i]);
796 }
797 }
798 }
799
800 /// A body that ends without finishing panics (after the threads are
801 /// done with its messages), and so does one that panics itself.
802 #[test]
803 #[cfg_attr(not(panic = "unwind"), ignore = "catches panics, which this target aborts on")]
804 fn test_stream_panics() {
805 let input = std::vec![9u8; 300_000];
806 for unwinding in [false, true] {
807 let result = std::panic::catch_unwind(|| {
808 let mut got = places(4);
809 stream(
810 Mode::Hash,
811 |_: &mut [Hash]| {},
812 |s| {
813 for (len, out) in [10, 100_000, 64, 300_000].into_iter().zip(&mut got) {
814 s.submit(&input[..len], out);
815 }
816 assert!(!unwinding, "the body panics");
817 },
818 )
819 });
820 assert!(result.is_err());
821 }
822 }
823
824 /// A reader that yields `data` in reads of varying sizes (1 byte to
825 /// 9000), interrupted now and then, failing after `fail_at` bytes.
826 struct Uneven<'a> {
827 data: &'a [u8],
828 at: usize,
829 calls: usize,
830 fail_at: Option<usize>,
831 }
832
833 impl std::io::Read for Uneven<'_> {
834 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
835 self.calls += 1;
836 if self.calls % 7 == 0 {
837 return Err(std::io::ErrorKind::Interrupted.into());
838 }
839 if self.fail_at.is_some_and(|f| self.at >= f) {
840 return Err(std::io::Error::other("the reader fails"));
841 }
842 let limit = self.fail_at.unwrap_or(self.data.len()).min(self.data.len());
843 let n = buf.len().min([1, 9000, 333, 65536, 4096][self.calls % 5]).min(limit - self.at);
844 buf[..n].copy_from_slice(&self.data[self.at..][..n]);
845 self.at += n;
846 Ok(n)
847 }
848 }
849
850 /// A few messages through a stream, small enough for Miri: the handover
851 /// of entries to the pool's threads and back, checked for data races.
852 /// Keep: it detects, by hanging, mutants the clock then stops
853 /// (tools/mutants/timeouts-x86_64.txt: Task::run, run_task).
854 #[test]
855 fn test_miri_stream() {
856 let input = std::vec![3u8; 3 * PIECE + 5];
857 let lens = [0, 64, 64, 1000, PIECE + 1, 3 * PIECE + 5];
858 let mut got = places(2 * lens.len() + 2 + 7);
859 let mut handed = std::vec::Vec::new();
860 let mut rest: &mut [Hash] = &mut got;
861 stream(Mode::Hash, record(&mut handed), |s| {
862 for len in lens {
863 s.submit(&input[..len], &mut take(&mut rest, 1)[0]);
864 s.submit_with(
865 len,
866 |at, room| {
867 assert_eq!(room.as_ptr() as usize % 64, 0, "a room starts a cache line");
868 room.copy_from_slice(&input[at..][..room.len()])
869 },
870 &mut take(&mut rest, 1)[0],
871 );
872 }
873 assert_eq!(s.submit_reader(&input[..2 * PIECE + 1], None, &mut take(&mut rest, 1)[0]).unwrap(), 2 * PIECE as u64 + 1);
874 assert!(s.submit_reader(Uneven { data: &input, at: 0, calls: 0, fail_at: Some(PIECE + 1) }, None, &mut take(&mut rest, 1)[0]).is_err());
875 s.submit_batch(&input[..4 * 64], 64, take(&mut rest, 4));
876 s.submit_batch_with(
877 64,
878 |at, room| {
879 assert_eq!(room.as_ptr() as usize % 64, 0, "a room starts a cache line");
880 room.copy_from_slice(&input[at..][..room.len()])
881 },
882 take(&mut rest, 3),
883 );
884 s.finish();
885 });
886 let mut want: std::vec::Vec<Hash> = lens.iter().flat_map(|&l| [crate::hash(&input[..l]); 2]).collect();
887 want.push(crate::hash(&input[..2 * PIECE + 1]));
888 want.push(Hash::from_bytes([0; 32]));
889 want.extend([crate::hash(&input[..64]); 7]);
890 assert_eq!(got, want);
891 assert_eq!(handed.len(), 2 * lens.len() + 1 + 2, "a place back per submission, none for the failed reader");
892 }
893}