bpf+bpftrace: percpu_kaddr emits ld_imm64 PSEUDO_BTF_ID — verifier-accepted percpu_ptr

The verifier rejected \`bpf_per_cpu_ptr' with \`R1 type=scalar
expected=percpu_ptr_*' because we loaded the percpu symbol's
address as a plain 64-bit literal (src_reg=0). The kernel needs
the address to come from an ld_imm64 marked with
src_reg=BPF_PSEUDO_BTF_ID(3): the loader-side patches the imm
slots into the runtime address AND the verifier types the
destination register as the symbol's actual percpu_ptr_<T>.

End-to-end plumbing:

* bpf.lisp: \`+bpf-pseudo-btf-id+' (3) and \`emit-ld-btf-id'
  encode the two-slot instruction with imm0=btf_id and
  imm1=btf_obj_fd (0 for vmlinux).

* vmlinux.lisp: \`btf-find-var' walks BTF for a BTF_KIND_VAR
  (kernel global with type info) by name. Returns the type-id
  used as the ld_imm64 imm.

* lower.lisp: new builtin form \`(ld-btf-id BTF-ID)' lowers to a
  \`:mov VREG (:btf-id N)' IR insn. \`*whistler-builtins*' gains
  \`LD-BTF-ID' so the macro-expander leaves it alone.

* emit.lisp: \`emit-mov-insn' recognises \`(:btf-id N)' alongside
  \`(:imm N)' and routes through \`emit-ld-btf-id'. Reg-allocated
  to a register directly; stack-spill spills via R0 just like
  large literals do.

* codegen.lisp: \`lower-percpu-kaddr-call' tries BTF first
  (\`btf-find-var') and emits \`(per-cpu-ptr (ld-btf-id ID) cpu)'.
  Falls back to the prior kallsyms literal only when the symbol
  has no BTF entry (verifier will still reject those at load —
  the caller learns sooner via the helper's diagnostic).

ssa-opt's constant propagation only matches \`(:imm N)' arg shapes,
so \`(:btf-id N)' loads survive the optimizer intact. \`percpu_kaddr'
already had no other path through ssa-opt — no callers do arithmetic
on the typed pointer.

Resolves \`call.percpu_kaddr this cpu' modulo CAP_BPF.
Anthony Green2026-05-30 22:03:50 -0400 53e1c12f9cda0ab3076d7311dc14b540232688a4