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) + ) + ) + ) + ) +)