Skip to content

Commit 7288ed5

Browse files
authored
[NFC] Refactor common logic for JS interface (#8458)
Both Unsubtyping and GTO need to analyzer JS-called functions as well as imports and exports to analyze types that might flow in from or out to JS. Extract the common logic into a shared utility in a new js-utils.h header. Also move the related jsPossibleJSPrototypeField method to this header.
1 parent 6a64cbc commit 7288ed5

File tree

4 files changed

+139
-153
lines changed

4 files changed

+139
-153
lines changed

src/ir/js-utils.h

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2026 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_ir_js_utils_h
18+
#define wasm_ir_js_utils_h
19+
20+
#include "ir/intrinsics.h"
21+
#include "wasm-type.h"
22+
#include "wasm.h"
23+
24+
namespace wasm::JSUtils {
25+
26+
// Whether this is a descriptor struct type whose first field is immutable and a
27+
// subtype of externref.
28+
inline bool hasPossibleJSPrototypeField(HeapType type) {
29+
if (!type.getDescribedType()) {
30+
return false;
31+
}
32+
assert(type.isStruct());
33+
const auto& fields = type.getStruct().fields;
34+
if (fields.empty()) {
35+
return false;
36+
}
37+
if (fields[0].mutable_ == Mutable) {
38+
return false;
39+
}
40+
if (!fields[0].type.isRef()) {
41+
return false;
42+
}
43+
return fields[0].type.getHeapType().isMaybeShared(HeapType::ext);
44+
}
45+
46+
// Calls flowIn and flowOut on all types that may flow in from or out to JS.
47+
template<typename In, typename Out>
48+
void iterJSInterface(Module& wasm, In flowIn, Out flowOut) {
49+
// @binaryen.js.called functions are called from JS. Their parameters flow
50+
// in from JS and their results flow back out.
51+
for (auto f : Intrinsics(wasm).getJSCalledFunctions()) {
52+
auto* func = wasm.getFunction(f);
53+
for (auto type : func->getParams()) {
54+
flowIn(type);
55+
}
56+
for (auto type : func->getResults()) {
57+
flowOut(type);
58+
}
59+
}
60+
61+
for (auto& ex : wasm.exports) {
62+
switch (ex->kind) {
63+
case ExternalKindImpl::Function: {
64+
// Exported functions are also called from JS. Their parameters flow
65+
// in from JS and their result flow back out.
66+
auto* func = wasm.getFunction(*ex->getInternalName());
67+
for (auto type : func->getParams()) {
68+
flowIn(type);
69+
}
70+
for (auto type : func->getResults()) {
71+
flowOut(type);
72+
}
73+
break;
74+
}
75+
case ExternalKindImpl::Table: {
76+
// Exported tables let values flow in and out.
77+
auto* table = wasm.getTable(*ex->getInternalName());
78+
flowOut(table->type);
79+
flowIn(table->type);
80+
break;
81+
}
82+
case ExternalKindImpl::Global: {
83+
// Exported globals let values flow out. Iff they are mutable, they
84+
// also let values flow back in.
85+
auto* global = wasm.getGlobal(*ex->getInternalName());
86+
flowOut(global->type);
87+
if (global->mutable_) {
88+
flowIn(global->type);
89+
}
90+
break;
91+
}
92+
case ExternalKindImpl::Memory:
93+
case ExternalKindImpl::Tag:
94+
case ExternalKindImpl::Invalid:
95+
break;
96+
}
97+
}
98+
for (auto& func : wasm.functions) {
99+
// Imported functions are the opposite of exported functions. Their
100+
// parameters flow out and their results flow in.
101+
if (func->imported()) {
102+
for (auto type : func->getParams()) {
103+
flowOut(type);
104+
}
105+
for (auto type : func->getResults()) {
106+
flowIn(type);
107+
}
108+
}
109+
}
110+
for (auto& table : wasm.tables) {
111+
// Imported tables, like exported tables, let values flow in and out.
112+
if (table->imported()) {
113+
flowOut(table->type);
114+
flowIn(table->type);
115+
}
116+
}
117+
for (auto& global : wasm.globals) {
118+
// Imported mutable globals let values flow in and out. Imported immutable
119+
// globals imply that values will flow in.
120+
if (global->imported()) {
121+
flowIn(global->type);
122+
if (global->mutable_) {
123+
flowOut(global->type);
124+
}
125+
}
126+
}
127+
}
128+
129+
} // namespace wasm::JSUtils
130+
131+
#endif // wasm_ir_js_utils_h

src/ir/struct-utils.h

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,6 @@ 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-
5333
// A value that has a single bool, and implements combine() so it can be used in
5434
// StructValues.
5535
struct CombinableBool {

src/passes/GlobalTypeOptimization.cpp

Lines changed: 5 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
//
2424

2525
#include "ir/eh-utils.h"
26-
#include "ir/intrinsics.h"
26+
#include "ir/js-utils.h"
2727
#include "ir/localize.h"
2828
#include "ir/names.h"
2929
#include "ir/ordering.h"
@@ -123,7 +123,7 @@ struct FieldInfoScanner
123123
return;
124124
}
125125
if (auto desc = curr->value->type.getHeapType().getDescriptorType();
126-
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
126+
desc && JSUtils::hasPossibleJSPrototypeField(*desc)) {
127127
auto exact = curr->value->type.getExactness();
128128
functionSetGetInfos[getFunction()][{*desc, exact}][0].noteRead();
129129
}
@@ -427,7 +427,7 @@ struct GlobalTypeOptimization : public Pass {
427427
// know we have to propate the exposure to subtypes.
428428
auto noteExposed = [&](HeapType type, Exactness exact = Inexact) -> bool {
429429
if (auto desc = type.getDescriptorType();
430-
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
430+
desc && JSUtils::hasPossibleJSPrototypeField(*desc)) {
431431
// This field holds a JS-visible prototype. Do not remove it.
432432
combinedSetGetInfos[std::make_pair(*desc, exact)][0].noteRead();
433433
}
@@ -445,58 +445,9 @@ struct GlobalTypeOptimization : public Pass {
445445
}
446446
};
447447

448-
// @binaryen.js.called functions are called from JS. Their results flow back
449-
// out to JS.
450-
for (auto f : Intrinsics(wasm).getJSCalledFunctions()) {
451-
auto* func = wasm.getFunction(f);
452-
for (auto type : func->getResults()) {
453-
flowOut(type);
454-
}
455-
}
456-
457-
for (auto& ex : wasm.exports) {
458-
switch (ex->kind) {
459-
case ExternalKindImpl::Function: {
460-
auto* func = wasm.getFunction(*ex->getInternalName());
461-
for (auto type : func->getResults()) {
462-
flowOut(type);
463-
}
464-
break;
465-
}
466-
case ExternalKindImpl::Table: {
467-
auto* table = wasm.getTable(*ex->getInternalName());
468-
flowOut(table->type);
469-
break;
470-
}
471-
case ExternalKindImpl::Global: {
472-
auto* global = wasm.getGlobal(*ex->getInternalName());
473-
flowOut(global->type);
474-
break;
475-
}
476-
case ExternalKindImpl::Memory:
477-
case ExternalKindImpl::Tag:
478-
case ExternalKindImpl::Invalid:
479-
break;
480-
}
481-
}
448+
auto flowIn = [&](Type type) {};
482449

483-
for (auto& func : wasm.functions) {
484-
if (func->imported()) {
485-
for (auto type : func->getParams()) {
486-
flowOut(type);
487-
}
488-
}
489-
}
490-
for (auto& table : wasm.tables) {
491-
if (table->imported()) {
492-
flowOut(table->type);
493-
}
494-
}
495-
for (auto& global : wasm.globals) {
496-
if (global->imported() && global->mutable_) {
497-
flowOut(global->type);
498-
}
499-
}
450+
JSUtils::iterJSInterface(wasm, flowIn, flowOut);
500451

501452
// Any type that is a subtype of an exposed type is also exposed. Propagate
502453
// from supertypes to subtypes.

src/passes/Unsubtyping.cpp

Lines changed: 3 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <memory>
2020

2121
#include "ir/effects.h"
22+
#include "ir/js-utils.h"
2223
#include "ir/localize.h"
2324
#include "ir/module-utils.h"
2425
#include "ir/names.h"
@@ -624,7 +625,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
624625
void noteExposedToJS(HeapType type, Exactness exact = Inexact) {
625626
// Keep any descriptor that may configure a prototype.
626627
if (auto desc = type.getDescriptorType();
627-
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
628+
desc && JSUtils::hasPossibleJSPrototypeField(*desc)) {
628629
noteDescriptor(type, *desc);
629630
}
630631
if (exact == Inexact) {
@@ -667,84 +668,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
667668
}
668669
};
669670

670-
// @binaryen.js.called functions are called from JS. Their parameters flow
671-
// in from JS and their results flow back out.
672-
for (auto f : Intrinsics(wasm).getJSCalledFunctions()) {
673-
auto* func = wasm.getFunction(f);
674-
for (auto type : func->getParams()) {
675-
flowIn(type);
676-
}
677-
for (auto type : func->getResults()) {
678-
flowOut(type);
679-
}
680-
}
681-
682-
for (auto& ex : wasm.exports) {
683-
switch (ex->kind) {
684-
case ExternalKindImpl::Function: {
685-
// Exported functions are also called from JS. Their parameters flow
686-
// in from JS and their result flow back out.
687-
auto* func = wasm.getFunction(*ex->getInternalName());
688-
for (auto type : func->getParams()) {
689-
flowIn(type);
690-
}
691-
for (auto type : func->getResults()) {
692-
flowOut(type);
693-
}
694-
break;
695-
}
696-
case ExternalKindImpl::Table: {
697-
// Exported tables let values flow in and out.
698-
auto* table = wasm.getTable(*ex->getInternalName());
699-
flowOut(table->type);
700-
flowIn(table->type);
701-
break;
702-
}
703-
case ExternalKindImpl::Global: {
704-
// Exported globals let values flow out. Iff they are mutable, they
705-
// also let values flow back in.
706-
auto* global = wasm.getGlobal(*ex->getInternalName());
707-
flowOut(global->type);
708-
if (global->mutable_) {
709-
flowIn(global->type);
710-
}
711-
break;
712-
}
713-
case ExternalKindImpl::Memory:
714-
case ExternalKindImpl::Tag:
715-
case ExternalKindImpl::Invalid:
716-
break;
717-
}
718-
}
719-
for (auto& func : wasm.functions) {
720-
// Imported functions are the opposite of exported functions. Their
721-
// parameters flow out and their results flow in.
722-
if (func->imported()) {
723-
for (auto type : func->getParams()) {
724-
flowOut(type);
725-
}
726-
for (auto type : func->getResults()) {
727-
flowIn(type);
728-
}
729-
}
730-
}
731-
for (auto& table : wasm.tables) {
732-
// Imported tables, like exported tables, let values flow in and out.
733-
if (table->imported()) {
734-
flowOut(table->type);
735-
flowIn(table->type);
736-
}
737-
}
738-
for (auto& global : wasm.globals) {
739-
// Imported mutable globals let values flow in and out. Imported immutable
740-
// globals imply that values will flow in.
741-
if (global->imported()) {
742-
flowIn(global->type);
743-
if (global->mutable_) {
744-
flowOut(global->type);
745-
}
746-
}
747-
}
671+
JSUtils::iterJSInterface(wasm, flowIn, flowOut);
748672
}
749673

750674
void analyzeModule(Module& wasm) {

0 commit comments

Comments
 (0)