; ============================================================
; IR LESSON 2: a pointer SILENTLY TRUNCATED through a 32-bit slot
; ------------------------------------------------------------
; Hand-authored LLVM IR (opaque pointers, LLVM 21/23 compatible). It takes a
; valid 64-bit pointer, ptrtoint's it to i64, TRUNCATES to i32 (dropping the
; high 32 bits of the address), stores those 32 bits, reloads them, and rebuilds
; a pointer via inttoptr. On x86-64 a stack address looks like 0x00007ffff7....
; so chopping the high half yields a bogus address -> the final store SIGSEGVs.
;
; THE LESSON (this is the whole point):
; `opt -passes=verify` calls this IR **VALID**. Verification checks STRUCTURE
; (types of each instruction's operands, dominance, well-formed CFG), not
; whether your pointer arithmetic is *sane*. With OPAQUE pointers, `ptr`
; carries no element/width type, so a 64->32 truncation of an address is just
; "an i64 truncated to i32" — perfectly well-typed. The size mismatch that
; destroys the pointer is INVISIBLE to the verifier. A verifier is not a bug
; finder; "verified" only means "well-formed".
;
; WORKFLOW (run from ir/, with the LOCAL LLVM on PATH):
; export PATH=$HOME/llvm-project/build/bin:$PATH
; llvm-as lesson2_truncptr.ll -o lesson2.bc # assemble .ll -> .bc
; opt -passes=verify lesson2_truncptr.ll -disable-output && echo VALID
; # => prints VALID (!)
; llc -filetype=obj lesson2_truncptr.ll -o lesson2.o # IR -> object
; clang lesson2.o -o lesson2_exe # link
; ./lesson2_exe ; echo "exit=$?" # => SIGSEGV, exit 139
; lli lesson2_truncptr.ll ; echo "exit=$?" # JIT-run the IR: same SIGSEGV
; opt -passes=debugify lesson2_truncptr.ll -S -o lesson2.dbgfy.ll
; # attach per-instr lines,
; # then llc+gdb to map the
; # fault to an IR line.
; (See ir/README.md for the REAL captured output of every command above.)
; ============================================================
target triple = "x86_64-unknown-linux-gnu"
define i32 @main() {
entry:
; a real 4-byte object we intend to write to
%target = alloca i32, align 4
store i32 123, ptr %target, align 4
; a 32-bit slot -- TOO SMALL to hold a 64-bit pointer
%slot = alloca i32, align 4
; address-of target as a 64-bit integer
%as_int = ptrtoint ptr %target to i64
; *** TRUNCATE 64 -> 32: silently drops the high half of the address ***
%trunc = trunc i64 %as_int to i32
; stash the truncated address in the 32-bit slot (well-typed; semantically wrong)
store i32 %trunc, ptr %slot, align 4
; read it back and rebuild a pointer from the 32 surviving bits
%back32 = load i32, ptr %slot, align 4
%back64 = zext i32 %back32 to i64
%badptr = inttoptr i64 %back64 to ptr ; high 32 bits are now ZERO
; write through the broken pointer -> almost certainly SIGSEGV
store i32 456, ptr %badptr, align 4 ; <-- THE BUG (faults here)
ret i32 0
}A lab is done when you can say what it showed on your machine — not when you have read it.
It decides which problems come first, which drills open, which labs you see and how the Learn surface is organised. Change it any time from the switch in the top bar, or press Escape to look around first.