@@ -395,13 +395,23 @@ struct EscapeAnalyzer {
395395 // Whether the cast succeeds or fails, it does not escape.
396396 escapes = false ;
397397
398- // If the cast fails then the allocation is fully consumed and does not
399- // flow any further (instead, we trap).
400- if (!Type::isSubType (allocation->type , curr->type )) {
398+ if (curr->ref == child) {
399+ // If the cast fails then the allocation is fully consumed and does
400+ // not flow any further (instead, we trap).
401+ if (!Type::isSubType (allocation->type , curr->type )) {
402+ fullyConsumes = true ;
403+ }
404+ } else {
405+ assert (curr->desc == child);
401406 fullyConsumes = true ;
402407 }
403408 }
404409
410+ void visitRefGetDesc (RefGetDesc* curr) {
411+ escapes = false ;
412+ fullyConsumes = true ;
413+ }
414+
405415 // GC operations.
406416 void visitStructSet (StructSet* curr) {
407417 // The reference does not escape (but the value is stored to memory and
@@ -598,10 +608,14 @@ struct Struct2Local : PostWalker<Struct2Local> {
598608 : allocation(allocation), analyzer(analyzer), func(func), wasm(wasm),
599609 builder (wasm), fields(allocation->type.getHeapType().getStruct().fields) {
600610
601- // Allocate locals to store the allocation's fields in.
611+ // Allocate locals to store the allocation's fields and descriptor in.
602612 for (auto field : fields) {
603613 localIndexes.push_back (builder.addVar (func, field.type ));
604614 }
615+ if (allocation->descriptor ) {
616+ localIndexes.push_back (
617+ builder.addVar (func, allocation->descriptor ->type ));
618+ }
605619
606620 // Replace the things we need to using the visit* methods.
607621 walk (func->body );
@@ -708,61 +722,54 @@ struct Struct2Local : PostWalker<Struct2Local> {
708722 // First, assign the initial values to the new locals.
709723 std::vector<Expression*> contents;
710724
711- if (!allocation->isWithDefault ()) {
712- // We must assign the initial values to temp indexes, then copy them
713- // over all at once. If instead we did set them as we go, then we might
714- // hit a problem like this:
715- //
716- // (local.set X (new_X))
717- // (local.set Y (block (result ..)
718- // (.. (local.get X) ..) ;; returns new_X, wrongly
719- // (new_Y)
720- // )
721- //
722- // Note how we assign to the local X and use it during the assignment to
723- // the local Y - but we should still see the old value of X, not new_X.
724- // Temp locals X', Y' can ensure that:
725- //
726- // (local.set X' (new_X))
727- // (local.set Y' (block (result ..)
728- // (.. (local.get X) ..) ;; returns the proper, old X
729- // (new_Y)
730- // )
731- // ..
732- // (local.set X (local.get X'))
733- // (local.set Y (local.get Y'))
734- std::vector<Index> tempIndexes;
735-
725+ // We might be in a loop, so the locals representing the struct fields might
726+ // already have values. Furthermore, the computation of the new field values
727+ // might depend on the old field values. If we naively assign the new values
728+ // to the locals as they are computed, the computation of a later field may
729+ // use the new value of an earlier field where it should have used the old
730+ // value of the earlier field. To avoid this problem, we store all the
731+ // nontrivial new values in temp locals, and only once they have fully been
732+ // computed do we copy them into the locals representing the fields.
733+ std::vector<Index> tempIndexes;
734+ Index numTemps =
735+ (curr->isWithDefault () ? 0 : fields.size ()) + bool (curr->descriptor );
736+ tempIndexes.reserve (numTemps);
737+
738+ // Create the temp variables.
739+ if (!curr->isWithDefault ()) {
736740 for (auto field : fields) {
737741 tempIndexes.push_back (builder.addVar (func, field.type ));
738742 }
743+ }
744+ if (curr->descriptor ) {
745+ tempIndexes.push_back (builder.addVar (func, curr->descriptor ->type ));
746+ }
739747
740- // Store the initial values into the temp locals.
741- for (Index i = 0 ; i < tempIndexes.size (); i++) {
748+ // Store the initial values into the temp locals.
749+ if (!curr->isWithDefault ()) {
750+ for (Index i = 0 ; i < fields.size (); i++) {
742751 contents.push_back (
743- builder.makeLocalSet (tempIndexes[i], allocation->operands [i]));
744- }
745-
746- // Copy them to the normal ones.
747- for (Index i = 0 ; i < tempIndexes.size (); i++) {
748- auto * value = builder.makeLocalGet (tempIndexes[i], fields[i].type );
749- contents.push_back (builder.makeLocalSet (localIndexes[i], value));
752+ builder.makeLocalSet (tempIndexes[i], curr->operands [i]));
750753 }
754+ }
755+ if (curr->descriptor ) {
756+ contents.push_back (
757+ builder.makeLocalSet (tempIndexes[numTemps - 1 ], curr->descriptor ));
758+ }
751759
752- // TODO Check if the nondefault case does not increase code size in some
753- // cases. A heap allocation that implicitly sets the default values
754- // is smaller than multiple explicit settings of locals to
755- // defaults.
756- } else {
757- // Set the default values.
758- //
759- // Note that we must assign the defaults because we might be in a loop,
760- // that is, there might be a previous value.
761- for (Index i = 0 ; i < localIndexes.size (); i++) {
762- contents.push_back (builder.makeLocalSet (
763- localIndexes[i],
764- builder.makeConstantExpression (Literal::makeZero (fields[i].type ))));
765- }
760+ // Store the values into the locals representing the fields.
761+ for (Index i = 0 ; i < fields.size (); ++i) {
762+ auto * val =
763+ curr->isWithDefault ()
764+ ? builder.makeConstantExpression (Literal::makeZero (fields[i].type ))
765+ : builder.makeLocalGet (tempIndexes[i], fields[i].type );
766+ contents.push_back (builder.makeLocalSet (localIndexes[i], val));
767+ }
768+ if (curr->descriptor ) {
769+ auto * val =
770+ builder.makeLocalGet (tempIndexes[numTemps - 1 ], curr->descriptor ->type );
771+ contents.push_back (
772+ builder.makeLocalSet (localIndexes[fields.size ()], val));
766773 }
767774
768775 // Replace the allocation with a null reference. This changes the type
@@ -838,25 +845,69 @@ struct Struct2Local : PostWalker<Struct2Local> {
838845 return ;
839846 }
840847
841- // We know this RefCast receives our allocation, so we can see whether it
842- // succeeds or fails.
843- if (Type::isSubType (allocation->type , curr->type )) {
844- // The cast succeeds, so it is a no-op, and we can skip it, since after we
845- // remove the allocation it will not even be needed for validation.
846- replaceCurrent (curr->ref );
848+ if (curr->desc ) {
849+ // If we are doing a ref.cast_desc of the optimized allocation, but we
850+ // know it does not have a descriptor, then we know the cast must fail. We
851+ // also know the cast must fail if the optimized allocation flows in as
852+ // the descriptor, since it cannot possibly have been used in the
853+ // allocation of the cast value without having been considered to escape.
854+ if (!allocation->descriptor || analyzer.getInteraction (curr->desc ) ==
855+ ParentChildInteraction::Flows) {
856+ // The allocation does not have a descriptor, so there is no way for the
857+ // cast to succeed.
858+ replaceCurrent (builder.blockify (builder.makeDrop (curr->ref ),
859+ builder.makeDrop (curr->desc ),
860+ builder.makeUnreachable ()));
861+ } else {
862+ // The cast succeeds iff the optimized allocation's descriptor is the
863+ // same as the given descriptor and traps otherwise.
864+ auto type = allocation->descriptor ->type ;
865+ replaceCurrent (builder.blockify (
866+ builder.makeDrop (curr->ref ),
867+ builder.makeIf (
868+ builder.makeRefEq (
869+ curr->desc ,
870+ builder.makeLocalGet (localIndexes[fields.size ()], type)),
871+ builder.makeRefNull (allocation->type .getHeapType ()),
872+ builder.makeUnreachable ())));
873+ }
847874 } else {
848- // The cast fails, so this must trap.
849- replaceCurrent (builder.makeSequence (builder.makeDrop (curr->ref ),
850- builder.makeUnreachable ()));
875+ // We know this RefCast receives our allocation, so we can see whether it
876+ // succeeds or fails.
877+ if (Type::isSubType (allocation->type , curr->type )) {
878+ // The cast succeeds, so it is a no-op, and we can skip it, since after
879+ // we remove the allocation it will not even be needed for validation.
880+ replaceCurrent (curr->ref );
881+ } else {
882+ // The cast fails, so this must trap.
883+ replaceCurrent (builder.makeSequence (builder.makeDrop (curr->ref ),
884+ builder.makeUnreachable ()));
885+ }
851886 }
852887
853- // Either way , we need to refinalize here (we either added an unreachable,
888+ // In any case , we need to refinalize here (we either added an unreachable,
854889 // or we replaced a cast with the value being cast, which may have a less-
855890 // refined type - it will not be used after we remove the allocation, but we
856891 // must still fix that up for validation).
857892 refinalize = true ;
858893 }
859894
895+ void visitRefGetDesc (RefGetDesc* curr) {
896+ if (analyzer.getInteraction (curr) == ParentChildInteraction::None) {
897+ return ;
898+ }
899+
900+ auto type = allocation->descriptor ->type ;
901+ if (type != curr->type ) {
902+ // We know exactly the allocation that flows into this expression, so we
903+ // know the exact type of the descriptor. This type may be more precise
904+ // than the static type of this expression.
905+ refinalize = true ;
906+ }
907+ auto * value = builder.makeLocalGet (localIndexes[fields.size ()], type);
908+ replaceCurrent (builder.blockify (builder.makeDrop (curr->ref ), value));
909+ }
910+
860911 void visitStructSet (StructSet* curr) {
861912 if (analyzer.getInteraction (curr) == ParentChildInteraction::None) {
862913 return ;
0 commit comments