Skip to content

Commit e103d6d

Browse files
authored
Fix Unsubtyping and GTO for configureAll (#8267)
Take into account the implicit casts and conversions that happen on the boundary with JS as well as the fact that JS can essentially read the first field on descriptors with configured prototypes.
1 parent 8a12ceb commit e103d6d

5 files changed

Lines changed: 571 additions & 3 deletions

File tree

src/ir/struct-utils.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ using StructField = std::pair<HeapType, Index>;
3030

3131
namespace StructUtils {
3232

33+
// Whether this is a descriptor struct type whose first field is immutable and a
34+
// subtype of externref.
35+
inline bool hasPossibleJSPrototypeField(HeapType type) {
36+
if (!type.getDescribedType()) {
37+
return false;
38+
}
39+
assert(type.isStruct());
40+
const auto& fields = type.getStruct().fields;
41+
if (fields.empty()) {
42+
return false;
43+
}
44+
if (fields[0].mutable_ == Mutable) {
45+
return false;
46+
}
47+
if (!fields[0].type.isRef()) {
48+
return false;
49+
}
50+
return fields[0].type.getHeapType().isMaybeShared(HeapType::ext);
51+
}
52+
3353
// A value that has a single bool, and implements combine() so it can be used in
3454
// StructValues.
3555
struct CombinableBool {
@@ -184,8 +204,7 @@ struct FunctionStructValuesMap
184204
// Descriptors are treated as fields in that we call the above functions on
185205
// them. We pass DescriptorIndex for their index as a fake value.
186206
template<typename T, typename SubType>
187-
struct StructScanner
188-
: public WalkerPass<PostWalker<StructScanner<T, SubType>>> {
207+
struct StructScanner : public WalkerPass<PostWalker<SubType>> {
189208
bool isFunctionParallel() override { return true; }
190209

191210
bool modifiesBinaryenIR() override { return false; }

src/passes/GlobalTypeOptimization.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
//
2424

2525
#include "ir/eh-utils.h"
26+
#include "ir/intrinsics.h"
2627
#include "ir/localize.h"
2728
#include "ir/names.h"
2829
#include "ir/ordering.h"
@@ -111,6 +112,22 @@ struct FieldInfoScanner
111112
info.noteRead();
112113
info.noteWrite();
113114
}
115+
116+
// Converting a reference to externref makes the prototype field on its
117+
// descriptor available to be read by JS, if such a field exists.
118+
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 && StructUtils::hasPossibleJSPrototypeField(*desc)) {
127+
auto exact = curr->value->type.getExactness();
128+
functionSetGetInfos[getFunction()][{*desc, exact}][0].noteRead();
129+
}
130+
}
114131
};
115132

116133
struct GlobalTypeOptimization : public Pass {
@@ -151,6 +168,10 @@ struct GlobalTypeOptimization : public Pass {
151168
// Combine the data from the functions.
152169
functionSetGetInfos.combineInto(combinedSetGetInfos);
153170

171+
// Analyze functions called by JS to find fields holding configured
172+
// prototypes that cannot be removed.
173+
analyzeJSCalledFunctions(*module);
174+
154175
// Propagate information to super and subtypes on set/get infos:
155176
//
156177
// * For removing unread fields, we can only remove a field if it is never
@@ -201,7 +222,7 @@ struct GlobalTypeOptimization : public Pass {
201222
auto& fields = type.getStruct().fields;
202223
// Use the exact entry because information from the inexact entry in
203224
// dataFromSupersMap will have been propagated down into it but not vice
204-
// versa. (This doesn't matter or dataFromSubsAndSupers because the exact
225+
// versa. (This doesn't matter for dataFromSubsAndSupers because the exact
205226
// and inexact entries will have the same data.)
206227
auto ht = std::make_pair(type, Exact);
207228
auto& dataFromSubsAndSupers = dataFromSubsAndSupersMap[ht];
@@ -395,6 +416,27 @@ struct GlobalTypeOptimization : public Pass {
395416
}
396417
}
397418

419+
void analyzeJSCalledFunctions(Module& wasm) {
420+
if (!wasm.features.hasCustomDescriptors()) {
421+
return;
422+
}
423+
for (auto func : Intrinsics(wasm).getConfigureAllFunctions()) {
424+
// Look at the result types being returned to JS and make sure we preserve
425+
// any configured prototypes they might expose.
426+
for (auto type : wasm.getFunction(func)->getResults()) {
427+
if (!type.isRef()) {
428+
continue;
429+
}
430+
if (auto desc = type.getHeapType().getDescriptorType();
431+
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
432+
// This field holds a JS-visible prototype. Do not remove it.
433+
auto exact = type.getExactness();
434+
combinedSetGetInfos[std::make_pair(*desc, exact)][0].noteRead();
435+
}
436+
}
437+
}
438+
}
439+
398440
void updateTypes(Module& wasm) {
399441
class TypeRewriter : public GlobalTypeRewriter {
400442
GlobalTypeOptimization& parent;

src/passes/Unsubtyping.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "ir/localize.h"
3030
#include "ir/module-utils.h"
3131
#include "ir/names.h"
32+
#include "ir/struct-utils.h"
3233
#include "ir/subtype-exprs.h"
3334
#include "ir/type-updating.h"
3435
#include "ir/utils.h"
@@ -554,6 +555,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
554555
// Initialize the subtype relation based on what is immediately required to
555556
// keep the code and public types valid.
556557
analyzePublicTypes(*wasm);
558+
analyzeJSCalledFunctions(*wasm);
557559
analyzeModule(*wasm);
558560

559561
// Find further subtypings and iterate to a fixed point.
@@ -605,6 +607,33 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
605607
}
606608
}
607609

610+
void analyzeJSCalledFunctions(Module& wasm) {
611+
if (!wasm.features.hasCustomDescriptors()) {
612+
return;
613+
}
614+
Type anyref(HeapType::any, Nullable);
615+
for (auto func : Intrinsics(wasm).getConfigureAllFunctions()) {
616+
// Parameter types flow into Wasm and are implicitly cast from any.
617+
for (auto type : wasm.getFunction(func)->getParams()) {
618+
if (Type::isSubType(type, anyref)) {
619+
noteCast(HeapType::any, type);
620+
}
621+
}
622+
for (auto type : wasm.getFunction(func)->getResults()) {
623+
// Result types flow into JS and are implicitly converted from any to
624+
// extern. They may also expose configured prototypes that we must keep.
625+
if (Type::isSubType(type, anyref)) {
626+
auto heapType = type.getHeapType();
627+
noteSubtype(heapType, HeapType::any);
628+
if (auto desc = heapType.getDescriptorType();
629+
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
630+
noteDescriptor(heapType, *desc);
631+
}
632+
}
633+
}
634+
}
635+
}
636+
608637
void analyzeModule(Module& wasm) {
609638
struct Info {
610639
// (source, target) pairs for casts.
@@ -746,6 +775,14 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
746775
for (auto& [sub, super] : collectedInfo.subtypings) {
747776
noteSubtype(sub, super);
748777
}
778+
// Combine casts we have already noted into the newly gathered casts.
779+
for (auto& [src, dsts] : casts) {
780+
for (auto dst : dsts) {
781+
collectedInfo.casts.insert({src, dst});
782+
}
783+
dsts.clear();
784+
}
785+
// Record the deduplicated cast info.
749786
for (auto [src, dst] : collectedInfo.casts) {
750787
casts[src].push_back(dst);
751788
}

0 commit comments

Comments
 (0)