Week 3 test guide
A test failed and you don't know why? Find it here: what it checks, and the plausible bugs that are known to fail it. Then read the matching part of the week 3 spec.
How to use this
Run python tools/test.py --compiler PATH --week 3 -v, copy the failing test's name, and search below. "Bugs known to fail it" lists mistakes that were planted in the reference compiler and caught by this test, so one of them is a good first guess. It's not a complete list, and the debugging guide covers what to do when none fit.
| test | kind | what it checks | bugs known to fail it |
|---|---|---|---|
render/r_bounds_from_ranges | render | render version of run/bounds_from_ranges | end doesn't close its range final version not followed render loop starts at 1 stores under ends unsequenced values hoisted to outer loops |
render/r_computed_bound | render | render version of run/computed_bound | end doesn't close its range final version not followed stores under ends unsequenced |
render/r_conv1d_i32_pad2 | render | padding 2 on i32 | C cmplt as <= C where swapped end doesn't close its range final version not followed loads never move into loops render loop starts at 1 stores under ends unsequenced values hoisted to outer loops |
render/r_conv1d_padded | render | a padded conv1d with masked taps | C cmplt as <= C where swapped end doesn't close its range final version not followed loads never move into loops render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_conv1d_valid | render | a valid conv1d | end doesn't close its range final version not followed loads never move into loops render loop starts at 1 stores under ends unsequenced values hoisted to outer loops |
render/r_conv2d_i32 | render | an i32 conv2d | end doesn't close its range final version not followed loads never move into loops render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_conv2d_valid | render | a valid conv2d | end doesn't close its range final version not followed loads never move into loops render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_dynamic_trip | render | a trip count loaded from memory: the index is masked with mod so the view is provable | end doesn't close its range final version not followed render loop uses <= stores under ends unsequenced |
render/r_gemm_1x5x1 | render | a dot product | end doesn't close its range final version not followed loads never move into loops render loop starts at 1 stores under ends unsequenced values hoisted to outer loops |
render/r_gemm_2x3x2 | render | looped matmul in C | end doesn't close its range final version not followed loads never move into loops render loop starts at 1 stores under ends unsequenced values hoisted to outer loops |
render/r_gemm_3x1x4 | render | an outer product (K = 1) | end doesn't close its range final version not followed render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_gemm_4x3x5 | render | a non-square matmul | end doesn't close its range final version not followed loads never move into loops render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_gemm_f32_rounding | render | f32 accumulation order is exact: products of thirds | end doesn't close its range final version not followed loads never move into loops render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_gemm_i32_wrap | render | i32 matmul wraps through uint32_t inside loops | C plain signed add end doesn't close its range final version not followed loads never move into loops render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_in_loop_after_chain | render | render version of run/in_loop_after_chain | end doesn't close its range final version not followed render loop starts at 1 stores under ends unsequenced |
render/r_loop_carried_load | render | render version of run/loop_carried_load | end doesn't close its range final version not followed loads never move into loops render loop starts at 1 render loop uses <= stores under ends unsequenced |
render/r_loop_invariant_value | render | render version of run/loop_invariant_value | end doesn't close its range final version not followed render loop starts at 1 stores under ends unsequenced |
render/r_mixed_dtypes_loop | render | one loop writes f32, i32 and bool buffers | end doesn't close its range final version not followed render loop uses <= stores under ends unsequenced |
render/r_multi_effect_end | render | render version of run/multi_effect_end | end doesn't close its range final version not followed render loop uses <= stores under ends unsequenced |
render/r_negative_trip | render | render version of run/negative_trip | stores under ends unsequenced |
render/r_negzero_data | render | data=[-0.0 0.0] round-trips through a loop: recip of each lands in the output as -inf, inf | end doesn't close its range final version not followed render loop starts at 1 stores under ends unsequenced |
render/r_old_version_after_loop | render | render version of run/old_version_after_loop | end doesn't close its range final version not followed render loop starts at 1 stores under ends unsequenced |
render/r_prefix_f32 | render | an f32 running total | C alloc not zeroed end doesn't close its range final version not followed loads never move into loops render loop starts at 1 stores under ends unsequenced |
render/r_prefix_sum | render | a running total | C alloc not zeroed C plain signed add end doesn't close its range final version not followed loads never move into loops render loop starts at 1 stores under ends unsequenced |
render/r_reduce_max_nan | render | a max accumulator meets nan | C max ignores nan end doesn't close its range final version not followed loads never move into loops stores under ends unsequenced |
render/r_reduce_max_negzero | render | max over -0.0 values | end doesn't close its range final version not followed render loop uses <= stores under ends unsequenced |
render/r_reduce_product_wrap | render | an i32 product accumulator wraps | C plain signed add loads never move into loops render loop starts at 1 stores under ends unsequenced |
render/r_reduce_sum_order | render | 1e8 + 1 + -1e8 + 1 sums to 1.0 in f32 | end doesn't close its range final version not followed render loop starts at 1 stores under ends unsequenced |
render/r_rowsum | render | row sums | end doesn't close its range final version not followed loads never move into loops render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_skip_middle_range | render | render version of run/skip_middle_range | end doesn't close its range final version not followed nesting demands every range render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_split_use_across_asserts | render | render must place the load where the whole program says, even though render ignores the assert that pins it | end doesn't close its range final version not followed loads follow their deepest use placement sees only the roots stores under ends unsequenced |
render/r_split_use_load | render | render version of run/split_use_load | end doesn't close its range final version not followed stores under ends unsequenced |
render/r_three_deep | render | three nested loops writing a 2x3x2 index cube | end doesn't close its range final version not followed render loop starts at 1 stores under ends unsequenced values hoisted to outer loops |
render/r_transpose_3x4 | render | transpose | end doesn't close its range final version not followed render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_transpose_f32 | render | f32 transpose keeps -0.0 and nan | end doesn't close its range final version not followed render loop starts at 1 render loop uses <= stores under ends unsequenced values hoisted to outer loops |
render/r_wrap_in_loop | render | adding 2^30 four times wraps through INT_MIN back to 0 | end doesn't close its range final version not followed stores under ends unsequenced |
render/r_zero_trip | render | render version of run/zero_trip | render loop uses <= stores under ends unsequenced |
run/bounds_from_ranges | run: pass | i*4 + k with i < 3 and k < 4 is provably inside 12 elements | end doesn't close its range evaluates everything loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/computed_bound | run: pass | a range bound can be any i32 with no open ranges | end doesn't close its range evaluates everything loop runs one extra time stores under ends unsequenced |
run/conv1d_i32_padded2 | run: pass | padding of 2 on i32 | cmplt is <= end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced values hoisted to outer loops where branches swapped |
run/conv1d_padded | run: pass | a padded convolution masks out-of-range taps with where, after clamping the index into a provable range | cmplt is <= end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced values hoisted to outer loops where branches swapped |
run/conv1d_valid | run: pass | a valid 1-d convolution: output o sums x[o+k]*w[k] | end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/conv2d_valid | run: pass | a valid 2-d convolution: four nested loops | end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/err_after_loop_stale_version | run: invalid | after names %o, but the loop stores into %o0, the version after the reset: the versions would fork | after-on-loop accepts a stale version |
run/err_after_wrong_memory_loop | run: invalid | after names memory the loop never stores into | after-on-loop skips memory check |
run/err_assert_closed_range | run: invalid | an assert can't read a range even when the range's loop is closed elsewhere | asserts may read open ranges |
run/err_assert_open_range | run: invalid | an assert can't depend on an open range | none recorded |
run/err_call_open_range | run: invalid | a call can't take an argument that depends on an open range | calls may take open-range args |
run/err_end_last_not_range | run: invalid | the last operand of end must be a range | none recorded |
run/err_end_unused_range | run: invalid | end closes a range its effect doesn't use | none recorded |
run/err_end_unused_range_closed | run: invalid | end over j repeats a store that doesn't use j. Otherwise j is well formed, so only the 'end must use its range' rule rejects it | end need not use its range |
run/err_end_value_not_effect | run: invalid | end repeats effects, not values | end accepts values |
run/err_loop_never_closed | run: invalid | a store inside a loop that no end closes | unclosed loops allowed |
run/err_loop_store_unsequenced | run: invalid | the end runs the store, but no after sequences the loop | unsequenced store ok |
run/err_memory_in_call_body_loop | run: invalid | a call body can't read memory, even when called from a loop | memory in call body ok |
run/err_range_bound_depends | run: invalid | a range's bound can't depend on another range (no triangular loops in week 3) | bounds may depend on ranges |
run/err_range_f32_bound | run: invalid | a range bound is an i32 | none recorded |
run/err_range_overrun | run: invalid | range 4 indexes a 3-element buffer: offset in [0, 3] can't be proven inside | bounds off by one bounds unchecked range interval is [0, 0] range interval one too tight |
run/err_range_two_ends | run: invalid | a range is closed by exactly one end | a range may close twice |
run/err_range_value_escapes | run: invalid | a value that depends on a range can't leak into an assert outside the loop | asserts may read open ranges |
run/err_two_stores_one_version_loop | run: invalid | two stores into the same version inside one loop body are unordered | two stores per version |
run/err_wrong_stride | run: invalid | i*3 + k with k < 4 overruns a 6-element buffer at i=1, k=3 | bounds off by one bounds unchecked range interval is [0, 0] range interval one too tight |
run/gemm_1xK | run: pass | a 1xK @ Kx1 matmul is a dot product | end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/gemm_2x3x2 | run: pass | matmul by hand: three loops, an accumulator reset once per output element | end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/gemm_Kx1_outer | run: pass | Kx1 @ 1xN with K=1 inner loop is an outer product | end doesn't close its range evaluates everything load ignores the offset loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/gemm_i32_wrap | run: pass | i32 matmul wraps inside the loop | end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/gemm_nonsquare | run: pass | 3x4 @ 4x2 | end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/in_loop_after_chain | run: pass | two stores into one element in one iteration: the second reads the first through an after inside the loop | end doesn't close its range evaluates everything loop runs one extra time stores under ends unsequenced |
run/loop_assert_fail | run: assert_fail | the loop writes squares, but the assert expects the wrong last value | end doesn't close its range evaluates everything loop runs one extra time stores under ends unsequenced |
run/loop_carried_load | run: pass | a load whose only use is inside a loop that stores into its version is reloaded every iteration: 10, 11, 12, 13 | end doesn't close its range evaluates everything loads never move into loops loop runs one extra time stores under ends unsequenced |
run/loop_invariant_value | run: pass | a value with no range dependence is the same in every iteration | end doesn't close its range evaluates everything loop runs one extra time stores under ends unsequenced |
run/loop_runtime_error | run: invalid | a division by zero inside a loop an assert depends on is a runtime error | div by zero returns 0 |
run/multi_effect_end | run: pass | one end can repeat several stores into different buffers | end doesn't close its range evaluates everything loop runs one extra time stores under ends unsequenced |
run/negative_trip | run: pass | a negative bound also runs zero times | end doesn't close its range evaluates everything stores under ends unsequenced zero-trip loops run once |
run/negzero_a_plus | run: pass | a buffer of +0.0 (parsed before negzero_b_minus, with the same names) | evaluates everything |
run/negzero_b_minus | run: pass | data=[-0.0] must survive even if an identical buffer of +0.0 was built earlier: recip tells them apart | evaluates everything recip 0 ignores sign |
run/old_version_after_loop | run: pass | a load of the pre-loop version, used after the loop, still sees the old contents | end doesn't close its range evaluates everything loop runs one extra time stores under ends unsequenced |
run/pinned_load_outside | run: pass | the load's first use (x + 1) doesn't depend on the range, so both run once before the loop: every iteration writes 11 | end doesn't close its range evaluates everything stores under ends unsequenced |
run/prefix_sum | run: pass | a running total: two stores per iteration into two different memories, one end | alloc not zeroed end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs backwards loop runs one extra time stores under ends unsequenced |
run/prefix_sum_wrap | run: pass | a running total that wraps | add doesn't wrap alloc not zeroed end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs backwards loop runs one extra time stores under ends unsequenced |
run/reduce_max_nan | run: pass | a max accumulator that meets nan stays nan | NaN != NaN in assert end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time max ignores NaN stores under ends unsequenced |
run/reduce_max_plain | run: pass | a max accumulator | end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced |
run/reduce_product_wrap | run: pass | a product accumulator wraps in i32 | end doesn't close its range evaluates everything loads never move into loops loop runs one extra time mul doesn't wrap stores under ends unsequenced |
run/reduce_sum_f32_order | run: pass | the sum runs in increasing k: 1e8 + 1 + -1e8 + 1 is 1.0 in f32, not 2.0 | end doesn't close its range evaluates everything f32 ops not rounded load ignores the offset loop runs backwards loop runs one extra time stores under ends unsequenced |
run/rowsum_3x4 | run: pass | row sums: one accumulator per row, reset by a store that depends on the row | end doesn't close its range evaluates everything load ignores the offset loads never move into loops loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/skip_middle_range | run: pass | an inner loop inside two loops can ignore the middle range, and it just reruns | end doesn't close its range evaluates everything loop runs one extra time nesting demands every range stores under ends unsequenced values hoisted to outer loops |
run/split_use_across_asserts | run: pass | where a load runs depends on every use in the program: the second assert uses %x outside the loop, so the loop sees the pre-loop 10 every time, even while checking the first assert alone | end doesn't close its range evaluates everything loads follow their deepest use placement sees only the roots stores under ends unsequenced |
run/split_use_load | run: pass | a load with uses inside and outside the loop runs once, before it: every iteration writes 10 + 1 | end doesn't close its range evaluates everything stores under ends unsequenced |
run/transpose_2x3 | run: pass | transpose: read row-major, write column-major | end doesn't close its range evaluates everything load ignores the offset loop runs one extra time stores under ends unsequenced values hoisted to outer loops |
run/zero_trip | run: pass | range 0 runs zero times: the buffer keeps its data | end doesn't close its range evaluates everything loop runs one extra time stores under ends unsequenced zero-trip loops run once |