diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 8989105af30..97bbea7a77d 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -529,6 +529,8 @@ class TranslateToFuzzReader { Expression* makeRefCast(Type type); Expression* makeRefGetDesc(Type type); Expression* makeBrOn(Type type); + Expression* makeContBind(Type type); + // TODO: Expression* makeResume(Type type); // Decide to emit a signed Struct/ArrayGet sometimes, when the field is // packed. diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 8ee053eb371..a2fe1b1407b 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -2570,6 +2570,10 @@ Expression* TranslateToFuzzReader::_makeConcrete(Type type) { options.add(FeatureSet::ReferenceTypes | FeatureSet::GC, &Self::makeRefGetDesc); } + if (heapType.isContinuation()) { + options.add(FeatureSet::ReferenceTypes | FeatureSet::StackSwitching, + &Self::makeContBind); + } } if (wasm.features.hasGC()) { if (typeStructFields.find(type) != typeStructFields.end()) { @@ -5457,6 +5461,23 @@ Expression* TranslateToFuzzReader::makeBrOn(Type type) { builder.makeBrOn(op, targetName, make(refType), castType)); } +Expression* TranslateToFuzzReader::makeContBind(Type type) { + auto sig = type.getHeapType().getContinuation().type.getSignature(); + // Add a single param to be bound. TODO: Add multiple, and look in + // interestingHeapTypes. + std::vector newParams; + for (auto t : sig.params) { + newParams.push_back(t); + } + auto newParam = getSingleConcreteType(); + newParams.insert(newParams.begin(), newParam); + auto newSig = Signature(Type(newParams), sig.results); + auto newCont = Continuation(newSig); + auto newType = Type(newCont, NonNullable, Exact); + std::vector newArgs{make(newParam)}; + return builder.makeContBind(type.getHeapType(), newArgs, make(newType)); +} + bool TranslateToFuzzReader::maybeSignedGet(const Field& field) { if (field.isPacked()) { return oneIn(2); diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index af8e235e442..4fef0da80c8 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -46,7 +46,9 @@ struct HeapTypeGeneratorImpl { struct SignatureKind {}; struct StructKind {}; struct ArrayKind {}; - using HeapTypeKind = std::variant; + struct ContinuationKind {}; + using HeapTypeKind = + std::variant; std::vector typeKinds; // For each type, the index one past the end of its recursion group, used to @@ -115,6 +117,10 @@ struct HeapTypeGeneratorImpl { return size; } + // We can only emit continuations after emitting a valid signature for them, + // as the signature must appear first. + bool canEmitContinuation = false; + void planType(size_t i, size_t numRoots, size_t remaining, @@ -233,9 +239,24 @@ struct HeapTypeGeneratorImpl { builder[i].setShared(HeapType(builder[*describedIndices[i]]).getShared()); } else { // This is a root type with no supertype. Choose a kind for this type. - typeKinds.emplace_back(generateHeapTypeKind()); - builder[i].setShared( - !features.hasSharedEverything() || rand.oneIn(2) ? Unshared : Shared); + auto kind = generateHeapTypeKind(); + if (std::get_if(&kind) && !canEmitContinuation) { + // No signature for a continuation. Emit a signature so we can emit one + // later. + kind = SignatureKind{}; + } + typeKinds.emplace_back(kind); + // Continuations cannot be shared, but other things can. + auto shared = Unshared; + if (features.hasSharedEverything() && + !std::get_if(&kind) && rand.oneIn(2)) { + shared = Shared; + } + builder[i].setShared(shared); + // Once we emit a non-shared signature, continuations are possible. + if (std::get_if(&kind) && shared == Unshared) { + canEmitContinuation = true; + } } // Plan this descriptor chain for this type if it is not already determined @@ -283,6 +304,8 @@ struct HeapTypeGeneratorImpl { builder[index] = generateStruct(share, isDesc); } else if (std::get_if(&kind)) { builder[index] = generateArray(share); + } else if (std::get_if(&kind)) { + builder[index] = generateContinuation(share); } else { WASM_UNREACHABLE("unexpected kind"); } @@ -300,7 +323,9 @@ struct HeapTypeGeneratorImpl { builder[index] = generateSubArray(supertype.getArray()); break; case wasm::HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); + builder[index] = + generateSubContinuation(supertype.getContinuation()); + break; case wasm::HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -310,11 +335,20 @@ struct HeapTypeGeneratorImpl { HeapType::BasicHeapType generateBasicHeapType(Shareability share) { // Choose bottom types more rarely. - // TODO: string and cont types + // TODO: string types if (rand.oneIn(16)) { - HeapType ht = - rand.pick(HeapType::noext, HeapType::nofunc, HeapType::none); - return ht.getBasic(share); + std::vector bottoms{ + HeapType::noext, HeapType::nofunc, HeapType::none}; + // Continuations cannot be shared. + if (features.hasStackSwitching() && share == Unshared) { + bottoms.push_back(HeapType::nocont); + } + return rand.pick(bottoms).getBasic(share); + } + + // Sometimes emit shared in unshared contexts. + if (share == Unshared && features.hasSharedEverything() && rand.oneIn(4)) { + share = Shared; } std::vector options{HeapType::func, @@ -324,15 +358,14 @@ struct HeapTypeGeneratorImpl { HeapType::i31, HeapType::struct_, HeapType::array}; + if (features.hasStackSwitching() && share == Unshared) { + options.push_back(HeapType::cont); + } // Avoid shared exn, which we cannot generate. if (features.hasExceptionHandling() && share == Unshared) { options.push_back(HeapType::exn); } auto ht = rand.pick(options); - if (share == Unshared && features.hasSharedEverything() && - ht != HeapType::exn && rand.oneIn(2)) { - share = Shared; - } return ht.getBasic(share); } @@ -443,6 +476,22 @@ struct HeapTypeGeneratorImpl { Array generateArray(Shareability share) { return {generateField(share)}; } + Continuation generateContinuation(Shareability share) { + auto type = pickKind(share); + // There must be signatures to pick from. + assert(type); + return Continuation(*type); + } + + Continuation generateSubContinuation(Continuation super) { + auto subType = pickSubHeapType(super.type); + if (subType.isBasic()) { + // We cannot use a bottom type here. + subType = super.type; + } + return Continuation(subType); + } + template std::vector getKindCandidates(Shareability share) { std::vector candidates; @@ -518,6 +567,23 @@ struct HeapTypeGeneratorImpl { } } + HeapType pickSubCont(Shareability share) { + auto choice = rand.upTo(8); + switch (choice) { + case 0: + return HeapTypes::cont.getBasic(share); + case 1: + return HeapTypes::nocont.getBasic(share); + default: { + if (auto type = pickKind(share)) { + return *type; + } + HeapType ht = (choice % 2) ? HeapType::cont : HeapType::nocont; + return ht.getBasic(share); + } + } + } + HeapType pickSubEq(Shareability share) { auto choice = rand.upTo(16); switch (choice) { @@ -585,6 +651,8 @@ struct HeapTypeGeneratorImpl { auto* kind = &typeKinds[it->second]; if (std::get_if(kind)) { return HeapTypes::nofunc.getBasic(share); + } else if (std::get_if(kind)) { + return HeapTypes::nocont.getBasic(share); } else { return HeapTypes::none.getBasic(share); } @@ -603,7 +671,7 @@ struct HeapTypeGeneratorImpl { case HeapType::func: return pickSubFunc(share); case HeapType::cont: - WASM_UNREACHABLE("not implemented"); + return pickSubCont(share); case HeapType::any: return pickSubAny(share); case HeapType::eq: @@ -654,6 +722,9 @@ struct HeapTypeGeneratorImpl { } else if (std::get_if(kind)) { candidates.push_back(HeapTypes::func.getBasic(share)); return rand.pick(candidates); + } else if (std::get_if(kind)) { + candidates.push_back(HeapTypes::cont.getBasic(share)); + return rand.pick(candidates); } else { WASM_UNREACHABLE("unexpected kind"); } @@ -685,7 +756,7 @@ struct HeapTypeGeneratorImpl { case HeapType::nofunc: return pickSubFunc(share); case HeapType::nocont: - WASM_UNREACHABLE("not implemented"); + return pickSubCont(share); case HeapType::noext: candidates.push_back(HeapTypes::ext.getBasic(share)); break; @@ -798,13 +869,21 @@ struct HeapTypeGeneratorImpl { } HeapTypeKind generateHeapTypeKind() { - switch (rand.upTo(3)) { + // Emit continuations less frequently, as we need fewer of them to get + // interesting results. + uint32_t numKinds = features.hasStackSwitching() ? 7 : 6; + switch (rand.upTo(numKinds)) { case 0: - return SignatureKind{}; case 1: - return StructKind{}; + return SignatureKind{}; case 2: + case 3: + return StructKind{}; + case 4: + case 5: return ArrayKind{}; + case 6: + return ContinuationKind{}; } WASM_UNREACHABLE("unexpected index"); } @@ -871,7 +950,7 @@ struct Inhabitator { Inhabitator::Variance Inhabitator::getVariance(FieldPos fieldPos) { auto [type, idx] = fieldPos; - assert(!type.isBasic() && !type.isSignature()); + assert(!type.isBasic() && !type.isSignature() && !type.isContinuation()); auto field = GCTypeUtils::getField(type, idx); assert(field); if (field->mutable_ == Mutable) { @@ -929,9 +1008,9 @@ void Inhabitator::markNullable(FieldPos field) { void Inhabitator::markBottomRefsNullable() { for (auto type : types) { - if (type.isSignature()) { - // Functions can always be instantiated, even if their types refer to - // uninhabitable types. + if (type.isSignature() || type.isContinuation()) { + // Functions/continuations can always be instantiated, even if their types + // refer to uninhabitable types. continue; } auto children = type.getTypeChildren(); @@ -951,9 +1030,9 @@ void Inhabitator::markExternRefsNullable() { // TODO: Remove this once the fuzzer imports externref globals or gets some // other way to instantiate externrefs. for (auto type : types) { - if (type.isSignature()) { - // Functions can always be instantiated, even if their types refer to - // uninhabitable types. + if (type.isSignature() || type.isContinuation()) { + // Functions/continuations can always be instantiated, even if their types + // refer to uninhabitable types. continue; } auto children = type.getTypeChildren(); @@ -1062,8 +1141,9 @@ void Inhabitator::breakNonNullableCycles() { // Skip references to function types. Functions types can always be // instantiated since functions can be created even with uninhabitable // params or results. Function references therefore break cycles that - // would otherwise produce uninhabitability. - if (heapType.isSignature()) { + // would otherwise produce uninhabitability. (Continuations are + // similar.) + if (heapType.isSignature() || heapType.isContinuation()) { ++index; continue; } @@ -1156,8 +1236,15 @@ std::vector Inhabitator::build() { builder[i] = copy; continue; } - case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Cont: { + Continuation copy = type.getContinuation(); + auto heapType = copy.type; + if (auto it = typeIndices.find(heapType); it != typeIndices.end()) { + copy.type = builder.getTempHeapType(it->second); + } + builder[i] = copy; + continue; + } case HeapTypeKind::Basic: break; } @@ -1190,9 +1277,12 @@ std::vector Inhabitator::build() { builder[i].setShared(types[i].getShared()); } - auto built = builder.build(); - assert(!built.getError() && "unexpected build error"); - return *built; + auto result = builder.build(); + if (auto* err = result.getError()) { + Fatal() << "Failed to build heap types: " << err->reason << " at index " + << err->index; + } + return *result; } } // anonymous namespace diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index dc04ae96733..31809658844 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -321,7 +321,8 @@ void Fuzzer::checkCanonicalization() { builder[index] = getArray(type.getArray()); continue; case HeapTypeKind::Cont: - WASM_UNREACHABLE("TODO: cont"); + builder[index] = getContinuation(type.getContinuation()); + continue; case HeapTypeKind::Basic: break; } @@ -465,6 +466,11 @@ void Fuzzer::checkCanonicalization() { old.element = getField(old.element); return old; } + + Continuation getContinuation(Continuation old) { + old.type = getChildHeapType(old.type).get(); + return old; + } }; Copier{*this, builder}; diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 1740e369d5d..b17ca328bc0 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -1319,6 +1319,16 @@ class Builder { ret->finalize(); return ret; } + template + ContBind* + makeContBind(HeapType targetType, const T& operands, Expression* cont) { + auto* ret = wasm.allocator.alloc(); + ret->type = Type(targetType, NonNullable, Exact); + ret->operands.set(operands); + ret->cont = cont; + ret->finalize(); + return ret; + } Suspend* makeSuspend(Name tag, const std::vector& args) { auto* ret = wasm.allocator.alloc(); ret->tag = tag; @@ -1340,6 +1350,21 @@ class Builder { ret->finalize(); return ret; } + template + Resume* makeResume(const std::vector& handlerTags, + const std::vector& handlerBlocks, + const std::vector& sentTypes, + ExpressionList& operands, + Expression* cont) { + auto* ret = wasm.allocator.alloc(); + ret->handlerTags.set(handlerTags); + ret->handlerBlocks.set(handlerBlocks); + ret->sentTypes.set(sentTypes); + ret->operands.set(operands); + ret->cont = cont; + ret->finalize(); + return ret; + } ResumeThrow* makeResumeThrow(Name tag, const std::vector& handlerTags, const std::vector& handlerBlocks, diff --git a/test/lit/fuzz-types.test b/test/lit/fuzz-types.test index 44d475f6e3d..b3683141c96 100644 --- a/test/lit/fuzz-types.test +++ b/test/lit/fuzz-types.test @@ -1,56 +1,64 @@ -;; RUN: wasm-fuzz-types -v --seed=2 | filecheck %s +;; RUN: wasm-fuzz-types -v --seed=3 | filecheck %s -;; CHECK: Running with seed 2 +;; CHECK: Running with seed 3 ;; CHECK-NEXT: Built 20 types: -;; CHECK-NEXT: (type $0 (shared (struct))) -;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $1 (array (ref $2))) -;; CHECK-NEXT: (type $2 (sub (shared (array (mut i16))))) -;; CHECK-NEXT: (type $3 (sub (shared (array i32)))) -;; CHECK-NEXT: (type $4 (sub (descriptor $5) (struct (field (mut (ref $0)))))) -;; CHECK-NEXT: (type $5 (sub (describes $4) (struct (field f64) (field (mut i64))))) -;; CHECK-NEXT: (type $6 (sub (array v128))) -;; CHECK-NEXT: (type $7 (shared (struct (field f32) (field (mut (ref $0)))))) -;; CHECK-NEXT: (type $8 (sub (shared (struct (field f64) (field (mut (ref (shared struct)))) (field (mut f64)) (field i16) (field i32) (field i64))))) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $9 (descriptor $12) (struct (field i64) (field i16))) -;; CHECK-NEXT: (type $10 (array (mut (ref null $5)))) -;; CHECK-NEXT: (type $11 (sub (shared (func (param (ref $7) f64 (ref $9)) (result (ref null $10)))))) -;; CHECK-NEXT: (type $12 (sub (describes $9) (descriptor $13) (struct (field (ref (shared any))) (field (mut (ref extern))) (field v128) (field (ref null $17))))) -;; CHECK-NEXT: (type $13 (sub (describes $12) (descriptor $17) (struct (field externref) (field (mut i8)) (field (mut i32)) (field (mut f32)) (field i16) (field (mut (ref null $6)))))) -;; CHECK-NEXT: (type $14 (sub (func (result i64)))) -;; CHECK-NEXT: (type $15 (sub (shared (func)))) -;; CHECK-NEXT: (type $16 (shared (func (result (ref null $0))))) -;; CHECK-NEXT: (type $17 (sub (describes $13) (struct (field (ref extern))))) -;; CHECK-NEXT: (type $18 (sub (func (param v128 (ref null $10))))) -;; CHECK-NEXT: (type $19 (sub final $11 (shared (func (param (ref null (shared any)) f64 (ref any)) (result (ref $10)))))) +;; CHECK-NEXT: (rec +;; CHECK-NEXT: (type $0 (sub (shared (func (result (ref $3) i64))))) +;; CHECK-NEXT: (type $1 (sub (shared (struct (field (mut (ref null (shared extern)))) (field (ref null $3)) (field (mut v128)) (field v128) (field (mut (ref (shared any)))) (field (ref (shared i31))))))) +;; CHECK-NEXT: (type $2 (sub (func (param (ref $1))))) +;; CHECK-NEXT: (type $3 (shared (struct (field f32) (field (mut (ref null (shared eq)))) (field (ref (shared extern))) (field (mut (ref null (shared struct)))) (field (mut f32)) (field (mut (ref null $0)))))) +;; CHECK-NEXT: (type $4 (sub (func (result i64)))) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (rec +;; CHECK-NEXT: (type $5 (struct (field (mut (ref $0))) (field i8) (field i31ref) (field (ref func)) (field f32))) +;; CHECK-NEXT: (type $6 (sub (func (param f64 (ref $7) (ref null $5) f32) (result i32)))) +;; CHECK-NEXT: (type $7 (sub (shared (func (param (ref null $4) (ref null $6)))))) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (rec +;; CHECK-NEXT: (type $8 (struct (field (mut v128)) (field (mut (ref $5))) (field (mut v128)) (field (mut f64)) (field (mut i8)))) +;; CHECK-NEXT: (type $9 (shared (struct (field i32) (field (mut i32)) (field (mut i16))))) +;; CHECK-NEXT: (type $10 (sub (func (param f32 (ref null $4) f64) (result (ref (shared struct)))))) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (rec +;; CHECK-NEXT: (type $11 (cont $16)) +;; CHECK-NEXT: (type $12 (sub final $10 (func (param f32 funcref f64) (result (ref $9))))) +;; CHECK-NEXT: (type $13 (cont $19)) +;; CHECK-NEXT: (type $14 (sub $2 (func (param (ref null (shared struct)))))) +;; CHECK-NEXT: (type $15 (sub (struct (field (mut (ref $7))) (field (mut i8)) (field (mut v128)) (field f64) (field f64) (field i64)))) +;; CHECK-NEXT: (type $16 (sub final $14 (func (param (ref null (shared eq)))))) +;; CHECK-NEXT: (type $17 (sub (func (param f32 v128 (ref $13)) (result (ref $5))))) +;; CHECK-NEXT: (type $18 (struct (field (ref null $6)) (field (ref $17)) (field (mut f64)) (field (mut i64)))) +;; CHECK-NEXT: (type $19 (sub $4 (func (result i64)))) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ;; CHECK-NEXT: Inhabitable types: ;; CHECK-NEXT: ;; CHECK-NEXT: Built 20 types: -;; CHECK-NEXT: (type $0 (shared (struct))) -;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $1 (array (ref $2))) -;; CHECK-NEXT: (type $2 (sub (shared (array (mut i16))))) -;; CHECK-NEXT: (type $3 (sub (shared (array i32)))) -;; CHECK-NEXT: (type $4 (sub (descriptor $5) (struct (field (mut (ref $0)))))) -;; CHECK-NEXT: (type $5 (sub (describes $4) (struct (field f64) (field (mut i64))))) -;; CHECK-NEXT: (type $6 (sub (array v128))) -;; CHECK-NEXT: (type $7 (shared (struct (field f32) (field (mut (ref $0)))))) -;; CHECK-NEXT: (type $8 (sub (shared (struct (field f64) (field (mut (ref (shared struct)))) (field (mut f64)) (field i16) (field i32) (field i64))))) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (rec -;; CHECK-NEXT: (type $9 (descriptor $12) (struct (field i64) (field i16))) -;; CHECK-NEXT: (type $10 (array (mut (ref null $5)))) -;; CHECK-NEXT: (type $11 (sub (shared (func (param (ref $7) f64 (ref $9)) (result (ref null $10)))))) -;; CHECK-NEXT: (type $12 (sub (describes $9) (descriptor $13) (struct (field (ref (shared any))) (field (mut externref)) (field v128) (field (ref null $17))))) -;; CHECK-NEXT: (type $13 (sub (describes $12) (descriptor $17) (struct (field externref) (field (mut i8)) (field (mut i32)) (field (mut f32)) (field i16) (field (mut (ref null $6)))))) -;; CHECK-NEXT: (type $14 (sub (func (result i64)))) -;; CHECK-NEXT: (type $15 (sub (shared (func)))) -;; CHECK-NEXT: (type $16 (shared (func (result (ref null $0))))) -;; CHECK-NEXT: (type $17 (sub (describes $13) (struct (field externref)))) -;; CHECK-NEXT: (type $18 (sub (func (param v128 (ref null $10))))) -;; CHECK-NEXT: (type $19 (sub final $11 (shared (func (param (ref null (shared any)) f64 (ref any)) (result (ref $10)))))) +;; CHECK-NEXT: (rec +;; CHECK-NEXT: (type $0 (sub (shared (func (result (ref $3) i64))))) +;; CHECK-NEXT: (type $1 (sub (shared (struct (field (mut (ref null (shared extern)))) (field (ref null $3)) (field (mut v128)) (field v128) (field (mut (ref (shared any)))) (field (ref (shared i31))))))) +;; CHECK-NEXT: (type $2 (sub (func (param (ref $1))))) +;; CHECK-NEXT: (type $3 (shared (struct (field f32) (field (mut (ref null (shared eq)))) (field (ref null (shared extern))) (field (mut (ref null (shared struct)))) (field (mut f32)) (field (mut (ref null $0)))))) +;; CHECK-NEXT: (type $4 (sub (func (result i64)))) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (rec +;; CHECK-NEXT: (type $5 (struct (field (mut (ref $0))) (field i8) (field i31ref) (field (ref func)) (field f32))) +;; CHECK-NEXT: (type $6 (sub (func (param f64 (ref $7) (ref null $5) f32) (result i32)))) +;; CHECK-NEXT: (type $7 (sub (shared (func (param (ref null $4) (ref null $6)))))) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (rec +;; CHECK-NEXT: (type $8 (struct (field (mut v128)) (field (mut (ref $5))) (field (mut v128)) (field (mut f64)) (field (mut i8)))) +;; CHECK-NEXT: (type $9 (shared (struct (field i32) (field (mut i32)) (field (mut i16))))) +;; CHECK-NEXT: (type $10 (sub (func (param f32 (ref null $4) f64) (result (ref (shared struct)))))) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (rec +;; CHECK-NEXT: (type $11 (cont $16)) +;; CHECK-NEXT: (type $12 (sub final $10 (func (param f32 funcref f64) (result (ref $9))))) +;; CHECK-NEXT: (type $13 (cont $19)) +;; CHECK-NEXT: (type $14 (sub $2 (func (param (ref null (shared struct)))))) +;; CHECK-NEXT: (type $15 (sub (struct (field (mut (ref $7))) (field (mut i8)) (field (mut v128)) (field f64) (field f64) (field i64)))) +;; CHECK-NEXT: (type $16 (sub final $14 (func (param (ref null (shared eq)))))) +;; CHECK-NEXT: (type $17 (sub (func (param f32 v128 (ref $13)) (result (ref $5))))) +;; CHECK-NEXT: (type $18 (struct (field (ref null $6)) (field (ref $17)) (field (mut f64)) (field (mut i64)))) +;; CHECK-NEXT: (type $19 (sub $4 (func (result i64)))) ;; CHECK-NEXT: )