| //===----------------------------------------------------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "WebAssembly.h" | |
| #include "WebAssemblyTargetMachine.h" | |
| #include "llvm/IR/Analysis.h" | |
| #include "llvm/IR/IntrinsicInst.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/IR/PassManager.h" | |
| #include "llvm/MC/MCSubtargetInfo.h" | |
| #include "llvm/Pass.h" | |
| #include "llvm/Transforms/Scalar/LowerAtomicPass.h" | |
| using namespace llvm; | |
| #define DEBUG_TYPE "wasm-coalesce-features-and-strip-atomics" | |
| namespace { | |
| class WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy final | |
| : public ModulePass { | |
| // Take the union of all features used in the module and use it for each | |
| // function individually, since having multiple feature sets in one module | |
| // currently does not make sense for WebAssembly. If atomics are not enabled, | |
| // also strip atomic operations and thread local storage. | |
| WebAssemblyTargetMachine *WasmTM; | |
| public: | |
| static char ID; | |
| WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy( | |
| WebAssemblyTargetMachine *WasmTM) | |
| : ModulePass(ID), WasmTM(WasmTM) {} | |
| bool runOnModule(Module &M) override; | |
| }; | |
| } // namespace | |
| char WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy::ID = 0; | |
| INITIALIZE_PASS(WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy, DEBUG_TYPE, | |
| "Coalesce features and strip atomics", true, false) | |
| ModulePass *llvm::createWebAssemblyCoalesceFeaturesAndStripAtomicsLegacyPass( | |
| WebAssemblyTargetMachine &TM) { | |
| return new WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy(&TM); | |
| } | |
| static std::string getFeatureString(const MCSubtargetInfo &STI, | |
| const FeatureBitset &Features) { | |
| std::string Ret; | |
| for (const SubtargetFeatureKV &KV : STI.getAllProcessorFeatures()) { | |
| if (Features[KV.Value]) | |
| Ret += (StringRef("+") + KV.key() + ",").str(); | |
| else | |
| Ret += (StringRef("-") + KV.key() + ",").str(); | |
| } | |
| // remove trailing ',' | |
| Ret.pop_back(); | |
| return Ret; | |
| } | |
| static std::pair<FeatureBitset, std::string> | |
| coalesceFeatures(const Module &M, WebAssemblyTargetMachine *WasmTM) { | |
| // Union the features of all defined functions. Start with an empty set, so | |
| // that if a feature is disabled in every function, we'll compute it as | |
| // disabled. If any function lacks a target-features attribute, it'll | |
| // default to the target CPU from the `TargetMachine`. | |
| FeatureBitset Features; | |
| bool AnyDefined = false; | |
| for (auto &F : M) { | |
| if (F.isDeclaration()) | |
| continue; | |
| Features |= WasmTM->getSubtargetImpl(F)->getFeatureBits(); | |
| AnyDefined = true; | |
| } | |
| // If we have no defined functions, use the module-wide feature bits from the | |
| // `TargetMachine`. | |
| if (!AnyDefined) | |
| Features = WasmTM->getMCSubtargetInfo().getFeatureBits(); | |
| return {Features, getFeatureString(WasmTM->getMCSubtargetInfo(), Features)}; | |
| } | |
| static void replaceFeatures(Function &F, const std::string &Features) { | |
| F.removeFnAttr("target-features"); | |
| F.removeFnAttr("target-cpu"); | |
| F.addFnAttr("target-features", Features); | |
| } | |
| static bool stripAtomics(Module &M) { | |
| // Detect whether any atomics will be lowered, since there is no way to tell | |
| // whether the LowerAtomic pass lowers e.g. stores. | |
| bool Stripped = false; | |
| for (auto &F : M) { | |
| for (auto &B : F) { | |
| for (auto &I : B) { | |
| if (I.isAtomic()) { | |
| Stripped = true; | |
| goto done; | |
| } | |
| } | |
| } | |
| } | |
| done: | |
| if (!Stripped) | |
| return false; | |
| LowerAtomicPass Lowerer; | |
| FunctionAnalysisManager FAM; | |
| for (auto &F : M) | |
| Lowerer.run(F, FAM); | |
| return true; | |
| } | |
| static bool stripThreadLocals(Module &M) { | |
| bool Stripped = false; | |
| for (auto &GV : M.globals()) { | |
| if (GV.isThreadLocal()) { | |
| // replace `@llvm.threadlocal.address.pX(GV)` with `GV`. | |
| for (Use &U : make_early_inc_range(GV.uses())) { | |
| if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(U.getUser())) { | |
| if (II->getIntrinsicID() == Intrinsic::threadlocal_address && | |
| II->getArgOperand(0) == &GV) { | |
| II->replaceAllUsesWith(&GV); | |
| II->eraseFromParent(); | |
| } | |
| } | |
| } | |
| Stripped = true; | |
| GV.setThreadLocal(false); | |
| } | |
| } | |
| return Stripped; | |
| } | |
| static void recordFeatures(Module &M, const WebAssemblySubtarget *ST, | |
| const FeatureBitset &Features, bool Stripped) { | |
| for (const SubtargetFeatureKV &KV : ST->getAllProcessorFeatures()) { | |
| if (Features[KV.Value]) { | |
| // Mark features as used | |
| std::string MDKey = (StringRef("wasm-feature-") + KV.key()).str(); | |
| M.addModuleFlag(Module::ModFlagBehavior::Error, MDKey, | |
| wasm::WASM_FEATURE_PREFIX_USED); | |
| } | |
| } | |
| // Code compiled without atomics or bulk-memory may have had its atomics or | |
| // thread-local data lowered to nonatomic operations or non-thread-local | |
| // data. In that case, we mark the pseudo-feature "shared-mem" as disallowed | |
| // to tell the linker that it would be unsafe to allow this code to be used | |
| // in a module with shared memory. | |
| if (Stripped) { | |
| M.addModuleFlag(Module::ModFlagBehavior::Error, "wasm-feature-shared-mem", | |
| wasm::WASM_FEATURE_PREFIX_DISALLOWED); | |
| } | |
| } | |
| static bool coalesceFeaturesAndStripAtomics(Module &M, | |
| WebAssemblyTargetMachine *WasmTM) { | |
| auto [Features, FeatureStr] = coalesceFeatures(M, WasmTM); | |
| WasmTM->setTargetFeatureString(FeatureStr); | |
| for (auto &F : M) | |
| replaceFeatures(F, FeatureStr); | |
| bool StrippedAtomics = false; | |
| bool StrippedTLS = false; | |
| // In cooperative threading mode, thread locals are meaningful even without | |
| // atomics. | |
| const WebAssemblySubtarget *ST = WasmTM->getSubtargetImpl( | |
| WasmTM->getTargetCPU(), WasmTM->getTargetFeatureString(), | |
| WasmTM->getTargetABIName(M)); | |
| bool CooperativeThreading = ST->hasCooperativeMultithreading(); | |
| if (!Features[WebAssembly::FeatureAtomics]) { | |
| StrippedAtomics = stripAtomics(M); | |
| if (!CooperativeThreading) | |
| StrippedTLS = stripThreadLocals(M); | |
| } | |
| if (!Features[WebAssembly::FeatureBulkMemory] && !StrippedTLS) { | |
| StrippedTLS = stripThreadLocals(M); | |
| } | |
| if (StrippedAtomics && !StrippedTLS && !CooperativeThreading) | |
| stripThreadLocals(M); | |
| else if (StrippedTLS && !StrippedAtomics) | |
| stripAtomics(M); | |
| recordFeatures(M, ST, Features, StrippedAtomics || StrippedTLS); | |
| // Conservatively assume we have made some change | |
| return true; | |
| } | |
| bool WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy::runOnModule(Module &M) { | |
| return coalesceFeaturesAndStripAtomics(M, WasmTM); | |
| } | |
| PreservedAnalyses WebAssemblyCoalesceFeaturesAndStripAtomicsPass::run( | |
| Module &M, ModuleAnalysisManager &MAM) { | |
| return coalesceFeaturesAndStripAtomics(M, &TM) ? PreservedAnalyses::none() | |
| : PreservedAnalyses::all(); | |
| } |