Week 5 · Lesson 6
The week 5 lab
Lift the rank limit. Your type checker learns shapes of any rank and the four movement ops, run evaluates them, and your rangeify pass turns each one into index arithmetic. You're done when every week 5 test passes, weeks 1 to 4 still pass, and you've written 10 new tests of your own.
Warm up
One movement op and one reduce. The trick is that reduce only folds leading axes.
Warm-up: row sums with permute
Slot 0 holds a 2x3 i32 matrix, row-major. Store its two row sums into slot 1. The starter reduces the first axis directly, which gives column sums.
The command-line contract
Nothing new on the command line. What changes is which programs the commands accept, and when render may print loops.
| command | week 5 changes |
|---|---|
your.exe run FILE | Values may have any rank. reshape, permute, pad and value shrink are new, and expand, stack, index and reduce pop=m work at any rank. Static errors (exit 2 with an invalid: line) include: a reshape that changes the element count, an order that isn't a permutation, the wrong number of pads, a shrink offset that can't be proven in range, an expand that grows an axis that isn't size 1, a reduce popping more axes than the value has, storing a value whose shape isn't the view's (buffers stay rank 1), and a param of rank 2 or more. |
your.exe render FILE | Still at most 20,000 bytes, bit-exact. for loops are allowed when the program has a range, any shape op (reduce, expand, stage, reshape, permute, pad, value shrink), an elementwise op on a shaped value, a value of rank 2 or more, or a rank-1 value with more than 32 elements. Otherwise it stays loopless, as in week 2. |
How render tests are graded
The same five steps as week 4. For each # render test the grader:
- runs
your.exe render FILEand fails the test on a nonzero exit; - fails the test if your output is over 20,000 bytes;
- decides with the oracle whether loops are allowed (the rule above), strips comments and strings from your C, and fails the test on
while,do,goto, or a disallowedfor; - compiles your C with its own
main:clang -std=c11 -O2 -ffp-contract=off -fsanitize=undefined -fsanitize-trap=undefined ... -lm; - runs it and compares every buffer with the oracle: i32 and bool exactly, f32 bit for bit, any NaN matching any NaN.
Two things bite this week. A reduce over several axes must fold them in row-major order, the same order the oracle uses, or f32 bits drift. And a padded read must clamp its index: an out-of-bounds read is undefined behavior even when the mask throws the value away. The sanitizer won't always catch it, so don't count on the grader to: clamp because the C must be correct.
Running the suite
From the course repo root, keeping every earlier week green:
python tools/test.py --compiler "C:/path/to/your.exe" --week 5
python tools/test.py --compiler "C:/path/to/your.exe" --week 5 -k render -v # the render tests, with details
python tools/test.py --compiler "C:/path/to/your.exe" --week 4
python tools/test.py --compiler "C:/path/to/your.exe" --week 3
python tools/test.py --compiler "C:/path/to/your.exe" --week 2
python tools/test.py --compiler "C:/path/to/your.exe" --week 1
Pass the built executable, not dune exec.the same wrapper script as in week 1. Tests live in tests/week5/run/ and tests/week5/render/.
Write your 10 tests
Ten tests in path/to/your-repo/tests/week5/, each aimed at a rule someone will get wrong. Check them against the oracle:
python tools/test.py --check-tests path/to/your-repo/tests/week5
Good targets: a transpose tested with distinct values (constant data hides every bug), a reshape after a permute versus before it, and a pad on the right only. Also try a shrink whose offset comes from a mod mask, plus one that can't be proven (invalid). Then add a reduce pop=2 whose bits depend on the fold order, the syllabus GEMM with A stored the wrong way, and a large render test that must stay under the size limit.
A build order that works
Shapes of any rank
Lift the rank limit everywhere except memory and params. ALU ops,
where,castandasserttake one shared shape of any rank.stacktakes items of one shape and adds a leading axis, andindexpops one leading axis per index.Checkpoint: every week 1 to 4 test still passes.
Type the movement ops
reshape(same element count),permute(a permutation),pad(two numbers per axis), valueshrink(one provable offset per axis, using your week 1 bounds),expand(scalar, or size-1 axes only),reduce pop=m.Checkpoint: every file in
tests/week5/run/parses or is rejected with exit 2.Evaluate them
In
run, represent a value as a shape and a flat row-major array. Each movement op is a function from output index to input index. Tabulate it over the output shape.Checkpoint: every week 5 run test passes.
Index tuples in rangeify
Change "your element at index i" to take one index per axis. Write flatten and unflatten once, as helpers that build
mul,add,divandmodnodes.Checkpoint: a reshape round trip renders to one loop that passes. Its index math (a flatten, an unflatten and a leftover
% n) needn't be simplified to a plain copy. Correct is enough this week.Each op as index arithmetic
Permute reorders the tuple, shrink adds offsets, expand reads size-1 axes at 0, and pad clamps the index and masks the value with
where.Checkpoint: the pad and transpose render tests pass bit for bit.
Reduce over several axes
Use one accumulator, seeded with the first element, and one loop over a flat counter covering the other
T - 1combinations of the leading axes (T is the product of the popped axis sizes), unflattened in row-major order.Checkpoint: the movement GEMM tests pass, including the large one.
Clean up the index math
Optional but worthwhile: run your week 1 simplifier on the lowered index arithmetic. Constant folding and bounds-based
modelimination remove most of what flatten and unflatten leave behind.Checkpoint: your rendered GEMM has no
% con a value already known to be belowc.Your 10 tests
Write them, check them with
--check-tests, and use them to break your compiler before the suite does.
Checklist
Your repo's AGENTS.md hasn't changed: an AI assistant may explain concepts, draw diagrams, and help you debug by asking questions, but must never write or rewrite compiler code. "Why does my reshape after a permute read the wrong element?" is a fine question. "Write my unflatten helper" is not.
The oracle in _reference/ stays closed. Running python _reference/uirc.py render FILE on your own test to see the index arithmetic it produces is fine.
Check yourself
%m = reshape %x shape=[2 2] on a loaded 4-element buffer, then store %b %m. Valid?
What shape is a whole-buffer view?
The view has shape (4,) and the value (2, 2). The oracle exits 2: "the value must match the view's dtype and shape".
Which of these is a static error?
Count the elements before and after.
3 elements can't become 4. The oracle: "reshape keeps the element count: 3 != 4".
reduce %m op=add pop=3 on a value of shape (2, 2)?
pop counts leading axes, between 1 and the rank.
A rank-2 value has two axes to pop. The oracle rejects pop=3.
In the warm-up, why does reducing the (2, 3) matrix directly give column sums?
Which index varies along axis 0 of a row-major matrix?
Folding axis 0 combines m[0][j] with m[1][j]: one sum per column. Transposing first puts each row on axis 0.