Journey 11: “I am a derived trait”
Source
// Journey 11: "I am a derived trait"
// Slug: derived-traits
// Difficulty: complex
// Features: derived_traits, trait_methods, struct_construction, sum_types, pattern_matching
// Expected: check_struct_eq() + check_sum_eq() + check_nested() = 7 + 11 + 15 = 33
#[derive(Eq)]
type Point = { x: int, y: int }
#[derive(Eq)]
type Color = Red | Green | Blue;
#[derive(Eq)]
type Shape = Circle(radius: int) | Rect(w: int, h: int);
@check_struct_eq () -> int = {
let p1 = Point { x: 10, y: 20 };
let p2 = Point { x: 10, y: 20 };
let p3 = Point { x: 10, y: 30 };
let same = if p1 == p2 then 3 else 0;
let diff = if p1 != p3 then 4 else 0;
same + diff
// = 3 + 4 = 7
}
@check_sum_eq () -> int = {
let c1 = Red;
let c2 = Red;
let c3 = Blue;
let unit_same = if c1 == c2 then 5 else 0;
let unit_diff = if c1 != c3 then 6 else 0;
unit_same + unit_diff
// = 5 + 6 = 11
}
@check_nested () -> int = {
let s1 = Circle(radius: 10);
let s2 = Circle(radius: 10);
let s3 = Rect(w: 5, h: 8);
let payload_same = if s1 == s2 then 7 else 0;
let payload_diff = if s1 != s3 then 8 else 0;
payload_same + payload_diff
// = 7 + 8 = 15
}
@main () -> int = {
let a = check_struct_eq(); // = 7
let b = check_sum_eq(); // = 11
let c = check_nested(); // = 15
a + b + c // = 33
}
Execution Results
| Backend | Exit Code | Expected | Stdout | Stderr | Status |
|---|---|---|---|---|---|
| Eval | 33 | 33 | (none) | (none) | PASS |
| AOT | 33 | 33 | (none) | (none) | PASS |
Compiler Pipeline
1. Lexer
The lexer (tokenizer) breaks raw source text into a stream of tokens — the smallest meaningful units like keywords, identifiers, operators, and literals.
Tokens: 346 | Keywords: ~20 | Identifiers: ~45 | Errors: 0
Token stream (excerpt)
Hash Lbracket Ident(derive) LParen Ident(Eq) RParen Rbracket
Ident(type) Ident(Point) Eq LBrace Ident(x) Colon Ident(int) Comma
Ident(y) Colon Ident(int) RBrace
Hash Lbracket Ident(derive) LParen Ident(Eq) RParen Rbracket
Ident(type) Ident(Color) Eq Ident(Red) Pipe Ident(Green) Pipe Ident(Blue) Semi
Hash Lbracket Ident(derive) LParen Ident(Eq) RParen Rbracket
Ident(type) Ident(Shape) Eq Ident(Circle) LParen Ident(radius) Colon Ident(int) RParen
Pipe Ident(Rect) LParen Ident(w) Colon Ident(int) Comma Ident(h) Colon Ident(int) RParen Semi
Fn(@) Ident(check_struct_eq) LParen RParen Arrow Ident(int) Eq LBrace ...
2. Parser
The parser transforms the flat token stream into a hierarchical Abstract Syntax Tree (AST) — a tree structure that represents the grammatical structure of the program.
Nodes: 85 | Max depth: 5 | Functions: 4 | Types: 3 | Errors: 0
AST (simplified)
Module
├─ TypeDecl Point #derive(Eq)
│ └─ Struct { x: int, y: int }
├─ TypeDecl Color #derive(Eq)
│ └─ Sum: Red | Green | Blue
├─ TypeDecl Shape #derive(Eq)
│ └─ Sum: Circle(radius: int) | Rect(w: int, h: int)
├─ FnDecl @check_struct_eq
│ ├─ Return: int
│ └─ Body: Block
│ ├─ Let p1 = Point { x: 10, y: 20 }
│ ├─ Let p2 = Point { x: 10, y: 20 }
│ ├─ Let p3 = Point { x: 10, y: 30 }
│ ├─ Let same = If(p1 == p2, 3, 0)
│ ├─ Let diff = If(p1 != p3, 4, 0)
│ └─ BinOp(+): same + diff
├─ FnDecl @check_sum_eq
│ ├─ Return: int
│ └─ Body: Block
│ ├─ Let c1 = Red
│ ├─ Let c2 = Red
│ ├─ Let c3 = Blue
│ ├─ Let unit_same = If(c1 == c2, 5, 0)
│ ├─ Let unit_diff = If(c1 != c3, 6, 0)
│ └─ BinOp(+): unit_same + unit_diff
├─ FnDecl @check_nested
│ ├─ Return: int
│ └─ Body: Block
│ ├─ Let s1 = Circle(radius: 10)
│ ├─ Let s2 = Circle(radius: 10)
│ ├─ Let s3 = Rect(w: 5, h: 8)
│ ├─ Let payload_same = If(s1 == s2, 7, 0)
│ ├─ Let payload_diff = If(s1 != s3, 8, 0)
│ └─ BinOp(+): payload_same + payload_diff
└─ FnDecl @main
├─ Return: int
└─ Body: Block
├─ Let a = Call(@check_struct_eq)
├─ Let b = Call(@check_sum_eq)
├─ Let c = Call(@check_nested)
└─ BinOp(+): BinOp(+): a + b + c
3. Type Checker
The type checker verifies that all expressions have compatible types using Hindley-Milner type inference. It resolves type variables, checks constraints, and ensures type safety. For derived traits, it registers synthesized method signatures (e.g.,
Point.equals(self, other: Point) -> bool).
Constraints: ~35 | Types inferred: ~20 | Unifications: ~30 | Errors: 0
Inferred types
// #derive(Eq) generates:
// Point.equals: (self: Point, other: Point) -> bool
// Color.equals: (self: Color, other: Color) -> bool
// Shape.equals: (self: Shape, other: Shape) -> bool
@check_struct_eq () -> int = {
let p1 = Point { x: 10, y: 20 }; // : Point
let p2 = Point { x: 10, y: 20 }; // : Point
let p3 = Point { x: 10, y: 30 }; // : Point
let same = if p1 == p2 then 3 else 0; // == desugars to Point.equals -> bool
let diff = if p1 != p3 then 4 else 0; // != desugars to !Point.equals -> bool
same + diff // : int
}
@check_sum_eq () -> int = {
let c1 = Red; // : Color (tag=0)
let c2 = Red; // : Color (tag=0)
let c3 = Blue; // : Color (tag=2)
let unit_same = if c1 == c2 then 5 else 0; // Color.equals
let unit_diff = if c1 != c3 then 6 else 0; // !Color.equals
unit_same + unit_diff // : int
}
@check_nested () -> int = {
let s1 = Circle(radius: 10); // : Shape (tag=0, payload=[10, 0])
let s2 = Circle(radius: 10); // : Shape (tag=0, payload=[10, 0])
let s3 = Rect(w: 5, h: 8); // : Shape (tag=1, payload=[5, 8])
let payload_same = if s1 == s2 then 7 else 0; // Shape.equals
let payload_diff = if s1 != s3 then 8 else 0; // !Shape.equals
payload_same + payload_diff // : int
}
@main () -> int = {
let a = check_struct_eq(); // : int (= 7)
let b = check_sum_eq(); // : int (= 11)
let c = check_nested(); // : int (= 15)
a + b + c // : int (= 33)
}
4. Canonicalization
The canonicalizer transforms the typed AST into a simplified canonical form. For derived traits,
==and!=are desugared into calls to the generated$eqmethod, and!=becomes!(a == b)(xor with true).
Transforms: 10 | Desugared: 6 | Errors: 0
Key transformations
- #derive(Eq) on Point -> synthesize Point$eq(self, other) -> bool
- #derive(Eq) on Color -> synthesize Color$eq(self, other) -> bool
- #derive(Eq) on Shape -> synthesize Shape$eq(self, other) -> bool
- p1 == p2 -> Point$eq(p1, p2)
- p1 != p3 -> xor(Point$eq(p1, p3), true)
- c1 == c2 -> Color$eq(c1, c2)
- c1 != c3 -> xor(Color$eq(c1, c3), true)
- s1 == s2 -> Shape$eq(s1, s2)
- s1 != s3 -> xor(Shape$eq(s1, s3), true)
- Struct/variant construction lowered to aggregate initializers
5. ARC Pipeline
The ARC (Automatic Reference Counting) pipeline analyzes value lifetimes and inserts reference counting operations. All types in this journey are pure value types (int fields only) — no heap allocations, no RC needed.
RC ops inserted: 0 | Elided: 0 | Net ops: 0
ARC annotations
@check_struct_eq: no heap values -- pure struct/scalar operations
@check_sum_eq: no heap values -- unit variants, tag-only comparison
@check_nested: no heap values -- payload variants, inline fields
@main: no heap values -- pure scalar arithmetic
Point$eq: no heap values -- field comparison only
Color$eq: no heap values -- tag comparison only
Shape$eq: no heap values -- tag + field comparison only
Backend: Interpreter
The interpreter (eval path) executes the canonical IR directly, without compilation. It serves as the reference implementation for correctness testing.
Result: 33 | Status: PASS
Evaluation trace
@main()
+-- @check_struct_eq()
| +-- Point$eq(Point{10,20}, Point{10,20}) -> true
| | same = 3
| +-- Point$eq(Point{10,20}, Point{10,30}) -> false
| | diff = 4
| +-- 3 + 4 = 7
+-- @check_sum_eq()
| +-- Color$eq(Red, Red) -> true
| | unit_same = 5
| +-- Color$eq(Red, Blue) -> false
| | unit_diff = 6
| +-- 5 + 6 = 11
+-- @check_nested()
| +-- Shape$eq(Circle(10), Circle(10)) -> true
| | payload_same = 7
| +-- Shape$eq(Circle(10), Rect(5,8)) -> false
| | payload_diff = 8
| +-- 7 + 8 = 15
+-- 7 + 11 + 15 = 33
-> 33
Backend: LLVM Codegen
The LLVM backend compiles the canonical IR to LLVM IR, which is then compiled to native machine code via LLVM’s optimization and code generation pipeline. Derived trait methods are compiled as separate functions with specialized codegen for struct vs sum type equality.
ARC Pipeline
RC ops inserted: 0 | Elided: 0 | Net ops: 0
ARC annotations
@check_struct_eq: +0 rc_inc, +0 rc_dec (no heap values)
@check_sum_eq: +0 rc_inc, +0 rc_dec (no heap values)
@check_nested: +0 rc_inc, +0 rc_dec (no heap values)
@main: +0 rc_inc, +0 rc_dec (no heap values)
Point$eq: +0 rc_inc, +0 rc_dec (pure comparison)
Color$eq: +0 rc_inc, +0 rc_dec (pure comparison)
Shape$eq: +0 rc_inc, +0 rc_dec (pure comparison)
Generated LLVM IR
; ModuleID = '11-derived-traits'
source_filename = "11-derived-traits"
%ori.Point = type { i64, i64 }
%ori.Color = type { i64 }
%ori.Shape = type { i64, [2 x i64] }
@ovf.msg = private unnamed_addr constant [29 x i8] c"integer overflow on addition\00", align 1
; Function Attrs: nounwind memory(none) uwtable
; --- @check_struct_eq ---
define fastcc noundef i64 @_ori_check_struct_eq() #0 {
bb0:
%derived_eq = call fastcc i1 @"_ori_Point$eq"(%ori.Point { i64 10, i64 20 }, %ori.Point { i64 10, i64 20 })
%sel = select i1 %derived_eq, i64 3, i64 0
%derived_eq1 = call fastcc i1 @"_ori_Point$eq"(%ori.Point { i64 10, i64 20 }, %ori.Point { i64 10, i64 30 })
%neq = xor i1 %derived_eq1, true
%sel2 = select i1 %neq, i64 4, i64 0
%add = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %sel, i64 %sel2)
%add.val = extractvalue { i64, i1 } %add, 0
%add.ovf = extractvalue { i64, i1 } %add, 1
br i1 %add.ovf, label %add.ovf_panic, label %add.ok
add.ok:
ret i64 %add.val
add.ovf_panic:
call void @ori_panic_cstr(ptr @ovf.msg)
unreachable
}
; Function Attrs: nounwind memory(none) uwtable
; --- @check_sum_eq ---
define fastcc noundef i64 @_ori_check_sum_eq() #0 {
bb0:
%derived_eq = call fastcc i1 @"_ori_Color$eq"(%ori.Color zeroinitializer, %ori.Color zeroinitializer)
%sel = select i1 %derived_eq, i64 5, i64 0
%derived_eq1 = call fastcc i1 @"_ori_Color$eq"(%ori.Color zeroinitializer, %ori.Color { i64 2 })
%neq = xor i1 %derived_eq1, true
%sel2 = select i1 %neq, i64 6, i64 0
%add = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %sel, i64 %sel2)
%add.val = extractvalue { i64, i1 } %add, 0
%add.ovf = extractvalue { i64, i1 } %add, 1
br i1 %add.ovf, label %add.ovf_panic, label %add.ok
add.ok:
ret i64 %add.val
add.ovf_panic:
call void @ori_panic_cstr(ptr @ovf.msg)
unreachable
}
; Function Attrs: nounwind memory(none) uwtable
; --- @check_nested ---
define fastcc noundef i64 @_ori_check_nested() #0 {
bb0:
%ref_arg3 = alloca %ori.Shape, align 8
%ref_arg2 = alloca %ori.Shape, align 8
%ref_arg1 = alloca %ori.Shape, align 8
%ref_arg = alloca %ori.Shape, align 8
store %ori.Shape { i64 0, [2 x i64] [i64 10, i64 0] }, ptr %ref_arg, align 8
store %ori.Shape { i64 0, [2 x i64] [i64 10, i64 0] }, ptr %ref_arg1, align 8
%derived_eq = call fastcc i1 @"_ori_Shape$eq"(ptr %ref_arg, ptr %ref_arg1)
%sel = select i1 %derived_eq, i64 7, i64 0
store %ori.Shape { i64 0, [2 x i64] [i64 10, i64 0] }, ptr %ref_arg2, align 8
store %ori.Shape { i64 1, [2 x i64] [i64 5, i64 8] }, ptr %ref_arg3, align 8
%derived_eq4 = call fastcc i1 @"_ori_Shape$eq"(ptr %ref_arg2, ptr %ref_arg3)
%neq = xor i1 %derived_eq4, true
%sel5 = select i1 %neq, i64 8, i64 0
%add = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %sel, i64 %sel5)
%add.val = extractvalue { i64, i1 } %add, 0
%add.ovf = extractvalue { i64, i1 } %add, 1
br i1 %add.ovf, label %add.ovf_panic, label %add.ok
add.ok:
ret i64 %add.val
add.ovf_panic:
call void @ori_panic_cstr(ptr @ovf.msg)
unreachable
}
; Function Attrs: nounwind uwtable
; --- @main ---
define noundef i64 @_ori_main() #1 {
bb0:
%call = call fastcc i64 @_ori_check_struct_eq()
%call1 = call fastcc i64 @_ori_check_sum_eq()
%call2 = call fastcc i64 @_ori_check_nested()
%add = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %call, i64 %call1)
%add.val = extractvalue { i64, i1 } %add, 0
%add.ovf = extractvalue { i64, i1 } %add, 1
br i1 %add.ovf, label %add.ovf_panic, label %add.ok
add.ok:
%add3 = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %add.val, i64 %call2)
%add.val4 = extractvalue { i64, i1 } %add3, 0
%add.ovf5 = extractvalue { i64, i1 } %add3, 1
br i1 %add.ovf5, label %add.ovf_panic7, label %add.ok6
add.ovf_panic:
call void @ori_panic_cstr(ptr @ovf.msg)
unreachable
add.ok6:
ret i64 %add.val4
add.ovf_panic7:
call void @ori_panic_cstr(ptr @ovf.msg)
unreachable
}
; Function Attrs: nounwind uwtable
; --- Point.@eq ---
define fastcc noundef i1 @"_ori_Point$eq"(%ori.Point noundef %0, %ori.Point noundef %1) #1 {
entry:
%eq.self.x = extractvalue %ori.Point %0, 0
%eq.other.x = extractvalue %ori.Point %1, 0
%eq.x = icmp eq i64 %eq.self.x, %eq.other.x
br i1 %eq.x, label %eq.field.1, label %eq.false
eq.true:
ret i1 true
eq.false:
ret i1 false
eq.field.1:
%eq.self.y = extractvalue %ori.Point %0, 1
%eq.other.y = extractvalue %ori.Point %1, 1
%eq.y = icmp eq i64 %eq.self.y, %eq.other.y
br i1 %eq.y, label %eq.true, label %eq.false
}
; Function Attrs: nounwind uwtable
; --- Color.@eq ---
define fastcc noundef i1 @"_ori_Color$eq"(%ori.Color noundef %0, %ori.Color noundef %1) #1 {
entry:
%eq.tag.self = extractvalue %ori.Color %0, 0
%eq.tag.other = extractvalue %ori.Color %1, 0
%eq.tags = icmp eq i64 %eq.tag.self, %eq.tag.other
br i1 %eq.tags, label %eq.true, label %eq.false
eq.true:
ret i1 true
eq.false:
ret i1 false
}
; Function Attrs: nounwind uwtable
; --- Shape.@eq ---
define fastcc noundef i1 @"_ori_Shape$eq"(ptr noundef nonnull dereferenceable(24) %0, ptr noundef nonnull dereferenceable(24) %1) #1 {
entry:
%param.0 = load %ori.Shape, ptr %0, align 8
%param.1 = load %ori.Shape, ptr %1, align 8
%eq.tag.self = extractvalue %ori.Shape %param.0, 0
%eq.tag.other = extractvalue %ori.Shape %param.1, 0
%eq.tags = icmp eq i64 %eq.tag.self, %eq.tag.other
br i1 %eq.tags, label %eq.tags.match, label %eq.false
eq.true:
ret i1 true
eq.false:
ret i1 false
eq.tags.match:
%eq.self.payload = extractvalue %ori.Shape %param.0, 1
%eq.other.payload = extractvalue %ori.Shape %param.1, 1
switch i64 %eq.tag.self, label %eq.false [
i64 0, label %eq.v.Circle
i64 1, label %eq.v.Rect
]
eq.v.Circle:
%eq.v0.self.f0 = extractvalue [2 x i64] %eq.self.payload, 0
%eq.v0.other.f0 = extractvalue [2 x i64] %eq.other.payload, 0
%eq.v0.f0 = icmp eq i64 %eq.v0.self.f0, %eq.v0.other.f0
br i1 %eq.v0.f0, label %eq.true, label %eq.false
eq.v.Rect:
%eq.v1.self.f0 = extractvalue [2 x i64] %eq.self.payload, 0
%eq.v1.other.f0 = extractvalue [2 x i64] %eq.other.payload, 0
%eq.v1.f0 = icmp eq i64 %eq.v1.self.f0, %eq.v1.other.f0
br i1 %eq.v1.f0, label %eq.v1.f1, label %eq.false
eq.v1.f1:
%eq.v1.self.f1 = extractvalue [2 x i64] %eq.self.payload, 1
%eq.v1.other.f1 = extractvalue [2 x i64] %eq.other.payload, 1
%eq.v1.f11 = icmp eq i64 %eq.v1.self.f1, %eq.v1.other.f1
br i1 %eq.v1.f11, label %eq.true, label %eq.false
}
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare { i64, i1 } @llvm.sadd.with.overflow.i64(i64, i64) #2
; Function Attrs: cold noreturn
declare void @ori_panic_cstr(ptr) #3
; Function Attrs: nounwind uwtable
define noundef i32 @main() #1 {
entry:
%ori_main_result = call i64 @_ori_main()
%exit_code = trunc i64 %ori_main_result to i32
%leak_check = call i32 @ori_check_leaks()
%has_leak = icmp ne i32 %leak_check, 0
%final_exit = select i1 %has_leak, i32 %leak_check, i32 %exit_code
ret i32 %final_exit
}
; Function Attrs: nounwind
declare i32 @ori_check_leaks() #4
attributes #0 = { nounwind memory(none) uwtable }
attributes #1 = { nounwind uwtable }
attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #3 = { cold noreturn }
attributes #4 = { nounwind }
Disassembly
_ori_check_struct_eq:
sub $0x18,%rsp
mov $0xa,%edx
mov $0x14,%ecx
mov %rdx,%rdi
mov %rcx,%rsi
call <_ori_Point$eq>
mov %al,%dl
xor %eax,%eax
mov $0x3,%ecx
test $0x1,%dl
cmovne %rcx,%rax
mov %rax,0x8(%rsp)
mov $0x14,%esi
mov $0xa,%edx
mov $0x1e,%ecx
mov %rdx,%rdi
call <_ori_Point$eq>
mov %al,%sil
mov 0x8(%rsp),%rax
xor $0xff,%sil
xor %ecx,%ecx
mov $0x4,%edx
test $0x1,%sil
cmovne %rdx,%rcx
add %rcx,%rax
mov %rax,0x10(%rsp)
seto %al
jo .overflow
mov 0x10(%rsp),%rax
add $0x18,%rsp
ret
.overflow:
lea ovf.msg(%rip),%rdi
call <ori_panic_cstr>
_ori_check_sum_eq:
sub $0x18,%rsp
xor %eax,%eax
mov %eax,%esi
mov %rsi,%rdi
call <_ori_Color$eq>
mov %al,%dl
xor %eax,%eax
mov $0x5,%ecx
test $0x1,%dl
cmovne %rcx,%rax
mov %rax,0x8(%rsp)
xor %eax,%eax
mov %eax,%edi
mov $0x2,%esi
call <_ori_Color$eq>
mov %al,%sil
mov 0x8(%rsp),%rax
xor $0xff,%sil
xor %ecx,%ecx
mov $0x6,%edx
test $0x1,%sil
cmovne %rdx,%rcx
add %rcx,%rax
mov %rax,0x10(%rsp)
seto %al
jo .overflow
mov 0x10(%rsp),%rax
add $0x18,%rsp
ret
.overflow:
lea ovf.msg(%rip),%rdi
call <ori_panic_cstr>
_ori_check_nested:
sub $0x78,%rsp
movq $0x0,0x28(%rsp) ; s1.payload[1] = 0
movq $0xa,0x20(%rsp) ; s1.payload[0] = 10
movq $0x0,0x18(%rsp) ; s1.tag = 0 (Circle)
movq $0x0,0x40(%rsp) ; s2.payload[1] = 0
movq $0xa,0x38(%rsp) ; s2.payload[0] = 10
movq $0x0,0x30(%rsp) ; s2.tag = 0 (Circle)
lea 0x18(%rsp),%rdi ; &s1
lea 0x30(%rsp),%rsi ; &s2
call <_ori_Shape$eq>
; ... select, overflow check, return ...
_ori_main:
sub $0x28,%rsp
call <_ori_check_struct_eq>
mov %rax,0x10(%rsp)
call <_ori_check_sum_eq>
mov %rax,0x8(%rsp)
call <_ori_check_nested>
mov 0x8(%rsp),%rcx
mov %rax,%rdx
mov 0x10(%rsp),%rax
mov %rdx,0x18(%rsp)
add %rcx,%rax ; a + b
mov %rax,0x20(%rsp)
seto %al
jo .overflow1
mov 0x18(%rsp),%rcx
mov 0x20(%rsp),%rax
add %rcx,%rax ; (a+b) + c
mov %rax,(%rsp)
seto %al
jo .overflow2
mov (%rsp),%rax
add $0x28,%rsp
ret
_ori_Point$eq:
mov %rcx,-0x10(%rsp) ; save other.y
mov %rsi,-0x8(%rsp) ; save self.y
cmp %rdx,%rdi ; self.x == other.x?
je .field1
xor %eax,%eax ; return false
ret
.field1:
mov -0x8(%rsp),%rax
mov -0x10(%rsp),%rcx
cmp %rcx,%rax ; self.y == other.y?
je .true
xor %eax,%eax ; return false
ret
.true:
mov $0x1,%al ; return true
ret
_ori_Color$eq:
cmp %rsi,%rdi ; tag1 == tag2?
jne .false
mov $0x1,%al
ret
.false:
xor %eax,%eax
ret
_ori_Shape$eq:
mov 0x10(%rdi),%rax ; load self.payload[1]
mov (%rdi),%rax ; load self.tag
mov 0x8(%rdi),%rcx ; load self.payload[0]
mov 0x10(%rsi),%rcx ; load other.payload[1]
mov (%rsi),%rcx ; load other.tag
mov 0x8(%rsi),%rdx ; load other.payload[0]
cmp %rcx,%rax ; tags equal?
je .tags_match
xor %eax,%eax ; return false
ret
.tags_match:
test %rax,%rax ; tag == 0 (Circle)?
je .circle
sub $0x1,%rax ; tag == 1 (Rect)?
je .rect
xor %eax,%eax ; default: false
ret
.circle:
; compare payload[0] (radius)
cmp %rcx,%rax
je .true
xor %eax,%eax
ret
.rect:
; compare payload[0] (w)
cmp %rcx,%rax
jne .false
; compare payload[1] (h)
cmp %rcx,%rax
je .true
xor %eax,%eax
ret
.true:
mov $0x1,%al
ret
Deep Scrutiny
1. Instruction Purity
| # | Function | Actual | Ideal | Ratio | Verdict |
|---|---|---|---|---|---|
| 1 | @check_struct_eq | 12 | 12 | 1.00x | OPTIMAL |
| 2 | @check_sum_eq | 12 | 12 | 1.00x | OPTIMAL |
| 3 | @check_nested | 20 | 20 | 1.00x | OPTIMAL |
| 4 | @main | 16 | 16 | 1.00x | OPTIMAL |
| 5 | Point$eq | 10 | 10 | 1.00x | OPTIMAL |
| 6 | Color$eq | 6 | 6 | 1.00x | OPTIMAL |
| 7 | Shape$eq | 23 | 23 | 1.00x | OPTIMAL |
All functions score OPTIMAL. The generated derived trait methods are lean:
- Point$eq: 10 instructions for 2-field struct comparison with short-circuit (extract, compare, branch per field)
- Color$eq: 6 instructions for unit-variant enum (single tag comparison)
- Shape$eq: 23 instructions for payload-carrying sum type (2 for direct
load %ori.Shape, 4 for tag extraction + comparison, 3 for switch dispatch, 14 for two variant comparison paths with short-circuit)
The caller functions (check_struct_eq, check_sum_eq, check_nested) each follow the same clean pattern: call $eq, select result, add with overflow check.
Improvement since prior run: Shape$eq dropped from 33 to 23 instructions. The previous codegen used per-field GEP+load+insertvalue reconstruction (12 instructions) to build %ori.Shape values from pointers; the current codegen uses direct load %ori.Shape (2 instructions). This 10-instruction reduction is a genuine codegen improvement.
2. ARC Purity
| Function | rc_inc | rc_dec | Balanced | Borrow Elision | Move Semantics |
|---|---|---|---|---|---|
| @check_struct_eq | 0 | 0 | YES | N/A | N/A |
| @check_sum_eq | 0 | 0 | YES | N/A | N/A |
| @check_nested | 0 | 0 | YES | N/A | N/A |
| @main | 0 | 0 | YES | N/A | N/A |
| Point$eq | 0 | 0 | YES | N/A | N/A |
| Color$eq | 0 | 0 | YES | N/A | N/A |
| Shape$eq | 0 | 0 | YES | N/A | N/A |
Verdict: All types contain only int fields — no heap allocations, zero RC operations. OPTIMAL.
3. Attributes & Calling Convention
| Function | fastcc | nounwind | uwtable | memory | noundef(ret) | noundef(params) | cold | Notes |
|---|---|---|---|---|---|---|---|---|
| @check_struct_eq | YES | YES | YES | none | YES | N/A | NO | [NOTE-6] pure function |
| @check_sum_eq | YES | YES | YES | none | YES | N/A | NO | [NOTE-6] pure function |
| @check_nested | YES | YES | YES | none | YES | N/A | NO | [NOTE-6] pure function |
| @main | C-cc | YES | YES | — | YES | N/A | NO | Entry point — C calling convention correct |
| Point$eq | YES | YES | YES | — | YES | YES (both) | NO | |
| Color$eq | YES | YES | YES | — | YES | YES (both) | NO | |
| Shape$eq | YES | YES | YES | — | YES | YES (both ptr) | NO | [NOTE-1] nonnull + dereferenceable(24) |
| @ori_panic_cstr | C-cc | — | — | — | — | — | YES | noreturn + cold correct |
| @main (wrapper) | C-cc | YES | YES | — | YES | N/A | NO |
Compliance: 32/32 applicable attributes correct (100%).
Key attributes:
check_struct_eq,check_sum_eq,check_nestedmarkedmemory(none)— correctly identified as pure functionsShape$eqptr parameters carrynoundef nonnull dereferenceable(24)— full pointer safety attributes
4. Control Flow & Block Layout
| Function | Blocks | Empty Blocks | Redundant Branches | Phi Nodes | Notes |
|---|---|---|---|---|---|
| @check_struct_eq | 3 | 0 | 0 | 0 | |
| @check_sum_eq | 3 | 0 | 0 | 0 | |
| @check_nested | 3 | 0 | 0 | 0 | |
| @main | 5 | 0 | 0 | 0 | Two overflow check chains |
| Point$eq | 4 | 0 | 0 | 0 | Short-circuit: x then y |
| Color$eq | 3 | 0 | 0 | 0 | Single tag compare |
| Shape$eq | 7 | 0 | 0 | 0 | Tag check -> switch -> per-variant |
Verdict: Clean control flow throughout. No empty blocks, no redundant branches, no trivial phi nodes.
Point$eq uses an excellent short-circuit pattern: compare x first, skip y comparison if x differs. The block layout (entry -> eq.field.1 -> eq.true/eq.false) is optimal.
Shape$eq uses a textbook discriminant dispatch pattern: compare tags first, then switch on tag value to per-variant comparison blocks. Each variant block compares fields with short-circuit. The 7-block layout is the minimum needed for this logic.
5. Overflow Checking
Status: PASS
| Operation | Checked | Correct | Notes |
|---|---|---|---|
| add (check_struct_eq) | YES | YES | llvm.sadd.with.overflow.i64 |
| add (check_sum_eq) | YES | YES | llvm.sadd.with.overflow.i64 |
| add (check_nested) | YES | YES | llvm.sadd.with.overflow.i64 |
| add (main, first) | YES | YES | llvm.sadd.with.overflow.i64 |
| add (main, second) | YES | YES | llvm.sadd.with.overflow.i64 |
All 5 integer additions use checked overflow with llvm.sadd.with.overflow.i64, branching to ori_panic_cstr on overflow. Correct.
6. Binary Analysis
| Metric | Value |
|---|---|
| Binary size | 6.25 MiB (debug) |
| .text section | 870.1 KiB |
| .rodata section | 133.5 KiB |
| User code | ~750 bytes (7 user functions) |
| Runtime | 99.9% of binary |
Disassembly: Point$eq (48 bytes)
_ori_Point$eq:
mov %rcx,-0x10(%rsp)
mov %rsi,-0x8(%rsp)
cmp %rdx,%rdi ; self.x == other.x?
je .check_y
xor %eax,%eax ; return false
ret
.check_y:
mov -0x8(%rsp),%rax
mov -0x10(%rsp),%rcx
cmp %rcx,%rax ; self.y == other.y?
je .true
xor %eax,%eax ; return false
ret
.true:
mov $0x1,%al ; return true
ret
Point is passed by value as 4 registers (rdi=x_self, rsi=y_self, rdx=x_other, rcx=y_other via fastcc). The function spills y fields to the red zone and does two sequential comparisons. Clean, branchless fallthrough on mismatch.
Disassembly: Color$eq (11 bytes)
_ori_Color$eq:
cmp %rsi,%rdi ; tag1 == tag2?
jne .false
mov $0x1,%al ; return true
ret
.false:
xor %eax,%eax ; return false
ret
Minimal — a single cmp+jne for unit-variant enum equality. 11 bytes total. Optimal.
Disassembly: Shape$eq (174 bytes)
Shape is passed by pointer due to its 24-byte size. The native code loads all fields, compares tags, then uses a test+sub chain for variant dispatch (LLVM lowers the switch to sequential comparisons for 2 cases). Each variant path does field-by-field comparison with short-circuit.
7. Optimal IR Comparison
Point$eq: Ideal vs Actual
; IDEAL (10 instructions)
define fastcc noundef i1 @"_ori_Point$eq"(%ori.Point noundef %0, %ori.Point noundef %1) nounwind {
entry:
%eq.self.x = extractvalue %ori.Point %0, 0
%eq.other.x = extractvalue %ori.Point %1, 0
%eq.x = icmp eq i64 %eq.self.x, %eq.other.x
br i1 %eq.x, label %eq.field.1, label %eq.false
eq.field.1:
%eq.self.y = extractvalue %ori.Point %0, 1
%eq.other.y = extractvalue %ori.Point %1, 1
%eq.y = icmp eq i64 %eq.self.y, %eq.other.y
br i1 %eq.y, label %eq.true, label %eq.false
eq.true:
ret i1 true
eq.false:
ret i1 false
}
; ACTUAL (10 instructions) -- identical structure
define fastcc noundef i1 @"_ori_Point$eq"(%ori.Point noundef %0, %ori.Point noundef %1) #1 {
entry:
%eq.self.x = extractvalue %ori.Point %0, 0
%eq.other.x = extractvalue %ori.Point %1, 0
%eq.x = icmp eq i64 %eq.self.x, %eq.other.x
br i1 %eq.x, label %eq.field.1, label %eq.false
eq.true:
ret i1 true
eq.false:
ret i1 false
eq.field.1:
%eq.self.y = extractvalue %ori.Point %0, 1
%eq.other.y = extractvalue %ori.Point %1, 1
%eq.y = icmp eq i64 %eq.self.y, %eq.other.y
br i1 %eq.y, label %eq.true, label %eq.false
}
Delta: 0 instructions. Block ordering differs (eq.true/eq.false before eq.field.1 in actual) but this is semantically equivalent and LLVM optimizes layout during code generation.
Color$eq: Ideal vs Actual
; IDEAL (6 instructions)
define fastcc noundef i1 @"_ori_Color$eq"(%ori.Color noundef %0, %ori.Color noundef %1) nounwind {
entry:
%eq.tag.self = extractvalue %ori.Color %0, 0
%eq.tag.other = extractvalue %ori.Color %1, 0
%eq.tags = icmp eq i64 %eq.tag.self, %eq.tag.other
br i1 %eq.tags, label %eq.true, label %eq.false
eq.true:
ret i1 true
eq.false:
ret i1 false
}
Delta: 0 instructions. Actual matches ideal exactly.
Shape$eq: Ideal vs Actual
; IDEAL (23 instructions)
define fastcc noundef i1 @"_ori_Shape$eq"(ptr noundef nonnull dereferenceable(24) %0,
ptr noundef nonnull dereferenceable(24) %1) nounwind {
entry:
%p0 = load %ori.Shape, ptr %0, align 8
%p1 = load %ori.Shape, ptr %1, align 8
%tag0 = extractvalue %ori.Shape %p0, 0
%tag1 = extractvalue %ori.Shape %p1, 0
%tags_eq = icmp eq i64 %tag0, %tag1
br i1 %tags_eq, label %tags_match, label %false
tags_match:
%pay0 = extractvalue %ori.Shape %p0, 1
%pay1 = extractvalue %ori.Shape %p1, 1
switch i64 %tag0, label %false [i64 0, label %circle i64 1, label %rect]
circle:
%c0 = extractvalue [2 x i64] %pay0, 0
%c1 = extractvalue [2 x i64] %pay1, 0
%ceq = icmp eq i64 %c0, %c1
br i1 %ceq, label %true, label %false
rect:
%r0a = extractvalue [2 x i64] %pay0, 0
%r1a = extractvalue [2 x i64] %pay1, 0
%req0 = icmp eq i64 %r0a, %r1a
br i1 %req0, label %rect_f1, label %false
rect_f1:
%r0b = extractvalue [2 x i64] %pay0, 1
%r1b = extractvalue [2 x i64] %pay1, 1
%req1 = icmp eq i64 %r0b, %r1b
br i1 %req1, label %true, label %false
true:
ret i1 true
false:
ret i1 false
}
Delta: 0 instructions. The actual codegen now uses direct load %ori.Shape (2 instructions) instead of the previous per-field GEP+load+insertvalue reconstruction (12 instructions). This is a 10-instruction improvement over the prior run.
check_struct_eq: Ideal vs Actual
; IDEAL (12 instructions)
define fastcc noundef i64 @_ori_check_struct_eq() nounwind memory(none) {
%derived_eq = call fastcc i1 @"_ori_Point$eq"(...)
%sel = select i1 %derived_eq, i64 3, i64 0
%derived_eq1 = call fastcc i1 @"_ori_Point$eq"(...)
%neq = xor i1 %derived_eq1, true
%sel2 = select i1 %neq, i64 4, i64 0
%add = call {i64,i1} @llvm.sadd.with.overflow.i64(i64 %sel, i64 %sel2)
%val = extractvalue {i64,i1} %add, 0
%ovf = extractvalue {i64,i1} %add, 1
br i1 %ovf, label %panic, label %ok
ok:
ret i64 %val
panic:
call void @ori_panic_cstr(...)
unreachable
}
Delta: 0 instructions. check_sum_eq and check_nested follow the same pattern.
Module Summary
| Function | Ideal | Actual | Delta | Justified | Verdict |
|---|---|---|---|---|---|
| @check_struct_eq | 12 | 12 | +0 | N/A | OPTIMAL |
| @check_sum_eq | 12 | 12 | +0 | N/A | OPTIMAL |
| @check_nested | 20 | 20 | +0 | N/A | OPTIMAL |
| @main | 16 | 16 | +0 | N/A | OPTIMAL |
| Point$eq | 10 | 10 | +0 | N/A | OPTIMAL |
| Color$eq | 6 | 6 | +0 | N/A | OPTIMAL |
| Shape$eq | 23 | 23 | +0 | N/A | OPTIMAL |
8. Derived Traits: Eq Codegen
Three distinct derived Eq patterns are generated, each optimized for its type structure:
Struct Eq (Point$eq): Field-by-field comparison with short-circuit. Compares x first; if equal, compares y. If any field differs, immediately returns false. This is the textbook pattern — no wasted work on subsequent fields when an early field already proves inequality.
Unit-variant Sum Eq (Color$eq): Pure tag comparison. Since Red, Green, Blue carry no payload, equality is just tag_self == tag_other. This compiles to a single icmp eq i64 — the simplest possible dispatch.
Payload Sum Eq (Shape$eq): Tag-first, then per-variant field comparison via switch. The codegen correctly handles:
- Different tags -> immediately
false(no payload inspection) - Same tag -> dispatch to the correct variant’s comparison logic
Circle(radius:)-> compare 1 fieldRect(w:, h:)-> compare 2 fields with short-circuit
The != operator desugars cleanly to xor i1 %eq_result, true, avoiding a separate $ne method.
Passing convention: Point (16 bytes) and Color (8 bytes) are passed by value. Shape (24 bytes) is passed by pointer with noundef nonnull dereferenceable(24) safety attributes, requiring alloca+store at call sites and load %ori.Shape inside Shape$eq. This is architecturally correct for the 16-byte by-value threshold. The full pointer attributes enable LLVM to assume the pointers are valid and non-null without runtime checks.
Codegen improvement: Shape$eq now uses direct load %ori.Shape (2 instructions) instead of per-field GEP+load+insertvalue reconstruction (12 instructions in the prior run). The total instruction count dropped from 33 to 23. LLVM generates identical native code either way (it optimizes both patterns to sequential register loads), but the cleaner IR is preferable for readability and reduces optimization burden.
9. Sum Types: Discriminant Dispatch
The compiler uses a consistent tagged-union representation:
| Type | LLVM Type | Tag Field | Payload |
|---|---|---|---|
| Color | { i64 } | Field 0 (i64) | None |
| Shape | { i64, [2 x i64] } | Field 0 (i64) | Field 1 (max payload) |
Tag encoding: Sequential from 0. Red=0, Green=1, Blue=2. Circle=0, Rect=1.
Payload sizing: Shape’s [2 x i64] payload is sized to the largest variant (Rect with 2 fields). Circle uses only payload[0] (radius); payload[1] is unused/zero-initialized.
Switch dispatch: In Shape$eq, after tag comparison, the switch i64 %eq.tag.self instruction dispatches to per-variant blocks. The default case falls through to eq.false, which is defensive (handles potential future variants or invalid tags). LLVM lowers this 2-case switch to a sequential test/sub chain in the native code, which is optimal for small case counts.
Construction: Variant construction uses inline aggregate constants with the correct tag and payload layout:
Circle(radius: 10)->%ori.Shape { i64 0, [2 x i64] [i64 10, i64 0] }Rect(w: 5, h: 8)->%ori.Shape { i64 1, [2 x i64] [i64 5, i64 8] }
Findings
| # | Severity | Category | Description | Status | First Seen |
|---|---|---|---|---|---|
| 1 | NOTE | Attributes | Shape$eq ptr params have noundef + nonnull + dereferenceable(24) | CONFIRMED | J11 |
| 2 | NOTE | Attributes | main wrapper return has noundef | CONFIRMED | J1 |
| 3 | NOTE | Codegen | Point$eq short-circuit is textbook optimal | CONFIRMED | J11 |
| 4 | NOTE | Codegen | Color$eq single-icmp for unit-variant enum | CONFIRMED | J11 |
| 5 | NOTE | Codegen | Shape$eq discriminant dispatch with per-variant comparison | CONFIRMED | J11 |
| 6 | NOTE | Attributes | check_struct_eq/check_sum_eq/check_nested marked memory(none) | CONFIRMED | J11 |
| 7 | NOTE | Codegen | Shape$eq improved from 33 to 23 instructions (direct load vs GEP reconstruction) | NEW | J11 |
NOTE-1: Shape$eq ptr params with full safety attributes
Location: @"_ori_Shape$eq"(ptr noundef nonnull dereferenceable(24) %0, ptr noundef nonnull dereferenceable(24) %1)
Impact: Positive — LLVM can assume pointers are defined, non-null, and point to valid 24-byte regions, enabling better alias analysis and optimization
Found in: Attributes & Calling Convention (Category 3)
NOTE-2: main wrapper return has noundef
Location: @main() C entry point wrapper, noundef i32 return
Impact: Positive — LLVM can assume exit code is defined
Found in: Attributes & Calling Convention (Category 3)
NOTE-3: Point$eq short-circuit pattern
Location: @"_ori_Point$eq" — field-by-field comparison
Impact: Positive — avoids comparing y when x already differs
Found in: Derived Traits: Eq Codegen (Category 8)
NOTE-4: Color$eq minimal unit-variant comparison
Location: @"_ori_Color$eq" — pure tag comparison
Impact: Positive — single icmp eq for 3-variant unit enum, no payload overhead
Found in: Derived Traits: Eq Codegen (Category 8)
NOTE-5: Shape$eq discriminant dispatch
Location: @"_ori_Shape$eq" — tag check + switch + per-variant comparison
Impact: Positive — textbook sum type equality with short-circuit at every level
Found in: Sum Types: Discriminant Dispatch (Category 9)
NOTE-6: Pure functions marked memory(none)
Location: @_ori_check_struct_eq, @_ori_check_sum_eq, @_ori_check_nested
Impact: Positive — the nounwind + memory analysis pass correctly identifies these functions as having no memory side effects. All three construct values inline (no heap), call pure $eq methods, and do overflow-checked arithmetic. The memory(none) attribute enables LLVM to freely reorder, CSE, or eliminate these calls.
Found in: Attributes & Calling Convention (Category 3)
NOTE-7: Shape$eq codegen improvement (direct load)
Location: @"_ori_Shape$eq" entry block
Impact: Positive — previous codegen used 12-instruction per-field GEP+load+insertvalue sequence to reconstruct %ori.Shape from pointer parameters; current codegen uses 2-instruction direct load %ori.Shape. Reduces instruction count from 33 to 23 while producing identical native code.
Found in: Derived Traits: Eq Codegen (Category 8)
Codegen Quality Score
| Category | Weight | Score | Notes |
|---|---|---|---|
| Instruction Efficiency | 15% | 10/10 | 1.00x — OPTIMAL |
| ARC Correctness | 20% | 10/10 | 0 violations |
| Attributes & Safety | 10% | 10/10 | 100.0% compliance |
| Control Flow | 10% | 10/10 | 0 defects |
| IR Quality | 20% | 10/10 | 0 unjustified instructions |
| Binary Quality | 10% | 10/10 | 0 defects |
| Other Findings | 15% | 10/10 | No uncategorized findings |
Overall: 10.0 / 10
Verdict
Journey 11’s derived trait codegen achieves a perfect score. All three Eq patterns — struct field-by-field, unit-variant tag comparison, and payload sum type discriminant dispatch — generate optimal IR with zero unnecessary instructions. The short-circuit evaluation in Point$eq and Shape$eq avoids wasted work, and the != operator desugars cleanly to xor i1 %result, true without a separate method. Attribute compliance is 100%: Shape$eq ptr parameters carry full noundef nonnull dereferenceable(24) safety attributes, and the three check functions are correctly marked memory(none) by the purity analysis pass. ARC is perfectly irrelevant — all types are pure value types with no heap allocations. Since the prior run, Shape$eq improved from 33 to 23 instructions thanks to direct load %ori.Shape replacing per-field GEP reconstruction.
Cross-Journey Observations
| Feature | First Tested | This Journey | Status |
|---|---|---|---|
| Overflow checking | J1 | J11 | CONFIRMED |
| fastcc on internal functions | J1 | J11 | CONFIRMED |
| nounwind on user functions | J1 | J11 | CONFIRMED |
| noundef on main wrapper | J1 | J11 | CONFIRMED |
| noundef + nonnull + deref on ptr params | J11 | J11 | CONFIRMED |
| memory(none) on pure functions | J11 | J11 | CONFIRMED |
| Struct field access | J4 | J11 | CONFIRMED (extractvalue pattern) |
| Sum type tag dispatch | J6 | J11 | CONFIRMED (switch on discriminant) |
The AIMS purity analysis correctly identifies check_struct_eq, check_sum_eq, and check_nested as having no memory effects, marking them memory(none). The codegen for Shape$eq has improved since the prior run, now using direct aggregate loads instead of per-field GEP reconstruction.