/* * Copyright 2017 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_module_h #define wasm_ir_module_h #include "pass.h" #include "support/insert_ordered.h" #include "support/unique_deferring_queue.h" #include "wasm.h" namespace wasm::ModuleUtils { // Copies a function into a module. If newName is provided it is used as the // name of the function (otherwise the original name is copied). When specified, // fileIndexMap and symbolNameIndexMap are used to rename source map filename // and symbol name indices when copying the function from one module to another // one. Function* copyFunction( Function* func, Module& out, Name newName = Name(), std::optional<std::vector<Index>> fileIndexMap = std::nullopt, std::optional<std::vector<Index>> symbolNameIndexMap = std::nullopt); // As above, but does not add the copy to the module. std::unique_ptr<Function> copyFunctionWithoutAdd( Function* func, Module& out, Name newName = Name(), std::optional<std::vector<Index>> fileIndexMap = std::nullopt, std::optional<std::vector<Index>> symbolNameIndexMap = std::nullopt); Global* copyGlobal(Global* global, Module& out); Tag* copyTag(Tag* tag, Module& out); ElementSegment* copyElementSegment(const ElementSegment* segment, Module& out); Table* copyTable(const Table* table, Module& out); Memory* copyMemory(const Memory* memory, Module& out); DataSegment* copyDataSegment(const DataSegment* segment, Module& out); // Copies named toplevel module items (things of kind ModuleItemKind). See // copyModule() for something that also copies exports, the start function, etc. void copyModuleItems(const Module& in, Module& out); void copyModule(const Module& in, Module& out); void clearModule(Module& wasm); // Renaming // Rename functions along with all their uses. // Note that for this to work the functions themselves don't necessarily need // to exist. For example, it is possible to remove a given function and then // call this to redirect all of its uses. template<typename T> void renameFunctions(Module& wasm, T& map); void renameFunction(Module& wasm, Name oldName, Name newName); // Convenient iteration over imported/non-imported module elements template<typename T> inline void iterImportedMemories(Module& wasm, T visitor) { for (auto& import : wasm.memories) { if (import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterDefinedMemories(Module& wasm, T visitor) { for (auto& import : wasm.memories) { if (!import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterMemorySegments(Module& wasm, Name memory, T visitor) { for (auto& segment : wasm.dataSegments) { if (!segment->isPassive && segment->memory == memory) { visitor(segment.get()); } } } template<typename T> inline void iterActiveDataSegments(Module& wasm, T visitor) { for (auto& segment : wasm.dataSegments) { if (!segment->isPassive) { visitor(segment.get()); } } } template<typename T> inline void iterImportedTables(Module& wasm, T visitor) { for (auto& import : wasm.tables) { if (import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterDefinedTables(Module& wasm, T visitor) { for (auto& import : wasm.tables) { if (!import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterTableSegments(Module& wasm, Name table, T visitor) { // Just a precaution so that we don't iterate over passive elem segments by // accident assert(table.is() && "Table name must not be null"); for (auto& segment : wasm.elementSegments) { if (segment->table == table) { visitor(segment.get()); } } } template<typename T> inline void iterActiveElementSegments(Module& wasm, T visitor) { for (auto& segment : wasm.elementSegments) { if (segment->table.is()) { visitor(segment.get()); } } } template<typename T> inline void iterImportedGlobals(Module& wasm, T visitor) { for (auto& import : wasm.globals) { if (import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterDefinedGlobals(Module& wasm, T visitor) { for (auto& import : wasm.globals) { if (!import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterImportedFunctions(Module& wasm, T visitor) { for (auto& import : wasm.functions) { if (import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterDefinedFunctions(Module& wasm, T visitor) { for (auto& import : wasm.functions) { if (!import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterImportedTags(Module& wasm, T visitor) { for (auto& import : wasm.tags) { if (import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterDefinedTags(Module& wasm, T visitor) { for (auto& import : wasm.tags) { if (!import->imported()) { visitor(import.get()); } } } template<typename T> inline void iterImports(Module& wasm, T visitor) { iterImportedMemories(wasm, visitor); iterImportedTables(wasm, visitor); iterImportedGlobals(wasm, visitor); iterImportedFunctions(wasm, visitor); iterImportedTags(wasm, visitor); } // Iterates over all importable module items. The visitor provided should have // signature void(ExternalKind, Importable*). template<typename T> inline void iterImportable(Module& wasm, T visitor) { for (auto& curr : wasm.functions) { if (curr->imported()) { visitor(ExternalKind::Function, curr.get()); } } for (auto& curr : wasm.tables) { if (curr->imported()) { visitor(ExternalKind::Table, curr.get()); } } for (auto& curr : wasm.memories) { if (curr->imported()) { visitor(ExternalKind::Memory, curr.get()); } } for (auto& curr : wasm.globals) { if (curr->imported()) { visitor(ExternalKind::Global, curr.get()); } } for (auto& curr : wasm.tags) { if (curr->imported()) { visitor(ExternalKind::Tag, curr.get()); } } } // Iterates over all module items. The visitor provided should have signature // void(ModuleItemKind, Named*). template<typename T> inline void iterModuleItems(Module& wasm, T visitor) { for (auto& curr : wasm.functions) { visitor(ModuleItemKind::Function, curr.get()); } for (auto& curr : wasm.tables) { visitor(ModuleItemKind::Table, curr.get()); } for (auto& curr : wasm.memories) { visitor(ModuleItemKind::Memory, curr.get()); } for (auto& curr : wasm.globals) { visitor(ModuleItemKind::Global, curr.get()); } for (auto& curr : wasm.tags) { visitor(ModuleItemKind::Tag, curr.get()); } for (auto& curr : wasm.dataSegments) { visitor(ModuleItemKind::DataSegment, curr.get()); } for (auto& curr : wasm.elementSegments) { visitor(ModuleItemKind::ElementSegment, curr.get()); } } // Helper class for performing an operation on all the functions in the module, // in parallel, with an Info object for each one that can contain results of // some computation that the operation performs. // The operation performed should not modify the wasm module in any way, by // default - otherwise, set the Mutability to Mutable. (This is not enforced at // compile time - TODO find a way - but at runtime in pass-debug mode it is // checked.) template<typename K, typename V> using DefaultMap = std::map<K, V>; template<typename T, Mutability Mut = Immutable, template<typename, typename> class MapT = DefaultMap> struct ParallelFunctionAnalysis { Module& wasm; using Map = MapT<Function*, T>; Map map; using Func = std::function<void(Function*, T&)>; ParallelFunctionAnalysis(Module& wasm, Func work) : wasm(wasm) { // Fill in the map as we operate on it in parallel (each function to its own // entry). for (auto& func : wasm.functions) { map[func.get()]; } doAnalysis(work); } // Perform an analysis by operating on each function, in parallel. // // This is called from the constructor (with the work function given there), // and can also be called later as well if the user has additional operations // to perform. void doAnalysis(Func work) { // Run on the imports first. TODO: parallelize this too for (auto& func : wasm.functions) { if (func->imported()) { work(func.get(), map[func.get()]); } } struct Mapper : public WalkerPass<PostWalker<Mapper>> { bool isFunctionParallel() override { return true; } bool modifiesBinaryenIR() override { return Mut; } Mapper(Module& module, Map& map, Func work) : module(module), map(map), work(work) {} std::unique_ptr<Pass> create() override { return std::make_unique<Mapper>(module, map, work); } void doWalkFunction(Function* curr) { assert(map.count(curr)); work(curr, map[curr]); } private: Module& module; Map& map; Func work; }; PassRunner runner(&wasm); Mapper(wasm, map, work).run(&runner, &wasm); } }; // Helper class for analyzing the call graph. // // Provides hooks for running some initial calculation on each function (which // is done in parallel), writing to a FunctionInfo structure for each function. // Then you can call propagateBack() to propagate a property of interest to the // calling functions, transitively. // // For example, if some functions are known to call an import "foo", then you // can use this to find which functions call something that might eventually // reach foo, by initially marking the direct callers as "calling foo" and // propagating that backwards. template<typename T> struct CallGraphPropertyAnalysis { Module& wasm; // The basic information for each function about whom it calls and who is // called by it. struct FunctionInfo { std::set<Function*> callsTo; std::set<Function*> calledBy; // A non-direct call is any call that is not direct. That includes // CallIndirect and CallRef. bool hasNonDirectCall = false; }; using Map = std::map<Function*, T>; Map map; using Func = std::function<void(Function*, T&)>; CallGraphPropertyAnalysis(Module& wasm, Func work) : wasm(wasm) { ParallelFunctionAnalysis<T> analysis(wasm, [&](Function* func, T& info) { work(func, info); if (func->imported()) { return; } struct Mapper : public PostWalker<Mapper> { Mapper(Module* module, T& info, Func work) : module(module), info(info), work(work) {} void visitCall(Call* curr) { info.callsTo.insert(module->getFunction(curr->target)); } void visitCallIndirect(CallIndirect* curr) { info.hasNonDirectCall = true; } void visitCallRef(CallRef* curr) { info.hasNonDirectCall = true; } private: Module* module; T& info; Func work; } mapper(&wasm, info, work); mapper.walk(func->body); }); map.swap(analysis.map); // Find what is called by what. for (auto& [func, info] : map) { for (auto* target : info.callsTo) { map[target].calledBy.insert(func); } } } enum NonDirectCalls { IgnoreNonDirectCalls, NonDirectCallsHaveProperty }; // Propagate a property from a function to those that call it. // // hasProperty() - Check if the property is present. // canHaveProperty() - Check if the property could be present. // addProperty() - Adds the property. // logVisit() - Log each visit of the propagation. This is called before // we check if the function already has the property. // // Note that the order of propagation here is *not* deterministic, for // efficiency reasons (specifically, |calledBy| is unordered and also is // generated by |callsTo| which is likewise unordered). If the order matters // we could add an ordered variant of this. For now, users that care about // ordering in the middle need to handle this (e.g. Asyncify - if we add such // an ordered variant, we could use it there). void propagateBack(std::function<bool(const T&)> hasProperty, std::function<bool(const T&)> canHaveProperty, std::function<void(T&)> addProperty, std::function<void(const T&, Function*)> logVisit, NonDirectCalls nonDirectCalls) { // The work queue contains items we just learned can change the state. UniqueDeferredQueue<Function*> work; for (auto& func : wasm.functions) { if (hasProperty(map[func.get()]) || (nonDirectCalls == NonDirectCallsHaveProperty && map[func.get()].hasNonDirectCall)) { addProperty(map[func.get()]); work.push(func.get()); } } while (!work.empty()) { auto* func = work.pop(); for (auto* caller : map[func].calledBy) { // Skip functions forbidden from getting this property. if (!canHaveProperty(map[caller])) { continue; } // Log now, even if the function already has the property. logVisit(map[caller], func); // If we don't already have the property, then add it now, and propagate // further. if (!hasProperty(map[caller])) { addProperty(map[caller]); work.push(caller); } } } } }; // Which types to collect. // // AllTypes - Any type anywhere reachable from anything. // // UsedIRTypes - Same as AllTypes, but excludes types reachable only because // they are in a rec group with some other used type and types that are only // used from other unreachable types. // // BinaryTypes - Only types that need to appear in the module's type section. // enum class TypeInclusion { AllTypes, UsedIRTypes, BinaryTypes }; // Whether to classify collected types as public and private. enum class VisibilityHandling { NoVisibility, FindVisibility }; // Whether a type is public or private. If visibility is not analyzed, the // visibility will be Unknown instead. enum class Visibility { Unknown, Public, Private }; struct HeapTypeInfo { Index useCount = 0; Visibility visibility = Visibility::Unknown; }; InsertOrderedMap<HeapType, HeapTypeInfo> collectHeapTypeInfo( Module& wasm, TypeInclusion inclusion = TypeInclusion::AllTypes, VisibilityHandling visibility = VisibilityHandling::NoVisibility); // Helper function for collecting all the non-basic heap types used in the // module, i.e. the types that would appear in the type section. std::vector<HeapType> collectHeapTypes(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<HeapType> getPublicHeapTypes(Module& wasm); // getHeapTypes - getPublicHeapTypes std::vector<HeapType> getPrivateHeapTypes(Module& wasm); struct IndexedHeapTypes { std::vector<HeapType> types; std::unordered_map<HeapType, Index> indices; }; // Similar to `collectHeapTypes`, but provides fast lookup of the index for each // type as well. Also orders the types to be valid and sorts the types by // frequency of use to minimize code size. IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm); } // namespace wasm::ModuleUtils #endif // wasm_ir_module_h