|  | 
|  | 1 | +# TypeTrees for Autodiff | 
|  | 2 | + | 
|  | 3 | +## What are TypeTrees? | 
|  | 4 | +Memory layout descriptors for Enzyme. Tell Enzyme exactly how types are structured in memory so it can compute derivatives efficiently. | 
|  | 5 | + | 
|  | 6 | +## Structure | 
|  | 7 | +```rust | 
|  | 8 | +TypeTree(Vec<Type>) | 
|  | 9 | + | 
|  | 10 | +Type { | 
|  | 11 | +    offset: isize,  // byte offset (-1 = everywhere) | 
|  | 12 | +    size: usize,    // size in bytes | 
|  | 13 | +    kind: Kind,     // Float, Integer, Pointer, etc. | 
|  | 14 | +    child: TypeTree // nested structure | 
|  | 15 | +} | 
|  | 16 | +``` | 
|  | 17 | + | 
|  | 18 | +## Example: `fn compute(x: &f32, data: &[f32]) -> f32` | 
|  | 19 | + | 
|  | 20 | +**Input 0: `x: &f32`** | 
|  | 21 | +```rust | 
|  | 22 | +TypeTree(vec![Type { | 
|  | 23 | +    offset: -1, size: 8, kind: Pointer, | 
|  | 24 | +    child: TypeTree(vec![Type { | 
|  | 25 | +        offset: 0, size: 4, kind: Float,  // Single value: use offset 0 | 
|  | 26 | +        child: TypeTree::new() | 
|  | 27 | +    }]) | 
|  | 28 | +}]) | 
|  | 29 | +``` | 
|  | 30 | + | 
|  | 31 | +**Input 1: `data: &[f32]`** | 
|  | 32 | +```rust | 
|  | 33 | +TypeTree(vec![Type { | 
|  | 34 | +    offset: -1, size: 8, kind: Pointer, | 
|  | 35 | +    child: TypeTree(vec![Type { | 
|  | 36 | +        offset: -1, size: 4, kind: Float,  // -1 = all elements | 
|  | 37 | +        child: TypeTree::new() | 
|  | 38 | +    }]) | 
|  | 39 | +}]) | 
|  | 40 | +``` | 
|  | 41 | + | 
|  | 42 | +**Output: `f32`** | 
|  | 43 | +```rust | 
|  | 44 | +TypeTree(vec![Type { | 
|  | 45 | +    offset: 0, size: 4, kind: Float,  // Single scalar: use offset 0 | 
|  | 46 | +    child: TypeTree::new() | 
|  | 47 | +}]) | 
|  | 48 | +``` | 
|  | 49 | + | 
|  | 50 | +## Why Needed? | 
|  | 51 | +- Enzyme can't deduce complex type layouts from LLVM IR | 
|  | 52 | +- Prevents slow memory pattern analysis | 
|  | 53 | +- Enables correct derivative computation for nested structures | 
|  | 54 | +- Tells Enzyme which bytes are differentiable vs metadata | 
|  | 55 | + | 
|  | 56 | +## What Enzyme Does With This Information: | 
|  | 57 | + | 
|  | 58 | +Without TypeTrees: | 
|  | 59 | +```llvm | 
|  | 60 | +; Enzyme sees generic LLVM IR: | 
|  | 61 | +define float @distance(ptr %p1, ptr %p2) { | 
|  | 62 | +; Has to guess what these pointers point to | 
|  | 63 | +; Slow analysis of all memory operations | 
|  | 64 | +; May miss optimization opportunities | 
|  | 65 | +} | 
|  | 66 | +``` | 
|  | 67 | + | 
|  | 68 | +With TypeTrees: | 
|  | 69 | +```llvm | 
|  | 70 | +define "enzyme_type"="{[-1]:Float@float}" float @distance( | 
|  | 71 | +    ptr "enzyme_type"="{[-1]:Pointer, [-1,0]:Float@float}" %p1,  | 
|  | 72 | +    ptr "enzyme_type"="{[-1]:Pointer, [-1,0]:Float@float}" %p2 | 
|  | 73 | +) { | 
|  | 74 | +; Enzyme knows exact type layout | 
|  | 75 | +; Can generate efficient derivative code directly | 
|  | 76 | +} | 
|  | 77 | +``` | 
|  | 78 | + | 
|  | 79 | +# TypeTrees - Offset and -1 Explained | 
|  | 80 | + | 
|  | 81 | +## Type Structure | 
|  | 82 | + | 
|  | 83 | +```rust | 
|  | 84 | +Type { | 
|  | 85 | +    offset: isize, // WHERE this type starts | 
|  | 86 | +    size: usize,   // HOW BIG this type is | 
|  | 87 | +    kind: Kind,    // WHAT KIND of data (Float, Int, Pointer) | 
|  | 88 | +    child: TypeTree // WHAT'S INSIDE (for pointers/containers) | 
|  | 89 | +} | 
|  | 90 | +``` | 
|  | 91 | + | 
|  | 92 | +## Offset Values | 
|  | 93 | + | 
|  | 94 | +### Regular Offset (0, 4, 8, etc.) | 
|  | 95 | +**Specific byte position within a structure** | 
|  | 96 | + | 
|  | 97 | +```rust | 
|  | 98 | +struct Point { | 
|  | 99 | +    x: f32, // offset 0, size 4 | 
|  | 100 | +    y: f32, // offset 4, size 4 | 
|  | 101 | +    id: i32, // offset 8, size 4 | 
|  | 102 | +} | 
|  | 103 | +``` | 
|  | 104 | + | 
|  | 105 | +TypeTree for `&Point` (internal representation): | 
|  | 106 | +```rust | 
|  | 107 | +TypeTree(vec![ | 
|  | 108 | +    Type { offset: 0, size: 4, kind: Float },   // x at byte 0 | 
|  | 109 | +    Type { offset: 4, size: 4, kind: Float },   // y at byte 4 | 
|  | 110 | +    Type { offset: 8, size: 4, kind: Integer }  // id at byte 8 | 
|  | 111 | +]) | 
|  | 112 | +``` | 
|  | 113 | + | 
|  | 114 | +Generates LLVM | 
|  | 115 | +```llvm | 
|  | 116 | +"enzyme_type"="{[-1]:Pointer, [-1,0]:Float@float, [-1,4]:Float@float, [-1,8]:Integer, [-1,9]:Integer, [-1,10]:Integer, [-1,11]:Integer}" | 
|  | 117 | +``` | 
|  | 118 | + | 
|  | 119 | +### Offset -1 (Special: "Everywhere") | 
|  | 120 | +**Means "this pattern repeats for ALL elements"** | 
|  | 121 | + | 
|  | 122 | +#### Example 1: Direct Array `[f32; 100]` (no pointer indirection) | 
|  | 123 | +```rust | 
|  | 124 | +TypeTree(vec![Type { | 
|  | 125 | +    offset: -1, // ALL positions | 
|  | 126 | +    size: 4,    // each f32 is 4 bytes | 
|  | 127 | +    kind: Float, // every element is float | 
|  | 128 | +}]) | 
|  | 129 | +``` | 
|  | 130 | + | 
|  | 131 | +Generates LLVM: `"enzyme_type"="{[-1]:Float@float}"` | 
|  | 132 | + | 
|  | 133 | +#### Example 1b: Array Reference `&[f32; 100]` (with pointer indirection)   | 
|  | 134 | +```rust | 
|  | 135 | +TypeTree(vec![Type { | 
|  | 136 | +    offset: -1, size: 8, kind: Pointer, | 
|  | 137 | +    child: TypeTree(vec![Type { | 
|  | 138 | +        offset: -1, // ALL array elements | 
|  | 139 | +        size: 4,    // each f32 is 4 bytes | 
|  | 140 | +        kind: Float, // every element is float | 
|  | 141 | +    }]) | 
|  | 142 | +}]) | 
|  | 143 | +``` | 
|  | 144 | + | 
|  | 145 | +Generates LLVM: `"enzyme_type"="{[-1]:Pointer, [-1,-1]:Float@float}"` | 
|  | 146 | + | 
|  | 147 | +Instead of listing 100 separate Types with offsets `0,4,8,12...396` | 
|  | 148 | + | 
|  | 149 | +#### Example 2: Slice `&[i32]` | 
|  | 150 | +```rust | 
|  | 151 | +// Pointer to slice data | 
|  | 152 | +TypeTree(vec![Type { | 
|  | 153 | +    offset: -1, size: 8, kind: Pointer, | 
|  | 154 | +    child: TypeTree(vec![Type { | 
|  | 155 | +        offset: -1, // ALL slice elements | 
|  | 156 | +        size: 4,    // each i32 is 4 bytes | 
|  | 157 | +        kind: Integer | 
|  | 158 | +    }]) | 
|  | 159 | +}]) | 
|  | 160 | +``` | 
|  | 161 | + | 
|  | 162 | +Generates LLVM: `"enzyme_type"="{[-1]:Pointer, [-1,-1]:Integer}"` | 
|  | 163 | + | 
|  | 164 | +#### Example 3: Mixed Structure | 
|  | 165 | +```rust | 
|  | 166 | +struct Container { | 
|  | 167 | +    header: i64,        // offset 0 | 
|  | 168 | +    data: [f32; 1000],  // offset 8, but elements use -1 | 
|  | 169 | +} | 
|  | 170 | +``` | 
|  | 171 | + | 
|  | 172 | +```rust | 
|  | 173 | +TypeTree(vec![ | 
|  | 174 | +    Type { offset: 0, size: 8, kind: Integer }, // header | 
|  | 175 | +    Type { offset: 8, size: 4000, kind: Pointer, | 
|  | 176 | +        child: TypeTree(vec![Type { | 
|  | 177 | +            offset: -1, size: 4, kind: Float // ALL array elements | 
|  | 178 | +        }]) | 
|  | 179 | +    } | 
|  | 180 | +]) | 
|  | 181 | +``` | 
|  | 182 | + | 
|  | 183 | +## Key Distinction: Single Values vs Arrays | 
|  | 184 | + | 
|  | 185 | +**Single Values** use offset `0` for precision: | 
|  | 186 | +- `&f32` has exactly one f32 value at offset 0 | 
|  | 187 | +- More precise than using -1 ("everywhere")   | 
|  | 188 | +- Generates: `{[-1]:Pointer, [-1,0]:Float@float}` | 
|  | 189 | + | 
|  | 190 | +**Arrays** use offset `-1` for efficiency: | 
|  | 191 | +- `&[f32; 100]` has the same pattern repeated 100 times | 
|  | 192 | +- Using -1 avoids listing 100 separate offsets | 
|  | 193 | +- Generates: `{[-1]:Pointer, [-1,-1]:Float@float}` | 
0 commit comments