← demo0x7848de…a799Succeededblock 90
No position yet0 / 101
FetchingOpeningPositioning
noir
Narrow session: Code, Call Trace and Values only, read-only. The event log and stepping need a wider viewport.
Code
1 seed = "203"
2 limit = "500"
1 // A small value object with an immutable-reference method, so the tour has a
2 // `&T` beside its `&mut T` — the two are different forms and only one of them
3 // is affected by the recorder gap NR-04.
4
5 pub struct Bounds {
6 pub low: u32,
7 pub high: u32,
8 }
9
10 impl Bounds {
11 pub fn new(low: u32, high: u32) -> Self {
12 Bounds { low, high }
13 }
14
15 // Takes `self` by IMMUTABLE reference and returns a new value. Reading
16 // through `&self` is recorded correctly; it is only writing through a
17 // reference that is not (NR-04).
18 pub unconstrained fn span(self: &Self) -> u32 {
19 (*self).high - (*self).low
20 }
21
22 pub unconstrained fn widen(self, by: u32) -> Self {
23 let grown = Bounds { low: self.low, high: self.high + by };
24 assert(grown.span_by_value() >= self.span_by_value());
25 grown
26 }
27
28 pub unconstrained fn span_by_value(self) -> u32 {
29 self.high - self.low
30 }
31 }
1 // TOUR: bounds, limits and overflow
2 //
3 // The subject is what happens at the EDGES of a type. In a circuit this is not
4 // a curiosity: an addition that overflows is a failed constraint, and the
5 // difference between "this wrapped" and "this aborted" is the difference
6 // between a proof and no proof. A debugger is where that becomes visible.
7 //
8 // This program exists because `tools/noir-coverage.mjs` enumerates the
9 // compiler's own AST and reported a cluster of forms as neither demonstrated
10 // nor explained — globals, type aliases, shadowing, `loop`, the shift and
11 // compound-assignment operators, `Option`, `u32::max_value()` and the wrapping
12 // arithmetic traits. They are gathered here rather than sprinkled about because
13 // they share a subject: every one of them is about the boundary of a type.
14
15 mod checked;
16
17 use checked::Bounds;
18 use std::ops::WrappingAdd;
19
20 // ── a global constant, and a type alias over it ───────────────────────────
21 // A `global` is evaluated once at compile time and is in scope everywhere; the
22 // alias gives the width a name that says what it is FOR rather than how wide it
23 // is, which is the whole reason aliases exist.
24 global HEADROOM: u32 = 8;
25 type Counter = u32;
26
27 // ── a NUMERIC type alias: the alias computes a length ─────────────────────
28 // `Window<N>` is `N * 2` used in array-length position. The generic is a value,
29 // not a type, so this is arithmetic the compiler performs on types.
30 type Window<let N: u32>: u32 = N * 2;
31
32 unconstrained fn saturating_add(a: u32, b: u32) -> u32 {
33 // `u32::max_value()` is a method reached through the TYPE rather than
34 // through a value — a path form of its own.
35 let ceiling = u32::max_value();
36 if a > ceiling - b {
37 ceiling
38 } else {
39 a + b
40 }
41 }
42
43 unconstrained fn wrapping_walk(start: u32) -> u32 {
44 // The wrapping traits are the language's way of asking for modular
45 // arithmetic explicitly, instead of getting it by accident. `wrapping_add`
46 // past the ceiling comes back to zero, and the recording shows it doing so
47 // one step at a time.
48 let mut v = start;
49 for _ in 0..3 {
50 v = v.wrapping_add(u32::max_value() / 2);
51 }
52 v
53 }
54
55 unconstrained fn shift_ladder(seed: u32) -> u32 {
56 // Every compound assignment operator the language has, in one place, plus
57 // both shifts. `seed` is masked first so the ladder cannot overflow —
58 // which is itself the point of the mask.
59 let mut v = seed & 0xff;
60 v <<= 3;
61 v |= 1;
62 v ^= 10;
63 v &= 0xfff;
64 v >>= 2;
65 v += HEADROOM;
66 v -= 1;
67 v *= 2;
68 v /= 3;
69 v %= 1000;
70 v
71 }
72
73 unconstrained fn first_over(limit: u32) -> Option<u32> {
74 // `loop` — unconstrained only, and it must contain a reachable `break`.
75 // The trip count is in no bound anywhere in the source, so the recording is
76 // the only place it exists.
77 //
78 // `Option` is how a Noir function says "there may be no answer" without a
79 // sentinel value a caller could mistake for a real one.
80 let mut n: Counter = 1;
81 loop {
82 n = n * 3;
83 if n > limit {
84 break;
85 }
86 }
87 if n > limit {
88 Option::some(n)
89 } else {
90 Option::none()
91 }
92 }
93
94 unconstrained fn main(seed: u32, limit: pub u32) -> pub u32 {
95 let sat = saturating_add(u32::max_value() - 2, 10);
96 let wrapped = wrapping_walk(seed);
97 let laddered = shift_ladder(seed);
98
99 // SHADOWING: the second `bounds` is a new binding with the same name, and
100 // the first is still in the recording at the steps before this line. That
101 // is the case a values pane has to get right and a printout cannot show at
102 // all.
103 let bounds = Bounds::new(0, HEADROOM);
104 let bounds = bounds.widen(limit);
105
106 // Destructuring a struct, and a tuple pattern beside it.
107 // `span` takes `&self` — reading THROUGH a reference, which the recorder
108 // handles correctly. Only writing through one does not (NR-04).
109 let by_ref = bounds.span();
110 let Bounds { low, high } = bounds;
111 let (span, midpoint) = (high - low, (high + low) / 2);
112
113 // A window whose length is computed by the numeric alias: N = 3, so 6.
114 // The turbofish is required in array-length position.
115 let window: [u32; Window::<3>] = [0, 1, 2, 3, 4, 5];
116
117 // `_` discards a value the program does not need, which is a binding form
118 // rather than an absence of one.
119 let _ = window[5];
120
121 let found = first_over(limit);
122 let reached = if found.is_some() { found.unwrap() } else { 0 };
123
124 assert_eq(sat, u32::max_value());
125 assert(span >= HEADROOM);
126 assert_eq(by_ref, span);
127 assert(window.len() == 6);
128
129 sat / 1000 + wrapped % 1000 + laddered + span + midpoint + reached % 1000
130 }
1 [package]
2 name = "tour_limits"
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.