diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 0f8cb4805f9..7c8dcab97be 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -5888,9 +5888,13 @@ void BinaryenSetTrapsNeverHappen(bool on) { globalPassOptions.trapsNeverHappen = on; } -bool BinaryenGetClosedWorld(void) { return globalPassOptions.closedWorld; } +bool BinaryenGetClosedWorld(void) { + return globalPassOptions.worldMode == WorldMode::Closed; +} -void BinaryenSetClosedWorld(bool on) { globalPassOptions.closedWorld = on; } +void BinaryenSetClosedWorld(bool on) { + globalPassOptions.worldMode = on ? WorldMode::Closed : WorldMode::Open; +} bool BinaryenGetLowMemoryUnused(void) { return globalPassOptions.lowMemoryUnused; diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 6ee94d35d65..cf00f201119 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -481,12 +481,16 @@ struct CodeScanner : PostWalker { }; void classifyTypeVisibility(Module& wasm, - InsertOrderedMap& types); + InsertOrderedMap& types, + WorldMode worldMode); } // anonymous namespace -InsertOrderedMap collectHeapTypeInfo( - Module& wasm, TypeInclusion inclusion, VisibilityHandling visibility) { +InsertOrderedMap +collectHeapTypeInfo(Module& wasm, + WorldMode worldMode, + TypeInclusion inclusion, + VisibilityHandling visibility) { // Collect module-level info. TypeInfos info; CodeScanner(wasm, info).walkModuleCode(&wasm); @@ -593,7 +597,7 @@ InsertOrderedMap collectHeapTypeInfo( } if (visibility == VisibilityHandling::FindVisibility) { - classifyTypeVisibility(wasm, info.info); + classifyTypeVisibility(wasm, info.info, worldMode); } return std::move(info.info); @@ -602,8 +606,9 @@ InsertOrderedMap collectHeapTypeInfo( namespace { void classifyTypeVisibility(Module& wasm, - InsertOrderedMap& types) { - for (auto type : getPublicHeapTypes(wasm)) { + InsertOrderedMap& types, + WorldMode worldMode) { + for (auto type : getPublicHeapTypes(wasm, worldMode)) { if (auto it = types.find(type); it != types.end()) { it->second.visibility = Visibility::Public; } @@ -624,7 +629,7 @@ void setIndices(IndexedHeapTypes& indexedTypes) { } // anonymous namespace std::vector collectHeapTypes(Module& wasm) { - auto info = collectHeapTypeInfo(wasm); + auto info = collectHeapTypeInfo(wasm, WorldMode::Open); std::vector types; types.reserve(info.size()); for (auto& [type, _] : info) { @@ -633,18 +638,22 @@ std::vector collectHeapTypes(Module& wasm) { return types; } -std::vector getPublicHeapTypes(Module& wasm) { +std::vector getExposedPublicHeapTypes(Module& wasm) { // Look at the types of imports as exports to get an initial set of public // types, then traverse the types used by public types and collect the // transitively reachable public types as well. std::vector workList; std::unordered_set publicGroups; + std::unordered_set publicBasicTypes; // The collected types. std::vector publicTypes; auto notePublic = [&](HeapType type) { if (type.isBasic()) { + if (publicBasicTypes.insert(type).second) { + publicTypes.push_back(type); + } return; } auto group = type.getRecGroup(); @@ -725,9 +734,23 @@ std::vector getPublicHeapTypes(Module& wasm) { return publicTypes; } -std::vector getPrivateHeapTypes(Module& wasm) { - auto info = collectHeapTypeInfo( - wasm, TypeInclusion::UsedIRTypes, VisibilityHandling::FindVisibility); +std::vector getPublicHeapTypes(Module& wasm, WorldMode worldMode) { + auto exposed = getExposedPublicHeapTypes(wasm); + std::vector publicTypes; + publicTypes.reserve(exposed.size()); + for (auto type : exposed) { + if (!type.isBasic()) { + publicTypes.push_back(type); + } + } + return publicTypes; +} + +std::vector getPrivateHeapTypes(Module& wasm, WorldMode worldMode) { + auto info = collectHeapTypeInfo(wasm, + worldMode, + TypeInclusion::UsedIRTypes, + VisibilityHandling::FindVisibility); std::vector types; types.reserve(info.size()); for (auto& [type, typeInfo] : info) { @@ -739,7 +762,8 @@ std::vector getPrivateHeapTypes(Module& wasm) { } IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { - auto counts = collectHeapTypeInfo(wasm, TypeInclusion::BinaryTypes); + auto counts = + collectHeapTypeInfo(wasm, WorldMode::Open, TypeInclusion::BinaryTypes); // Collect the rec groups. std::unordered_map groupIndices; diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 50b67df7cea..2bb8dd914d7 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -472,6 +472,7 @@ struct HeapTypeInfo { InsertOrderedMap collectHeapTypeInfo( Module& wasm, + WorldMode worldMode, TypeInclusion inclusion = TypeInclusion::AllTypes, VisibilityHandling visibility = VisibilityHandling::NoVisibility); @@ -479,13 +480,14 @@ InsertOrderedMap collectHeapTypeInfo( // module, i.e. the types that would appear in the type section. std::vector collectHeapTypes(Module& wasm); +std::vector getExposedPublicHeapTypes(Module& wasm); + // Collect all the heap types visible on the module boundary that cannot be -// changed. TODO: For open world use cases, this needs to include all subtypes -// of public types as well. -std::vector getPublicHeapTypes(Module& wasm); +// changed. +std::vector getPublicHeapTypes(Module& wasm, WorldMode worldMode); // getHeapTypes - getPublicHeapTypes -std::vector getPrivateHeapTypes(Module& wasm); +std::vector getPrivateHeapTypes(Module& wasm, WorldMode worldMode); struct IndexedHeapTypes { std::vector types; diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 4b76f467825..a8a01841fd6 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -684,7 +684,7 @@ struct InfoCollector SignatureResultLocation{func->type.getHeapType(), i}}); } - if (!options.closedWorld) { + if (options.worldMode == WorldMode::Open) { info.calledFromOutside.insert(curr->func); } } @@ -1711,7 +1711,7 @@ void TNHOracle::scan(Function* func, void visitCallRef(CallRef* curr) { // We can only optimize call_ref in closed world, as otherwise the // call can go somewhere we can't see. - if (options.closedWorld) { + if (options.worldMode == WorldMode::Closed) { info.callRefs.push_back(curr); } } @@ -1834,7 +1834,7 @@ void TNHOracle::infer() { // that type or a subtype, i.e., might be called when that type is seen in a // call_ref target. std::unordered_map> typeFunctions; - if (options.closedWorld) { + if (options.worldMode == WorldMode::Closed) { for (auto& func : wasm.functions) { auto type = func->type; auto& info = map[wasm.getFunction(func->name)]; @@ -1895,7 +1895,7 @@ void TNHOracle::infer() { // We should only get here in a closed world, in which we know which // functions might be called (the scan phase only notes callRefs if we are // in fact in a closed world). - assert(options.closedWorld); + assert(options.worldMode == WorldMode::Closed); auto iter = typeFunctions.find(targetType.getHeapType()); if (iter == typeFunctions.end()) { @@ -2535,8 +2535,8 @@ Flower::Flower(Module& wasm, const PassOptions& options) } // In open world, public heap types may be written to from the outside. - if (!options.closedWorld) { - for (auto type : ModuleUtils::getPublicHeapTypes(wasm)) { + if (options.worldMode == WorldMode::Open) { + for (auto type : ModuleUtils::getPublicHeapTypes(wasm, options.worldMode)) { if (type.isStruct()) { auto& fields = type.getStruct().fields; for (Index i = 0; i < fields.size(); i++) { diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 69f29101c86..0ca698a7568 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -26,7 +26,7 @@ namespace wasm { -GlobalTypeRewriter::GlobalTypeRewriter(Module& wasm) +GlobalTypeRewriter::GlobalTypeRewriter(Module& wasm, WorldMode worldMode) : wasm(wasm), publicGroups(wasm.features) { // Find the heap types that are not publicly observable. Even in a closed // world scenario, don't modify public types because we assume that they may @@ -34,6 +34,7 @@ GlobalTypeRewriter::GlobalTypeRewriter(Module& wasm) // will be located in the builder. typeInfo = ModuleUtils::collectHeapTypeInfo( wasm, + worldMode, ModuleUtils::TypeInclusion::UsedIRTypes, ModuleUtils::VisibilityHandling::FindVisibility); diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h index 0e050becc59..7aaf02a8234 100644 --- a/src/ir/type-updating.h +++ b/src/ir/type-updating.h @@ -358,7 +358,7 @@ class GlobalTypeRewriter { // private types do not conflict with public types. UniqueRecGroups publicGroups; - GlobalTypeRewriter(Module& wasm); + GlobalTypeRewriter(Module& wasm, WorldMode worldMode); virtual ~GlobalTypeRewriter() {} // Main entry point. This performs the entire process of creating new heap @@ -427,7 +427,9 @@ class GlobalTypeRewriter { // Helper for the repeating pattern of just updating Signature types using a // map of old heap type => new Signature. - static void updateSignatures(const SignatureUpdates& updates, Module& wasm) { + static void updateSignatures(const SignatureUpdates& updates, + Module& wasm, + WorldMode worldMode) { if (updates.empty()) { return; } @@ -436,8 +438,10 @@ class GlobalTypeRewriter { const SignatureUpdates& updates; public: - SignatureRewriter(Module& wasm, const SignatureUpdates& updates) - : GlobalTypeRewriter(wasm), updates(updates) { + SignatureRewriter(Module& wasm, + const SignatureUpdates& updates, + WorldMode worldMode) + : GlobalTypeRewriter(wasm, worldMode), updates(updates) { update(); } @@ -448,7 +452,7 @@ class GlobalTypeRewriter { sig.results = getTempType(iter->second.results); } } - } rewriter(wasm, updates); + } rewriter(wasm, updates, worldMode); } protected: @@ -473,8 +477,8 @@ class TypeMapper : public GlobalTypeRewriter { const TypeUpdates& mapping; - TypeMapper(Module& wasm, const TypeUpdates& mapping) - : GlobalTypeRewriter(wasm), mapping(mapping) {} + TypeMapper(Module& wasm, const TypeUpdates& mapping, WorldMode worldMode) + : GlobalTypeRewriter(wasm, worldMode), mapping(mapping) {} void map() { // Update the internals of types (struct fields, signatures, etc.) to diff --git a/src/pass.h b/src/pass.h index f5088e5e7ee..7d7dc0f1afc 100644 --- a/src/pass.h +++ b/src/pass.h @@ -110,6 +110,30 @@ struct InliningOptions { Index partialInliningIfs = 0; }; +// Assume code outside of the module does not inspect or interact with GC and +// function references, with the goal of being able to aggressively optimize +// all user-defined types. The outside may hold on to references and pass them +// back in, but may not inspect their contents, call them, or reflect on their +// types in any way. +// +// By default we do not make this assumption, and assume anything that escapes +// to the outside may be inspected in detail, which prevents us from e.g. +// changing the type of any value that may escape except by refining it (so we +// can't remove or refine fields on an escaping struct type, for example, +// unless the new type declares the original type as a supertype). +// +// Note that the module can still have imports and exports - otherwise it +// could do nothing at all! - so the meaning of "closed world" is a little +// subtle here. We do still want to keep imports and exports unchanged, as +// they form a contract with the outside world. For example, if an import has +// two parameters, we can't remove one of them. A nuance regarding that is how +// type equality works between wasm modules using the isorecursive type +// system: not only do we need to not remove a parameter as just mentioned, +// but we also want to keep types of things on the boundary unchanged. For +// example, we should not change an exported function's signature, as the +// outside may need that type to properly call the export. +enum class WorldMode { Open, Closed }; + struct PassOptions { friend Pass; @@ -196,29 +220,7 @@ struct PassOptions { // creates it and we know it is all zeros right before the active segments are // applied.) bool zeroFilledMemory = false; - // Assume code outside of the module does not inspect or interact with GC and - // function references, with the goal of being able to aggressively optimize - // all user-defined types. The outside may hold on to references and pass them - // back in, but may not inspect their contents, call them, or reflect on their - // types in any way. - // - // By default we do not make this assumption, and assume anything that escapes - // to the outside may be inspected in detail, which prevents us from e.g. - // changing the type of any value that may escape except by refining it (so we - // can't remove or refine fields on an escaping struct type, for example, - // unless the new type declares the original type as a supertype). - // - // Note that the module can still have imports and exports - otherwise it - // could do nothing at all! - so the meaning of "closed world" is a little - // subtle here. We do still want to keep imports and exports unchanged, as - // they form a contract with the outside world. For example, if an import has - // two parameters, we can't remove one of them. A nuance regarding that is how - // type equality works between wasm modules using the isorecursive type - // system: not only do we need to not remove a parameter as just mentioned, - // but we also want to keep types of things on the boundary unchanged. For - // example, we should not change an exported function's signature, as the - // outside may need that type to properly call the export. - bool closedWorld = false; + WorldMode worldMode = WorldMode::Open; // Whether to try to preserve debug info through, which are special calls. bool debugInfo = false; // Whether to generate StackIR during binary writing. This is on by default diff --git a/src/passes/AbstractTypeRefining.cpp b/src/passes/AbstractTypeRefining.cpp index b75153e3d2d..a22488dd1c4 100644 --- a/src/passes/AbstractTypeRefining.cpp +++ b/src/passes/AbstractTypeRefining.cpp @@ -87,7 +87,7 @@ struct AbstractTypeRefining : public Pass { return; } - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "AbstractTypeRefining requires --closed-world"; } @@ -116,7 +116,8 @@ struct AbstractTypeRefining : public Pass { // module, given closed world, but we'd also need to make sure that // we don't need to make any changes to public types that refer to // them. - for (auto type : ModuleUtils::getPublicHeapTypes(*module)) { + for (auto type : + ModuleUtils::getPublicHeapTypes(*module, getPassOptions().worldMode)) { createdTypes.insert(type); } @@ -289,8 +290,10 @@ struct AbstractTypeRefining : public Pass { // that for Unsubtyping. class AbstractTypeRefiningTypeMapper : public TypeMapper { public: - AbstractTypeRefiningTypeMapper(Module& wasm, const TypeUpdates& mapping) - : TypeMapper(wasm, mapping) {} + AbstractTypeRefiningTypeMapper(Module& wasm, + const TypeUpdates& mapping, + WorldMode worldMode) + : TypeMapper(wasm, mapping, worldMode) {} std::optional getDeclaredSuperType(HeapType oldType) override { // We do not want to update subtype relationships. @@ -298,7 +301,8 @@ struct AbstractTypeRefining : public Pass { } }; - AbstractTypeRefiningTypeMapper(*module, mapping).map(); + AbstractTypeRefiningTypeMapper(*module, mapping, getPassOptions().worldMode) + .map(); // Refinalize to propagate the type changes we made. For example, a refined // cast may lead to a struct.get reading a more refined type using that diff --git a/src/passes/ConstantFieldPropagation.cpp b/src/passes/ConstantFieldPropagation.cpp index 0063a8d3a69..66f45bb6047 100644 --- a/src/passes/ConstantFieldPropagation.cpp +++ b/src/passes/ConstantFieldPropagation.cpp @@ -542,7 +542,7 @@ struct ConstantFieldPropagation : public Pass { return; } - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "CFP requires --closed-world"; } diff --git a/src/passes/DeadArgumentElimination2.cpp b/src/passes/DeadArgumentElimination2.cpp index 11dc2cec1fb..1ebc576fa9a 100644 --- a/src/passes/DeadArgumentElimination2.cpp +++ b/src/passes/DeadArgumentElimination2.cpp @@ -235,7 +235,7 @@ struct DAE2 : public Pass { } optimizeReferencedFuncs = - getPassOptions().closedWorld && wasm->features.hasGC(); + getPassOptions().worldMode == WorldMode::Closed && wasm->features.hasGC(); TIME(Timer timer); @@ -579,7 +579,8 @@ void DAE2::analyzeModule() { // // TODO: Analyze tags and remove their unused parameters. std::unordered_set unrewritableRoots; - publicHeapTypes = ModuleUtils::getPublicHeapTypes(*wasm); + publicHeapTypes = + ModuleUtils::getPublicHeapTypes(*wasm, getPassOptions().worldMode); for (auto type : publicHeapTypes) { if (type.isSignature()) { unrewritableRoots.insert(getRootType(type)); @@ -728,7 +729,8 @@ void DAE2::computeFixedPoint() { struct DAETypeUpdater : GlobalTypeRewriter { DAE2& parent; DAETypeUpdater(DAE2& parent) - : GlobalTypeRewriter(*parent.wasm), parent(parent) {} + : GlobalTypeRewriter(*parent.wasm, parent.getPassOptions().worldMode), + parent(parent) {} void modifySignature(HeapType oldType, Signature& sig) override { // All signature types in a type tree will have the same parameters removed diff --git a/src/passes/GlobalEffects.cpp b/src/passes/GlobalEffects.cpp index ca82b2b3aea..4cacefd09b5 100644 --- a/src/passes/GlobalEffects.cpp +++ b/src/passes/GlobalEffects.cpp @@ -87,7 +87,8 @@ std::map analyzeFuncs(Module& module, if (auto* call = curr->dynCast()) { // Note the direct call. funcInfo.calledFunctions.insert(call->target); - } else if (effects.calls && options.closedWorld) { + } else if (effects.calls && + options.worldMode == WorldMode::Closed) { HeapType type; if (auto* callRef = curr->dynCast()) { // call_ref on unreachable does not have a call effect, @@ -102,7 +103,7 @@ std::map analyzeFuncs(Module& module, funcInfo.indirectCalledTypes.insert(type); } else if (effects.calls) { - assert(!options.closedWorld); + assert(options.worldMode == WorldMode::Open); funcInfo.effects = UnknownEffects; } else { // No call here, but update throwing if we see it. (Only do so, @@ -144,9 +145,9 @@ using CallGraph = CallGraph buildCallGraph(const Module& module, const std::map& funcInfos, - bool closedWorld) { + WorldMode worldMode) { CallGraph callGraph; - if (!closedWorld) { + if (worldMode == WorldMode::Open) { for (const auto& [caller, callerInfo] : funcInfos) { auto& callees = callGraph[caller]; @@ -344,7 +345,7 @@ struct GenerateGlobalEffects : public Pass { analyzeFuncs(*module, getPassOptions()); auto callGraph = - buildCallGraph(*module, funcInfos, getPassOptions().closedWorld); + buildCallGraph(*module, funcInfos, getPassOptions().worldMode); propagateEffects(*module, getPassOptions(), funcInfos, callGraph); diff --git a/src/passes/GlobalRefining.cpp b/src/passes/GlobalRefining.cpp index 87dc5b259cf..95389f8ccfc 100644 --- a/src/passes/GlobalRefining.cpp +++ b/src/passes/GlobalRefining.cpp @@ -80,7 +80,7 @@ struct GlobalRefining : public Pass { std::unordered_set exportedGlobals(exportedGlobalsVec.begin(), exportedGlobalsVec.end()); for (auto* global : exportedGlobalsVec) { - if (getPassOptions().closedWorld || global->mutable_) { + if (getPassOptions().worldMode == WorldMode::Closed || global->mutable_) { unoptimizable.insert(global->name); } } diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index bb5a077648a..8de7f20fa50 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -109,7 +109,7 @@ struct GlobalStructInference : public Pass { subTypes = std::make_unique(*module); } - if (getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Closed) { analyzeClosedWorld(module); } diff --git a/src/passes/GlobalTypeOptimization.cpp b/src/passes/GlobalTypeOptimization.cpp index 8171ce1c501..46eb698eaa4 100644 --- a/src/passes/GlobalTypeOptimization.cpp +++ b/src/passes/GlobalTypeOptimization.cpp @@ -153,8 +153,7 @@ struct GlobalTypeOptimization : public Pass { if (!module->features.hasGC()) { return; } - - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "GTO requires --closed-world"; } @@ -207,7 +206,8 @@ struct GlobalTypeOptimization : public Pass { propagator.propagateToSubTypes(dataFromSupersMap); // Find the public types, which we must not modify. - auto publicTypes = ModuleUtils::getPublicHeapTypes(*module); + auto publicTypes = + ModuleUtils::getPublicHeapTypes(*module, getPassOptions().worldMode); std::unordered_set publicTypesSet(publicTypes.begin(), publicTypes.end()); @@ -479,7 +479,8 @@ struct GlobalTypeOptimization : public Pass { public: TypeRewriter(Module& wasm, GlobalTypeOptimization& parent) - : GlobalTypeRewriter(wasm), parent(parent) {} + : GlobalTypeRewriter(wasm, parent.getPassOptions().worldMode), + parent(parent) {} void modifyStruct(HeapType oldStructType, Struct& struct_) override { auto& newFields = struct_.fields; diff --git a/src/passes/J2CLItableMerging.cpp b/src/passes/J2CLItableMerging.cpp index 68e610755d2..55bcfc6a917 100644 --- a/src/passes/J2CLItableMerging.cpp +++ b/src/passes/J2CLItableMerging.cpp @@ -72,7 +72,7 @@ struct J2CLItableMerging : public Pass { return; } - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "--merge-j2cl-itables requires --closed-world"; } @@ -384,7 +384,8 @@ struct J2CLItableMerging : public Pass { public: TypeRewriter(Module& wasm, J2CLItableMerging& parent) - : GlobalTypeRewriter(wasm), parent(parent) {} + : GlobalTypeRewriter(wasm, parent.getPassOptions().worldMode), + parent(parent) {} void modifyStruct(HeapType oldStructType, Struct& struct_) override { auto structInfoIt = parent.structInfoByVtableType.find(oldStructType); diff --git a/src/passes/MinimizeRecGroups.cpp b/src/passes/MinimizeRecGroups.cpp index a4d5e8211ac..e6757c8ac96 100644 --- a/src/passes/MinimizeRecGroups.cpp +++ b/src/passes/MinimizeRecGroups.cpp @@ -302,6 +302,7 @@ struct MinimizeRecGroups : Pass { auto typeInfo = ModuleUtils::collectHeapTypeInfo( *module, + getPassOptions().worldMode, ModuleUtils::TypeInclusion::AllTypes, ModuleUtils::VisibilityHandling::FindVisibility); @@ -771,7 +772,7 @@ struct MinimizeRecGroups : Pass { ++i; } } - GlobalTypeRewriter rewriter(wasm); + GlobalTypeRewriter rewriter(wasm, getPassOptions().worldMode); rewriter.mapTypes(oldToNew); rewriter.mapTypeNamesAndIndices(oldToNew); } diff --git a/src/passes/RemoveUnusedModuleElements.cpp b/src/passes/RemoveUnusedModuleElements.cpp index fd026490cb1..864123c4d99 100644 --- a/src/passes/RemoveUnusedModuleElements.cpp +++ b/src/passes/RemoveUnusedModuleElements.cpp @@ -444,7 +444,7 @@ struct Analyzer { } void useRefFunc(Name func) { - if (!options.closedWorld) { + if (options.worldMode == WorldMode::Open) { // The world is open, so assume the worst and something (inside or outside // of the module) can call this. use({ModuleElementKind::Function, func}); @@ -610,8 +610,8 @@ struct Analyzer { // outside of the code we can see), and when it is reached (if it's // unreachable then we don't know the type, and can defer that to DCE to // remove). - if (!options.closedWorld || curr->type == Type::unreachable || - !curr->is()) { + if (options.worldMode == WorldMode::Open || + curr->type == Type::unreachable || !curr->is()) { for (auto* child : ChildIterator(curr)) { use(child); } diff --git a/src/passes/RemoveUnusedTypes.cpp b/src/passes/RemoveUnusedTypes.cpp index b3b0f4a6dd9..3e4e2f83b88 100644 --- a/src/passes/RemoveUnusedTypes.cpp +++ b/src/passes/RemoveUnusedTypes.cpp @@ -39,14 +39,14 @@ struct RemoveUnusedTypes : Pass { // would change the identity of $A. Currently we would incorrectly remove // $unused. To fix that, we need to fix our collection of public types to // consider $A (and $unused) public in an open world. - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "RemoveUnusedTypes requires --closed-world"; } // We're not changing the contents of any of the types, so we just round // trip them through GlobalTypeRewriter which will put all the private types // in a single new rec group and leave out all the unused types. - GlobalTypeRewriter(*module).update(); + GlobalTypeRewriter(*module, getPassOptions().worldMode).update(); } }; diff --git a/src/passes/ReorderTypes.cpp b/src/passes/ReorderTypes.cpp index e120822da51..0b394890aab 100644 --- a/src/passes/ReorderTypes.cpp +++ b/src/passes/ReorderTypes.cpp @@ -41,8 +41,8 @@ struct ReorderingTypeRewriter : GlobalTypeRewriter { static constexpr float maxFactor = 1.0; static constexpr Index numFactors = 21; - ReorderingTypeRewriter(Module& wasm, bool forTesting) - : GlobalTypeRewriter(wasm), forTesting(forTesting) {} + ReorderingTypeRewriter(Module& wasm, bool forTesting, WorldMode worldMode) + : GlobalTypeRewriter(wasm, worldMode), forTesting(forTesting) {} std::vector getSortedTypes(PredecessorGraph preds) override { auto numTypes = preds.size(); @@ -142,11 +142,12 @@ struct ReorderTypes : Pass { } // See note in RemoveUnusedTypes. - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "ReorderTypes requires --closed-world"; } - ReorderingTypeRewriter(*module, forTesting).update(); + ReorderingTypeRewriter(*module, forTesting, getPassOptions().worldMode) + .update(); } }; diff --git a/src/passes/SignaturePruning.cpp b/src/passes/SignaturePruning.cpp index 4670d1c0015..fc95a66bad8 100644 --- a/src/passes/SignaturePruning.cpp +++ b/src/passes/SignaturePruning.cpp @@ -65,7 +65,7 @@ struct SignaturePruning : public Pass { return; } - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "SignaturePruning requires --closed-world"; } @@ -187,7 +187,8 @@ struct SignaturePruning : public Pass { } // Find the public types, which cannot be modified. - for (auto type : ModuleUtils::getPublicHeapTypes(*module)) { + for (auto type : + ModuleUtils::getPublicHeapTypes(*module, getPassOptions().worldMode)) { if (type.isFunction()) { allInfo[type].optimizable = false; } @@ -339,7 +340,8 @@ struct SignaturePruning : public Pass { } // Rewrite the types. - GlobalTypeRewriter::updateSignatures(newSignatures, *module); + GlobalTypeRewriter::updateSignatures( + newSignatures, *module, getPassOptions().worldMode); if (callTargetsToLocalize.empty()) { return false; diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp index c1d72bc8d77..5137b1899a5 100644 --- a/src/passes/SignatureRefining.cpp +++ b/src/passes/SignatureRefining.cpp @@ -156,7 +156,8 @@ struct SignatureRefining : public Pass { } // Find the public types, which we must not modify. - for (auto type : ModuleUtils::getPublicHeapTypes(*module)) { + for (auto type : + ModuleUtils::getPublicHeapTypes(*module, getPassOptions().worldMode)) { if (type.isFunction()) { allInfo[type].canModify = false; } @@ -337,7 +338,8 @@ struct SignatureRefining : public Pass { CodeUpdater(*this, *module).run(getPassRunner(), module); // Rewrite the types. - GlobalTypeRewriter::updateSignatures(newSignatures, *module); + GlobalTypeRewriter::updateSignatures( + newSignatures, *module, getPassOptions().worldMode); // Update intrinsics. updateIntrinsics(module, allInfo); diff --git a/src/passes/StringLowering.cpp b/src/passes/StringLowering.cpp index bd753efaf91..c9e836aefe8 100644 --- a/src/passes/StringLowering.cpp +++ b/src/passes/StringLowering.cpp @@ -338,7 +338,7 @@ struct StringLowering : public StringGathering { // Strings turn into externref. updates[HeapType::string] = HeapType::ext; - TypeMapper(*module, updates).map(); + TypeMapper(*module, updates, getPassOptions().worldMode).map(); } // Imported string functions. diff --git a/src/passes/TypeFinalizing.cpp b/src/passes/TypeFinalizing.cpp index 5ba3459da46..85d218da327 100644 --- a/src/passes/TypeFinalizing.cpp +++ b/src/passes/TypeFinalizing.cpp @@ -52,7 +52,8 @@ struct TypeFinalizing : public Pass { // Note we don't need to worry about signature-called functions here // (configureAll) because such calls don't care about finality. - auto privateTypes = ModuleUtils::getPrivateHeapTypes(*module); + auto privateTypes = + ModuleUtils::getPrivateHeapTypes(*module, getPassOptions().worldMode); for (auto type : privateTypes) { // If we are finalizing types then we can only do that to leaf types. If // we are unfinalizing, we can do that unconditionally. @@ -66,7 +67,8 @@ struct TypeFinalizing : public Pass { public: TypeRewriter(Module& wasm, TypeFinalizing& parent) - : GlobalTypeRewriter(wasm), parent(parent) {} + : GlobalTypeRewriter(wasm, parent.getPassOptions().worldMode), + parent(parent) {} void modifyTypeBuilderEntry(TypeBuilder& typeBuilder, Index i, diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index 8ca33699716..64a2df6bb7d 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -243,13 +243,14 @@ void TypeMerging::run(Module* module_) { return; } - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "TypeMerging requires --closed-world"; } // First, find all the cast types and private types. We will need these to // determine whether types are eligible to be merged. - mergeable = ModuleUtils::getPrivateHeapTypes(*module); + mergeable = + ModuleUtils::getPrivateHeapTypes(*module, getPassOptions().worldMode); privateTypes = std::unordered_set(mergeable.begin(), mergeable.end()); auto casts = findCastTypes(); @@ -303,7 +304,8 @@ bool TypeMerging::merge(MergeKind kind) { Partitions partitions; #if TYPE_MERGING_DEBUG - auto printedPrivateTypes = ModuleUtils::getPrivateHeapTypes(*module); + auto printedPrivateTypes = + ModuleUtils::getPrivateHeapTypes(*module, getPassOptions().worldMode); using Fallback = IndexedTypeNameGenerator; Fallback printPrivate(printedPrivateTypes, "private."); ModuleTypeNameGenerator print(*module, printPrivate); @@ -640,7 +642,7 @@ void TypeMerging::applyMerges() { // We found things to optimize! Rewrite types in the module to apply those // changes. - TypeMapper(*module, replacements).map(); + TypeMapper(*module, replacements, getPassOptions().worldMode).map(); } bool shapeEq(HeapType a, HeapType b) { diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index 201360e5aca..720233cf9ae 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -145,7 +145,7 @@ struct TypeRefining : public Pass { return; } - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "TypeRefining requires --closed-world"; } @@ -262,7 +262,8 @@ struct TypeRefining : public Pass { bool canOptimize = false; // We cannot modify public types. - auto publicTypes = ModuleUtils::getPublicHeapTypes(*module); + auto publicTypes = + ModuleUtils::getPublicHeapTypes(*module, getPassOptions().worldMode); std::unordered_set publicTypesSet(publicTypes.begin(), publicTypes.end()); @@ -454,7 +455,8 @@ struct TypeRefining : public Pass { public: TypeRewriter(Module& wasm, TypeRefining& parent) - : GlobalTypeRewriter(wasm), parent(parent) {} + : GlobalTypeRewriter(wasm, parent.getPassOptions().worldMode), + parent(parent) {} void modifyStruct(HeapType oldStructType, Struct& struct_) override { const auto& oldFields = oldStructType.getStruct().fields; diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index f3165b8147c..866e32ba58a 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -567,8 +567,7 @@ struct Unsubtyping : Pass, Noter { if (!wasm->features.hasGC()) { return; } - - if (!getPassOptions().closedWorld) { + if (getPassOptions().worldMode == WorldMode::Open) { Fatal() << "Unsubtyping requires --closed-world"; } @@ -635,7 +634,8 @@ struct Unsubtyping : Pass, Noter { void analyzePublicTypes(Module& wasm) { // We cannot change supertypes for anything public. - for (auto type : ModuleUtils::getPublicHeapTypes(wasm)) { + for (auto type : + ModuleUtils::getPublicHeapTypes(wasm, getPassOptions().worldMode)) { if (auto super = type.getDeclaredSuperType()) { noteSubtype(type, *super); } @@ -1038,7 +1038,8 @@ struct Unsubtyping : Pass, Noter { struct Rewriter : GlobalTypeRewriter { Unsubtyping& parent; Rewriter(Unsubtyping& parent, Module& wasm) - : GlobalTypeRewriter(wasm), parent(parent) {} + : GlobalTypeRewriter(wasm, parent.getPassOptions().worldMode), + parent(parent) {} std::optional getDeclaredSuperType(HeapType type) override { if (auto super = parent.types.getSupertype(type); super && !super->isBasic()) { diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index d29a6fcebf5..7235d777ddc 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -760,7 +760,7 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() { addIfNoDWARFIssues("once-reduction"); } if (wasm->features.hasGC() && options.optimizeLevel >= 2) { - if (options.closedWorld) { + if (options.worldMode == WorldMode::Closed) { addIfNoDWARFIssues("type-refining"); addIfNoDWARFIssues("signature-pruning"); addIfNoDWARFIssues("signature-refining"); @@ -770,11 +770,11 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() { // remove ref.funcs that were once assigned to vtables but are no longer // needed, which can allow more code to be removed globally. After those, // constant field propagation can be more effective. - if (options.closedWorld) { + if (options.worldMode == WorldMode::Closed) { addIfNoDWARFIssues("gto"); } addIfNoDWARFIssues("remove-unused-module-elements"); - if (options.closedWorld) { + if (options.worldMode == WorldMode::Closed) { addIfNoDWARFIssues("remove-unused-types"); // Allow ref.tests in cfp if we are aggressively optimizing for speed. if (options.optimizeLevel >= 3) { @@ -784,7 +784,7 @@ void PassRunner::addDefaultGlobalOptimizationPrePasses() { } } addIfNoDWARFIssues("gsi"); - if (options.closedWorld) { + if (options.worldMode == WorldMode::Closed) { addIfNoDWARFIssues("abstract-type-refining"); addIfNoDWARFIssues("unsubtyping"); } diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 803e13d5d0b..fa819e20772 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -121,10 +121,10 @@ class TranslateToFuzzReader { public: TranslateToFuzzReader(Module& wasm, std::vector&& input, - bool closedWorld = false); + WorldMode worldMode = WorldMode::Open); TranslateToFuzzReader(Module& wasm, std::string& filename, - bool closedWorld = false); + WorldMode worldMode = WorldMode::Open); void pickPasses(OptimizationOptions& options); void setAllowMemory(bool allowMemory_) { allowMemory = allowMemory_; } @@ -141,7 +141,7 @@ class TranslateToFuzzReader { private: // Whether the module will be tested in a closed-world environment. - bool closedWorld; + WorldMode worldMode; Builder builder; Random random; Intrinsics intrinsics; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 759061da88f..69e97fa5aa2 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -62,8 +62,8 @@ std::vector getMemoryOrders(const FeatureSet& features) { TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, std::vector&& input, - bool closedWorld) - : wasm(wasm), closedWorld(closedWorld), builder(wasm), + WorldMode worldMode) + : wasm(wasm), worldMode(worldMode), builder(wasm), random(std::move(input), wasm.features), intrinsics(wasm), loggableTypes(getLoggableTypes(wasm.features)), atomicMemoryOrders(getMemoryOrders(wasm.features)), @@ -123,10 +123,9 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm, std::string& filename, - bool closedWorld) - : TranslateToFuzzReader(wasm, - read_file>(filename, Flags::Binary), - closedWorld) {} + WorldMode worldMode) + : TranslateToFuzzReader( + wasm, read_file>(filename, Flags::Binary), worldMode) {} void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { // Pick random passes to further shape the wasm. This is similar to how we @@ -274,8 +273,8 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { // Most of these depend on closed world, so just set that. Set it both // on the global pass options, and in the internal state of this // TranslateToFuzzReader instance. - options.passOptions.closedWorld = true; - closedWorld = true; + options.passOptions.worldMode = WorldMode::Closed; + worldMode = WorldMode::Closed; switch (upTo(16)) { case 0: @@ -343,8 +342,8 @@ void TranslateToFuzzReader::pickPasses(OptimizationOptions& options) { options.passOptions.shrinkLevel = upTo(3); } - if (!options.passOptions.closedWorld && oneIn(2)) { - options.passOptions.closedWorld = true; + if (options.passOptions.worldMode == WorldMode::Open && oneIn(2)) { + options.passOptions.worldMode = WorldMode::Closed; } // Prune things that error in JS if we call them (like SIMD), some of the @@ -1683,7 +1682,7 @@ void TranslateToFuzzReader::processFunctions() { // Also fix up closed world, if we need to. We must do this at the end, so // nothing can break the closed world assumptions after. - if (closedWorld) { + if (worldMode == WorldMode::Closed) { for (auto& func : wasm.functions) { if (!func->imported()) { fixClosedWorld(func.get()); @@ -2194,7 +2193,7 @@ void TranslateToFuzzReader::mutate(Function* func) { } void TranslateToFuzzReader::fixClosedWorld(Function* func) { - assert(closedWorld); + assert(worldMode == WorldMode::Closed); struct Fixer : public ExpressionStackWalker> { @@ -6699,7 +6698,7 @@ bool TranslateToFuzzReader::isValidRefFuncTarget(Name func) { // reference, but in that mode we must only pass in jsCalled functions. We // handle direct calls in fixClosedWorld, but cannot handle indirect ones // easily, so just disallow taking references of those functions. - if (!closedWorld) { + if (worldMode == WorldMode::Open) { return true; } return !isCallRefImport(func); diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 87362d455d2..1f3ec266bc7 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -173,7 +173,7 @@ struct ToolOptions : public Options { ToolOptionsCategory, Options::Arguments::Zero, [this](Options*, const std::string&) { - passOptions.closedWorld = true; + passOptions.worldMode = WorldMode::Closed; }) .add( "--preserve-type-order", diff --git a/src/tools/wasm-opt.cpp b/src/tools/wasm-opt.cpp index f593428d2b6..9bdfa4b06f7 100644 --- a/src/tools/wasm-opt.cpp +++ b/src/tools/wasm-opt.cpp @@ -353,7 +353,7 @@ For more on how to optimize effectively, see } if (translateToFuzz) { TranslateToFuzzReader reader( - wasm, options.extra["infile"], options.passOptions.closedWorld); + wasm, options.extra["infile"], options.passOptions.worldMode); reader.setAllowMemory(fuzzMemory); reader.setAllowOOB(fuzzOOB); reader.setPreserveImportsAndExports(fuzzPreserveImportsAndExports); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 64b67e51edd..a8c9f40e188 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -250,7 +250,7 @@ void validateExactReferences(Module& module, ValidationInfo& info) { return; } - for (auto type : ModuleUtils::getPublicHeapTypes(module)) { + for (auto type : ModuleUtils::getExposedPublicHeapTypes(module)) { for (auto child : type.getTypeChildren()) { if (child.isExact()) { std::string typeName;