← demo0x64492d…211fSucceededblock 90
No position yet0 / 118
FetchingOpeningPositioning
noir
Narrow session: Code, Call Trace and Values only, read-only. The event log and stepping need a wider viewport.
Code
1 start = "5"
2 step = "7"
1 // A struct whose methods return a NEW value rather than writing through
2 // `&mut self`. The binding in `run_counter` is reassigned on every call, so the
3 // recording holds each intermediate struct and the Values pane shows a nested
4 // field moving while the binding does not.
5 //
6 // Written this way deliberately: the `&mut self` spelling is idiomatic and
7 // compiles, but the recorder does not currently capture writes made through a
8 // reference, so a counter written that way would appear never to count.
9
10 pub struct Counter {
11 pub value: u32,
12 pub ticks: u32,
13 }
14
15 impl Counter {
16 pub fn new(start: u32) -> Self {
17 Counter { value: start, ticks: 0 }
18 }
19
20 pub unconstrained fn tick(self) -> Self {
21 Counter { value: self.value + 1, ticks: self.ticks + 1 }
22 }
23
24 // A method whose body only sometimes changes anything — so between two
25 // calls the pane shows a field that moved and a field that did not, and
26 // the branch marking says which arm was responsible.
27 pub unconstrained fn reset_if_above(self, limit: u32) -> Self {
28 if self.value > limit {
29 Counter { value: 0, ticks: self.ticks }
30 } else {
31 Counter { value: self.value, ticks: self.ticks + 100 }
32 }
33 }
34 }
1 // TOUR: mutation over time
2 //
3 // The subject is a value that CHANGES. Everything else in this tour shows
4 // values being computed and bound once; this one shows the same binding
5 // holding a different value at a different time, which is the case a
6 // time-travelling debugger answers and a printout of the final state does not.
7 //
8 // The program is arranged so that the forms the recorder captures come first
9 // and can be relied on, and the one form it does NOT capture is last, on its
10 // own, labelled. A demo that mixed them would leave a reader unable to tell a
11 // value that did not change from a change that was not recorded — which is the
12 // single worst thing a values pane can do.
13
14 mod counter;
15
16 use counter::Counter;
17
18 struct Acc {
19 total: u32,
20 hits: u32,
21 }
22
23 // ── recorded: plain reassignment ──────────────────────────────────────────
24 // Each statement rebinds `level`, so the trace holds its whole history and
25 // stepping backward through this block walks it in reverse.
26 unconstrained fn ramp(start: u32, step: u32) -> u32 {
27 let mut level = start;
28 level = level + step;
29 level = level * 2;
30 level = level - 1;
31 level
32 }
33
34 // ── recorded: compound assignment inside a loop ───────────────────────────
35 // `total += ...` is its own kind of statement, and a tracer that did not
36 // instrument it would show this variable frozen at 0 while the program
37 // computed the right answer. That is a defect this fork has actually shipped
38 // and fixed, so it is demonstrated here rather than assumed: the recording
39 // carries every intermediate value, not just the result.
40 unconstrained fn accumulate(values: [u32; 5]) -> u32 {
41 let mut total: u32 = 0;
42 for i in 0..5 {
43 total += values[i];
44 total -= 1;
45 }
46 total
47 }
48
49 // ── recorded: assignment INTO a compound, by field and by index ───────────
50 // `a.total = …`, `a.hits += 1` and `arr[1] = …` are three distinct kinds of
51 // assignment target — a member, a compound member, and an index — and the
52 // recorder captures all three, holding the whole struct and the whole array at
53 // every step. They are here because the language HAS them, not because anybody
54 // remembered them: `tools/noir-coverage.mjs` enumerates the compiler's own
55 // `LValue` variants and reported these as neither demonstrated nor explained.
56 unconstrained fn tally(n: u32) -> u32 {
57 let mut a = Acc { total: 0, hits: 0 };
58 for i in 0..n {
59 a.total = a.total + i;
60 a.hits += 1;
61 }
62 let mut arr: [u32; 3] = [0, 0, 0];
63 arr[1] = a.total;
64 arr[2] = a.hits;
65 assert_eq(arr[1], a.total);
66 arr[1] + arr[2]
67 }
68
69 // ── recorded: a compound value whose FIELD moves ──────────────────────────
70 // The methods return a new `Counter` rather than writing through `&mut self`,
71 // so each step rebinds the whole struct and the recording holds the field's
72 // history. The Values pane shows one row whose nested field changes while the
73 // binding's name and type do not.
74 unconstrained fn run_counter(start: u32) -> Counter {
75 let mut c = Counter::new(start);
76 c = c.tick();
77 c = c.tick();
78 c = c.tick();
79 c = c.reset_if_above(start + 2);
80 c = c.tick();
81 c
82 }
83
84 // ── a write through a mutable reference — KNOWN FAILURE, defect NR-04 ─────
85 //
86 // WHAT SHOULD HAPPEN: `bump` is called three times, so the recording should
87 // show `slot` taking 5, 12, 19 and 26 in `main`, and `*slot` should have a
88 // value inside this frame.
89 //
90 // What happens instead: `slot` is recorded once, as 5, and never again, and
91 // inside `bump` the reference records with no dereferenced value at all. The
92 // circuit is correct — `main` asserts the result below and the assertion holds —
93 // so every one of the three writes happened. Only the recording is missing them.
94 //
95 // The defect is UPSTREAM Noir's, not ours: its debug instrumenter emits a
96 // literal `0` where the assignment oracle should go, the stub it would have
97 // called is named `__debug_dereference_assign` while the debugger dispatches on
98 // `__debug_deref_assign`, and the handler behind that is `unimplemented!()`.
99 // See `docs/NOIR-RECORDER-DEFECTS.md` NR-04.
100 //
101 // This is stated as what SHOULD happen rather than as an assertion that the
102 // value stays frozen, and that is deliberate: a corpus asserting the broken
103 // behaviour would teach the next reader that absence is correct, and would go
104 // red the day somebody fixed it. `check-corpus.sh` carries the entry and fails
105 // LOUDLY the moment `slot` reaches 26.
106 unconstrained fn bump(slot: &mut u32, by: u32) {
107 *slot = *slot + by;
108 }
109
110 unconstrained fn main(start: u32, step: pub u32) -> pub u32 {
111 let level = ramp(start, step);
112 let acc = accumulate([1, 2, 3, 4, 5]);
113 let t = tally(4);
114 let c = run_counter(start);
115
116 // The one unrecorded form, exercised three times.
117 let mut slot = start;
118 bump(&mut slot, step);
119 bump(&mut slot, step);
120 bump(&mut slot, step);
121
122 // The circuit did the work even though the recording does not show it, and
123 // this assertion is the proof: 5 + 7 + 7 + 7.
124 assert(slot == start + 3 * step, "the reference writes did happen");
125 assert(level == 23, "ramp: (5 + 7) * 2 - 1");
126 assert(acc == 10);
127 assert(c.value == 1);
128 assert(c.ticks == 4);
129 assert_eq(t, 10);
130
131 level + acc + c.value + slot + t
132 }
1 [package]
2 name = "tour_mutation"
3 type = "bin"
4 authors = ["BlockTracer capability tour"]
5
6 [dependencies]
Event Log

The recorded event stream is in the published recording. Reading it needs the replay engine, which this page has not started.

Call Trace

The call structure is in the published recording. Reading it needs the replay engine, which this page has not started.

Values

The recorded values are in the published recording. Reading them needs the replay engine, which this page has not started.