diff --git a/src/ir/js-utils.h b/src/ir/js-utils.h new file mode 100644 index 00000000000..7591b4fe7e0 --- /dev/null +++ b/src/ir/js-utils.h @@ -0,0 +1,131 @@ +/* + * Copyright 2026 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef wasm_ir_js_utils_h +#define wasm_ir_js_utils_h + +#include "ir/intrinsics.h" +#include "wasm-type.h" +#include "wasm.h" + +namespace wasm::JSUtils { + +// 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); +} + +// Calls flowIn and flowOut on all types that may flow in from or out to JS. +template +void iterJSInterface(Module& wasm, In flowIn, Out flowOut) { + // @binaryen.js.called functions are called from JS. Their parameters flow + // in from JS and their results flow back out. + for (auto f : Intrinsics(wasm).getJSCalledFunctions()) { + auto* func = wasm.getFunction(f); + for (auto type : func->getParams()) { + flowIn(type); + } + for (auto type : func->getResults()) { + flowOut(type); + } + } + + for (auto& ex : wasm.exports) { + switch (ex->kind) { + case ExternalKindImpl::Function: { + // Exported functions are also called from JS. Their parameters flow + // in from JS and their result flow back out. + auto* func = wasm.getFunction(*ex->getInternalName()); + for (auto type : func->getParams()) { + flowIn(type); + } + for (auto type : func->getResults()) { + flowOut(type); + } + break; + } + case ExternalKindImpl::Table: { + // Exported tables let values flow in and out. + auto* table = wasm.getTable(*ex->getInternalName()); + flowOut(table->type); + flowIn(table->type); + break; + } + case ExternalKindImpl::Global: { + // Exported globals let values flow out. Iff they are mutable, they + // also let values flow back in. + auto* global = wasm.getGlobal(*ex->getInternalName()); + flowOut(global->type); + if (global->mutable_) { + flowIn(global->type); + } + break; + } + case ExternalKindImpl::Memory: + case ExternalKindImpl::Tag: + case ExternalKindImpl::Invalid: + break; + } + } + for (auto& func : wasm.functions) { + // Imported functions are the opposite of exported functions. Their + // parameters flow out and their results flow in. + if (func->imported()) { + for (auto type : func->getParams()) { + flowOut(type); + } + for (auto type : func->getResults()) { + flowIn(type); + } + } + } + for (auto& table : wasm.tables) { + // Imported tables, like exported tables, let values flow in and out. + if (table->imported()) { + flowOut(table->type); + flowIn(table->type); + } + } + for (auto& global : wasm.globals) { + // Imported mutable globals let values flow in and out. Imported immutable + // globals imply that values will flow in. + if (global->imported()) { + flowIn(global->type); + if (global->mutable_) { + flowOut(global->type); + } + } + } +} + +} // namespace wasm::JSUtils + +#endif // wasm_ir_js_utils_h diff --git a/src/ir/struct-utils.h b/src/ir/struct-utils.h index 036cd9e4876..471a580a9d4 100644 --- a/src/ir/struct-utils.h +++ b/src/ir/struct-utils.h @@ -30,26 +30,6 @@ using StructField = std::pair; 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 { diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index d5f4be3ec47..8133c025ddd 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -23,7 +23,7 @@ // #include "ir/eh-utils.h" -#include "ir/intrinsics.h" +#include "ir/js-utils.h" #include "ir/localize.h" #include "ir/names.h" #include "ir/ordering.h" @@ -123,7 +123,7 @@ struct FieldInfoScanner return; } if (auto desc = curr->value->type.getHeapType().getDescriptorType(); - desc && StructUtils::hasPossibleJSPrototypeField(*desc)) { + desc && JSUtils::hasPossibleJSPrototypeField(*desc)) { auto exact = curr->value->type.getExactness(); functionSetGetInfos[getFunction()][{*desc, exact}][0].noteRead(); } @@ -427,7 +427,7 @@ struct GlobalTypeOptimization : public Pass { // know we have to propate the exposure to subtypes. auto noteExposed = [&](HeapType type, Exactness exact = Inexact) -> bool { if (auto desc = type.getDescriptorType(); - desc && StructUtils::hasPossibleJSPrototypeField(*desc)) { + desc && JSUtils::hasPossibleJSPrototypeField(*desc)) { // This field holds a JS-visible prototype. Do not remove it. combinedSetGetInfos[std::make_pair(*desc, exact)][0].noteRead(); } @@ -445,58 +445,9 @@ struct GlobalTypeOptimization : public Pass { } }; - // @binaryen.js.called functions are called from JS. Their results flow back - // out to JS. - for (auto f : Intrinsics(wasm).getJSCalledFunctions()) { - auto* func = wasm.getFunction(f); - for (auto type : func->getResults()) { - flowOut(type); - } - } - - for (auto& ex : wasm.exports) { - switch (ex->kind) { - case ExternalKindImpl::Function: { - auto* func = wasm.getFunction(*ex->getInternalName()); - for (auto type : func->getResults()) { - flowOut(type); - } - break; - } - case ExternalKindImpl::Table: { - auto* table = wasm.getTable(*ex->getInternalName()); - flowOut(table->type); - break; - } - case ExternalKindImpl::Global: { - auto* global = wasm.getGlobal(*ex->getInternalName()); - flowOut(global->type); - break; - } - case ExternalKindImpl::Memory: - case ExternalKindImpl::Tag: - case ExternalKindImpl::Invalid: - break; - } - } + auto flowIn = [&](Type type) {}; - for (auto& func : wasm.functions) { - if (func->imported()) { - for (auto type : func->getParams()) { - flowOut(type); - } - } - } - for (auto& table : wasm.tables) { - if (table->imported()) { - flowOut(table->type); - } - } - for (auto& global : wasm.globals) { - if (global->imported() && global->mutable_) { - flowOut(global->type); - } - } + JSUtils::iterJSInterface(wasm, flowIn, flowOut); // Any type that is a subtype of an exposed type is also exposed. Propagate // from supertypes to subtypes. diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index e44b50fb7c0..ccb9ecda6db 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -19,6 +19,7 @@ #include #include "ir/effects.h" +#include "ir/js-utils.h" #include "ir/localize.h" #include "ir/module-utils.h" #include "ir/names.h" @@ -624,7 +625,7 @@ struct Unsubtyping : Pass, Noter { void noteExposedToJS(HeapType type, Exactness exact = Inexact) { // Keep any descriptor that may configure a prototype. if (auto desc = type.getDescriptorType(); - desc && StructUtils::hasPossibleJSPrototypeField(*desc)) { + desc && JSUtils::hasPossibleJSPrototypeField(*desc)) { noteDescriptor(type, *desc); } if (exact == Inexact) { @@ -667,84 +668,7 @@ struct Unsubtyping : Pass, Noter { } }; - // @binaryen.js.called functions are called from JS. Their parameters flow - // in from JS and their results flow back out. - for (auto f : Intrinsics(wasm).getJSCalledFunctions()) { - auto* func = wasm.getFunction(f); - for (auto type : func->getParams()) { - flowIn(type); - } - for (auto type : func->getResults()) { - flowOut(type); - } - } - - for (auto& ex : wasm.exports) { - switch (ex->kind) { - case ExternalKindImpl::Function: { - // Exported functions are also called from JS. Their parameters flow - // in from JS and their result flow back out. - auto* func = wasm.getFunction(*ex->getInternalName()); - for (auto type : func->getParams()) { - flowIn(type); - } - for (auto type : func->getResults()) { - flowOut(type); - } - break; - } - case ExternalKindImpl::Table: { - // Exported tables let values flow in and out. - auto* table = wasm.getTable(*ex->getInternalName()); - flowOut(table->type); - flowIn(table->type); - break; - } - case ExternalKindImpl::Global: { - // Exported globals let values flow out. Iff they are mutable, they - // also let values flow back in. - auto* global = wasm.getGlobal(*ex->getInternalName()); - flowOut(global->type); - if (global->mutable_) { - flowIn(global->type); - } - break; - } - case ExternalKindImpl::Memory: - case ExternalKindImpl::Tag: - case ExternalKindImpl::Invalid: - break; - } - } - for (auto& func : wasm.functions) { - // Imported functions are the opposite of exported functions. Their - // parameters flow out and their results flow in. - if (func->imported()) { - for (auto type : func->getParams()) { - flowOut(type); - } - for (auto type : func->getResults()) { - flowIn(type); - } - } - } - for (auto& table : wasm.tables) { - // Imported tables, like exported tables, let values flow in and out. - if (table->imported()) { - flowOut(table->type); - flowIn(table->type); - } - } - for (auto& global : wasm.globals) { - // Imported mutable globals let values flow in and out. Imported immutable - // globals imply that values will flow in. - if (global->imported()) { - flowIn(global->type); - if (global->mutable_) { - flowOut(global->type); - } - } - } + JSUtils::iterJSInterface(wasm, flowIn, flowOut); } void analyzeModule(Module& wasm) {