Butterfly-less JSObject for Wasm GC
55783b9
Source/JavaScriptCore/runtime/JSObject.h
- WriteBarrier<Unknown> m_butterfly;
+ // m_butterfly moved to JSObjectWithButterfly
Butterfly* butterfly()
{
+ auto* b = *std::bit_cast<Butterfly**>(std::bit_cast<char*>(this) + butterflyOffset());
+ if (type() == WebAssemblyGCObjectType) [[unlikely]]
+ b = nullptr;
+ return b;
}
Source/JavaScriptCore/jit/AssemblyHelpers.cpp
- static_cast<int32_t>(sizeof(JSObject)) -
+ static_cast<int32_t>(JSObject::offsetOfInlineStorage()) -
Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
- loadp JSObject::m_butterfly[objectAndStorage], objectAndStorage
+ loadp JSObjectWithButterfly::m_butterfly[objectAndStorage], objectAndStorage
- addp sizeof JSObject - (firstOutOfLineOffset - 2) * 8, objectAndStorage
+ addp sizeof JSObjectWithButterfly - (firstOutOfLineOffset - 2) * 8, objectAndStorage
In JSC, the butterfly is a heap-allocated sidecar structure hanging off JSObject that holds both out-of-line named properties (to the left of the pointer) and indexed storage (to the right). Its offset within JSObject is a load-bearing constant baked into JIT code, LLInt assembly macros, and B3 IR lowering. Wasm GC objects (structs and arrays defined via the GC proposal) have a fixed, typed layout with no JS properties, so their butterfly is structurally impossible and was always nullptr — pure waste of 8 bytes per object.
This commit splits JSObject into two classes: a butterfly-less JSObject base and a new JSObjectWithButterfly subclass that carries m_butterfly. Wasm GC objects inherit directly from JSObject. The butterfly() accessor compensates with a speculative load trick — it unconditionally reads from butterflyOffset() (always valid because HeapCell atoms are ≥16 bytes), then zeroes the result when type() == WebAssemblyGCObjectType, keeping the common path branchless. JIT-generated butterfly loads skip the type check entirely, relying on the invariant that Wasm GC objects can never satisfy the structure/indexing-type guards that precede any butterfly dereference.
Before: After:
JSObject { m_butterfly, inline... } JSObject { inline... } ← no m_butterfly
├── JSNonFinalObject ├── JSObjectWithButterfly { m_butterfly }
├── JSFinalObject │ ├── JSNonFinalObject
└── WebAssemblyGCObjectBase │ └── JSFinalObject
(m_butterfly always nullptr) └── WebAssemblyGCObjectBase ← 8 bytes saved
Significance
This restructures one of JSC's most fundamental object hierarchies — every offset calculation, JIT inline cache, LLInt macro, and B3 lowering that touches the butterfly pointer or inline storage had to be audited and updated, and any missed site is a silent memory corruption. The immediate payoff is reduced Wasm GC heap footprint.
Audit directions
a Aaaaa Aaaaaaaaaaa Aaaa Aa Aaaaaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaaa Aaaa Aa Aaaaaa Aaaaaaaa Aaaaaaa Aa Aaa Aaaaaaaaa Aaaa Aaaaaaaa Aaaaa Aaa Aaa Aaaaaa Aa Aaa Aaaaaaaaaa Aaaa Aaa Aaaaaaa a Aaaaaa Aaaaaa Aaaaaaa Aaaa Aa Aaaaaa Aaaa Aa Aa Aaaaaaaaaaaaa Aaaaa Aaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaaaa Aaaa Aa Aaaaaaaaaa Aaaa Aaaaaaaaa Aaaa Aaaaaaaa
a Aaaaa Aaaaaa Aa Aaa Aaaa Aaaaaaaa Aaa Aaaaaa Aaaaaa Aaaaaaaaaaaaa Aaaaaaaaa Aaaaa Aaa Aaaa Aaaaaaa Aaaaaa Aaaaaaa Aaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaa Aaaaaaa Aaaaa Aaaa Aa Aa Aaaaaaaa Aaaaaaaaaa Aaa Aa Aaaaaaaaaa Aaa Aaa Aaaa Aaaa Aa Aaaaaaaaaa Aaaa Aaaaaaaa Aaaaaaaaa Aaaaaaa Aaaaa Aaaaaa Aaaaa Aaaaaaaa Aaaa Aaaaaaa Aaa Aaaaaa Aaaaaaaa Aaaaaaaaaaaaaaa Aaaa Aaaaaa Aaa Aa Aaaaaaaaaa Aaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaa Aaaa Aaaa Aaaa Aaaaaa
a Aaaaaaaaaaaaaaaa a Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaa Aaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaa Aaaaa Aaaaaaa Aaaaaaaaaaaaaa Aa Aaa Aaaa Aaaaa Aaaaaaaaaaaaaa Aa Aa Aaaaaa Aaaa Aa Aaaaaaaa a Aaaaa Aaaaaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaa Aaaaa Aa Aa Aaaaaaaaaa Aaaaaa Aaaaaaaaa Aaaaaa Aaa Aaaa Aaaaa Aaa Aaaaaaa Aa Aaaaaaaaaa Aaaaaa
a Aaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaa Aaaaaa Aaaaaaa Aaaaaa Aaaaaaa Aaaaaaa Aaaaaaaaaaaaa Aaaaaa Aaa Aaaaaaaaa Aaa Aa Aaaaaaaaaaaaaaaaaa Aa a Aaaaa Aaa Aaaaaa Aaaaaaa Aaaaa Aa Aaa Aaaaaa Aaa Aaaaaaaaaaaaaaaaa Aaa Aaaaa Aaaaa Aaa Aaaaaaa Aa Aaa Aaaa a Aaaa Aaa Aaaaa Aaaaaaaaa Aaaa Aa Aaaaaaaaaaaaaaaaa Aaa Aaaaaaaaa
🔒The speculative butterfly load and the JIT bypass invariant both have edge cases in newly introduced code paths worth investigating.
Subscribe to read more