Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/ir/struct-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ using StructField = std::pair<HeapType, Index>;

namespace StructUtils {

// Whether this is a descriptor struct type whose first field is immutable and a
// subtype of externref.
inline bool hasPossibleJSPrototypeField(HeapType type) {
if (!type.getDescribedType()) {
return false;
}
assert(type.isStruct());
const auto& fields = type.getStruct().fields;
if (fields.empty()) {
return false;
}
if (fields[0].mutable_ == Mutable) {
return false;
}
if (!fields[0].type.isRef()) {
return false;
}
return fields[0].type.getHeapType().isMaybeShared(HeapType::ext);
}

// A value that has a single bool, and implements combine() so it can be used in
// StructValues.
struct CombinableBool {
Expand Down Expand Up @@ -184,8 +204,7 @@ struct FunctionStructValuesMap
// Descriptors are treated as fields in that we call the above functions on
// them. We pass DescriptorIndex for their index as a fake value.
template<typename T, typename SubType>
struct StructScanner
: public WalkerPass<PostWalker<StructScanner<T, SubType>>> {
struct StructScanner : public WalkerPass<PostWalker<SubType>> {
bool isFunctionParallel() override { return true; }

bool modifiesBinaryenIR() override { return false; }
Expand Down
44 changes: 43 additions & 1 deletion src/passes/GlobalTypeOptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
//

#include "ir/eh-utils.h"
#include "ir/intrinsics.h"
#include "ir/localize.h"
#include "ir/names.h"
#include "ir/ordering.h"
Expand Down Expand Up @@ -111,6 +112,22 @@ struct FieldInfoScanner
info.noteRead();
info.noteWrite();
}

// Converting a reference to externref makes the prototype field on its
// descriptor available to be read by JS, if such a field exists.
void visitRefAs(RefAs* curr) {
if (curr->op != ExternConvertAny) {
return;
}
if (!curr->value->type.isRef()) {
return;
}
if (auto desc = curr->value->type.getHeapType().getDescriptorType();
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
auto exact = curr->value->type.getExactness();
functionSetGetInfos[getFunction()][{*desc, exact}][0].noteRead();
}
}
};

struct GlobalTypeOptimization : public Pass {
Expand Down Expand Up @@ -151,6 +168,10 @@ struct GlobalTypeOptimization : public Pass {
// Combine the data from the functions.
functionSetGetInfos.combineInto(combinedSetGetInfos);

// Analyze functions called by JS to find fields holding configured
// prototypes that cannot be removed.
analyzeJSCalledFunctions(*module);

// Propagate information to super and subtypes on set/get infos:
//
// * For removing unread fields, we can only remove a field if it is never
Expand Down Expand Up @@ -201,7 +222,7 @@ struct GlobalTypeOptimization : public Pass {
auto& fields = type.getStruct().fields;
// Use the exact entry because information from the inexact entry in
// dataFromSupersMap will have been propagated down into it but not vice
// versa. (This doesn't matter or dataFromSubsAndSupers because the exact
// versa. (This doesn't matter for dataFromSubsAndSupers because the exact
// and inexact entries will have the same data.)
auto ht = std::make_pair(type, Exact);
auto& dataFromSubsAndSupers = dataFromSubsAndSupersMap[ht];
Expand Down Expand Up @@ -395,6 +416,27 @@ struct GlobalTypeOptimization : public Pass {
}
}

void analyzeJSCalledFunctions(Module& wasm) {
if (!wasm.features.hasCustomDescriptors()) {
return;
}
for (auto func : Intrinsics(wasm).getConfigureAllFunctions()) {
// Look at the result types being returned to JS and make sure we preserve
// any configured prototypes they might expose.
for (auto type : wasm.getFunction(func)->getResults()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a comment here as to why only the results matter?

if (!type.isRef()) {
continue;
}
if (auto desc = type.getHeapType().getDescriptorType();
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
// This field holds a JS-visible prototype. Do not remove it.
auto exact = type.getExactness();
combinedSetGetInfos[std::make_pair(*desc, exact)][0].noteRead();
}
}
}
}

void updateTypes(Module& wasm) {
class TypeRewriter : public GlobalTypeRewriter {
GlobalTypeOptimization& parent;
Expand Down
37 changes: 37 additions & 0 deletions src/passes/Unsubtyping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "ir/localize.h"
#include "ir/module-utils.h"
#include "ir/names.h"
#include "ir/struct-utils.h"
#include "ir/subtype-exprs.h"
#include "ir/type-updating.h"
#include "ir/utils.h"
Expand Down Expand Up @@ -554,6 +555,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
// Initialize the subtype relation based on what is immediately required to
// keep the code and public types valid.
analyzePublicTypes(*wasm);
analyzeJSCalledFunctions(*wasm);
analyzeModule(*wasm);

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

void analyzeJSCalledFunctions(Module& wasm) {
if (!wasm.features.hasCustomDescriptors()) {
return;
}
Type anyref(HeapType::any, Nullable);
for (auto func : Intrinsics(wasm).getConfigureAllFunctions()) {
// Parameter types flow into Wasm and are implicitly cast from any.
for (auto type : wasm.getFunction(func)->getParams()) {
if (Type::isSubType(type, anyref)) {
noteCast(HeapType::any, type);
}
}
for (auto type : wasm.getFunction(func)->getResults()) {
// Result types flow into JS and are implicitly converted from any to
// extern. They may also expose configured prototypes that we must keep.
if (Type::isSubType(type, anyref)) {
auto heapType = type.getHeapType();
noteSubtype(heapType, HeapType::any);
if (auto desc = heapType.getDescriptorType();
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
noteDescriptor(heapType, *desc);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the cast symmetric in params and results, but the prototype only appears for the results?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameters are values flowing from JS to Wasm and the results are values flowing from Wasm to JS. Only JS->Wasm involves an implicit cast, only Wasm->JS involves an implict extern.convert_any, and only Wasm->JS makes a configured prototype visible to JS.

}
}
}
}

void analyzeModule(Module& wasm) {
struct Info {
// (source, target) pairs for casts.
Expand Down Expand Up @@ -746,6 +775,14 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
for (auto& [sub, super] : collectedInfo.subtypings) {
noteSubtype(sub, super);
}
// Combine casts we have already noted into the newly gathered casts.
for (auto& [src, dsts] : casts) {
for (auto dst : dsts) {
collectedInfo.casts.insert({src, dst});
}
dsts.clear();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an unrelated fix?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. We now have casts that were noted before the parallel function analysis. This is merging them into the parallel function analysis sets so we don't end up with unnecessary duplicated entries.

// Record the deduplicated cast info.
for (auto [src, dst] : collectedInfo.casts) {
casts[src].push_back(dst);
}
Expand Down
Loading
Loading