Penguin/BIR Custom-Op Codegen & visitInstCustomOp
All symbols, addresses, and Python line numbers on this page apply to
neuronx_cc2.24.5133.0+58f8de22 (cp310). The three Penguin codegen methods live in the UNSTRIPPED Cython extensionsneuronxcc/starfish/penguin/targets/codegen/BirCodeGenLoop.cpython-310-x86_64-linux-gnu.so(classBirCodeGenLoop, the IMPL) andneuronxcc/starfish/penguin/targets/generated/BirCodeGenLoopGen.cpython-310-x86_64-linux-gnu.so(classBirCodeGenLoopGen, the GEN base). The single backend emitterCoreV2GenImpl::visitInstCustomOplives inlibwalrus.so. Addresses are pinned against the IDA decompile/disasm sidecars andnm. Evidence anchors: D-Q09 (the three Penguin codegen methods, full IDA ctree decompile + per-method disasm) and D-J14 (theCoreV2GenImplemitter census). Treat every address as version-pinned. See Build & Version Provenance.
Abstract
A custom op is the compiler's escape hatch for an arbitrary user kernel running on the 8-core Xtensa GPSIMD CPU cluster. Three distinct phases turn a NKI/Penguin custom-op carrier into wire bytes, and this page documents the middle phase — the IR-to-IR lowering that sits between 6.5.8 builtin_custom_op (which builds the penguin.ir.CustomOp carrier) and 11.5 the wire layout (which byte-encodes the resulting BIR node). The middle phase is BirCodeGenLoop's Penguin-level codegen: it allocates a bir::InstCustomOp via the birpy Opcode.CustomOp factory, copies seven scalar/shape fields off the Penguin carrier onto it, and binds each operand as an access pattern.
There are three codegen methods, not two — and the naming is a trap. BirCodeGenLoopGen.codegenSundaCustomOp (_69) is the GEN-base allocator: it is the single point where the BIR InstCustomOp is born (bb.addInstruction(Opcode.CustomOp(build_debuginfo("I-%s" % inst.id)))). The IMPL class overrides custom-op codegen with two sibling entry points, both of which delegate up to that allocator via super(): codegenCustomOp (_215) is the generic / pre-Cayman path (binds operands with the untyped self.addSeqAccess), and codegenSundaCustomOp (_259) is the Sunda/Cayman override (binds operands with the typed, directional self.addAP(…, isOutput=…) and opens with an arch-gate assertion). The dispatch chooses one of the two siblings by target uarch; it is not codegenCustomOp calling codegenSundaCustomOp.
The bar for this page: a reader understands (a) which of the three methods does what, (b) the exact field-copy order and operand-binding helper each path uses, (c) the arch gate that distinguishes the Sunda path, and (d) how the resulting BIR InstCustomOp reaches the single CoreV2GenImpl::visitInstCustomOp emitter that produces the 0x85/0x86 chain. Every method body, setter name, and helper register is pinned against the IDA decompile of these binaries.
| GEN allocator | BirCodeGenLoopGen.codegenSundaCustomOp (_69) @ 0x46d00, py309–312 |
| IMPL generic | BirCodeGenLoop.codegenCustomOp (_215) @ 0xc0840, py2601–2615 (7511 B) |
| IMPL Sunda override | BirCodeGenLoop.codegenSundaCustomOp (_259) @ 0x652d0, py3236 (12192 B) |
| BIR node built | bir::InstCustomOp via birpy module-global Opcode.CustomOp(debuginfo) |
| Debuginfo format | "I-%s" % inst.id (__pyx_kp_u_I_s, .rodata) |
| Operand bind (generic) | self.addSeqAccess(inst_bir, operand) — untyped sequence access |
| Operand bind (Sunda) | self.addAP(inst_bir, operand, isOutput=<bool>) — typed, directional |
| Arch gate (Sunda) | neuron_external_assert("self.target < Cayman", hardware_target=self.target.arch) |
| Backend emitter | CoreV2GenImpl::visitInstCustomOp @ 0x12613c0 → 0x85 header + 0x86 payloads |
| Evidence | D-Q09 (Penguin codegen) · D-J14 (CoreV2 emitter) |
NOTE — three "GPSIMD" name collisions, none of them this. The custom op on this page dispatches a kernel onto the programmable Xtensa CPU cluster (11.1) — a different subsystem from the
InstGPSIMDSB2SB(0xBF) Pool-alias SBUF↔SBUF mover (1.13). "Sunda" here is the gen3 DVE uarch family name, and "Cayman" is the arch-level threshold the Sunda path asserts against — neither is a GPSIMD engine. SORT / TOPK and arbitrary user NKI kernels are the canonical occupants of this custom-op path (11.2).
1. The three methods — allocator + two sibling IMPL entries
The MRO is BirCodeGenLoop (IMPL) → BirCodeGenLoopGen (GEN). nm confirms the GEN base exports exactly one codegen*CustomOp symbol — codegenSundaCustomOp (_69) — and no codegenCustomOp. The IMPL adds two: codegenCustomOp (_215) and an override codegenSundaCustomOp (_259). Both IMPL methods call super().codegenSundaCustomOp to reach the GEN allocator.
penguin.ir.CustomOp ← built upstream by NeuronCodegen.builtin_custom_op (6.5.8)
│ (.function_name, .lib_file_name, .ulib_to_{ucode,isa}_version,
│ .is_builtin, .srcs_shapes, .dsts_shapes, .srcs, .dsts)
▼ dispatch by target uarch
┌─────────────────────────────┬──────────────────────────────────────────┐
│ generic / pre-Cayman │ Sunda / Cayman │
│ codegenCustomOp (_215) │ codegenSundaCustomOp (_259, IMPL override)│
│ super()._69 alloc │ neuron_external_assert(< Cayman, arch) │
│ 7-field copy FIRST │ super()._69 alloc │
│ addSeqAccess per src/dst │ addAP(…,isOutput) per src/dst FIRST │
│ (untyped) │ 7-field copy │
└─────────────┬───────────────┴───────────────┬──────────────────────────┘
▼ ▼
BirCodeGenLoopGen.codegenSundaCustomOp (_69) @0x46d00 ← THE ALLOCATOR
bb.addInstruction(Opcode.CustomOp(build_debuginfo("I-%s" % inst.id)))
▼
bir::InstCustomOp (the birpy Opcode.CustomOp node added to the BasicBlock)
▼
CoreV2GenImpl::visitInstCustomOp @0x12613c0 → 0x85 header + (1+N)×0x86 payload (11.5)
GOTCHA — the two IMPL methods are SIBLINGS, not a chain. An earlier reading had
codegenCustomOp(_215) callcodegenSundaCustomOp(_259). The disasm shows_215'ssuper()getattr resolves the namecodegenSundaCustomOp(__pyx_n_s_codegenSundaCustomOp@0xc0ab6), and since the GEN base only defines_69under that name, thesuper()call lands on_69— the allocator — not on the IMPL_259. The dispatch (a target-method table, 5.x dispatch) picks_215or_259; the chosen one then calls the shared GEN allocator. CONFIRMED (D-Q09 — disasm of bothsuper()call sites).
2. The GEN allocator — codegenSundaCustomOp (_69)
This is the only place a BIR InstCustomOp is created. Both IMPL paths reach it via super(). The body is short and mechanical: format a debug label from inst.id, build a debuginfo object, then construct the birpy Opcode.CustomOp instruction and add it to the basic block.
/* BirCodeGenLoopGen.codegenSundaCustomOp (_69) @0x46d00 — py309..312 (annotated) */
PyObject *codegenSundaCustomOp(self, inst, bb) {
/* py310: id_str = str(inst.id) unless already a unicode object */
Attr = getattr(inst, "id"); /* __pyx_n_s_id @L530 */
id_str = PyObject_IsInstance(Attr, str) ? Attr : str(Attr);
/* py311: fmt = "I-%s" % id_str — the BIR-inst debug label */
fmt = PyUnicode_Format("I-%s", id_str); /* __pyx_kp_u_I_s @L554 (.rodata)*/
bd = lookup("build_debuginfo"); /* builtin/module global @L579/583 */
dbg = build_debuginfo(fmt, inst); /* passes the penguin inst */
/* py312: inst_bir = bb.addInstruction(Opcode.CustomOp(dbg)) */
add = getattr(bb, "addInstruction"); /* __pyx_n_s_addInstruction @L709 */
Op = lookup("Opcode"); /* module global @L835/839 */
ctor = getattr(Op, "CustomOp"); /* __pyx_n_s_CustomOp @L727 */
cust = ctor(dbg); /* birpy Opcode.CustomOp(dbg) */
return add(cust); /* → the new bir::InstCustomOp */
}
Opcodeis the module-globalneuronxcc.starfish.birpy.Opcodes(the GEN.rodatacarries both"neuronxcc.starfish.birpy.Opcodes"and the.generated.…spelling).Opcode.CustomOp(dbg)is thebirpyfactory for the BIR custom-op instruction.- The debug label format is
"I-%s"(verbatim.rodata__pyx_kp_u_I_s), not"I%s". This is the per-instruction identity string the BIR carries; the actual ABI dispatch handle (CustomOpFunctionId) is allocated later, in the backend (11.5 §6), not here.
NOTE — why a "Sunda"-named method is the generic allocator. The GEN base method is named
codegenSundaCustomOpbecause the code generator templates were emitted from the Sunda-arch description, but the body has no arch logic at all — it is a pure factory. The IMPL_259overrides it (same name) to add the arch gate and typed binding; the IMPL_215delegates to it under the inherited name viasuper(). CONFIRMED (D-Q09 — the GEN_69body has noneuron_external_assert, noaddAP, notargetread).
3. The generic path — codegenCustomOp (_215)
The pre-Cayman / generic lowering. Source py2601–2615. It (1) allocates via the GEN base, (2) copies the seven scalar/shape fields from the Penguin carrier onto the BIR inst, then (3) binds every operand with the untyped self.addSeqAccess. Note the order: fields first, operands second.
/* BirCodeGenLoop.codegenCustomOp (_215) @0xc0840 — py2601..2615 (annotated) */
PyObject *codegenCustomOp(self, inst, bb) {
/* py2601: inst_bir = super().codegenSundaCustomOp(inst, bb) → GEN _69 alloc */
proxy = builtin_super(BirCodeGenLoop, self); /* @0xc0a55 */
alloc = getattr(proxy, "codegenSundaCustomOp"); /* __pyx_n_s_… @0xc0ab6 → _69 */
inst_bir = alloc(inst, bb); /* the fresh bir::InstCustomOp */
/* py2604..2610: 1:1 field copy penguin.ir.CustomOp → BIR CustomOp (order pinned) */
inst_bir.setOpFunctionName (inst.function_name); /* L752 */
inst_bir.setOpLibFile (inst.lib_file_name); /* L801 */
inst_bir.set_ulib_to_ucode_version(inst.ulib_to_ucode_version); /* L855 ← ucode */
inst_bir.set_ulib_to_isa_version (inst.ulib_to_isa_version); /* L895 ← then isa*/
inst_bir.set_is_builtin (inst.is_builtin); /* L945 */
inst_bir.setSrcsShape (inst.srcs_shapes); /* L985 */
inst_bir.setDstsShape (inst.dsts_shapes); /* L1035 */
/* py2612: for src in inst.srcs: self.addSeqAccess(inst_bir, src) */
for (src in iter(inst.srcs)) /* __pyx_n_s_srcs */
self.addSeqAccess(inst_bir, src); /* __pyx_n_s_addSeqAccess @L1140*/
/* py2614: for dst in inst.dsts: self.addSeqAccess(inst_bir, dst) */
for (dst in iter(inst.dsts)) /* __pyx_n_s_dsts */
self.addSeqAccess(inst_bir, dst); /* __pyx_n_s_addSeqAccess @L1351*/
return None;
}
Key facts, all CONFIRMED against the decompile:
- The setter target is always
inst_bir(the GEN-alloc return); the value is always the matching attribute read offinst(the Penguin carrier). Each isgetattr(inst_bir, "setX")(getattr(inst, "x"))— a clean 1:1 copy. - Field order:
setOpFunctionName → setOpLibFile → set_ulib_to_ucode_version → set_ulib_to_isa_version → set_is_builtin → setSrcsShape → setDstsShape. The ulib setters are ucode-before-isa (py2606@ L855 precedespy2607@ L895). - The operand binder is
self.addSeqAccess, not a method onbbor oninst_bir. The getattr base isv130 = *__pyx_args=self(CONFIRMED —v130is assigned*__pyx_argsat the prologue). The call isself.addSeqAccess(inst_bir, operand)with empty kwargs. - The src/dst iterators take a fast path for
PyTuple_Type/PyList_Type, falling back toPyObject_GetIter— sosrcs/dstsmay be tuple, list, or any iterable.
GOTCHA —
addSeqAccessis the untyped binder. The generic path binds every operand the same way regardless of direction — there is no in/out tag. The directional split (which operand is an input vs the output) is reconstructed downstream from the operand position when the backend packs the access patterns. Only the Sunda path (§4) carries the in/out tag explicitly. CONFIRMED (D-Q09).
4. The Sunda/Cayman path — codegenSundaCustomOp (_259)
The IMPL override for the gen3 Sunda/Cayman uarch. Source py3236. It differs from the generic path in three ways: (1) it opens with an arch-gate assertion, (2) it binds operands with the typed, directional self.addAP(…, isOutput=…), and (3) it binds operands before the field copy (the reverse of _215's order). It calls the same GEN allocator via super().
/* BirCodeGenLoop.codegenSundaCustomOp (_259) @0x652d0 — py3236 (annotated) */
PyObject *codegenSundaCustomOp(self, inst, bb) {
/* ---- ARCH GATE: require the target uarch is at least Cayman/Sunda ---- */
t = self.target; /* __pyx_n_s_target @0x654f2 */
cmp = RichCompare(t, Cayman, <); /* t < Cayman (cached cmp 1271) */
args = ("neuronxcc", "EBCG", 1, cmp, /* n_u_neuronxcc / n_u_EBCG */
"self.target < Cayman", /* __pyx_kp_u_self_target_Cayman */
self.dl); /* __pyx_n_s_dl (device layer) */
kw = {"hardware_target": self.target.arch}; /* n_s_hardware_target @0x6567d */
neuron_external_assert(*args, **kw); /* @0x656d4 — the arch precondition */
/* ---- ALLOCATE via the GEN base ---- */
proxy = builtin_super(BirCodeGenLoop, self); /* @0x6573e */
inst_bir = proxy.codegenSundaCustomOp(inst, bb); /* → GEN _69 alloc @0x657a3 */
/* ---- OPERAND BINDING: typed access-pattern, directional ---- */
for (src in iter(inst.srcs)) /* __pyx_n_s_srcs_2 */
self.addAP(inst_bir, src, isOutput=False); /* addAP @0x65930; isOutput=False*/
for (dst in iter(inst.dsts)) /* __pyx_n_s_dst loop */
self.addAP(inst_bir, dst, isOutput=True); /* addAP @0x65a98; True @0x65b3c */
/* ---- FIELD COPY: the same 7 fields, same order as _215 ---- */
inst_bir.setOpFunctionName (inst.function_name); /* @0x65bb7 */
inst_bir.setOpLibFile (inst.lib_file_name); /* @0x65c56 */
inst_bir.set_ulib_to_ucode_version(inst.ulib_to_ucode_version); /* @0x65cf9 */
inst_bir.set_ulib_to_isa_version (inst.ulib_to_isa_version); /* @0x65d93 */
inst_bir.set_is_builtin (inst.is_builtin); /* @0x65e36 */
inst_bir.setSrcsShape (inst.srcs_shapes); /* @0x65ed0 */
inst_bir.setDstsShape (inst.dsts_shapes); /* @0x65f73 */
return inst_bir;
}
The arch gate, CONFIRMED token-by-token against the decompile/disasm:
neuron_external_assertis a module global (__pyx_n_s_neuron_external_assert, cached_pyx_dict_cached_value_1273).- The positional argument tuple is built (
PyTuple_New(6)) with[0]=u"neuronxcc",[1]=u"EBCG",[2]=int 1,[3]=<the t<Cayman compare result>,[4]=kp_u"self.target < Cayman"(the assert message),[5]=self.dl. - The keyword dict is
{"hardware_target": self.target.arch}— built viaPyDict_New()+PyDict_SetItemwith key__pyx_n_s_hardware_target(@0x6567d) and valueself.target.arch(__pyx_n_s_arch@ L705).
The operand binding, CONFIRMED:
- The binder is
self.addAP— getattr base isv6 = *__pyx_args=self(CONFIRMED in both loops; notbb, notinst_bir). - Each call passes a positional
(inst_bir, operand)plus a keywordisOutput. The src loop passesisOutput=False; the dst loop passesisOutput=True— the dst branch'sisOutputvalue is the literal_Py_TrueStruct_ptr(@0x65b3c), pinning the directional tag.
GOTCHA —
addAP'sisOutputis what produces the wire's in/out arg tags. The typed access-pattern binder tags each operand[in](src,isOutput=False) or[out](dst,isOutput=True). Downstream, the backend's per-payload encoder (11.5 §3) emits the output AP first (payload 0, the singleisOutput=Trueoperand) then theNinput APs — the directional split established here is exactly what the0x86payload ordering and the Xtensa SORT/TOPK leaf'sNEURON_ISA_TPB_CUSTOM_OP_ARG_TYPE_TENSOR[in]/[out]arg tags consume. CONFIRMED (D-Q09 binding) / STRONG (the binding→payload edge, via the J14 emitter).
QUIRK — Cython dedup suffixes are not distinct fields. The decompile shows
__pyx_n_s_srcs_2,srcs_shapes_2,dsts_shapes_2, and adstloop var. These are string-interning dedup suffixes for the same Python attributes (.srcs,.srcs_shapes,.dsts_shapes,.dst/.dsts) —.rodatacarries a single canonical spelling of each. They are not separate fields. CONFIRMED (D-Q09).
NOTE — the
EBCGtoken. Positional[1]of the assert is the.rodataunicodeEBCG— a codegen/build-config category label for the assertion (most plausibly an "External Backend Code Gen" / pass-id tag). The token is CONFIRMED in.rodata; its exact expansion is not byte-verifiable. SPECULATIVE (meaning) / CONFIRMED (token).
5. The seven fields — penguin.ir.CustomOp → BIR InstCustomOp
Both IMPL paths copy the same seven attributes. This is the inverse of 6.5.8 builtin_custom_op, which builds those attributes onto the penguin.ir.CustomOp carrier; the codegen here lowers them onto the BIR node. The table maps each Penguin attribute to its BIR setter and the downstream wire field it feeds.
Penguin CustomOp attr | BIR setter | wire (11.5) | Conf |
|---|---|---|---|
.function_name (op-name / ABI entry) | setOpFunctionName | header identity → CustomOpFunctionId (via registry) | CONFIRMED |
.lib_file_name (embedded .so) | setOpLibFile | registry lib id (off-wire, NEFF metadata) | CONFIRMED |
.ulib_to_ucode_version | set_ulib_to_ucode_version | ABI version handle | CONFIRMED |
.ulib_to_isa_version | set_ulib_to_isa_version | ABI version handle | CONFIRMED |
.is_builtin | set_is_builtin | builtin flag | CONFIRMED |
.srcs_shapes | setSrcsShape | input shape metadata (pairs with each input AP) | CONFIRMED |
.dsts_shapes | setDstsShape | output shape metadata (pairs with the output AP) | CONFIRMED |
.srcs (operand list) | addSeqAccess (_215) / addAP(…,isOutput=False) (_259) | one 0x86 payload per input AP | CONFIRMED |
.dsts (operand list) | addSeqAccess (_215) / addAP(…,isOutput=True) (_259) | the 0x86 output-AP payload | CONFIRMED |
GOTCHA — the op-name is copied, never resolved here.
setOpFunctionNamestamps thefunction_namestring onto the BIR inst verbatim; the binding of that name (and thelib_file_name) to the embedded Xtensa.soleaf is a bir/runtime concern (ModuleArtifactInfo/allocateCustomOpFunctionId, 11.5 §6). There is no name→.soresolution logic in_215or_259— the codegen is a pure field copy + operand bind. STRONG (D-Q09: no resolution code in either body; thefunction_name=op-name fact is from the upstream 6.5.8 emitter).
6. The single backend emitter — CoreV2GenImpl::visitInstCustomOp
Once the BIR InstCustomOp exists (built by _69, decorated by _215/_259), the backend lowers it to the 64-byte ISA wire. There is exactly one emitter — CoreV2GenImpl::visitInstCustomOp @ 0x12613c0 — and no CoreV3/CoreV4 override; gen3/gen4 backends inherit it verbatim. It is the largest of the CoreV2 long-tail encoders (1207 decompiled lines) and emits the 0x85 header + per-operand 0x86 payload chain that 11.5 byte-pins in full.
This page documents only how the codegen's output enters the emitter; the byte map is 11.5's subject. The points where this page and 11.5 must agree:
- The emitter reads
num_arguments = N(theInstCustomOpargument list length) and writes the headernum_payloadsas au16at offset+0x0Cvia the bounded settersub_123CA60("instr.num_payloads", destlea [r13+0Ch]), with the valueN + 2forN ≥ 1(header + output AP +Ninput APs) or1forN == 0. This page's operand-binding description (one binder call per src and per dst) is the upstream source of thatNand the one-output invariant.
CORRECTION (D-Q08) —
num_payloadsis at+0x0C, not+0x06. An earlier note here placed it at+0x06; that was a decompiler-misread ofsub_123CA60((_WORD*)hdr + 6, …)—hdris a_WORD*(u16), so+ 6is u16-pointer arithmetic =+0x0Cbytes. The encoder's store destination islea rdi,[r13+0Ch](machine bytes49 8d 7d 0c) @0x1262f75; the siblingnum_argumentssetter islea [r13+0Fh]@0x1262fbe. See 11.5 §2.1.
- The single output (the lone
dst/isOutput=Trueoperand) becomes payload 0; theNinputs (src/isOutput=False) become payloads1..N. The_259directional binding (§4) is what makes the output-first ordering meaningful. - The legality the emitter enforces (
"Custom ops cannot have more than 1 output", SBUF/HBM operand location, noTensorIndirectAP, ≤254 unique functions — the0xFEcap,0xFF= unset) is downstream of the codegen — the codegen does not pre-check these; the backendvisitInstCustomOpdoes.
/* CoreV2GenImpl::visitInstCustomOp @0x12613c0 — how the BIR node enters the wire (sketch) */
void visitInstCustomOp(InstCustomOp &inst) {
int N = arg_list_length(inst); /* = num_arguments, set by the codegen binds */
/* one 0x85 header: num_payloads(+0x0C)=N?N+2:1, FunctionId(+0x0E), num_arguments(+0x0F)=N */
/* one 0x86 payload per operand: OUTPUT AP first (the isOutput=True dst), then N input APs */
/* fwrite 0x40 per word; full byte map in 11.5 */
}
NOTE — BIR/NKI kernels are lowered to CustomOp before the backend. There is no
CoreV2GenImpl::visitInstBIRKernel/visitInstNKIKernel— those visitors exist only in the verifier/simulator. Whole BIR/NKI kernels (IT54/IT55) are lowered toInstCustomOpbefore backend codegen and emitted through this same0x85/0x86path. So this single emitter handles both explicit custom ops and lowered-away kernels. CONFIRMED (D-J14 — no Kernel emitter in anyCoreV*GenImpl).
7. End-to-end chain
NeuronCodegen.builtin_custom_op (6.5.8, NKI ENTRY)
│ op-name=function_name; srcs/dsts; lib_file; ulib↔{isa,ucode}; is_builtin; shapes
▼ builds penguin.ir.CustomOp
dispatch by target uarch (5.x target-method table)
├─ generic / pre-Cayman → codegenCustomOp (_215) @0xc0840
│ super()._69 alloc → Opcode.CustomOp(build_debuginfo("I-%s" % inst.id))
│ → 7-field copy (ucode before isa) → self.addSeqAccess per src & per dst
└─ Cayman / Sunda → codegenSundaCustomOp (_259) @0x652d0 (IMPL override)
neuron_external_assert("self.target < Cayman", hardware_target=self.target.arch)
super()._69 alloc (same Opcode.CustomOp)
→ self.addAP(inst_bir, src, isOutput=False) / addAP(inst_bir, dst, isOutput=True)
→ same 7-field copy
▼ bir::InstCustomOp (added to the BasicBlock by the GEN allocator _69)
CoreV2GenImpl::visitInstCustomOp @0x12613c0 (the single backend emitter)
▼ 0x85 CUSTOM_OP_HEADER (num_payloads@+0x0C, FunctionId@+0x0E, num_arguments@+0x0F)
+ (1+N)×0x86 CUSTOM_OP_PAYLOAD (output AP first, then N input APs) ← 11.5 byte map
Xtensa CPU leaf: SORT / TOPK / user NKI kernel, ARG_TYPE_TENSOR [in]/[out] arg tags
FunctionId → .so binding: bir/runtime ModuleArtifactInfo (off-wire NEFF metadata)
8. Adversarial self-verification
Five strongest claims, each re-checked against the binary:
_215delegates to the GEN allocator_69viasuper().codegenSundaCustomOp, not to the IMPL_259. Verified: the_215decompile at L611 reads__pyx_n_s_codegenSundaCustomOpoff thebuiltin_superproxy;nmshows the GEN base exports only_69under that name and nocodegenCustomOp, so the resolved target is_69. CONFIRMED._215binds operands withself.addSeqAccess;_259withself.addAP(…, isOutput=…). Verified:_215decompile has__pyx_n_s_addSeqAccessat L1140 (srcs) and L1351 (dsts) and noaddAP;_259has__pyx_n_s_addAPat L904/L1044 and noaddSeqAccess. The getattr base is*__pyx_args(self) in both (v130in_215,v6in_259). CONFIRMED.- The ulib version setters are ucode-before-isa. Verified: in
_215,set_ulib_to_ucode_versionis at L855 andset_ulib_to_isa_versionat L895 (ucode first);_259mirrors this (@0x65cf9then0x65d93). CONFIRMED — corrects an earlier isa-first listing. _259's arch gate isneuron_external_assertwith message"self.target < Cayman"and kwarghardware_target=self.target.arch. Verified:__pyx_n_s_neuron_external_assert(L584/588),__pyx_kp_u_self_target_Cayman(L675),__pyx_n_u_EBCG/__pyx_n_u_neuronxccin the arg tuple (L665–670), and__pyx_n_s_hardware_target@0x6567dpaired withtarget.archviaPyDict_New/PyDict_SetItem. CONFIRMED.- The GEN allocator uses the debug format
"I-%s" % inst.idandOpcode.CustomOp. Verified:_69decompile reads__pyx_n_s_id(L530),__pyx_kp_u_I_s(L554, the"I-%s"literal),build_debuginfo(L579/583),addInstructiononbb(L709), andOpcode→CustomOp(L835/839 + L727). CONFIRMED —"I-%s", not"I%s".
Two findings worth flagging:
- F1 — the "Sunda allocator" naming trap. The GEN base method named
codegenSundaCustomOp(_69) is the generic allocator with no arch logic; the IMPL_215(generic path) reaches it via the inherited name, while the IMPL_259(Sunda path) overrides it. So "Sunda" in the GEN symbol name is a template-origin artifact, not a runtime arch gate — the real arch gate is theneuron_external_assertonly_259carries. - F2 — directionality is established in codegen, consumed at the wire. The output-first
0x86payload ordering and the Xtensa leaf's[in]/[out]arg tags both trace back to_259'saddAP(…, isOutput=True/False)split. The generic_215path binds untyped (addSeqAccess), so on pre-Cayman targets the in/out split is reconstructed from operand position at the backend, not carried explicitly through the IR.
9. Confidence ledger
- CONFIRMED (nm + full IDA ctree decompile + per-method disasm of
BirCodeGenLoop/BirCodeGenLoopGen): all three symbols/addresses/py-lines;_215's full body, exact setter order,super().codegenSundaCustomOp→_69delegation, andaddSeqAccessper src/dst;_259's arch gate (neuron_external_assert("self.target < Cayman", hardware_target=arch)),super()._69alloc,addAP(…, isOutput=False/True)directional binding, and 7-field copy; the GEN_69allocator ("I-%s" % inst.id→bb.addInstruction(Opcode.CustomOp(build_debuginfo(...)))); the seven-field penguin→BIR map; the singleCoreV2GenImpl::visitInstCustomOp@0x12613c0(D-J14) and the no-CoreV3/CoreV4-override fact. - STRONG: the binding→payload edge (
addAP/addSeqAccess→0x86payload, output-first) and the field-copy→0x85-header edge, both via the J14 emitter / 11.5 byte map; thatfunction_nameis the op-name/ABI entry (from the upstream 6.5.8 emitter); that the arch-gate predicate is exactlytarget < Cayman(the.rodatamessage is CONFIRMED; whether the assert fires on the predicate or its negation is a message-wording detail). - INFERRED / DEFER: the dispatch that selects
_215(generic) vs_259(Sunda) by target uarch (a target-method table, 5.x target abstraction, not byte-traced here); thesrc-branchisOutput=Falsebeing the cachedFalsesingleton (thedst-branch's explicit_Py_TrueStruct_ptrmakesFalseforsrcthe only consistent read); the exactbuild_debuginfosignature beyond("I-%s" % id, inst). - SPECULATIVE: the
EBCGtoken expansion (CONFIRMED as a.rodatatoken; meaning unverifiable).
No fabricated symbols or offsets; every address is cited from the named binary's sidecar.
Cross-References
- 6.5.8 NeuronCodegen
builtin_custom_opEmitter — the NKI front door that builds thepenguin.ir.CustomOpcarrier these codegen methods lower; this page is its inverse. - 11.5 CUSTOM_OP Wire Byte-Layout (0x85 / 0x86) — the byte map of the
CoreV2GenImpl::visitInstCustomOpoutput; pinsnum_payloads@+0x0C,num_payloads = N+2, FunctionId @+0x0E, num_arguments @+0x0F. - 2.22 Collective / GPSIMD / CustomOp Encoding — the sibling ISA-encoding page; §10 first sketched the
0x85/0x86pair. - 11.1 The GPSIMD CPUs: 8-core Xtensa ELF Layout — the Xtensa CPU cluster the lowered custom op dispatches a kernel onto.
- 11.2 The Bitonic SORT / TOPK Builtin Algorithm — SORT/TOPK, the canonical custom-op occupants (no dedicated opcode; ride
0x85/0x86). - 11.7 FindCustomOpData Staging & FunctionId Binding — how the header's FunctionId byte maps to
(libfile, function-name)viaModuleArtifactInfo. - 1.13 GPSIMD Engine — the Pool-Alias Cross-Core SB2SB Mover — the other "GPSIMD" (the
0xBFPool-alias mover); resolves the name collision.