@@ -395,13 +395,23 @@ struct EscapeAnalyzer {
395
395
// Whether the cast succeeds or fails, it does not escape.
396
396
escapes = false ;
397
397
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);
401
406
fullyConsumes = true ;
402
407
}
403
408
}
404
409
410
+ void visitRefGetDesc (RefGetDesc* curr) {
411
+ escapes = false ;
412
+ fullyConsumes = true ;
413
+ }
414
+
405
415
// GC operations.
406
416
void visitStructSet (StructSet* curr) {
407
417
// The reference does not escape (but the value is stored to memory and
@@ -598,10 +608,14 @@ struct Struct2Local : PostWalker<Struct2Local> {
598
608
: allocation(allocation), analyzer(analyzer), func(func), wasm(wasm),
599
609
builder (wasm), fields(allocation->type.getHeapType().getStruct().fields) {
600
610
601
- // Allocate locals to store the allocation's fields in.
611
+ // Allocate locals to store the allocation's fields and descriptor in.
602
612
for (auto field : fields) {
603
613
localIndexes.push_back (builder.addVar (func, field.type ));
604
614
}
615
+ if (allocation->descriptor ) {
616
+ localIndexes.push_back (
617
+ builder.addVar (func, allocation->descriptor ->type ));
618
+ }
605
619
606
620
// Replace the things we need to using the visit* methods.
607
621
walk (func->body );
@@ -708,61 +722,54 @@ struct Struct2Local : PostWalker<Struct2Local> {
708
722
// First, assign the initial values to the new locals.
709
723
std::vector<Expression*> contents;
710
724
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 ()) {
736
740
for (auto field : fields) {
737
741
tempIndexes.push_back (builder.addVar (func, field.type ));
738
742
}
743
+ }
744
+ if (curr->descriptor ) {
745
+ tempIndexes.push_back (builder.addVar (func, curr->descriptor ->type ));
746
+ }
739
747
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++) {
742
751
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]));
750
753
}
754
+ }
755
+ if (curr->descriptor ) {
756
+ contents.push_back (
757
+ builder.makeLocalSet (tempIndexes[numTemps - 1 ], curr->descriptor ));
758
+ }
751
759
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));
766
773
}
767
774
768
775
// Replace the allocation with a null reference. This changes the type
@@ -838,25 +845,69 @@ struct Struct2Local : PostWalker<Struct2Local> {
838
845
return ;
839
846
}
840
847
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
+ }
847
874
} 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
+ }
851
886
}
852
887
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,
854
889
// or we replaced a cast with the value being cast, which may have a less-
855
890
// refined type - it will not be used after we remove the allocation, but we
856
891
// must still fix that up for validation).
857
892
refinalize = true ;
858
893
}
859
894
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
+
860
911
void visitStructSet (StructSet* curr) {
861
912
if (analyzer.getInteraction (curr) == ParentChildInteraction::None) {
862
913
return ;
0 commit comments