
=== 1_1. nodes of (p+q)*(p+q) + (p+q) ===
graph nodes: 5  tree nodes: 11
the nodes are p, q, p+q, (p+q)*(p+q), and the final add. the tree repeats p+q three times, 3 nodes each.

=== 1_2. demo 8: name and statement count of the relu function ===
function name: relu; statements before the return: 3

=== 1_3. relu when x*y+1 is nan ===
python contract: 0.0  compiled relu(nan, 1.0): 0.0
max(a,b) is a>b ? a : b, and nan > 0.0 is false, so the answer is the second argument, 0.0.

=== 1_4. x + x in the text format ===
; uop v1
%0 = param : dtype=i32 slot=0
%1 = add %0, %0
two lines: one param and one add. the add names %0 twice.

=== 2_1. wrap32 ===
-2147483641 2147483641

=== 2_2. bits of -1.0 ===
10111111100000000000000000000000  sign=1 exponent=01111111 fraction=00000000000000000000000

=== 2_3. smallest n with float32(n+1) == float32(n) ===
n = 16777216  gap above n = 2.0

=== 2_4. floor and truncating division ===
floor: q=-4 r=1.   truncating: q = -3  r = -1

=== 2_5. float32 addition is not associative ===
(a+b)+c = 1.0   a+(b+c) = 0.0
1e20, -1e20, 1.0 -> 1.0 0.0
three random values in [0,1): 0.956034243106842 0.9478274583816528 0.05655136704444885 -> 1.9604130983352661 1.9604129791259766
2**24, 1, 1 -> 16777216.0 16777218.0

=== 3_1. floor division of -8 by 3 ===
-3, 1, and 3 * -3 + 1 = -8

=== 3_2. float max with signed zero ===
max(0.0, -0.0) = -0.0  max(-0.0, 0.0) = 0.0
each call returns its second argument, since neither zero is greater. swapping the arguments changes the sign of the result.

=== 3_3. WHERE(x != 0, 100 // x, 0) ===
[0, 14, -34]

=== 3_4. division as a * recip(b) ===
7/3 via recip: 2.3333334922790527  numpy: 2.3333332538604736
a pair that differs: (74.24996185302734, 92.31017303466797, 0.8043529391288757, 0.8043529987335205)

=== 3_5. a <= b is not not(b < a) ===
nan <= 1.0: False    not (1.0 < nan): True

=== 4_1. (x+1)*(x+1) ===
nodes: 4  used twice: ['ADD']

=== 4_2. int plus float ===
error: ADD: needs two srcs of the same dtype

=== 4_3. text for x*2+3 ===
dumps of the real graph:
; uop v1
%0 = param : dtype=i32 slot=0
%1 = const : dtype=i32 value=2
%2 = mul %0, %1
%3 = const : dtype=i32 value=3
%4 = add %2, %3

=== 4_4. PARAM slot 2 with a CALL that passes two arguments ===
UOpError: CALL: PARAM slot 2 has no argument

=== 4_5. constants ===
True False

=== 5_1. (p+q)*(p+q) and (p+q)*(q+p) ===
same node before simplify: False    after: True

=== 5_2. p = p + p, 30 times ===
graph nodes: 31  tree nodes: 2147483647

=== 5_3. the id of a rebuilt graph ===
old id 73  new id 78

=== 5_4. float and int constants in the text format ===
float constant written as value=1: accepted, same node: True
int constant written as value=1.5: line 3: bad or missing key invalid literal for int() with base 10: '1.5'

=== 5_5. breaking _key_arg ===
exit code: 1
the error:     raise AssertionError(msg)
   AssertionError: 1/0 (float): got
   ; uop v1
   %0 = const : dtype=f32 value=-inf
   
   wanted
   ; uop v1
   %0 = const : dtype=f32 value=inf

=== 6_1. ranges of i*4+j and (i*4+j)%8 ===
i*4+j: (0, 63)  actual (0, 63)    (i*4+j)%8: (0, 7)  actual (0, 7)

=== 6_2. range of i - j ===
(-3, 15)

=== 6_3. ranges of x//5 and x%5 for x in [-8, 8] ===
x//5: (-2, 1) actual (-2, 1)   x%5: (0, 4) actual (0, 4)

=== 6_4. x*x safety ===
x in [-50000, 50000]: range (-2147483648, 2147483647)  safe=False   50000*50000 = 2500000000
x in [-46340, 46340]: range (-2147395600, 2147395600)  safe=True   46340*46340 = 2147395600

=== 6_5. x % 1 ===
range: (0, 0)  simplified: %0 = const : dtype=i32 value=0

=== 7_1. a rule that matches MUL(x, x) and returns None ===
on a*a the function ran with x = PARAM slot 0 ; on a*b it did not run. both returned None None

=== 7_2. the max(x,x) rule ===
the name x appears twice, so both sources must be the same node (compared with `is`). a.max(a) matches, a.max(b) does not.

=== 7_3. the pattern ADD(x, y) ===
a+b stores x=a, y=b: True    a+a stores x=a, y=a: True

=== 7_4. x - x -> 0 for ints ===
simplify(a - a) is const 0: True  rule: x+(-x) (int)

=== 7_5. x * recip(x) for floats ===
x = 0.0: 0.0 * inf = nan
a finite x: 2000.9886474609375   x * recip(x) = 0.9999999403953552

=== 8_1. trace of (a + 0) * 1 ===
step 1: an ADD node became a PARAM node, by the rule 'x+0 (int)'
step 2: an MUL node became a PARAM node, by the rule 'x*1'

=== 8_2. x + x where x is p + 0 ===
rule firings: {'x+0 (int)': 1}
p+0 is one node, so it is rewritten once. the memo then answers for both uses.

=== 8_3. distribute and factor ===
RewriteLimit: more than 50 rewrite steps; busiest rules: [('distribute', 23), ('factor', 22)]

=== 8_4. seed 551 in the order experiment ===
results differ: True    nodes 23 vs 24
fired only in the default order: {}   only in the reversed order: {'(a*c1+b)//c2': 1, '(x+c1)*c2': 1}

=== 8_5. two rules that disagree ===
order [adds, times2]: ADD   order [times2, adds]: MUL   same node: False
both together: more than 100 rewrite steps; busiest rules: [('double as add', 50), ('add as double', 49)]

=== 9_1. fold (2+3)*(10//4) ===
fold order: ['ADD', 'FLOORDIV', 'MUL']  result: 10

=== 9_2. relu(1.0, 3.0) by inlining ===
simplified call: CONST 4.0    compiled: 4.0

=== 9_3. x // y with y = 0 as a constant ===
result: CONST 0
the contract defines x//0 as 0, so the folder does not raise. the python // would raise ZeroDivisionError.

=== 9_4. x * 0 for an int with range and for a float ===
int: CONST 0    float: MUL
-0.0 and nan and inf all break x*0.0 == 0.0, so the float stays.

=== 9_5. a call whose body is a store is not inlined ===
op after simplify: CALL

=== 10_1. x * 2.0 -> x + x in float32 (z3) ===
z3 says: unsat

=== 10_2. -(x-y) against y-x ===
x = y = 1.5: -(x-y) = -0.0 (bits 80000000),  y-x = 0.0 (bits 00000000)

=== 10_3. x*x >= 0 ===
for all x: sat (counterexample is nan: True )   for x that is not nan: unsat

=== 10_4. x*3.0*5.0 against x*15.0, and x*2.0*3.0 against x*6.0 ===
x = 1.138539433479309  (x*3)*5 = 17.078092575073242  x*15 = 17.07809066772461
for the constants 2.0 and 3.0 z3 says: unsat (multiplying by 2.0 is exact, so there is no second rounding)

=== 10_5. simplify(x + 0.0) ===
strict: ADD   fast math: PARAM

=== 11_1. (i*8 + j) // 8 ===
j in [0,7]: simplified to i
j in [0,8]: simplified to ; uop v1 | %0 = param : dtype=i32 slot=0 min=0 max=15 | %1 = param : dtype=i32 slot=1 min=0 max=8 | %2 = const : dtype=i32 value=8 | %3 = floordiv %1, %2 | %4 = add %0, %3
with j = 8 the quotient is i + 1, so removing j would be wrong. the rule keeps j//8, whose range is [0, 1].

=== 11_2. (x//2)//3 and (x//-2)//3 ===
(x//2)//3 vs x//6 differs at: []    (x//-2)//3 vs x//-6 differs at: []    (x//2)//-3 vs x//-6 differs at: [-47, -41, -35, -29, -23, -17] ...
the identity holds whenever the outer divisor is positive, even if the inner one is negative. the rule's guard (both positive) is stricter than it needs to be. a negative outer divisor breaks it.

=== 11_3. (a*4 + b) // 4 and the safe guard ===
a in [0, 536870911]: a*4+b safe=True   split applied=True
a in [0, 536870912]: a*4+b safe=False   split applied=False
a in [0, 1073741824]: a*4+b safe=False   split applied=False
a in [0, 2147483647]: a*4+b safe=False   split applied=False

=== 11_4. (i*c) % c ===
i range (0, 1000): simplified to const 0: True
i range (None, None): simplified to const 0: False
without a range, (i*3)%3 at i = 1431655766 is 2 because i*3 wraps to 2

=== 11_5. (x*3)//3 -> x over 32 bit integers ===
z3 says: sat  counterexample x = -1431655766  x*3 wraps to -2  and divides to -1
with the guard (x*3 must not wrap) the split proves it, as chapter 11 shows.

=== 12_1. x // y with and without ranges ===
no ranges:    int32_t v0 = floordiv_i32(p0, p1);
with ranges:  int32_t v0 = p0 / p1;
for x >= 0 and y > 0, truncating and floor division agree, and y is never 0 or -1.

=== 12_2. max(0.0, x) ===
float v0 = (0.0f > p0) ? 0.0f : p0;
compiled, x = nan: nan   contract: nan
fmaxf(0.0f, nan) = 0.0

=== 12_3. cast of a float to int ===
int32_t v0 = f32_to_i32(p0);
  10000000000.0: 2147483647
  -10000000000.0: -2147483648
  nan: 0
  -0.5: 0

=== 12_4. x + 1 > x at -O0 to -O3, without -fwrapv ===
-O0:  without -fwrapv f(INT_MAX) = 0    with -fwrapv = 0
-O1:  without -fwrapv f(INT_MAX) = 1    with -fwrapv = 0
-O2:  without -fwrapv f(INT_MAX) = 1    with -fwrapv = 0
-O3:  without -fwrapv f(INT_MAX) = 1    with -fwrapv = 0

=== 12_5. the constants -2147483647 and 2147483647 ===
(-2147483647) 2147483647 (-2147483647 - 1)
2147483648 does not fit in an int, so the c compiler gives the literal a wider type. 2147483647 and -(2147483647) both fit. only INT_MIN needs the 1-less trick.

=== 13_1. two Programs for the same call ===
compiles: 1  hits: 1

=== 13_2. an extra -O3 flag ===
compiles went from 1 to 2  hits 1 to 1
the flags are part of the cache key, so the same source with different flags is a different file.

=== 13_3. 1,000,000 ctypes calls against a loop in c ===
python loop: 711 ns per call; one ctypes call running a c loop of 1,000,000: 0.64 ns per iteration; ratio about 1,118x (this machine, this run)

=== 13_4. a clang error ===
clang failed:
/tmp/tmpxo400gce/8e835b81c8b228bcf3522a6f.c:1:1: error: unknown type name 'floot'; did you mean 'float'?

=== 13_5. x + 1 > x with the runtime's flags, and without -fwrapv ===
-O0: with -fwrapv 0   without 0
-O1: with -fwrapv 0   without 1
-O2: with -fwrapv 0   without 1
-O3: with -fwrapv 0   without 1

=== 14_1. swap a[0] and a[1] with the builder ===
; uop v1
%0 = param : dtype=i32 slot=0 size=2
%1 = const : dtype=i32 value=0
%2 = index %0, %1
%3 = load %2
%4 = const : dtype=i32 value=1
%5 = index %0, %4
%6 = load %5
%7 = after %0, %3, %6
%8 = index %7, %1
%9 = store %8, %6
%10 = after %7, %9
%11 = index %10, %4
%12 = store %11, %3
%13 = sink %12
AFTER nodes in the body: 2
interpreter: [20, 10]
compiled: [20, 10]

=== 14_2. the same swap without AFTER ===
order by id: [20, 10]   order of the renderer: [20, 20]
the loads and stores are unordered, so the answer depends on the visiting order. the AFTER edges are what make it one answer.

=== 14_3. a[i+1] = a[i] for i = 0, 1, 2 ===
result: [1, 1, 1, 1]  compiled: [1, 1, 1, 1]  LOAD nodes: 3

=== 14_4. ALLOC read before write ===
interpreter: read of memory that was never written

=== 14_5. one buffer passed twice ===
two buffers: [0, 1, 2]   the same buffer twice: [1, 1, 1]
the second load reads what the first store wrote, because nothing orders loads of one buffer against stores to the other. the caller must pass distinct buffers.

=== 15_1. the matrix: which layer caught the most and the fewest ===
Traceback (most recent call last):
  File "/home/claude/book/module1/code/exercises/solutions.py", line 780, in <module>
    line = [l for l in open(short).read().splitlines() if l.startswith("bugs caught by each layer")][0]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range
