@@ -70,16 +70,20 @@ struct FieldInfo {
7070
7171struct FieldInfoScanner
7272 : public StructUtils::StructScanner<FieldInfo, FieldInfoScanner> {
73+ std::unordered_map<Function*, std::vector<Type>>& jsExposedTypes;
74+
7375 std::unique_ptr<Pass> create () override {
74- return std::make_unique<FieldInfoScanner>(functionNewInfos,
75- functionSetGetInfos);
76+ return std::make_unique<FieldInfoScanner>(
77+ functionNewInfos, functionSetGetInfos, jsExposedTypes );
7678 }
7779
7880 FieldInfoScanner (
7981 StructUtils::FunctionStructValuesMap<FieldInfo>& functionNewInfos,
80- StructUtils::FunctionStructValuesMap<FieldInfo>& functionSetGetInfos)
82+ StructUtils::FunctionStructValuesMap<FieldInfo>& functionSetGetInfos,
83+ std::unordered_map<Function*, std::vector<Type>>& jsExposedTypes)
8184 : StructUtils::StructScanner<FieldInfo, FieldInfoScanner>(
82- functionNewInfos, functionSetGetInfos) {}
85+ functionNewInfos, functionSetGetInfos),
86+ jsExposedTypes (jsExposedTypes) {}
8387
8488 void noteExpression (Expression* expr,
8589 HeapType type,
@@ -116,16 +120,8 @@ struct FieldInfoScanner
116120 // Converting a reference to externref makes the prototype field on its
117121 // descriptor available to be read by JS, if such a field exists.
118122 void visitRefAs (RefAs* curr) {
119- if (curr->op != ExternConvertAny) {
120- return ;
121- }
122- if (!curr->value ->type .isRef ()) {
123- return ;
124- }
125- if (auto desc = curr->value ->type .getHeapType ().getDescriptorType ();
126- desc && JSUtils::hasPossibleJSPrototypeField (*desc)) {
127- auto exact = curr->value ->type .getExactness ();
128- functionSetGetInfos[getFunction ()][{*desc, exact}][0 ].noteRead ();
123+ if (curr->op == ExternConvertAny && curr->value ->type .isRef ()) {
124+ jsExposedTypes.at (getFunction ()).push_back (curr->value ->type );
129125 }
130126 }
131127};
@@ -139,6 +135,11 @@ struct GlobalTypeOptimization : public Pass {
139135 // rare).
140136 std::unordered_map<HeapType, std::vector<bool >> canBecomeImmutable;
141137
138+ // Descriptor types that are exposed to JS but do _not_ configure prototypes
139+ // for their described types. We must avoid optimizing these types such that
140+ // they start configuring prototypes.
141+ std::unordered_set<HeapType> exposedNoProtoDescs;
142+
142143 // Maps each field to its new index after field removals. That is, this
143144 // takes into account that fields before this one may have been removed,
144145 // which would then reduce this field's index. If a field itself is removed,
@@ -149,6 +150,28 @@ struct GlobalTypeOptimization : public Pass {
149150 static const Index RemovedField = Index(-1 );
150151 std::unordered_map<HeapType, std::vector<Index>> indexesAfterRemovals;
151152
153+ struct IndexAnalysis {
154+ Index newSize = 0 ;
155+ bool hasPlaceholder = false ;
156+
157+ IndexAnalysis (const std::vector<Index>& indexes) {
158+ Index maxIndex = 0 ;
159+ bool hasKept = false ;
160+ bool hasIndexZero = false ;
161+ for (auto idx : indexes) {
162+ if (idx != RemovedField) {
163+ hasKept = true ;
164+ maxIndex = std::max (maxIndex, idx);
165+ if (idx == 0 ) {
166+ hasIndexZero = true ;
167+ }
168+ }
169+ }
170+ newSize = hasKept ? maxIndex + 1 : 0 ;
171+ hasPlaceholder = hasKept && !hasIndexZero;
172+ }
173+ };
174+
152175 void run (Module* module ) override {
153176 if (!module ->features .hasGC ()) {
154177 return ;
@@ -157,21 +180,32 @@ struct GlobalTypeOptimization : public Pass {
157180 Fatal () << " GTO requires --closed-world" ;
158181 }
159182
183+ std::unordered_map<Function*, std::vector<Type>> jsExposedTypesByFunction;
184+ jsExposedTypesByFunction[nullptr ];
185+ for (auto & func : module ->functions ) {
186+ jsExposedTypesByFunction[func.get ()];
187+ }
188+
160189 // Find and analyze struct operations inside each function.
161190 StructUtils::FunctionStructValuesMap<FieldInfo> functionNewInfos (*module ),
162191 functionSetGetInfos (*module );
163- FieldInfoScanner scanner (functionNewInfos, functionSetGetInfos);
192+ FieldInfoScanner scanner (
193+ functionNewInfos, functionSetGetInfos, jsExposedTypesByFunction);
164194 scanner.run (getPassRunner (), module );
165195 scanner.runOnModuleCode (getPassRunner (), module );
166196
167197 // Combine the data from the functions.
168198 functionSetGetInfos.combineInto (combinedSetGetInfos);
199+ std::vector<Type> jsExposedTypes;
200+ for (auto & [_, types] : jsExposedTypesByFunction) {
201+ jsExposedTypes.insert (jsExposedTypes.end (), types.begin (), types.end ());
202+ }
169203
170204 SubTypes subTypes (*module );
171205
172206 // Analyze the JS interface to find fields holding configured prototypes
173207 // that cannot be removed.
174- analyzeJSInterface (*module , subTypes);
208+ analyzeJSInterface (*module , subTypes, jsExposedTypes );
175209
176210 // Propagate information to super and subtypes on set/get infos:
177211 //
@@ -291,15 +325,16 @@ struct GlobalTypeOptimization : public Pass {
291325 }
292326
293327 // We need to compute the new set of indexes if we are removing fields, or
294- // if our parent removed fields. In the latter case, our parent may have
295- // reordered fields even if we ourselves are not removing anything, and we
296- // must update to match the parent's order.
328+ // if our parent removed fields, or if we might need a placeholder. If we
329+ // have a parent, it may have reordered fields even if we ourselves are
330+ // not removing anything, and we must update to match the parent's order.
297331 auto super = type.getDeclaredSuperType ();
298332 auto superHasUpdates = super && indexesAfterRemovals.contains (*super);
299- if (!removableIndexes.empty () || superHasUpdates) {
300- // We are removing fields. Reorder them to allow that, as in the general
301- // case we can only remove fields from the end, so that if our subtypes
302- // still need the fields they can append them. For example:
333+ bool isExposedNoProto = exposedNoProtoDescs.contains (type);
334+ if (!removableIndexes.empty () || superHasUpdates || isExposedNoProto) {
335+ // We might be removing fields. Reorder them to allow that, as in the
336+ // general case we can only remove fields from the end, so that if our
337+ // subtypes still need the fields they can append them. For example:
303338 //
304339 // type A = { x: i32, y: f64 };
305340 // type B : A = { x: 132, y: f64, z: v128 };
@@ -392,6 +427,37 @@ struct GlobalTypeOptimization : public Pass {
392427 }
393428 }
394429
430+ // If the type has no supertype (or its supertype has no fields), check
431+ // if its first field becomes prototype-exposing. If so, add a
432+ // placeholder at index 0 and shift all computed indices.
433+ if (isExposedNoProto && (!super || super->getStruct ().fields .empty ())) {
434+ // Find the field that will become field 0.
435+ Index i = 0 ;
436+ for (; i < fields.size (); ++i) {
437+ if (indexesAfterRemoval[i] == 0 ) {
438+ break ;
439+ }
440+ }
441+ // Check whether that field would expose a prototype.
442+ if (i < fields.size ()) {
443+ Field optimizedField = fields[i];
444+ if (auto it = canBecomeImmutable.find (type);
445+ it != canBecomeImmutable.end () && i < it->second .size () &&
446+ it->second [i]) {
447+ optimizedField.mutable_ = Immutable;
448+ }
449+ if (JSUtils::isPossibleJSPrototypeField (optimizedField)) {
450+ // The field exposes a prototype. Increment all field indices to
451+ // make room for a placeholder first field.
452+ for (auto & idx : indexesAfterRemoval) {
453+ if (idx != RemovedField) {
454+ ++idx;
455+ }
456+ }
457+ }
458+ }
459+ }
460+
395461 // Only store the new indexes we computed if we found something
396462 // interesting. We might not, if e.g. our parent removes fields and we
397463 // add them back in the exact order we started with. In such cases,
@@ -416,7 +482,9 @@ struct GlobalTypeOptimization : public Pass {
416482 }
417483 }
418484
419- void analyzeJSInterface (Module& wasm, const SubTypes& subTypes) {
485+ void analyzeJSInterface (Module& wasm,
486+ const SubTypes& subTypes,
487+ const std::vector<Type>& jsExposedTypes) {
420488 if (!wasm.features .hasCustomDescriptors ()) {
421489 return ;
422490 }
@@ -426,10 +494,16 @@ struct GlobalTypeOptimization : public Pass {
426494 // Mark the relevant prototype field as read and return true iff we newly
427495 // know we have to propagate the exposure to subtypes.
428496 auto noteExposed = [&](HeapType type, Exactness exact = Inexact) -> bool {
429- if (auto desc = type.getDescriptorType ();
430- desc && JSUtils::hasPossibleJSPrototypeField (*desc)) {
431- // This field holds a JS-visible prototype. Do not remove it.
432- combinedSetGetInfos[std::make_pair (*desc, exact)][0 ].noteRead ();
497+ if (auto desc = type.getDescriptorType ()) {
498+ if (JSUtils::hasPossibleJSPrototypeField (*desc)) {
499+ // This descriptor configures a JS-visible prototype. Do not remove
500+ // it.
501+ combinedSetGetInfos[std::make_pair (*desc, exact)][0 ].noteRead ();
502+ } else {
503+ // This descriptor does _not_ configure a JS prototype. Do not add
504+ // one.
505+ exposedNoProtoDescs.insert (*desc);
506+ }
433507 }
434508 if (exact == Inexact) {
435509 return subtypesExposed.insert (type).second ;
@@ -449,6 +523,12 @@ struct GlobalTypeOptimization : public Pass {
449523
450524 JSUtils::iterJSInterface (wasm, flowIn, flowOut);
451525
526+ for (auto type : jsExposedTypes) {
527+ if (type.isRef ()) {
528+ noteExposed (type.getHeapType (), type.getExactness ());
529+ }
530+ }
531+
452532 // Any type that is a subtype of an exposed type is also exposed. Propagate
453533 // from supertypes to subtypes.
454534 std::vector<HeapType> work (subtypesExposed.begin (), subtypesExposed.end ());
@@ -471,6 +551,18 @@ struct GlobalTypeOptimization : public Pass {
471551 }
472552 }
473553 }
554+
555+ // Also propagate exposed descriptors to supertypes so that descriptor
556+ // hierarchies have consistent layouts.
557+ for (auto type : subTypes.types ) {
558+ if (exposedNoProtoDescs.contains (type)) {
559+ auto curr = type.getDeclaredSuperType ();
560+ while (curr) {
561+ exposedNoProtoDescs.insert (*curr);
562+ curr = curr->getDeclaredSuperType ();
563+ }
564+ }
565+ }
474566 }
475567
476568 void updateTypes (Module& wasm) {
@@ -499,17 +591,19 @@ struct GlobalTypeOptimization : public Pass {
499591 auto remIter = parent.indexesAfterRemovals .find (oldStructType);
500592 if (remIter != parent.indexesAfterRemovals .end ()) {
501593 auto & indexesAfterRemoval = remIter->second ;
502- Index removed = 0 ;
594+ IndexAnalysis analysis (indexesAfterRemoval) ;
503595 auto copy = newFields;
504- for (Index i = 0 ; i < newFields.size (); i++) {
596+ newFields.resize (analysis.newSize );
597+ if (analysis.hasPlaceholder ) {
598+ newFields[0 ] = Field (Field::i8 , Immutable);
599+ }
600+ for (Index i = 0 ; i < copy.size (); i++) {
505601 auto newIndex = indexesAfterRemoval[i];
506602 if (newIndex != RemovedField) {
603+ assert (newIndex < newFields.size ());
507604 newFields[newIndex] = copy[i];
508- } else {
509- removed++;
510605 }
511606 }
512- newFields.resize (newFields.size () - removed);
513607
514608 // Update field names as well. The Type Rewriter cannot do this for
515609 // us, as it does not know which old fields map to which new ones (it
@@ -595,26 +689,30 @@ struct GlobalTypeOptimization : public Pass {
595689 auto & operands = curr->operands ;
596690 assert (indexesAfterRemoval.size () == operands.size ());
597691
598- Index removed = 0 ;
692+ IndexAnalysis analysis (indexesAfterRemoval) ;
599693 std::vector<Expression*> old (operands.begin (), operands.end ());
600694 for (Index i = 0 ; i < operands.size (); ++i) {
601- auto newIndex = indexesAfterRemoval[i];
602- if (newIndex != RemovedField) {
603- assert (newIndex < operands.size ());
604- operands[newIndex] = old[i];
605- } else {
606- ++removed;
695+ if (indexesAfterRemoval[i] == RemovedField) {
607696 if (!func &&
608697 EffectAnalyzer (getPassOptions (), *getModule (), old[i]).trap ) {
609698 removedTrappingInits.push_back (old[i]);
610699 }
611700 }
612701 }
613- if (removed) {
614- operands.resize (operands.size () - removed);
615- } else {
616- // If we didn't remove anything then we must have reordered (or else
617- // we have done pointless work).
702+ operands.resize (analysis.newSize );
703+ if (analysis.hasPlaceholder ) {
704+ operands[0 ] = Builder (*getModule ()).makeConst (Literal (int32_t (0 )));
705+ }
706+ for (Index i = 0 ; i < old.size (); ++i) {
707+ auto newIndex = indexesAfterRemoval[i];
708+ if (newIndex != RemovedField) {
709+ assert (newIndex < operands.size ());
710+ operands[newIndex] = old[i];
711+ }
712+ }
713+ if (analysis.newSize == old.size () && !analysis.hasPlaceholder ) {
714+ // If we didn't remove or insert anything then we must have reordered
715+ // (or else we have done pointless work).
618716 assert (indexesAfterRemoval !=
619717 makeIdentity (indexesAfterRemoval.size ()));
620718 }
@@ -697,7 +795,7 @@ struct GlobalTypeOptimization : public Pass {
697795 }
698796 auto & indexesAfterRemoval = iter->second ;
699797 auto newIndex = indexesAfterRemoval[index];
700- assert (newIndex < indexesAfterRemoval. size () ||
798+ assert (newIndex < IndexAnalysis (indexesAfterRemoval). newSize ||
701799 newIndex == RemovedField);
702800 return newIndex;
703801 }
0 commit comments