[5] OMG Wasm B3 IR type-tag mismatch on struct/array.new_default
OMG picked its zero-initialiser type from `elementSize()`. An `f32` field is 4 bytes — same width as `i32` — so the JIT tagged the zero `Int32`.
Rated Low because the diff prevents a RELEASE_ASSERT in B3's CSE / store-to-load forwarding triggered when an Int32(0) or Int64(0) constant is used as the zero-initializer for an f32/f64 Wasm field. Bit patterns of zero coincide between integer and float slots, so the harm is confined to the assertion abort itself; no downstream miscompilation or memory-safety primitive is visible in this commit.
The struct.new_default Wasm instruction was writing Int32(0) for f32 types. If store-to-load forwarding fired, B3::Value::replaceWithIdentity failed a RELEASE_ASSERT because the expected type was f32, not i32. The patch adjusts OMGIRGenerator::addStructNewDefault and OMGIRGenerator::addArrayNewDefault to construct a constant of the correct type via toB3Type(<wasmType>.unpacked()). A regression test (new_default-f32.js) builds a struct-of-f32 and an array-of-f32, calls the corresponding *.new_default followed by an immediate read, and runs the function in a hot loop to trigger OMG tier compilation.
Source/JavaScriptCore/wasm/WasmOMGIRGenerator.cpp
JSTests/wasm/stress/new_default-f32.js
Patch Details
The patch collapses the two byte-size-keyed branches (<= 4 → Int32(0), else → Int64(0)) into a single call that derives the B3 type from the declared Wasm value type via toB3Type(<wasmType>.unpacked()). Both addArrayNewDefault and addStructNewDefault receive the same treatment. The 16-byte vector branch is unchanged, and the reference-type branch is unchanged. The new regression test exercises f32 specifically — both struct field and array element variants — under a hot loop sized to promote into OMG.
Type-tagging mismatch in JIT IR construction — B3 constant produced from byte-size heuristic instead of from the declared Wasm value type, breaking the store-to-load forwarding type invariant.
Background
B3 is JSC's mid-level optimizing IR shared by FTL and OMG; every B3 Value carries a Type (Int32, Int64, Float, Double, V128, etc.) and IR-level invariants require that operations agree on operand types. CSE (common subexpression elimination) and store-to-load forwarding are B3 optimizations that detect a load from an address that was just written and replace the load with the stored value via Value::replaceWithIdentity, which requires the source and target B3 types to be equal. RELEASE_ASSERT in WebKit fires in both debug and release builds and aborts the process when its condition is false.
OMG is JSC's optimizing Wasm JIT tier; OMGIRGenerator walks the Wasm bytecode and emits B3 IR. struct.new_default / array.new_default are Wasm GC instructions that allocate a struct or array and zero-initialize every field/element. Wasm value types include i32, i64, f32, f64, v128, and reference types; f32 is 4 bytes, f64 is 8 bytes. toB3Type is the helper that maps a Wasm value type to its B3 type (f32 → Float, f64 → Double).
Analysis
Pre-fix, when OMG lowered struct.new_default and array.new_default to B3 IR, it picked the type of the zero-initializer purely from the element/field's byte size: <= 4 got Int32(0), otherwise Int64(0). For Wasm f32 fields/elements (4 bytes) this produced an Int32 B3 value that was fed into WasmStructSet / WasmArraySet, even though the field's declared B3 type is Float. The same mismatch existed for f64 — the old else branch produced Int64(0) for an 8-byte field whose B3 type is Double.
B3's CSE / store-to-load forwarding then identifies a later load from the same struct field or array slot and tries to replace that load with the previously stored value via Value::replaceWithIdentity. replaceWithIdentity requires the replacement value's B3 type to equal the load's type; here the load is Float (or Double) but the stored constant is Int32 (or Int64), so the RELEASE_ASSERT in B3 fires during OMG compilation and terminates the WebContent process. Routing the type through toB3Type(<wasmType>.unpacked()) restores B3's type invariant: the stored value's type matches the field/element's declared type.
This is a class of bug that recurs in JIT IR generators that pick IR types from byte-size or storage-size heuristics rather than from the source language's declared type. The same pattern — if (size == 4) Int32 else Int64 — silently works for integer-typed fields but breaks the moment a floating-point or vector field of the same size enters the picture.
The regression test is essentially the PoC: declare a struct or array type with at least one f32 (or f64) field, call struct.new_default / array.new_default, then immediately read the field back and store the result somewhere observable. The inner sequence struct.new_default 0 ; struct.get 0 0 ; global.set 0 (and the array variant) is exactly the shape that lets B3's store-to-load forwarding spot the redundant load and attempt the type-mismatched replaceWithIdentity.
The diff shows no evidence of a downstream memory-safety primitive: the bit pattern of Int32(0) and Float(0.0) is identical, so even if the assertion were elided the stored bits would be correct. A type-confused B3 IR could in principle mislead a later optimization pass, but no such downstream miscompilation is visible in the patch or surrounding code.
This vulnerability weakens WebContent process availability. The HTML/Wasm trust boundary assumes that any well-formed Wasm module — even adversarial ones — should compile through OMG without aborting the renderer. Pre-fix, a one-line Wasm module containing struct.new_default or array.new_default over an f32/f64 type, executed in a hot loop to promote it into OMG, reliably trips the assertion and kills the renderer. An attacker who serves such a module from web content could deterministically crash any tab that loads their page.
Audit directions
-
JIT IR generators picking IR operand types from byte-size heuristics (
if size == 4 ... Int32) rather than from the source-language declared type. This silently misclassifies floating-point and vector fields whose size collides with an integer width and breaks downstream IR-level type invariants. AuditWasmOMGIRGenerator.cpp,WasmBBQJIT64.cpp, and the BBQ/IPInt array/struct lowerings (addArrayNew,addArrayFill,addArrayCopy,addStructSet) for any constant or load/store that selects its B3/Air type fromelementSize()/typeSizeInBytes()instead oftoB3Type(unpacked()). -
B3 store-to-load forwarding assertion of type equality. Any IR site that writes a value of one B3 type into a typed slot followed by a load of a different B3 type from the same slot will
RELEASE_ASSERT. GrepSource/JavaScriptCore/b3/forreplaceWithIdentityandB3CSE, then for each producer of struct/array/global stores inWasmOMGIRGeneratorconfirm the produced B3 type matches the slot's declared type. Particular candidates: default initializers, generic boxing helpers, and anyconstant(Int32, 0)/constant(Int64, 0)pattern feeding a non-integer slot. -
Wasm GC (
struct/array) lowering paths across OMG, BBQ, IPInt, and ConstExprGenerator. A bug in one tier may not exist in the others, masking it during cross-tier testing. Verify that all fouraddStructNewDefault/addArrayNewDefaultimplementations agree on the type of the zero-initializer forf32,f64, and packed types (i8,i16); diff them side-by-side inWasmBBQJIT64.cpp,WasmIPIntGenerator.cpp,WasmConstExprGenerator.cpp, andWasmOMGIRGenerator.cpp. -
unpacked()vs rawStorageTypedivergence.f32/f64/i32/i64are unpacked, buti8/i16packed fields unpack toi32for read/write. Audit whether any default-init or generic-set helper uses the packed storage type to choose the B3 constant type while a siblingstruct.get_s/struct.get_ureads with the unpacked type — a smaller-scale variant of the same store-to-load type-mismatch class.
Note: B3's exact assertion site (
Value::replaceWithIdentityin CSE / store-to-load forwarding),toB3Type's mapping, and the framing thatInt32(0)/Float(0.0)bit-pattern coincidence renders the case harmless absent the assert are inferred from B3 conventions rather than visible in this commit. The corrective IR-type plumbing is fully visible.