
=== A. the text format: relu(x*y+1) ===
; uop v1
%0 = param : dtype=f32 slot=0
%1 = param : dtype=f32 slot=1
%2 = mul %0, %1
%3 = const : dtype=f32 value=1.0
%4 = add %2, %3
%5 = const : dtype=f32 value=0.0
%6 = max %4, %5
%7 = call %6, %5, %5 : name=relu

loads(dumps(call)) is call: True

=== B. ranges: every node of (i*4+j)//4 with i in [0,15], j in [0,3] ===
%0  param     0             range [0, 15]  safe=True
%1  const     4             range [4, 4]  safe=True
%2  mul      %0, %1       range [0, 60]  safe=True
%3  param     1             range [0, 3]  safe=True
%4  add      %2, %3       range [0, 63]  safe=True
%5  floordiv %4, %1       range [0, 15]  safe=True

big in [0, 2^30], big*4: range [-2147483648, 2147483647] safe=False   (4*2^30 = 2^32 does not fit, so it wraps)
(big*4)//4 simplified: FLOORDIV (unchanged, because the guard says the split could be wrong)

=== C. a rewrite trace: ((x*1)+0)*(2+3) with x an int ===
start: (((x * 1) + 0) * (2 + 3))
  step 1: (x * 1)  ->  x
  step 2: (x + 0)  ->  x
  step 3: (2 + 3)  ->  5
result: (x * 5)   total firings by rule: {'x*1': 1, 'x+0 (int)': 1, 'const fold': 1}

=== D. memory: out[0] = in[0] + 1; out[1] = out[0] * 2 ===
; uop v1
%0 = param : dtype=f32 slot=1 size=2
%1 = const : dtype=i32 value=0
%2 = index %0, %1
%3 = param : dtype=f32 slot=0 size=2
%4 = index %3, %1
%5 = load %4
%6 = const : dtype=f32 value=1.0
%7 = add %5, %6
%8 = store %2, %7
%9 = after %0, %8
%10 = index %9, %1
%11 = load %10
%12 = after %9, %11
%13 = const : dtype=i32 value=1
%14 = index %12, %13
%15 = const : dtype=f32 value=2.0
%16 = mul %11, %15
%17 = store %14, %16
%18 = sink %17
%19 = buffer : dtype=f32 slot=0 size=2
%20 = buffer : dtype=f32 slot=1 size=2
%21 = call %18, %19, %20 : name=bump

void bump(float *p0, float *p1) {
  float *v0 = p1 + 0;
  float *v1 = p0 + 0;
  float v2 = *v1;
  float v3 = v2 + 1.0f;
  *v0 = v3;
  float *v4 = p1 + 0;
  float v5 = *v4;
  float *v6 = p1 + 1;
  float v7 = v5 * 2.0f;
  *v6 = v7;
}

C result, out = [11.0, 22.0]  interpreter: [11.0, 22.0]

=== E. the hash consing trap: two loads of the same address ===
ld2 is ld1 (the load after the store is the same node): True
a load through AFTER(p, store) is a different node: True
  sequenced program, order=id: buf = [20.0]  (1 -> store 2 -> load 2 -> store 20)
  sequenced program, order=dfs: buf = [20.0]  (1 -> store 2 -> load 2 -> store 20)
  unsequenced program, order=id: buf = [5.0, 5.0, 0.0]
  unsequenced program, order=dfs: buf = [5.0, 1.0, 0.0]
  the two orders disagree: with no AFTER edge the program has no single meaning

=== F. a three point stencil: out[k] = in[k-1] + in[k] + in[k+1] for k = 1..3 ===
9 loads were written; the graph holds 5 LOAD nodes (the input is never written, so equal loads merge safely)
void stencil(float *p0, float *p1) {
  float *v0 = p1 + 1;
  float *v1 = p0 + 0;
  float v2 = *v1;
  float *v3 = p0 + 1;
  float v4 = *v3;
  float v5 = v2 + v4;
  float *v6 = p0 + 2;
  float v7 = *v6;
  float v8 = v5 + v7;
  *v0 = v8;
  float *v9 = p1 + 2;
  float v10 = v4 + v7;
  float *v11 = p0 + 3;
  float v12 = *v11;
  float v13 = v10 + v12;
  *v9 = v13;
  float *v14 = p1 + 3;
  float v15 = v7 + v12;
  float *v16 = p0 + 4;
  float v17 = *v16;
  float v18 = v15 + v17;
  *v14 = v18;
}

out = [0.0, 7.0, 14.0, 28.0, 0.0]

=== G. scratch memory that is never written, and an index outside the buffer ===
read of scratch: read of memory that was never written
store at 2 into a buffer of 2: index 2 is outside a buffer of 2
