2929#include " ir/type-updating.h"
3030#include " ir/utils.h"
3131#include " pass.h"
32+ #include " support/permutations.h"
3233#include " wasm-builder.h"
34+ #include " wasm-type-ordering.h"
3335#include " wasm-type.h"
3436#include " wasm.h"
3537
@@ -160,19 +162,23 @@ struct GlobalTypeOptimization : public Pass {
160162 // immutable). Note that by making more things immutable we therefore
161163 // make it possible to apply more specific subtypes in subtype fields.
162164 StructUtils::TypeHierarchyPropagator<FieldInfo> propagator (*module );
163- auto subSupers = combinedSetGetInfos;
164- propagator.propagateToSuperAndSubTypes (subSupers);
165- auto subs = std::move (combinedSetGetInfos);
166- propagator.propagateToSubTypes (subs);
167-
168- // Process the propagated info.
169- for (auto type : propagator.subTypes .types ) {
165+ auto dataFromSubsAndSupersMap = combinedSetGetInfos;
166+ propagator.propagateToSuperAndSubTypes (dataFromSubsAndSupersMap);
167+ auto dataFromSupersMap = std::move (combinedSetGetInfos);
168+ propagator.propagateToSubTypes (dataFromSupersMap);
169+
170+ // Process the propagated info. We look at supertypes first, as the order of
171+ // fields in a supertype is a constraint on what subtypes can do. That is,
172+ // we decide for each supertype what the optimal order is, and consider that
173+ // fixed, and then subtypes can decide how to sort fields that they append.
174+ HeapTypeOrdering::SupertypesFirst sorted;
175+ for (auto type : sorted.sort (propagator.subTypes .types )) {
170176 if (!type.isStruct ()) {
171177 continue ;
172178 }
173179 auto & fields = type.getStruct ().fields ;
174- auto & subSuper = subSupers [type];
175- auto & sub = subs [type];
180+ auto & dataFromSubsAndSupers = dataFromSubsAndSupersMap [type];
181+ auto & dataFromSupers = dataFromSupersMap [type];
176182
177183 // Process immutability.
178184 for (Index i = 0 ; i < fields.size (); i++) {
@@ -181,7 +187,7 @@ struct GlobalTypeOptimization : public Pass {
181187 continue ;
182188 }
183189
184- if (subSuper [i].hasWrite ) {
190+ if (dataFromSubsAndSupers [i].hasWrite ) {
185191 // A set exists.
186192 continue ;
187193 }
@@ -192,48 +198,132 @@ struct GlobalTypeOptimization : public Pass {
192198 vec[i] = true ;
193199 }
194200
195- // Process removability. We check separately for the ability to
196- // remove in a general way based on sub+super-propagated info (that is,
197- // fields that are not used in sub- or super-types, and so we can
198- // definitely remove them from all the relevant types) and also in the
199- // specific way that only works for removing at the end, which as
200- // mentioned above only looks at super-types.
201+ // Process removability.
201202 std::set<Index> removableIndexes;
202203 for (Index i = 0 ; i < fields.size (); i++) {
203- if (!subSuper[i].hasRead ) {
204- removableIndexes.insert (i);
205- }
206- }
207- for (int i = int (fields.size ()) - 1 ; i >= 0 ; i--) {
208- // Unlike above, a write would stop us here: above we propagated to both
209- // sub- and super-types, which means if we see no reads then there is no
210- // possible read of the data at all. But here we just propagated to
211- // subtypes, and so we need to care about the case where the parent
212- // writes to a field but does not read from it - we still need those
213- // writes to happen as children may read them. (Note that if no child
214- // reads this field, and since we check for reads in parents here, that
215- // means the field is not read anywhere at all, and we would have
216- // handled that case in the previous loop anyhow.)
217- if (!sub[i].hasRead && !sub[i].hasWrite ) {
204+ // If there is no read whatsoever, in either subs or supers, then we can
205+ // remove the field. That is so even if there are writes (it would be a
206+ // pointless "write-only field").
207+ auto hasNoReadsAnywhere = !dataFromSubsAndSupers[i].hasRead ;
208+
209+ // Check for reads or writes in ourselves and our supers. If there are
210+ // none, then operations only happen in our strict subtypes, and those
211+ // subtypes can define the field there, and we don't need it here.
212+ auto hasNoReadsOrWritesInSupers =
213+ !dataFromSupers[i].hasRead && !dataFromSupers[i].hasWrite ;
214+
215+ if (hasNoReadsAnywhere || hasNoReadsOrWritesInSupers) {
218216 removableIndexes.insert (i);
219- } else {
220- // Once we see something we can't remove, we must stop, as we can only
221- // remove from the end in this case.
222- break ;
223217 }
224218 }
225- if (!removableIndexes.empty ()) {
226- auto & indexesAfterRemoval = indexesAfterRemovals[type];
227- indexesAfterRemoval.resize (fields.size ());
228- Index skip = 0 ;
229- for (Index i = 0 ; i < fields.size (); i++) {
230- if (!removableIndexes.count (i)) {
231- indexesAfterRemoval[i] = i - skip;
219+
220+ // We need to compute the new set of indexes if we are removing fields, or
221+ // if our parent removed fields. In the latter case, our parent may have
222+ // reordered fields even if we ourselves are not removing anything, and we
223+ // must update to match the parent's order.
224+ auto super = type.getDeclaredSuperType ();
225+ auto superHasUpdates = super && indexesAfterRemovals.count (*super);
226+ if (!removableIndexes.empty () || superHasUpdates) {
227+ // We are removing fields. Reorder them to allow that, as in the general
228+ // case we can only remove fields from the end, so that if our subtypes
229+ // still need the fields they can append them. For example:
230+ //
231+ // type A = { x: i32, y: f64 };
232+ // type B : A = { x: 132, y: f64, z: v128 };
233+ //
234+ // If field x is used in B but never in A then we want to remove it, but
235+ // we cannot end up with this:
236+ //
237+ // type A = { y: f64 };
238+ // type B : A = { x: 132, y: f64, z: v128 };
239+ //
240+ // Here B no longer extends A's fields. Instead, we reorder A, which
241+ // then imposes the same order on B's fields:
242+ //
243+ // type A = { y: f64, x: i32 };
244+ // type B : A = { y: f64, x: i32, z: v128 };
245+ //
246+ // And after that, it is safe to remove x in A: B will then append it,
247+ // just like it appends z, leading to this:
248+ //
249+ // type A = { y: f64 };
250+ // type B : A = { y: f64, x: i32, z: v128 };
251+ //
252+ std::vector<Index> indexesAfterRemoval (fields.size ());
253+
254+ // The next new index to use.
255+ Index next = 0 ;
256+
257+ // If we have a super, then we extend it, and must match its fields.
258+ // That is, we can only append fields: we cannot reorder or remove any
259+ // field that is in the super.
260+ Index numSuperFields = 0 ;
261+ if (super) {
262+ // We have visited the super before. Get the information about its
263+ // fields.
264+ std::vector<Index> superIndexes;
265+ auto iter = indexesAfterRemovals.find (*super);
266+ if (iter != indexesAfterRemovals.end ()) {
267+ superIndexes = iter->second ;
232268 } else {
269+ // We did not store any information about the parent, because we
270+ // found nothing to optimize there. That means it is not removing or
271+ // reordering anything, so its new indexes are trivial.
272+ superIndexes = makeIdentity (super->getStruct ().fields .size ());
273+ }
274+
275+ numSuperFields = superIndexes.size ();
276+
277+ // Fields we keep but the super removed will be handled at the end.
278+ std::vector<Index> keptFieldsNotInSuper;
279+
280+ // Go over the super fields and handle them.
281+ for (Index i = 0 ; i < superIndexes.size (); ++i) {
282+ auto superIndex = superIndexes[i];
283+ if (superIndex == RemovedField) {
284+ if (removableIndexes.count (i)) {
285+ // This was removed in the super, and in us as well.
286+ indexesAfterRemoval[i] = RemovedField;
287+ } else {
288+ // This was removed in the super, but we actually need it. It
289+ // must appear after all other super fields, when we get to the
290+ // proper index for that, later. That is, we are reordering.
291+ keptFieldsNotInSuper.push_back (i);
292+ }
293+ } else {
294+ // The super kept this field, so we must keep it as well.
295+ assert (!removableIndexes.count (i));
296+ // We need to keep it at the same index so we remain compatible.
297+ indexesAfterRemoval[i] = superIndex;
298+ // Update |next| to refer to the next available index. Due to
299+ // possible reordering in the parent, we may not see indexes in
300+ // order here, so just take the max at each point in time.
301+ next = std::max (next, superIndex + 1 );
302+ }
303+ }
304+
305+ // Handle fields we keep but the super removed.
306+ for (auto i : keptFieldsNotInSuper) {
307+ indexesAfterRemoval[i] = next++;
308+ }
309+ }
310+
311+ // Go over the fields only defined in us, and not in any super.
312+ for (Index i = numSuperFields; i < fields.size (); ++i) {
313+ if (removableIndexes.count (i)) {
233314 indexesAfterRemoval[i] = RemovedField;
234- skip++;
315+ } else {
316+ indexesAfterRemoval[i] = next++;
235317 }
236318 }
319+
320+ // Only store the new indexes we computed if we found something
321+ // interesting. We might not, if e.g. our parent removes fields and we
322+ // add them back in the exact order we started with. In such cases,
323+ // avoid wasting memory and also time later.
324+ if (indexesAfterRemoval != makeIdentity (indexesAfterRemoval.size ())) {
325+ indexesAfterRemovals[type] = indexesAfterRemoval;
326+ }
237327 }
238328 }
239329
@@ -273,15 +363,16 @@ struct GlobalTypeOptimization : public Pass {
273363 }
274364 }
275365
276- // Remove fields where we can.
366+ // Remove/reorder fields where we can.
277367 auto remIter = parent.indexesAfterRemovals .find (oldStructType);
278368 if (remIter != parent.indexesAfterRemovals .end ()) {
279369 auto & indexesAfterRemoval = remIter->second ;
280370 Index removed = 0 ;
371+ auto copy = newFields;
281372 for (Index i = 0 ; i < newFields.size (); i++) {
282373 auto newIndex = indexesAfterRemoval[i];
283374 if (newIndex != RemovedField) {
284- newFields[newIndex] = newFields [i];
375+ newFields[newIndex] = copy [i];
285376 } else {
286377 removed++;
287378 }
@@ -347,26 +438,32 @@ struct GlobalTypeOptimization : public Pass {
347438 auto & operands = curr->operands ;
348439 assert (indexesAfterRemoval.size () == operands.size ());
349440
350- // Localize things so that we can simply remove the operands we no
351- // longer need .
441+ // Ensure any children with non-trivial effects are replaced with
442+ // local.gets, so that we can remove/reorder to our hearts' content .
352443 ChildLocalizer localizer (
353444 curr, getFunction (), *getModule (), getPassOptions ());
354445 replaceCurrent (localizer.getReplacement ());
355446
356- // Remove the unneeded operands.
447+ // Remove and reorder operands.
357448 Index removed = 0 ;
449+ std::vector<Expression*> old (operands.begin (), operands.end ());
358450 for (Index i = 0 ; i < operands.size (); i++) {
359451 auto newIndex = indexesAfterRemoval[i];
360452 if (newIndex != RemovedField) {
361453 assert (newIndex < operands.size ());
362- operands[newIndex] = operands [i];
454+ operands[newIndex] = old [i];
363455 } else {
364456 removed++;
365457 }
366458 }
367- operands.resize (operands.size () - removed);
368- // We should only get here if we did actual work.
369- assert (removed > 0 );
459+ if (removed) {
460+ operands.resize (operands.size () - removed);
461+ } else {
462+ // If we didn't remove anything then we must have reordered (or else
463+ // we have done pointless work).
464+ assert (indexesAfterRemoval !=
465+ makeIdentity (indexesAfterRemoval.size ()));
466+ }
370467 }
371468
372469 void visitStructSet (StructSet* curr) {
0 commit comments