[JSC] Fix wasm type parsing regression by making RTT formal canonicalized types
94e35cf parent — see above; this is 103a40a
// JSTests/wasm/stress/cross-module-rtt-identity.js
// Two separately compiled modules declare the same recursive struct type.
// Isorecursive canonicalization must map both to the same RTT pointer
// so that a ref.cast in module B on a value produced by module A succeeds.
const { makeLeaf, leafVal } = new WebAssembly.Instance(new WebAssembly.Module(M_A)).exports;
const { castAndRead } = new WebAssembly.Instance(new WebAssembly.Module(M_B)).exports;
const b = castAndRead(leaf);
if (b !== v)
throw new Error(`B castAndRead(${v}): expected ${v}, got ${b}`);
WebAssembly GC는 heap에 할당되는 struct, 배열, 재귀 타입을 wasm에 추가합니다. RTT(Runtime Type)는 타입 동일성을 담당하는 객체로, ref.cast와 subtype check 실행 시 포인터 동등성 비교에 사용됩니다. 이때 올바른 동작을 위해서는 isorecursive canonicalization이 필요합니다. 구조적으로 동일한 재귀 타입을 선언하는 두 모듈이 독립적으로 컴파일되더라도 동일한 canonical RTT 포인터를 받아야 하며, 이를 통해 spec이 요구하는 cross-module ref.cast가 정상적으로 성공할 수 있습니다.
이 commit은 JSC의 WebAssembly GC 타입 시스템에서 TypeDefinition을 완전히 제거하고, 모든 런타임 타입 표현을 RTT 중심으로 통합하였습니다. 먼저 type-section 파싱 중에만 존재하는 임시 파싱 구조체(Subtype, RecursionGroup, Projection)를 관리하기 위해 TypeSectionState가 도입되었습니다. 파싱이 완료되면 이 구조체들은 프로세스 전역 TypeInformation 싱글턴에 등록된 canonical RTT로 변환됩니다. 한편 RTT는 이제 sibling RTT에 대한 RefPtr 참조를 보유하며, 상호 재귀 타입에 대해 의도적으로 refcount cycle을 형성합니다. 이 cycle은 V8의 현재 방식과 동일하게 영구적으로 leak되며, 향후 cycle collector가 도입될 때까지 이 방식을 유지합니다. 또한 cross-module isorecursive RTT canonicalization 문제와 GC 구조체 구성 중 반복 순회로 인한 성능 저하(312331@main에서 도입된 regression)도 함께 수정되었습니다.
Significance
Wasm GC 타입 인프라를 전면 재작성한 변경으로, TypeSectionState의 임시 객체에서 포인터가 하나라도 외부로 누출되면 모든 cross-module ref.cast 경로에서 도달 가능한 use-after-free가 발생합니다.
Audit directions
- 재귀 타입 canonicalization hash/equality 점검.
WasmTypeDefinition.cpp의canonicalizeRecursionGroupImpl과canonicalizeSingletonImpl은 모든 cross-moduleref.cast의 기반이 되는 중복 제거 로직을 구현합니다. hash 함수(hashRTTForRecGroup)와 equality 함수(equalRTTsForRecGroup)는 구성 과정에서 placeholder 참조를 사용하여 재귀 구조를 처리합니다. 미묘한 구현 오류가 있으면 구조적으로 다른 타입이 동일하게 비교되어 type confusion이 발생하거나, 반대로 동일한 타입이 다르게 비교되어 fallback 경로에서 유효한 cast가 실패하는 상황이 발생할 수 있습니다. TypeSectionStatepointer escape 점검.Subtype,Projection,RecursionGroup객체는TypeSectionState내부의SegmentedVector에 할당되며, type-section 파싱이 완료되면 전부 해제됩니다. ~100개 파일에 걸친 리팩터링 전반에서 이 객체에 대한 포인터가 RTT,ModuleInformation, 또는 더 오래 살아있는 구조체에 남지 않도록 보장되어야 합니다. raw 포인터가 하나라도 누락되면 파싱 완료 이후 UAF가 발생합니다.- RTT cycle anchoring 완전성 점검. 이 commit은 raw
TypeIndex참조를RefPtr<const RTT>anchor 슬롯(TypeSlot::rttAnchor,StructFieldEntry::rttAnchor,RTTArrayPayload::m_elementTypeAnchor)으로 전환하여 cycle 안에서도 sibling RTT가 살아있도록 합니다. 다른 RTT에 대한 참조를 보유하면서 대응하는 anchorRefPtr이 없는 payload 필드가 있으면, 이 commit이 해결하려는 dangling pointer 문제가 다시 발생할 수 있습니다. ref.cast/ subtype check 정확성 점검.isSubRTT와isStrictSubRTT는 canonical RTT 포인터 동등성에 의존합니다. BBQ, OMG, IPInt, B3, DFG/FTL 전반에 걸쳐typeIndexFromFunctionIndexSpace를rttFromFunctionIndexSpace로 교체하는 작업은 기계적이지만 그 규모가 방대합니다. cast 또는 call-ref 경로의 호출 지점에서 off-by-one이 발생하거나 잘못된 RTT가 선택되면 type confusion primitive로 이어질 가능성이 있습니다.