pelican::Expr Core: Affine / Binary Nodes & Member Byte-Offsets
All addresses and offsets on this page apply to
neuronx_cc2.24.5133.0+58f8de22, the cp310 build oflibBIR.so(md512bb979f7ca41248252abb0f16b2da98, sha2562387d0d4…dc5a4cfa, 22151 functions). The cp311/cp312 builds carry the identical ABI but their virtual addresses drift; member offsets, object sizes, and kind tags are version-stable.
Abstract
pelican::Expr is the polymorphic root of Penguin's quasi-affine expression algebra — every tensor index, address term, stride, division, and predicate atom is an Expr subtree. The shape of that algebra (c + Σ cᵢ·idxᵢ with floordiv/mod locals, plus the collective-cyclic family) is documented from the Penguin side in Penguin AffineExpr Algebra, and the LLVM-RTTI'd class tree — 29 typeinfo records, 39 vtable slots — is the remit of the pelican::Expr hierarchy page. This page is the third leg: the byte-level struct layout of the affine/binary core nodes — the universal object header that every node shares, the BinaryExpr two-field payload, the AffineExpr/SumExpr compound bodies, and the integer kind tags that discriminate them.
The central reverse-engineering finding is that the whole hierarchy is built on two shared constructors and one fixed header. pelican::Expr is a RefCountedObject; its base constructor sub_5FE310 lays down a four-field header — vptr@0x00, refcount@0x08, kind@0x10, ctx@0x18 — identical for every node. Binary nodes (MultExpr, FloorDivExpr, ModuloExpr, CCDivExpr, CCModExpr) chain through a second base constructor sub_60F4B0 that appends operand@0x20 and intval@0x28. Compound nodes (AffineExpr, SumExpr) skip the binary base and grow a variable-length operand body at +0x20 — but the two use different containers: AffineExpr holds a boost::container::vector<pair<RefPtr<AffineIdx>, long>> (heap begin/end/cap triple, no inline buffer), while SumExpr holds an llvm::SmallVector<RefPtr<Expr>> with 8 inline slots (see the per-node sections below). No node moves a base field; subclasses only append.
CORRECTION — an earlier draft described the
AffineExprterm container as anllvm::SmallVector<pair<RefPtr<AffineIdx>, long>, 16>. That is wrong — onlySumExpr(kind 18) uses aSmallVector.AffineExpr(kind 17) uses aboost::container::vector. CONFIRMED fromlibBIR.so: factorysub_62BCF0@0x62bcf0zeroes a begin/end/cap triple ([e+0x20]=0; movups [e+0x28],xmm0) with no self-pointer to an inline buffer and no capacity literal — the boost empty-init, not the SmallVectorbegin = e+0x30; cap = 8pattern thatSumExpr'ssub_62C8D0does show. The populate-from-terms factorysub_635860@0x635860carries verbatim boost assert strings naming both the element typestd::pair<pelican::RefPtr<pelican::AffineIdx>, long int>and the source pathboost/container/vector.hpp, and frees the backing store unconditionally (nobegin != inline-bufferguard).
A reimplementer who reproduces the header, the two payload shapes, and the kind-tag literals can rebuild any core node.
Every offset below is anchored to a real mov [reg+N] store in a constructor (write side) and, where possible, cross-checked against an accessor or dispatch read (cmp dword [reg+10h], …). There is no pelican::* struct in IDA's type database — the binary carries only ELF/libc types — so this table is the definition.
Reimplementation contract
To rebuild a core node a reimplementer must reproduce:
- The universal 4-field header
{vptr@0, refcount@8, kind@0x10 (int32), ctx@0x18}written by theExprbase ctor, withrefcountinitialised to1and the vptr set toExpr-vtable + 0x10(later overwritten by the derived vtable). - The
BinaryExprpayload{operand@0x20, intval@0x28}written by the binary base ctor; the second field doubles as aRefPtr<Expr>operand or a rawlong(denominator / modulus), per node. - The kind tags
17(AffineExpr) ·18(SumExpr) ·23(MultExpr) ·25(FloorDivExpr) ·26(ModuloExpr) ·27(CCDivExpr) — plus28(CCModExpr),2(CCGetRankExpr),3(IndirectArgExpr) — read as a 4-byte field at+0x10by every dispatcher. - The object sizes:
0x30for the plain binary nodes,0x38for the CC binary nodes (one extra word),0x40forAffineExpr,0x70forSumExpr. - The
denom > 0invariant the div-like factories enforce before publishing the node.
| Defining binary | libBIR.so (cp310), 22151 functions |
| Expr base ctor | sub_5FE310 @ 0x5fe310 (30 bytes) — sets header |
| BinaryExpr base ctor | sub_60F4B0 @ 0x60f4b0 (82 bytes) — sets operand/intval |
| Expr vtable | _ZTVN7pelican4ExprE @ 0x90a920; vptr = symbol +0x10 = 0x90a930 |
| BinaryExpr vtable | _ZTVN7pelican10BinaryExprE @ 0x90be00; vptr = 0x90be10 |
| Kind tags (core) | 17 AffineExpr · 18 SumExpr · 23 MultExpr · 25 FloorDivExpr · 26 ModuloExpr · 27 CCDivExpr |
| Source path (asserts) | neuronxcc/pelican/include/pelican/IR/AffineExpr.h |
NOTE — the base-class chain (which node derives from
BinaryExprvsCompoundExprvsExpr) is owned by the hierarchy page. Here it is inferred from construction evidence only: a node that chains throughsub_60F4B0is aBinaryExpr; a node built directly onsub_5FE310with a vector body is aCompoundExpr. That inference agrees with the typeinfo tree but is restated, not re-derived.
The page proceeds: the universal header (§1), the BinaryExpr payload and the binary nodes (§2), the compound nodes AffineExpr/SumExpr (§3), the consolidated member-offset map (§4), the kind-tag census (§5), and the adversarial self-verification (§6).
1. The universal header
The base constructor
Every pelican::Expr node, regardless of subclass, begins life in sub_5FE310 — the pelican::Expr (≡ RefCountedObject) base constructor. It is 30 bytes and stores exactly four fields. CONFIRMED from the raw disassembly:
; sub_5FE310 @ 0x5fe310 — pelican::Expr::Expr(this, int kind, PelicanContext* ctx)
0x5fe310: lea rax, _ZTVN7pelican4ExprE ; Expr vtable @ 0x90a920
0x5fe317: mov qword [rdi+8], 1 ; refcount@0x08 = 1
0x5fe31f: mov [rdi+10h], esi ; kind@0x10 = <int kind> (32-bit!)
0x5fe322: add rax, 10h ; vptr = vtable + 0x10 (past RTTI header)
0x5fe326: mov [rdi+18h], rdx ; ctx@0x18 = PelicanContext*
0x5fe32a: mov [rdi], rax ; vptr@0x00
0x5fe32d: retn
Reconstructed as pseudocode:
// sub_5FE310 @ 0x5fe310 — pelican::Expr::Expr
void Expr_ctor(Expr *this, int kind, PelicanContext *ctx) {
this->refcount = 1; // [this+0x08]
this->kind = kind; // [this+0x10] (int32, NOT a full qword)
this->ctx = ctx; // [this+0x18]
this->vptr = &Expr_vtable + 2; // [this+0x00] = 0x90a930
}
Header field map
| Offset | Width | Field | Evidence |
|---|---|---|---|
0x00 | 8 | vptr | mov [rdi], rax where rax = _ZTVN7pelican4ExprE + 0x10 |
0x08 | 8 | refcount | mov qword [rdi+8], 1 — initialised to 1 |
0x10 | 4 | kind (ExprKind int) | mov [rdi+10h], esi — 32-bit store from esi |
0x14 | 4 | (padding / unused) | no store; 0x18 is 8-aligned |
0x18 | 8 | ctx (PelicanContext*) | mov [rdi+18h], rdx |
GOTCHA —
kindis a 32-bit field (mov [rdi+10h], esi, and every reader usescmp dword [reg+10h], …). The high 4 bytes at+0x14are never written by the base ctor and act as padding before the 8-alignedctx. Treat the discriminator asint32, notint64.
QUIRK — the vptr stored by the base ctor is the
Exprbase vtable (0x90a930), not the concrete node's. Every concrete factory overwrites[obj+0]with the derived vtable immediately after the base ctor returns (e.g.MultExprwritesoff_90B4E8). A node briefly carries the base vtable mid-construction — relevant only if a constructor throws (the unwind path resets the vptr to the base beforeoperator delete).
The refcount@0x08 confirms pelican::Expr : pelican::RefCountedObject: the typeinfo _ZTIN7pelican16RefCountedObjectE exists at 0x90a8f0, the increment/decrement helpers are sub_638F80 (retain) and sub_638FB0 (release, returns "was-last"), and every factory's tail calls sub_638F80(obj + 8) — i.e. it bumps the refcount field at +0x08, the second proof of the offset.
2. BinaryExpr & the binary nodes
The binary base constructor
MultExpr, FloorDivExpr, ModuloExpr, CCDivExpr, and CCModExpr are BinaryExpr nodes. They chain through sub_60F4B0, which calls the Expr base ctor and then appends two fields. CONFIRMED from disassembly:
; sub_60F4B0 @ 0x60f4b0 — pelican::BinaryExpr::BinaryExpr(this, int kind, ctx, RefPtr lhs, long rhs)
0x60f4bd: call sub_5FE310 ; header: vptr/refcount/kind/ctx
0x60f4c2: lea rax, _ZTVN7pelican10BinaryExprE ; BinaryExpr vtable @ 0x90be00
0x60f4c9: mov [rbx+20h], rbp ; operand@0x20 = lhs (4th arg, rcx)
0x60f4cd: add rax, 10h
0x60f4d1: mov [rbx], rax ; vptr := BinaryExpr vtable + 0x10
; … if (lhs != 0) retain(lhs+8) …
0x60f4df: mov [rbx+28h], r12 ; intval@0x28 = rhs (5th arg, r8)
// sub_60F4B0 @ 0x60f4b0 — pelican::BinaryExpr::BinaryExpr
void BinaryExpr_ctor(BinaryExpr *this, int kind, PelicanContext *ctx,
RefPtr<Expr> lhs, long rhs) {
Expr_ctor(this, kind, ctx); // header @ 0x00..0x18
this->operand = lhs; // [this+0x20]
this->vptr = &BinaryExpr_vtable + 2;
if (lhs) retain(&lhs->refcount); // sub_638F80(lhs + 8)
this->intval = rhs; // [this+0x28]
}
NOTE — the field at
+0x28is namedintvalhere but is polymorphic in role: forMultExprit is the integer coefficient; forFloorDivExpr/ModuloExprit is the denominator/modulus (a rawlong); forCCDivExpr/CCModExprit is the group-size denominator. The first operand+0x20is always aRefPtr<Expr>(retained). This single{RefPtr, long}pair is the entireBinaryExprpayload — there is no secondRefPtrslot; binary nodes are expr ⊕ constant, not expr ⊕ expr.
The five binary factories
Each concrete binary node has a factory that operator news the object, calls sub_60F4B0 with its kind tag, then publishes the derived vtable. CONFIRMED — kind literals and operator new sizes read directly from each factory's disassembly (mov edi, <size> before __Znwm, mov esi, <kind> before the base-ctor call):
| Factory | new size | kind tag | Final vtable | Node |
|---|---|---|---|---|
sub_62BAA0 @ 0x62baa0 | 0x30 | 0x17 = 23 | off_90B4E8 (MultExpr vt 0x90b4d8) | MultExpr |
sub_62B7A0 @ 0x62b7a0 | 0x30 | 0x19 = 25 | off_90B8D0 (FloorDivExpr vt 0x90b8c0) | FloorDivExpr |
sub_62B830 @ 0x62b830 | 0x30 | 0x1A = 26 | off_90BA20 (ModuloExpr vt 0x90ba10) | ModuloExpr |
sub_62B8C0 @ 0x62b8c0 | 0x38 | 0x1B = 27 | off_90BB70 (CCDivExpr vt 0x90bb60) | CCDivExpr |
sub_62B960 @ 0x62b960 | 0x38 | 0x1C = 28 | off_90BCC0 (CCModExpr vt 0x90bcb0) | CCModExpr |
// sub_62BAA0 @ 0x62baa0 — pelican::MultExpr factory (kind 23)
MultExpr *make_MultExpr(PelicanContext *ctx, RefPtr<Expr> sub, long coef) {
MultExpr *e = operator new(0x30);
BinaryExpr_ctor(e, 23, ctx, sub, coef); // sub_60F4B0
e->vptr = &MultExpr_vtable + 2; // off_90B4E8
if (e) retain(&e->refcount); // sub_638F80(e + 8)
return e;
}
GOTCHA — the div-like factories (
FloorDivExpr,ModuloExpr,CCDivExpr,CCModExpr) transiently storeunk_90B638— theDivLikeExprvtable (0x90b628 + 0x10) — before overwriting it with the concrete vtable.DivLikeExpris the abstract intermediate base for the div family; its vtable appears in the byte stream but no object is ever published with it. All four div factories share the assert stringAffineExpr.h:711: denom > 0and throw"Unsupported denom for DivLikeExpr!"if the denominator is≤ 0— the construction-time invariant onintval@0x28.
NOTE —
CCModExprfactory anchor. Thesub_62B960row is byte-pinned inlibBIR.so(cp310):62b974: bf 38 00 00 00 mov $0x38,%edi(size0x38) →62b982: call _Znwm@plt(operator new) →62b990: be 1c 00 00 00 mov $0x1c,%esi(kind0x1C= 28) →62b9b8: lea 0x2e02f1(%rip),%rax # 90bcb0then62b9cb: mov %rax,0x0(%rbp)(publishes vtable0x90bcb0) →62b9bf: mov %r13,0x30(%rbp)(replica_groups_id@+0x30). This is a distinct factory fromsub_62B830(ModuloExpr,0x30bytes / kind0x1A=26), so kind-28 is not shared withModuloExpr. The same node has a CERTAIN analogue in thelibwalrusframe —createCCModExpr(the libwalrus VA for the same0x38/kind-28/0x90bcb0-class constructor) — so the two frames cross-confirm the kind-tag.
CORRECTION — the Penguin-algebra page sketches
Sum/Mult/Modulo/FloorDivas direct children ofAffineExpr. The binary disagrees:MultExpr/FloorDivExpr/ModuloExpr/CCDivExpr/CCModExprchain through theBinaryExprbase ctor (sub_60F4B0), not theAffineExprbody. The clean affine-combination node (AffineExpr, kind 17) and the operand-listSumExpr(kind 18) are the compound nodes; the multiplicative/division nodes are binary. The vtable/typeinfo evidence (MultExprtypeinfo at0x90b448, distinct fromAffineExprat0x90aa80) is on the hierarchy page.
The CC binary nodes' extra word
CCDivExpr (27) and CCModExpr (28) are 0x38-byte objects — one word longer than the plain binary nodes — because they carry a replica_groups_id at +0x30. CONFIRMED: both factories write v10[6] = a5 ([obj+0x30]) after the base ctor.
| Offset | Field (CC binary) | Evidence |
|---|---|---|
0x00–0x18 | universal header | sub_5FE310 |
0x20 | operand (RefPtr<Expr>, the rank/index sub-expr) | sub_60F4B0 |
0x28 | intval (group size / denominator, > 0) | sub_60F4B0 |
0x30 | replica_groups_id (long) | mov [obj+0x30], a5 in factory |
3. Compound nodes: AffineExpr & SumExpr
The two compound core nodes skip the BinaryExpr base and build a variable-length operand body directly on the Expr header.
AffineExpr (kind 17) — object size 0x40
AffineExpr is the canonical affine combination c + Σ cᵢ·idxᵢ. Its body is a boost::container::vector<pair<RefPtr<AffineIdx>, long>> (the term list) plus a constant. CONFIRMED from sub_62BCF0 @ 0x62bcf0 (the empty-with-const factory) and sub_635860 @ 0x635860 (the populate-from-terms factory):
// sub_62BCF0 @ 0x62bcf0 — pelican::AffineExpr factory (empty body, kind 17)
AffineExpr *make_AffineExpr(PelicanContext *ctx, long c) {
AffineExpr *e = operator new(0x40);
Expr_ctor(e, 17, ctx); // sub_5FE310 — header
*(qword*)(e + 0x20) = 0; // terms.begin = null
*(qword*)(e + 0x38) = c; // const = c
e->vptr = &AffineExpr_vtable + 2; // off_90ABF0 (0x90abe0 + 0x10)
*(oword*)(e + 0x28) = 0; // terms.end / terms.cap = 0
if (e) retain(&e->refcount);
return e;
}
| Offset | Width | Field (AffineExpr) | Evidence |
|---|---|---|---|
0x00–0x18 | — | universal header | sub_5FE310 |
0x20 | 8 | terms.begin (vector data ptr) | [e+0x20] = 0 then filled by sub_62B490 |
0x28 | 8 | terms.end / size | oword [e+0x28] = 0; reader at +0x28 in sub_635860 |
0x30 | 8 | terms.capacity | second half of the oword zeroing |
0x38 | 8 | const (long) | [e+0x38] = c (sub_62BCF0) / = a4 (sub_635860) |
NOTE — the term list is a
boost::container::vectorofpair<RefPtr<AffineIdx>, long>(16-byte elements: the index ref + its integer coefficient).sub_635860's asserts name the element type verbatim —std::pair<pelican::RefPtr<pelican::AffineIdx>, long int>— and the boost source pathboost/container/vector.hpp. The factory copies an input term vector, thenerases zero-coefficient terms in place before publishing.
SumExpr (kind 18) — object size 0x70
SumExpr is the operand-list compound — a sum of arbitrary sub-Exprs — backed by an llvm::SmallVector<RefPtr<Expr>> with 8 inline slots. CONFIRMED from sub_62C8D0 @ 0x62c8d0:
// sub_62C8D0 @ 0x62c8d0 — pelican::SumExpr factory (kind 18)
SumExpr *make_SumExpr(PelicanContext *ctx, SmallVector<RefPtr<Expr>> *src) {
SumExpr *e = operator new(0x70);
Expr_ctor(e, 18, ctx); // sub_5FE310 — header
*(qword*)(e + 0x20) = e + 0x30; // ops.begin = inline buffer
e->vptr = &SumExpr_vtable + 2; // off_90B788 (0x90b778 + 0x10)
*(qword*)(e + 0x28) = 0x800000000; // size=0 (lo32), capacity=8 (hi32)
// … copy src elements into the SmallVector, retaining each …
}
| Offset | Width | Field (SumExpr) | Evidence |
|---|---|---|---|
0x00–0x18 | — | universal header | sub_5FE310 |
0x20 | 8 | ops.BeginX (SmallVector data ptr) | [e+0x20] = e+0x30 (points at inline buffer) |
0x28 | 4 | ops.Size | low dword of 0x800000000 → 0 |
0x2C | 4 | ops.Capacity | high dword → 8 |
0x30–0x6F | 64 | inline element buffer (8 × RefPtr<Expr>) | ops.begin aimed here; operator delete(e, 0x70) in dtor |
QUIRK —
SumExpr's SmallVector is inline-first:ops.begin@0x20initially points into the object itself (e + 0x30). The destructor (sub_616FB0/sub_6169A0) onlyfree()s the buffer whenops.begin != e + 0x30— i.e. when the sum spilled past 8 terms and heap-promoted. The0x70object size is exactly header(0x20) + SmallVector control(0x10) + 8 inlineRefPtrslots(0x40).
4. The consolidated member-offset map
Every cell is a field written by a constructor at the stated offset; blank = field does not exist in that node. Header rows (0x00–0x18) are universal — written by sub_5FE310 for all nodes.
| Offset | Expr base | BinaryExpr (Mult/Floor/Modulo) | CC binary (CCDiv/CCMod) | AffineExpr (k17) | SumExpr (k18) |
|---|---|---|---|---|---|
0x00 | vptr | vptr | vptr | vptr | vptr |
0x08 | refcount (=1) | refcount | refcount | refcount | refcount |
0x10 | kind (int32) | kind | kind | kind | kind |
0x14 | (pad) | (pad) | (pad) | (pad) | (pad) |
0x18 | ctx | ctx | ctx | ctx | ctx |
0x20 | — | operand (RefPtr<Expr>) | operand | terms.begin | ops.begin |
0x28 | — | intval (long) | intval (group size) | terms.end/size | ops.size(lo32) cap(hi32) |
0x30 | — | — | replica_groups_id | terms.capacity | inline buf[0] |
0x38 | — | — | — | const (long) | inline buf[1] |
0x40+ | — | — | — | (end, 0x40) | inline buf[2…7] |
| size | — | 0x30 | 0x38 | 0x40 | 0x70 |
The two-VA-frame artifact applies: the
nm-style symbol VAs above (e.g.0x5fe310) are from the per-binary frame; internal per-symbol sidecar offsets in IDA's split exports may use a second frame. All addresses on this page are the binary-VA frame, cross-checked against the disasm sidecars whose filenames embed the same VA.
5. The kind-tag census
The ExprKind discriminator at +0x10 is a dense small-integer enum. The core affine/binary band is 17–28; the collective leaves CCGetRankExpr/IndirectArgExpr sit low at 2/3. CONFIRMED kinds are pinned to a mov esi, <lit> immediately preceding a base-ctor call in the named factory:
| Kind | Hex | Node | Pinned at | Base ctor |
|---|---|---|---|---|
| 2 | 0x02 | CCGetRankExpr | CCGetRankExpr::create @ 0x3bebf0 | sub_5FE310 |
| 3 | 0x03 | IndirectArgExpr | IndirectArgExpr::create @ 0x3becf0 | sub_5FE310 |
| 17 | 0x11 | AffineExpr | sub_62BCF0, sub_635860 | sub_5FE310 |
| 18 | 0x12 | SumExpr | sub_62C8D0 | sub_5FE310 |
| 23 | 0x17 | MultExpr | sub_62BAA0 | sub_60F4B0 |
| 25 | 0x19 | FloorDivExpr | sub_62B7A0 | sub_60F4B0 |
| 26 | 0x1A | ModuloExpr | sub_62B830 | sub_60F4B0 |
| 27 | 0x1B | CCDivExpr | sub_62B8C0 | sub_60F4B0 |
| 28 | 0x1C | CCModExpr | sub_62B960 | sub_60F4B0 |
NOTE — the six tags the title calls out — 17, 18, 23, 25, 26, 27 — are exactly
{AffineExpr, SumExpr, MultExpr, FloorDivExpr, ModuloExpr, CCDivExpr}. Tag28(CCModExpr) completes the binary band;24is not observed among the affine/binary factories (a gap in the captured set — possiblyDivLikeExpras an abstract tag, never constructed). The low tags2/3belong to the collective/indirect leaves built directly on the simple base.
The read side independently confirms both the field offset and the tag values. isV1 (0x3b4110) dispatches on the discriminator:
// isV1 @ 0x3b4110 — version-1 affine-form predicate (reads kind@0x10)
v3 = *(int32*)( *(qword*)expr_ref + 0x10 ); // kind@0x10
if (v3 == 17) return true; // AffineExpr
if (v3 <= 0x11) return (v3 - 2 <= 1); // CCGetRank(2)/IndirectArg(3) band
if (v3 - 25 > 2) return false; // outside FloorDiv/Modulo/CCDiv(25..27)
return *(int32*)( operand_at_0x20->kind ) == 17; // recurse into operand@0x20
This single function pins three claims at once: kind is a 4-byte field at +0x10; the div-like band is the contiguous range 25..27; and the binary operand lives at +0x20 (v2[4]). bir::QuasiAffineExpr::isSimpleAffineExpr (0x3b5a20) reduces to *(int32*)(expr->kind) == 17, and QuasiAffineExprIterator::traverseChildren (0x3b07d0) switches on +0x10 against 0x11/0x12 — all reading the same field.
6. Adversarial self-verification
The five strongest claims, each re-checked against the binary from an independent site:
-
Universal header
{vptr@0, refcount@8, kind@0x10, ctx@0x18}. — CONFIRMED. Write side:sub_5FE310disasm stores[rdi+8]=1,[rdi+10h]=esi,[rdi+18h]=rdx,[rdi]=Expr_vtable+0x10. Read side:isV1/isSimpleAffineExpr/traverseChildrenall read[expr+0x10]as the kind; every factory's tailretain(obj+8)confirmsrefcount@0x08. No ambiguity. -
refcount@0x08initialised to1(≡RefCountedObject). — CONFIRMED.mov qword [rdi+8], 1in the base ctor;_ZTIN7pelican16RefCountedObjectE@0x90a8f0exists; retainsub_638F80/releasesub_638FB0operate onobj + 8. -
BinaryExprpayload{operand@0x20, intval@0x28}. — CONFIRMED.sub_60F4B0disasm:mov [rbx+20h], rbp(lhs),mov [rbx+28h], r12(rhs). Read side:isV1dereferencesv2[4](=+0x20) as the operand and recurses on its kind. -
Kind tags
17/18/23/25/26/27(+28/2/3). — CONFIRMED. Each tag read as themov esi, <imm>immediately before the base-ctor call in the named factory (0x11,0x12,0x17,0x19,0x1A,0x1B,0x1C), and ascmp dword [reg+10h], <imm>in the readers. Both sides agree. -
Object sizes
0x30 / 0x38 / 0x40 / 0x70. — CONFIRMED. Eachmov edi, <size>precedes theoperator newcall in the factory (0x30Mult/Floor/Modulo,0x38CCDiv/CCMod,0x40AffineExpr,0x70SumExpr);SumExpr's dtoroperator delete(e, 0x70)cross-checks.
Re-verify ceiling / what is INFERRED:
- The base-class chain (BinaryExpr ⊃ DivLikeExpr ⊃ {FloorDiv, CCDiv}, etc.) is STRONG-but-inferred here: argued from which base ctor each factory chains through and from the transient
DivLikeExprvtable, not from reading the__si_class_type_infobase pointers. The authoritative tree is the hierarchy page. - The field semantics of
intval@0x28(coefficient vs denominator vs modulus per node) are INFERRED from the assert strings (denom > 0) and the node names — the binary stores onelong; the role label is contextual. AffineExpr'stermstriple at0x20/0x28/0x30is identified as aboost::container::vectorfromsub_635860's asserts; the exact begin/end/cap field assignment is STRONG (matches the boost layout and the zeroing pattern) but the precise end-vs-size encoding at+0x28was not separately disassembled field-by-field.- Kind
24is unobserved — no captured factory emits it; whether it is a reserved/abstractDivLikeExprtag or a node outside this page's scope is SPECULATIVE.
Cross-references
- The
pelican::ExprHierarchy — the 29-typeinfo / 39-vtable-slot RTTI class tree (the types; this page is the bytes). - Penguin AffineExpr Algebra over
pelican::Expr— the algebra and access-descriptor layer (the meaning of the nodes laid out here). - AffineExpr / AffinePredicate ⇄ ISL ⇄ pelican Bridge — how these nodes round-trip through ISL.
- The
pelican::ExprJSON wire-form (toJsonv1/toJsonv2) and theFoldingSetuniquing are companion Part-7 pages (index/CC).