Skip to content

Commit 0d9ceef

Browse files
authored
Handle flow via imports and exports in Unsubtyping (#8439)
We previously upated Unsubtyping so that any subtype of the result of a JS-called function would keep its descriptor if that descriptor could configure a JS prototype. But configured prototypes can also become visible indirectly because they flow out of the module via imported or exported functions, globals, or tables. Handle these cases of values flowing in from or out to JS.
1 parent e99af57 commit 0d9ceef

File tree

2 files changed

+455
-20
lines changed

2 files changed

+455
-20
lines changed

src/passes/Unsubtyping.cpp

Lines changed: 103 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,8 @@ struct TypeTree {
451451
// the final update of data structures is different. This CRTP utility
452452
// deduplicates the shared logic.
453453
template<typename Self> struct Noter {
454+
DBG(Module* wasm = nullptr);
455+
454456
Self& self() { return *static_cast<Self*>(this); }
455457

456458
void noteSubtype(HeapType sub, HeapType super) {
@@ -559,8 +561,6 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
559561
// Map from cast source types to their destinations.
560562
Map<HeapType, std::vector<HeapType>> casts;
561563

562-
DBG(Module* wasm = nullptr);
563-
564564
void run(Module* wasm) override {
565565
DBG(this->wasm = wasm);
566566
if (!wasm->features.hasGC()) {
@@ -574,7 +574,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
574574
// Initialize the subtype relation based on what is immediately required to
575575
// keep the code and public types valid.
576576
analyzePublicTypes(*wasm);
577-
analyzeJSCalledFunctions(*wasm);
577+
analyzeJSInterface(*wasm);
578578
analyzeModule(*wasm);
579579

580580
// Find further subtypings and iterate to a fixed point.
@@ -635,25 +635,105 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
635635
}
636636
}
637637

638-
void analyzeJSCalledFunctions(Module& wasm) {
638+
void analyzeJSInterface(Module& wasm) {
639639
if (!wasm.features.hasCustomDescriptors()) {
640640
return;
641641
}
642642
Type anyref(HeapType::any, Nullable);
643-
for (auto func : Intrinsics(wasm).getJSCalledFunctions()) {
644-
// Parameter types flow into Wasm and are implicitly cast from any.
645-
for (auto type : wasm.getFunction(func)->getParams()) {
646-
if (Type::isSubType(type, anyref)) {
647-
noteCast(HeapType::any, type);
643+
644+
// Values flowing in from JS are implicitly cast from any.
645+
auto flowIn = [&](Type type) {
646+
if (Type::isSubType(type, anyref)) {
647+
noteCast(HeapType::any, type);
648+
}
649+
};
650+
651+
// Values flowing out to JS are converted to extern and might come back in
652+
// as anyrefs. Their descriptors may need to be kept to configure JS
653+
// prototypes.
654+
auto flowOut = [&](Type type) {
655+
if (Type::isSubType(type, anyref)) {
656+
auto heapType = type.getHeapType();
657+
noteSubtype(heapType, HeapType::any);
658+
noteExposedToJS(heapType);
659+
}
660+
};
661+
662+
// @binaryen.js.called functions are called from JS. Their parameters flow
663+
// in from JS and their results flow back out.
664+
for (auto f : Intrinsics(wasm).getJSCalledFunctions()) {
665+
auto* func = wasm.getFunction(f);
666+
for (auto type : func->getParams()) {
667+
flowIn(type);
668+
}
669+
for (auto type : func->getResults()) {
670+
flowOut(type);
671+
}
672+
}
673+
674+
for (auto& ex : wasm.exports) {
675+
switch (ex->kind) {
676+
case ExternalKindImpl::Function: {
677+
// Exported functions are also called from JS. Their parameters flow
678+
// in from JS and their result flow back out.
679+
auto* func = wasm.getFunction(*ex->getInternalName());
680+
for (auto type : func->getParams()) {
681+
flowIn(type);
682+
}
683+
for (auto type : func->getResults()) {
684+
flowOut(type);
685+
}
686+
break;
687+
}
688+
case ExternalKindImpl::Table: {
689+
// Exported tables let values flow in and out.
690+
auto* table = wasm.getTable(*ex->getInternalName());
691+
flowOut(table->type);
692+
flowIn(table->type);
693+
break;
694+
}
695+
case ExternalKindImpl::Global: {
696+
// Exported globals let values flow out. Iff they are mutable, they
697+
// also let values flow back in.
698+
auto* global = wasm.getGlobal(*ex->getInternalName());
699+
flowOut(global->type);
700+
if (global->mutable_) {
701+
flowIn(global->type);
702+
}
703+
break;
704+
}
705+
case ExternalKindImpl::Memory:
706+
case ExternalKindImpl::Tag:
707+
case ExternalKindImpl::Invalid:
708+
break;
709+
}
710+
}
711+
for (auto& func : wasm.functions) {
712+
// Imported functions are the opposite of exported functions. Their
713+
// parameters flow out and their results flow in.
714+
if (func->imported()) {
715+
for (auto type : func->getParams()) {
716+
flowOut(type);
648717
}
718+
for (auto type : func->getResults()) {
719+
flowIn(type);
720+
}
721+
}
722+
}
723+
for (auto& table : wasm.tables) {
724+
// Imported tables, like exported tables, let values flow in and out.
725+
if (table->imported()) {
726+
flowOut(table->type);
727+
flowIn(table->type);
649728
}
650-
for (auto type : wasm.getFunction(func)->getResults()) {
651-
// Result types flow into JS and are implicitly converted from any to
652-
// extern. They may also expose configured prototypes that we must keep.
653-
if (Type::isSubType(type, anyref)) {
654-
auto heapType = type.getHeapType();
655-
noteSubtype(heapType, HeapType::any);
656-
noteExposedToJS(heapType);
729+
}
730+
for (auto& global : wasm.globals) {
731+
// Imported mutable globals let values flow in and out. Imported immutable
732+
// globals imply that values will flow in.
733+
if (global->imported()) {
734+
flowIn(global->type);
735+
if (global->mutable_) {
736+
flowOut(global->type);
657737
}
658738
}
659739
}
@@ -683,8 +763,10 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
683763
Info& info;
684764
bool trapsNeverHappen;
685765

686-
Collector(Info& info, bool trapsNeverHappen)
687-
: info(info), trapsNeverHappen(trapsNeverHappen) {}
766+
Collector(Info& info, bool trapsNeverHappen, Module* wasm)
767+
: info(info), trapsNeverHappen(trapsNeverHappen) {
768+
DBG(this->wasm = wasm);
769+
}
688770

689771
void doNoteSubtype(HeapType sub, HeapType super) {
690772
info.subtypings.insert({sub, super});
@@ -783,7 +865,8 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
783865
ModuleUtils::ParallelFunctionAnalysis<Info> analysis(
784866
wasm, [&](Function* func, Info& info) {
785867
if (!func->imported()) {
786-
Collector(info, trapsNeverHappen).walkFunctionInModule(func, &wasm);
868+
Collector(info, trapsNeverHappen, &wasm)
869+
.walkFunctionInModule(func, &wasm);
787870
}
788871
});
789872

@@ -799,7 +882,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
799882
}
800883

801884
// Collect constraints from module-level code as well.
802-
Collector collector(collectedInfo, trapsNeverHappen);
885+
Collector collector(collectedInfo, trapsNeverHappen, &wasm);
803886
collector.walkModuleCode(&wasm);
804887
collector.setModule(&wasm);
805888
for (auto& global : wasm.globals) {

0 commit comments

Comments
 (0)