1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
//! The [`CaseSet`] API below is a safe and simplified version of the `case_set*` macros in `ctx.h`.
//!
//! The `case_set*` macros themselves replaced `memset`s in order to further optimize them
//! (in e3b5d4d044506f9e0e95e79b3de42fd94386cc61,
//! which performed the following optimizations:
//! 1. larger, inlinable writes for small power of 2 lengths
//! 2. aligned writes for the above
//! 3. 8-byte aligned fields [`BlockContext`]
//! * allows 8-byte aligned writes
//! * better cache boundary alignment
//!
//! (1) is easy to preserve in Rust, but (2) is difficult to do so without overhead,
//! as unaligned writes are UB, and so we'd need to check at runtime if they're aligned
//! (a runtime-determined `off`set is used, so we can't reasonably ensure this at compile-time).
//!
//! To more thoroughly check this, I ran the same benchmarks done in
//! e3b5d4d044506f9e0e95e79b3de42fd94386cc61, which introduced the `case_set*` macros:
//!
//! ```sh
//! cargo build --release && hyperfine './target/release/dav1d -i ./tests/large/chimera_8b_1080p.ivf -l 1000 -o /dev/null'
//! ```
//!
//! for 3 implementations:
//! 1. the original `case_set*` macros translated directly to `unsafe` Rust `fn`s
//! 2. the safe [`CaseSet`] implementation below using [`small_memset`] with its small powers of 2 optimization
//! 3. a safe [`CaseSet`] implementation using [`slice::fill`]/`memset` only
//!
//! The [`small_memset`] version was ~1.27% faster than the `case_set*` one,
//! and ~3.26% faster than the `memset` one.
//! The `case_set*` macros were also faster than `memset` in C by a similar margin,
//! meaning the `memset` option is the slowest in both C and Rust,
//! and since it was replaced with `case_set*` in C, we shouldn't use it in Rust.
//! Thus, the [`small_memset`] implementation seems optimal, as it:
//! * is the fastest of the Rust implementations
//! * is completely safe
//! * employs the same small powers of 2 optimization the `case_set*` implementation did
//! * is far simpler than the `case_set*` implementation, consisting of a `match` and array writes
//!
//! [`BlockContext`]: crate::src::env::BlockContext
use crateAsMutPtr;
use crateDisjointMut;
use zip;
use Deref;
use DerefMut;
/// Perform a `memset` optimized for lengths that are small powers of 2.
///
/// For power of 2 lengths `<= UP_TO`,
/// the `memset` is done as an array write of that exactly (compile-time known) length.
/// If the length is not a power of 2 or `> UP_TO`,
/// then the `memset` is done by [`slice::fill`] (a `memset` call) if `WITH_DEFAULT` is `true`,
/// or else skipped if `WITH_DEFAULT` is `false`.
///
/// This optimizes for the common cases where `buf.len()` is a small power of 2,
/// where the array write is optimized as few and large stores as possible.
/// The entrypoint to the [`CaseSet`] API.
///
/// `UP_TO` and `WITH_DEFAULT` are made const generic parameters rather than have multiple `case_set*` `fn`s,
/// and these are put in a separate `struct` so that these 2 generic parameters
/// can be manually specified while the ones on the methods are inferred.
;
/// Splat one per-block context update into BOTH directions of the neighbour
/// context: the worker's own **left** context (`l`) and the frame's shared
/// **above** context (`a`).
///
/// # Why this is not [`CaseSet::many`]
///
/// `many` takes a homogeneous `[T; N]`, so both directions must arrive as the
/// same reference type. `t.l` and `f.a[t.a]` are both `&BlockContext` today, so
/// they do — and that is precisely the problem: it forces the LEFT direction
/// through [`CaseSetter::set_disjoint`], i.e. through the borrow tracker, even
/// though `t.l` is a field of `Rav1dTaskContext` that no other worker can name.
/// Half of `ctx.rs`'s registrations were bought for nothing.
///
/// Splitting the two directions lets the left one take
/// [`CaseSetter::set_exclusive`] (`&mut`, no record) while the above one keeps
/// [`CaseSetter::set_disjoint`] (`&`, tracked, genuinely shared across tile
/// workers).
///
/// # Why a macro
///
/// The two directions need different *reference types*, which no closure
/// parameter can abstract over without either a runtime branch per field or a
/// per-field accessor trait. Two hand-written bodies would work and would drift:
/// these lists run to twelve fields, and a field updated on one side only is a
/// silent bitstream bug that no type checks. One field list, two expansions.
///
/// # Shape
///
/// ```ignore
/// case_set_al! {
/// <32, false>
/// l: (&mut t.l, bh4 as usize, by4 as usize),
/// a: (ta, bw4 as usize, bx4 as usize),
/// // field = (value written LEFT, value written ABOVE)
/// tx_intra = (t_dim.lh as i8, t_dim.lw as i8),
/// mode = (y_mode_nofilt, y_mode_nofilt),
/// r#ref[0] = (-1, -1),
/// }
/// ```
///
/// The value is always a pair, even where both directions write the same thing.
/// That is deliberate: the asymmetric sites (`t_dim.lh`/`lw`, `b_dim[3]`/`[2]`,
/// `dav1d_al_part_ctx[1]`/`[0]`) are the ones a reader must not miss, and a
/// single-value shorthand would hide which is which.
///
/// `also |case| { .. }` on either direction appends statements to that
/// direction's `CaseSet::one` body, for the few sites that also splat a
/// non-`BlockContext` array (`t.pal_sz_uv`) with the same offset and length.
/// The binder name is supplied by the caller so it resolves under macro
/// hygiene.
);
// ABOVE: shared across tile workers, so tracked exactly as before.
$crateone;
}};
}
pub use case_set_al;