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
131 changes: 131 additions & 0 deletions src/ir/js-utils.h
Original file line number Diff line number Diff line change
@@ -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<typename In, typename Out>
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
20 changes: 0 additions & 20 deletions src/ir/struct-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,6 @@ 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
59 changes: 5 additions & 54 deletions src/passes/GlobalTypeOptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
}
Expand All @@ -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.
Expand Down
82 changes: 3 additions & 79 deletions src/passes/Unsubtyping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <memory>

#include "ir/effects.h"
#include "ir/js-utils.h"
#include "ir/localize.h"
#include "ir/module-utils.h"
#include "ir/names.h"
Expand Down Expand Up @@ -624,7 +625,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
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) {
Expand Down Expand Up @@ -667,84 +668,7 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
}
};

// @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) {
Expand Down
Loading