From 1f67016b13ef87504a6d3f12a24f2459198f5ec9 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 25 Aug 2026 17:07:00 -0700 Subject: [PATCH] Avoid newly exposing prototypes in GTO When GTO removes fields or makes them immutable, it may introduce an immutable externref first field on the descriptor of a JS-exposed type where there was none before. That means that the described type could now have a JS-observable prototype where it did not before optimization, which makes this a misoptimization. Fix the problem by inserting an i8 placeholder first field wherever we would otherwise start exposing a prototype where there was none before. This is expected to be exceptionally rare in practice, so the extra memory use is not expected to be a real problem. Instead of adding a placeholder field, we could have inhibited optimization of the existing first field, but that would be more likely than an unaccessed placeholder field to have adverse effects in later passes. Fixes #9026. --- src/ir/js-utils.h | 20 +- src/passes/GlobalTypeOptimization.cpp | 191 +++-- test/lit/passes/gto-jsinterop.wast | 1026 +++++++++++++++++++++++++ 3 files changed, 1184 insertions(+), 53 deletions(-) diff --git a/src/ir/js-utils.h b/src/ir/js-utils.h index 105dea499cf..99d71e2388e 100644 --- a/src/ir/js-utils.h +++ b/src/ir/js-utils.h @@ -23,6 +23,18 @@ namespace wasm::JSUtils { +// Whether a field is immutable and a reference to a subtype of externref that +// could hold a JS prototype. +inline bool isPossibleJSPrototypeField(const Field& field) { + if (field.mutable_ != Immutable) { + return false; + } + if (!field.type.isRef()) { + return false; + } + return field.type.getHeapType().isMaybeShared(HeapType::ext); +} + // Whether this is a descriptor struct type whose first field is immutable and a // subtype of externref. inline bool hasPossibleJSPrototypeField(HeapType type) { @@ -34,13 +46,7 @@ inline bool hasPossibleJSPrototypeField(HeapType type) { if (fields.empty()) { return false; } - if (fields[0].mutable_ == Mutable) { - return false; - } - if (!fields[0].type.isRef()) { - return false; - } - return fields[0].type.getHeapType().isMaybeShared(HeapType::ext); + return isPossibleJSPrototypeField(fields[0]); } // Calls flowIn and flowOut on all types that may flow in from or out to JS. diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 46eb698eaa4..2a38d4e6da6 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -70,16 +70,20 @@ struct FieldInfo { struct FieldInfoScanner : public StructUtils::StructScanner { + std::unordered_map>& jsExposedTypes; + std::unique_ptr create() override { - return std::make_unique(functionNewInfos, - functionSetGetInfos); + return std::make_unique( + functionNewInfos, functionSetGetInfos, jsExposedTypes); } FieldInfoScanner( StructUtils::FunctionStructValuesMap& functionNewInfos, - StructUtils::FunctionStructValuesMap& functionSetGetInfos) + StructUtils::FunctionStructValuesMap& functionSetGetInfos, + std::unordered_map>& jsExposedTypes) : StructUtils::StructScanner( - functionNewInfos, functionSetGetInfos) {} + functionNewInfos, functionSetGetInfos), + jsExposedTypes(jsExposedTypes) {} void noteExpression(Expression* expr, HeapType type, @@ -116,16 +120,8 @@ struct FieldInfoScanner // Converting a reference to externref makes the prototype field on its // descriptor available to be read by JS, if such a field exists. void visitRefAs(RefAs* curr) { - if (curr->op != ExternConvertAny) { - return; - } - if (!curr->value->type.isRef()) { - return; - } - if (auto desc = curr->value->type.getHeapType().getDescriptorType(); - desc && JSUtils::hasPossibleJSPrototypeField(*desc)) { - auto exact = curr->value->type.getExactness(); - functionSetGetInfos[getFunction()][{*desc, exact}][0].noteRead(); + if (curr->op == ExternConvertAny && curr->value->type.isRef()) { + jsExposedTypes.at(getFunction()).push_back(curr->value->type); } } }; @@ -139,6 +135,11 @@ struct GlobalTypeOptimization : public Pass { // rare). std::unordered_map> canBecomeImmutable; + // Descriptor types that are exposed to JS but do _not_ configure prototypes + // for their described types. We must avoid optimizing these types such that + // they start configuring prototypes. + std::unordered_set exposedNoProtoDescs; + // Maps each field to its new index after field removals. That is, this // takes into account that fields before this one may have been removed, // which would then reduce this field's index. If a field itself is removed, @@ -149,6 +150,28 @@ struct GlobalTypeOptimization : public Pass { static const Index RemovedField = Index(-1); std::unordered_map> indexesAfterRemovals; + struct IndexAnalysis { + Index newSize = 0; + bool hasPlaceholder = false; + + IndexAnalysis(const std::vector& indexes) { + Index maxIndex = 0; + bool hasKept = false; + bool hasIndexZero = false; + for (auto idx : indexes) { + if (idx != RemovedField) { + hasKept = true; + maxIndex = std::max(maxIndex, idx); + if (idx == 0) { + hasIndexZero = true; + } + } + } + newSize = hasKept ? maxIndex + 1 : 0; + hasPlaceholder = hasKept && !hasIndexZero; + } + }; + void run(Module* module) override { if (!module->features.hasGC()) { return; @@ -157,21 +180,32 @@ struct GlobalTypeOptimization : public Pass { Fatal() << "GTO requires --closed-world"; } + std::unordered_map> jsExposedTypesByFunction; + jsExposedTypesByFunction[nullptr]; + for (auto& func : module->functions) { + jsExposedTypesByFunction[func.get()]; + } + // Find and analyze struct operations inside each function. StructUtils::FunctionStructValuesMap functionNewInfos(*module), functionSetGetInfos(*module); - FieldInfoScanner scanner(functionNewInfos, functionSetGetInfos); + FieldInfoScanner scanner( + functionNewInfos, functionSetGetInfos, jsExposedTypesByFunction); scanner.run(getPassRunner(), module); scanner.runOnModuleCode(getPassRunner(), module); // Combine the data from the functions. functionSetGetInfos.combineInto(combinedSetGetInfos); + std::vector jsExposedTypes; + for (auto& [_, types] : jsExposedTypesByFunction) { + jsExposedTypes.insert(jsExposedTypes.end(), types.begin(), types.end()); + } SubTypes subTypes(*module); // Analyze the JS interface to find fields holding configured prototypes // that cannot be removed. - analyzeJSInterface(*module, subTypes); + analyzeJSInterface(*module, subTypes, jsExposedTypes); // Propagate information to super and subtypes on set/get infos: // @@ -291,15 +325,16 @@ struct GlobalTypeOptimization : public Pass { } // We need to compute the new set of indexes if we are removing fields, or - // if our parent removed fields. In the latter case, our parent may have - // reordered fields even if we ourselves are not removing anything, and we - // must update to match the parent's order. + // if our parent removed fields, or if we might need a placeholder. If we + // have a parent, it may have reordered fields even if we ourselves are + // not removing anything, and we must update to match the parent's order. auto super = type.getDeclaredSuperType(); auto superHasUpdates = super && indexesAfterRemovals.contains(*super); - if (!removableIndexes.empty() || superHasUpdates) { - // We are removing fields. Reorder them to allow that, as in the general - // case we can only remove fields from the end, so that if our subtypes - // still need the fields they can append them. For example: + bool isExposedNoProto = exposedNoProtoDescs.contains(type); + if (!removableIndexes.empty() || superHasUpdates || isExposedNoProto) { + // We might be removing fields. Reorder them to allow that, as in the + // general case we can only remove fields from the end, so that if our + // subtypes still need the fields they can append them. For example: // // type A = { x: i32, y: f64 }; // type B : A = { x: 132, y: f64, z: v128 }; @@ -392,6 +427,37 @@ struct GlobalTypeOptimization : public Pass { } } + // If the type has no supertype (or its supertype has no fields), check + // if its first field becomes prototype-exposing. If so, add a + // placeholder at index 0 and shift all computed indices. + if (isExposedNoProto && (!super || super->getStruct().fields.empty())) { + // Find the field that will become field 0. + Index i = 0; + for (; i < fields.size(); ++i) { + if (indexesAfterRemoval[i] == 0) { + break; + } + } + // Check whether that field would expose a prototype. + if (i < fields.size()) { + Field optimizedField = fields[i]; + if (auto it = canBecomeImmutable.find(type); + it != canBecomeImmutable.end() && i < it->second.size() && + it->second[i]) { + optimizedField.mutable_ = Immutable; + } + if (JSUtils::isPossibleJSPrototypeField(optimizedField)) { + // The field exposes a prototype. Increment all field indices to + // make room for a placeholder first field. + for (auto& idx : indexesAfterRemoval) { + if (idx != RemovedField) { + ++idx; + } + } + } + } + } + // Only store the new indexes we computed if we found something // interesting. We might not, if e.g. our parent removes fields and we // add them back in the exact order we started with. In such cases, @@ -416,7 +482,9 @@ struct GlobalTypeOptimization : public Pass { } } - void analyzeJSInterface(Module& wasm, const SubTypes& subTypes) { + void analyzeJSInterface(Module& wasm, + const SubTypes& subTypes, + const std::vector& jsExposedTypes) { if (!wasm.features.hasCustomDescriptors()) { return; } @@ -426,10 +494,16 @@ struct GlobalTypeOptimization : public Pass { // Mark the relevant prototype field as read and return true iff we newly // know we have to propagate the exposure to subtypes. auto noteExposed = [&](HeapType type, Exactness exact = Inexact) -> bool { - if (auto desc = type.getDescriptorType(); - desc && JSUtils::hasPossibleJSPrototypeField(*desc)) { - // This field holds a JS-visible prototype. Do not remove it. - combinedSetGetInfos[std::make_pair(*desc, exact)][0].noteRead(); + if (auto desc = type.getDescriptorType()) { + if (JSUtils::hasPossibleJSPrototypeField(*desc)) { + // This descriptor configures a JS-visible prototype. Do not remove + // it. + combinedSetGetInfos[std::make_pair(*desc, exact)][0].noteRead(); + } else { + // This descriptor does _not_ configure a JS prototype. Do not add + // one. + exposedNoProtoDescs.insert(*desc); + } } if (exact == Inexact) { return subtypesExposed.insert(type).second; @@ -449,6 +523,12 @@ struct GlobalTypeOptimization : public Pass { JSUtils::iterJSInterface(wasm, flowIn, flowOut); + for (auto type : jsExposedTypes) { + if (type.isRef()) { + noteExposed(type.getHeapType(), type.getExactness()); + } + } + // Any type that is a subtype of an exposed type is also exposed. Propagate // from supertypes to subtypes. std::vector work(subtypesExposed.begin(), subtypesExposed.end()); @@ -471,6 +551,19 @@ struct GlobalTypeOptimization : public Pass { } } } + + // Also propagate exposed descriptors to supertypes so that descriptor + // hierarchies have consistent layouts. Do not propagate to supertypes that + // actually expose a prototype. + for (auto type : subTypes.types) { + if (exposedNoProtoDescs.contains(type)) { + auto curr = type.getDeclaredSuperType(); + while (curr && !JSUtils::hasPossibleJSPrototypeField(*curr)) { + exposedNoProtoDescs.insert(*curr); + curr = curr->getDeclaredSuperType(); + } + } + } } void updateTypes(Module& wasm) { @@ -499,17 +592,19 @@ struct GlobalTypeOptimization : public Pass { auto remIter = parent.indexesAfterRemovals.find(oldStructType); if (remIter != parent.indexesAfterRemovals.end()) { auto& indexesAfterRemoval = remIter->second; - Index removed = 0; + IndexAnalysis analysis(indexesAfterRemoval); auto copy = newFields; - for (Index i = 0; i < newFields.size(); i++) { + newFields.resize(analysis.newSize); + if (analysis.hasPlaceholder) { + newFields[0] = Field(Field::i8, Immutable); + } + for (Index i = 0; i < copy.size(); i++) { auto newIndex = indexesAfterRemoval[i]; if (newIndex != RemovedField) { + assert(newIndex < newFields.size()); newFields[newIndex] = copy[i]; - } else { - removed++; } } - newFields.resize(newFields.size() - removed); // Update field names as well. The Type Rewriter cannot do this for // us, as it does not know which old fields map to which new ones (it @@ -595,26 +690,30 @@ struct GlobalTypeOptimization : public Pass { auto& operands = curr->operands; assert(indexesAfterRemoval.size() == operands.size()); - Index removed = 0; + IndexAnalysis analysis(indexesAfterRemoval); std::vector old(operands.begin(), operands.end()); for (Index i = 0; i < operands.size(); ++i) { - auto newIndex = indexesAfterRemoval[i]; - if (newIndex != RemovedField) { - assert(newIndex < operands.size()); - operands[newIndex] = old[i]; - } else { - ++removed; + if (indexesAfterRemoval[i] == RemovedField) { if (!func && EffectAnalyzer(getPassOptions(), *getModule(), old[i]).trap) { removedTrappingInits.push_back(old[i]); } } } - if (removed) { - operands.resize(operands.size() - removed); - } else { - // If we didn't remove anything then we must have reordered (or else - // we have done pointless work). + operands.resize(analysis.newSize); + if (analysis.hasPlaceholder) { + operands[0] = Builder(*getModule()).makeConst(Literal(int32_t(0))); + } + for (Index i = 0; i < old.size(); ++i) { + auto newIndex = indexesAfterRemoval[i]; + if (newIndex != RemovedField) { + assert(newIndex < operands.size()); + operands[newIndex] = old[i]; + } + } + if (analysis.newSize == old.size() && !analysis.hasPlaceholder) { + // If we didn't remove or insert anything then we must have reordered + // (or else we have done pointless work). assert(indexesAfterRemoval != makeIdentity(indexesAfterRemoval.size())); } @@ -697,7 +796,7 @@ struct GlobalTypeOptimization : public Pass { } auto& indexesAfterRemoval = iter->second; auto newIndex = indexesAfterRemoval[index]; - assert(newIndex < indexesAfterRemoval.size() || + assert(newIndex < IndexAnalysis(indexesAfterRemoval).newSize || newIndex == RemovedField); return newIndex; } diff --git a/test/lit/passes/gto-jsinterop.wast b/test/lit/passes/gto-jsinterop.wast index 6376b7553f3..4cabdc0dbc5 100644 --- a/test/lit/passes/gto-jsinterop.wast +++ b/test/lit/passes/gto-jsinterop.wast @@ -685,3 +685,1029 @@ (local (ref null $sub)) ) ) + +(module + ;; Field 0 is mutable externref. It can become immutable, but doing so would + ;; expose it as a prototype. An immutable i8 placeholder is prepended. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc) (struct)) + (type $struct (descriptor $desc) (struct)) + ;; CHECK: (type $desc (describes $struct) (struct (field i8) (field externref))) + (type $desc (describes $struct) (struct (field (mut externref)))) + ) + + ;; CHECK: (type $2 (func (param (ref $desc)))) + + ;; CHECK: (type $3 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + + ;; CHECK: (func $test (type $3) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $struct + ;; CHECK-NEXT: (struct.new $desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $struct + (struct.new $desc + (extern.convert_any + (struct.new_default $desc) + ) + ) + ) + ) + + ;; CHECK: (func $get (type $2) (param $d (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $d (ref $desc)) + (drop + (struct.get $desc 0 + (local.get $d) + ) + ) + ) +) + +(module + ;; Field 0 is mutable non-nullable externref. It can become immutable, but + ;; doing so would expose it as a prototype. An immutable i8 placeholder is + ;; prepended. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc) (struct)) + (type $struct (descriptor $desc) (struct)) + ;; CHECK: (type $desc (describes $struct) (struct (field i8) (field (ref extern)))) + (type $desc (describes $struct) (struct (field (mut (ref extern))))) + ) + + ;; CHECK: (type $2 (func (param (ref $desc)))) + + ;; CHECK: (type $3 (func (param (ref extern)) (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $3) (param $e (ref extern)) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $struct + ;; CHECK-NEXT: (struct.new $desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (local.get $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $e (ref extern)) (result structref) + (struct.new_desc $struct + (struct.new $desc + (local.get $e) + ) + ) + ) + + ;; CHECK: (func $get (type $2) (param $d (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $d (ref $desc)) + (drop + (struct.get $desc 0 + (local.get $d) + ) + ) + ) +) + +(module + ;; Field 0 is unused and can be removed. Field 1 is immutable externref. + ;; Removing field 0 would shift field 1 into index 0 and expose it as a + ;; prototype. An immutable i8 placeholder is inserted at index 0. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc) (struct)) + (type $struct (descriptor $desc) (struct)) + ;; CHECK: (type $desc (describes $struct) (struct (field i8) (field externref))) + (type $desc (describes $struct) (struct (field i32) (field externref))) + ) + + ;; CHECK: (type $2 (func (param (ref $desc)))) + + ;; CHECK: (type $3 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $3) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $struct + ;; CHECK-NEXT: (struct.new $desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $struct + (struct.new $desc + (i32.const 42) + (extern.convert_any + (struct.new_default $desc) + ) + ) + ) + ) + + ;; CHECK: (func $get (type $2) (param $d (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get (param $d (ref $desc)) + (drop + (struct.get $desc 1 + (local.get $d) + ) + ) + ) +) + +(module + ;; Field 0 is unused in $super-desc and removed. Field 1 is an immutable + ;; externref. $sub-desc still uses field 0. $super-desc reorders field 1 to + ;; index 0 to remove field 0 from the end. Because field 0 is now externref, + ;; a placeholder is inserted. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super-desc) (struct))) + (type $super (sub (descriptor $super-desc) (struct))) + ;; CHECK: (type $super-desc (sub (describes $super) (struct (field i8) (field externref)))) + (type $super-desc (sub (describes $super) (struct (field i32) (field externref)))) + ) + (rec + ;; CHECK: (type $sub (sub $super (descriptor $sub-desc) (struct))) + (type $sub (sub $super (descriptor $sub-desc) (struct))) + ;; CHECK: (type $sub-desc (sub $super-desc (describes $sub) (struct (field i8) (field externref) (field i32)))) + (type $sub-desc (sub $super-desc (describes $sub) (struct (field i32) (field externref)))) + ) + + ;; CHECK: (type $4 (func (param (ref $super-desc)))) + + ;; CHECK: (type $5 (func (param (ref $sub-desc)))) + + ;; CHECK: (type $6 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $6) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $super + ;; CHECK-NEXT: (struct.new $super-desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $super-desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $super + (struct.new $super-desc + (i32.const 42) + (extern.convert_any + (struct.new_default $super-desc) + ) + ) + ) + ) + + ;; CHECK: (func $use-super (type $4) (param $d (ref $super-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super-desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-super (param $d (ref $super-desc)) + (drop + (struct.get $super-desc 1 + (local.get $d) + ) + ) + ) + + ;; CHECK: (func $use-sub (type $5) (param $d (ref $sub-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub-desc 2 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub-desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-sub (param $d (ref $sub-desc)) + (drop + (struct.get $sub-desc 0 + (local.get $d) + ) + ) + (drop + (struct.get $sub-desc 1 + (local.get $d) + ) + ) + ) +) + +(module + ;; The supertype descriptor is empty. The subtype descriptor has a mutable + ;; externref field. The subtype descriptor gets an i8 placeholder while + ;; continuing to validly subtype the supertype descriptor. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super-desc) (struct))) + (type $super (sub (descriptor $super-desc) (struct))) + ;; CHECK: (type $super-desc (sub (describes $super) (struct))) + (type $super-desc (sub (describes $super) (struct))) + ) + (rec + ;; CHECK: (type $sub (sub $super (descriptor $sub-desc) (struct))) + (type $sub (sub $super (descriptor $sub-desc) (struct))) + ;; CHECK: (type $sub-desc (sub $super-desc (describes $sub) (struct (field i8) (field externref)))) + (type $sub-desc (sub $super-desc (describes $sub) (struct (field (mut externref))))) + ) + + + ;; CHECK: (type $4 (func (param (ref $sub-desc)))) + + ;; CHECK: (type $5 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + + ;; CHECK: (func $test (type $5) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $sub + ;; CHECK-NEXT: (struct.new $sub-desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $sub + (struct.new $sub-desc + (ref.null extern) + ) + ) + ) + + ;; CHECK: (func $use (type $4) (param $d (ref $sub-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub-desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use (param $d (ref $sub-desc)) + (drop + (struct.get $sub-desc 0 + (local.get $d) + ) + ) + ) +) + +(module + ;; A supertype gets a placeholder, so its non-exposed subtype also gets a + ;; placeholder. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super-desc) (struct))) + (type $super (sub (descriptor $super-desc) (struct))) + ;; CHECK: (type $super-desc (sub (describes $super) (struct (field i8) (field externref)))) + (type $super-desc (sub (describes $super) (struct (field (mut externref))))) + ) + (rec + ;; CHECK: (type $sub (sub $super (descriptor $sub-desc) (struct))) + (type $sub (sub $super (descriptor $sub-desc) (struct))) + ;; CHECK: (type $sub-desc (sub $super-desc (describes $sub) (struct (field i8) (field externref) (field i32)))) + (type $sub-desc (sub $super-desc (describes $sub) (struct (field (mut externref)) (field (mut i32))))) + ) + + ;; CHECK: (type $4 (func (param (ref $super-desc)))) + + ;; CHECK: (type $5 (func (param (ref $sub-desc)))) + + ;; CHECK: (type $6 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $6) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $super + ;; CHECK-NEXT: (struct.new $super-desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $super-desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $super + (struct.new $super-desc + (extern.convert_any + (struct.new_default $super-desc) + ) + ) + ) + ) + + ;; CHECK: (func $use-super (type $4) (param $d (ref $super-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super-desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-super (param $d (ref $super-desc)) + (drop + (struct.get $super-desc 0 + (local.get $d) + ) + ) + ) + + ;; CHECK: (func $use-sub (type $5) (param $d (ref $sub-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub-desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub-desc 2 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-sub (param $d (ref $sub-desc)) + (drop + (struct.get $sub-desc 0 + (local.get $d) + ) + ) + (drop + (struct.get $sub-desc 1 + (local.get $d) + ) + ) + ) +) + +(module + ;; An exposed subtype gets a placeholder, so its non-empty supertype also gets + ;; a placeholder. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super-desc) (struct))) + (type $super (sub (descriptor $super-desc) (struct))) + ;; CHECK: (type $super-desc (sub (describes $super) (struct (field i8) (field externref)))) + (type $super-desc (sub (describes $super) (struct (field (mut externref))))) + ) + (rec + ;; CHECK: (type $sub (sub $super (descriptor $sub-desc) (struct))) + (type $sub (sub $super (descriptor $sub-desc) (struct))) + ;; CHECK: (type $sub-desc (sub $super-desc (describes $sub) (struct (field i8) (field externref)))) + (type $sub-desc (sub $super-desc (describes $sub) (struct (field (mut externref))))) + ) + + ;; CHECK: (type $4 (func (param (ref $super-desc)))) + + ;; CHECK: (type $5 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $5) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $sub + ;; CHECK-NEXT: (struct.new $sub-desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $sub-desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $sub + (struct.new $sub-desc + (extern.convert_any + (struct.new_default $sub-desc) + ) + ) + ) + ) + + ;; CHECK: (func $use-super (type $4) (param $d (ref $super-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super-desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-super (param $d (ref $super-desc)) + (drop + (struct.get $super-desc 0 + (local.get $d) + ) + ) + ) +) + +(module + ;; Multi-level subtyping: an exposed leaf subtype descriptor causes its + ;; grandparent descriptor (which defines field 0) to get a placeholder, + ;; propagating down the full inheritance chain. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $grandparent (sub (descriptor $grandparent-desc) (struct))) + (type $grandparent (sub (descriptor $grandparent-desc) (struct))) + ;; CHECK: (type $grandparent-desc (sub (describes $grandparent) (struct (field i8) (field externref)))) + (type $grandparent-desc (sub (describes $grandparent) (struct (field (mut externref))))) + ) + (rec + ;; CHECK: (type $parent (sub $grandparent (descriptor $parent-desc) (struct))) + (type $parent (sub $grandparent (descriptor $parent-desc) (struct))) + ;; CHECK: (type $parent-desc (sub $grandparent-desc (describes $parent) (struct (field i8) (field externref) (field i32)))) + (type $parent-desc (sub $grandparent-desc (describes $parent) (struct (field (mut externref)) (field (mut i32))))) + ) + (rec + ;; CHECK: (type $child (sub $parent (descriptor $child-desc) (struct))) + (type $child (sub $parent (descriptor $child-desc) (struct))) + ;; CHECK: (type $child-desc (sub $parent-desc (describes $child) (struct (field i8) (field externref) (field i32) (field i64)))) + (type $child-desc (sub $parent-desc (describes $child) (struct (field (mut externref)) (field (mut i32)) (field (mut i64))))) + ) + + ;; CHECK: (type $6 (func (param (ref $grandparent-desc) (ref $parent-desc) (ref $child-desc)))) + + ;; CHECK: (type $7 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $7) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $child + ;; CHECK-NEXT: (struct.new $child-desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $child-desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: (i64.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $child + (struct.new $child-desc + (extern.convert_any + (struct.new_default $child-desc) + ) + (i32.const 1) + (i64.const 2) + ) + ) + ) + + ;; CHECK: (func $use (type $6) (param $g (ref $grandparent-desc)) (param $p (ref $parent-desc)) (param $c (ref $child-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $grandparent-desc 1 + ;; CHECK-NEXT: (local.get $g) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $parent-desc 2 + ;; CHECK-NEXT: (local.get $p) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $child-desc 3 + ;; CHECK-NEXT: (local.get $c) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use (param $g (ref $grandparent-desc)) (param $p (ref $parent-desc)) (param $c (ref $child-desc)) + (drop (struct.get $grandparent-desc 0 (local.get $g))) + (drop (struct.get $parent-desc 1 (local.get $p))) + (drop (struct.get $child-desc 2 (local.get $c))) + ) +) + +(module + ;; Branching subtyping: an exposed subtype causes its supertype to get a + ;; placeholder, which is inherited by an unexposed sibling subtype. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super-desc) (struct))) + (type $super (sub (descriptor $super-desc) (struct))) + ;; CHECK: (type $super-desc (sub (describes $super) (struct (field i8) (field externref)))) + (type $super-desc (sub (describes $super) (struct (field (mut externref))))) + ) + (rec + ;; CHECK: (type $sub1 (sub $super (descriptor $sub1-desc) (struct))) + (type $sub1 (sub $super (descriptor $sub1-desc) (struct))) + ;; CHECK: (type $sub1-desc (sub $super-desc (describes $sub1) (struct (field i8) (field externref) (field i32)))) + (type $sub1-desc (sub $super-desc (describes $sub1) (struct (field (mut externref)) (field (mut i32))))) + ) + (rec + ;; CHECK: (type $sub2 (sub $super (descriptor $sub2-desc) (struct))) + (type $sub2 (sub $super (descriptor $sub2-desc) (struct))) + ;; CHECK: (type $sub2-desc (sub $super-desc (describes $sub2) (struct (field i8) (field externref) (field f64)))) + (type $sub2-desc (sub $super-desc (describes $sub2) (struct (field (mut externref)) (field (mut f64))))) + ) + + ;; CHECK: (type $6 (func (param (ref $super-desc)))) + + ;; CHECK: (type $7 (func (param (ref $sub1-desc)))) + + ;; CHECK: (type $8 (func (param (ref $sub2-desc)))) + + ;; CHECK: (type $9 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $9) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $sub1 + ;; CHECK-NEXT: (struct.new $sub1-desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $sub1-desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $sub1 + (struct.new $sub1-desc + (extern.convert_any + (struct.new_default $sub1-desc) + ) + (i32.const 1) + ) + ) + ) + + ;; CHECK: (func $use-super (type $6) (param $d (ref $super-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $super-desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-super (param $d (ref $super-desc)) + (drop (struct.get $super-desc 0 (local.get $d))) + ) + + ;; CHECK: (func $use-sub1 (type $7) (param $d (ref $sub1-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub1-desc 2 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-sub1 (param $d (ref $sub1-desc)) + (drop (struct.get $sub1-desc 1 (local.get $d))) + ) + + ;; CHECK: (func $use-sub2 (type $8) (param $d (ref $sub2-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub2-desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub2-desc 2 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-sub2 (param $d (ref $sub2-desc)) + (drop (struct.get $sub2-desc 0 (local.get $d))) + (drop (struct.get $sub2-desc 1 (local.get $d))) + ) +) + +(module + ;; Field 0 is a mutable non-externref field. It can become immutable without + ;; exposing a prototype, so no placeholder is inserted. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc) (struct)) + (type $struct (descriptor $desc) (struct)) + ;; CHECK: (type $desc (describes $struct) (struct (field anyref) (field anyref))) + (type $desc (describes $struct) (struct (field (mut anyref)) (field (mut anyref)))) + ) + + ;; CHECK: (type $2 (func (param (ref $desc)))) + + ;; CHECK: (type $3 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $3) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $struct + ;; CHECK-NEXT: (struct.new $desc + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $struct + (struct.new $desc + (ref.null any) + (ref.null any) + ) + ) + ) + + ;; CHECK: (func $use (type $2) (param $d (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $desc 0 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use (param $d (ref $desc)) + (drop (struct.get $desc 0 (local.get $d))) + (drop (struct.get $desc 1 (local.get $d))) + ) +) + +(module + ;; Field 0 is removed, and field 1 is an immutable non-externref field, so + ;; shifting it to index 0 does not expose a prototype and no placeholder is + ;; inserted. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc) (struct)) + (type $struct (descriptor $desc) (struct)) + ;; CHECK: (type $desc (describes $struct) (struct (field anyref))) + (type $desc (describes $struct) (struct (field i64) (field anyref))) + ) + + ;; CHECK: (type $2 (func (param (ref $desc)))) + + ;; CHECK: (type $3 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $3) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $struct + ;; CHECK-NEXT: (struct.new $desc + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $struct + (struct.new $desc + (i64.const 100) + (ref.null any) + ) + ) + ) + + ;; CHECK: (func $use (type $2) (param $d (ref $desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $desc 0 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use (param $d (ref $desc)) + (drop (struct.get $desc 1 (local.get $d))) + ) +) + +(module + ;; All fields of an exposed descriptor are unused and removed, resulting in an + ;; empty descriptor (struct). No placeholder is needed. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc) (struct)) + (type $struct (descriptor $desc) (struct)) + ;; CHECK: (type $desc (describes $struct) (struct)) + (type $desc (describes $struct) (struct (field (mut externref)) (field i32))) + ) + + ;; CHECK: (type $2 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $2) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $struct + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $struct + (struct.new $desc + (extern.convert_any + (struct.new_default $desc) + ) + (i32.const 42) + ) + ) + ) +) + +(module + ;; Exact type on the JS boundary: an exact (ref (exact $super)) does not + ;; propagate exposure to $sub-desc, so $sub-desc does not get an unnecessary + ;; placeholder when $super-desc is empty. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super-desc) (struct))) + (type $super (sub (descriptor $super-desc) (struct))) + ;; CHECK: (type $super-desc (sub (describes $super) (struct))) + (type $super-desc (sub (describes $super) (struct))) + ) + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $sub (sub $super (descriptor $sub-desc) (struct))) + (type $sub (sub $super (descriptor $sub-desc) (struct))) + ;; CHECK: (type $sub-desc (sub $super-desc (describes $sub) (struct (field externref)))) + (type $sub-desc (sub $super-desc (describes $sub) (struct (field (mut externref))))) + ) + + ;; CHECK: (type $4 (func (param (ref $sub-desc)))) + + ;; CHECK: (type $5 (func (param (ref null (exact $super))))) + + ;; CHECK: (import "" "" (func $import (type $5) (param (ref null (exact $super))))) + (import "" "" (func $import (param (ref null (exact $super))))) + + ;; CHECK: (func $use-sub (type $4) (param $d (ref $sub-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub-desc 0 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-sub (param $d (ref $sub-desc)) + (drop (struct.get $sub-desc 0 (local.get $d))) + ) +) + +(module + ;; Exact type flowing into extern.convert_any: an exact + ;; (extern.convert_any (ref (exact $super))) does not propagate exposure to + ;; $sub-desc, so $sub-desc does not get an unnecessary placeholder when + ;; $super-desc is empty. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super-desc) (struct))) + (type $super (sub (descriptor $super-desc) (struct))) + ;; CHECK: (type $super-desc (sub (describes $super) (struct))) + (type $super-desc (sub (describes $super) (struct))) + ) + (rec + ;; CHECK: (type $sub (sub $super (descriptor $sub-desc) (struct))) + (type $sub (sub $super (descriptor $sub-desc) (struct))) + ;; CHECK: (type $sub-desc (sub $super-desc (describes $sub) (struct (field externref)))) + (type $sub-desc (sub $super-desc (describes $sub) (struct (field (mut externref))))) + ) + + ;; CHECK: (type $4 (func (param (ref null (exact $super))))) + + ;; CHECK: (type $5 (func (param (ref $sub-desc)))) + + ;; CHECK: (func $test (type $4) (param $s (ref null (exact $super))) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (local.get $s) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $s (ref null (exact $super))) + (drop + (extern.convert_any + (local.get $s) + ) + ) + ) + + ;; CHECK: (func $use-sub (type $5) (param $d (ref $sub-desc)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $sub-desc 0 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use-sub (param $d (ref $sub-desc)) + (drop (struct.get $sub-desc 0 (local.get $d))) + ) +) + +(module + ;; An exposed descriptor that receives a placeholder is also used directly + ;; with struct.get / struct.set / struct.new; all instruction operands and + ;; field indices are shifted correctly. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (descriptor $desc) (struct)) + (type $struct (descriptor $desc) (struct)) + ;; CHECK: (type $desc (describes $struct) (struct (field i8) (field externref) (field (mut i32)))) + (type $desc (describes $struct) (struct (field (mut externref)) (field (mut i32)))) + ) + + ;; CHECK: (type $2 (func (param (ref $desc)) (result (ref $desc)))) + + ;; CHECK: (type $3 (func (result structref))) + + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + ;; CHECK: (func $test (type $3) (result structref) + ;; CHECK-NEXT: (struct.new_default_desc $struct + ;; CHECK-NEXT: (struct.new $desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default $desc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result structref) + (struct.new_desc $struct + (struct.new $desc + (extern.convert_any + (struct.new_default $desc) + ) + (i32.const 42) + ) + ) + ) + + ;; CHECK: (func $manipulate (type $2) (param $d (ref $desc)) (result (ref $desc)) + ;; CHECK-NEXT: (struct.set $desc 2 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: (i32.const 100) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $desc 2 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (i32.const 200) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $manipulate (param $d (ref $desc)) (result (ref $desc)) + (struct.set $desc 1 + (local.get $d) + (i32.const 100) + ) + (drop + (struct.get $desc 0 + (local.get $d) + ) + ) + (drop + (struct.get $desc 1 + (local.get $d) + ) + ) + (struct.new $desc + (ref.null extern) + (i32.const 200) + ) + ) +) + +(module + ;; A struct descriptor has an initial nullexternref field and a subsequent + ;; externref field. The initial nullexternref field does not expose a JS + ;; prototype. Removing the unread nullexternref field causes the externref + ;; field to shift to index 0. Because the descriptor has no prototype, an i8 + ;; placeholder is inserted at index 0 to keep the externref field at index 1. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $struct (sub (descriptor $desc) (struct))) + (type $struct (sub (descriptor $desc) (struct))) + ;; CHECK: (type $desc (sub (describes $struct) (struct (field i8) (field externref)))) + (type $desc (sub (describes $struct) (struct (field nullexternref) (field externref)))) + ) + + ;; CHECK: (type $2 (func (param (ref $desc)) (result externref))) + + ;; CHECK: (type $3 (func (result externref))) + + ;; CHECK: (import "env" "ext" (global $ext externref)) + (import "env" "ext" (global $ext externref)) + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + + ;; CHECK: (func $test (type $3) (result externref) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default_desc $struct + ;; CHECK-NEXT: (struct.new $desc + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (global.get $ext) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result externref) + (extern.convert_any + (struct.new_default_desc $struct + (struct.new $desc + (ref.null noextern) + (global.get $ext) + ) + ) + ) + ) + + ;; CHECK: (func $use (type $2) (param $d (ref $desc)) (result externref) + ;; CHECK-NEXT: (struct.get $desc 1 + ;; CHECK-NEXT: (local.get $d) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $use (param $d (ref $desc)) (result externref) + (struct.get $desc 1 + (local.get $d) + ) + ) +) + +(module + ;; The supertype descriptor has an externref prototype field. The subtype + ;; descriptor narrows the prototype field to nullexternref. The supertype + ;; descriptor exposes a prototype and must not have exposedNoProtoDescs + ;; propagated to it from the subtype descriptor, so it does not get an i8 + ;; placeholder inserted. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (descriptor $super-desc) (struct))) + (type $super (sub (descriptor $super-desc) (struct))) + ;; CHECK: (type $sub (sub final $super (descriptor $sub-desc) (struct))) + (type $sub (sub final $super (descriptor $sub-desc) (struct))) + ;; CHECK: (type $super-desc (sub (describes $super) (struct (field externref)))) + (type $super-desc (sub (describes $super) (struct (field externref)))) + ;; CHECK: (type $sub-desc (sub final $super-desc (describes $sub) (struct (field nullexternref)))) + (type $sub-desc (sub final $super-desc (describes $sub) (struct (field nullexternref)))) + ) + + ;; CHECK: (type $4 (func (result externref))) + + ;; CHECK: (import "env" "ext" (global $ext externref)) + (import "env" "ext" (global $ext externref)) + ;; CHECK: (export "export" (func $test)) + (export "export" (func $test)) + + ;; CHECK: (func $test (type $4) (result externref) + ;; CHECK-NEXT: (extern.convert_any + ;; CHECK-NEXT: (struct.new_default_desc $super + ;; CHECK-NEXT: (struct.new $super-desc + ;; CHECK-NEXT: (global.get $ext) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (result externref) + (extern.convert_any + (struct.new_default_desc $super + (struct.new $super-desc + (global.get $ext) + ) + ) + ) + ) +)