1//===----------------------------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "WebAssembly.h"
10#include "WebAssemblyTargetMachine.h"
11#include "llvm/IR/Analysis.h"
12#include "llvm/IR/IntrinsicInst.h"
13#include "llvm/IR/Module.h"
14#include "llvm/IR/PassManager.h"
15#include "llvm/MC/MCSubtargetInfo.h"
16#include "llvm/Pass.h"
17#include "llvm/Transforms/Scalar/LowerAtomicPass.h"
18
19using namespace llvm;
20
21#define DEBUG_TYPE "wasm-coalesce-features-and-strip-atomics"
22
23namespace {
24class WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy final
25 : public ModulePass {
26 // Take the union of all features used in the module and use it for each
27 // function individually, since having multiple feature sets in one module
28 // currently does not make sense for WebAssembly. If atomics are not enabled,
29 // also strip atomic operations and thread local storage.
30 WebAssemblyTargetMachine *WasmTM;
31
32public:
33 static char ID;
34
35 WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy(
36 WebAssemblyTargetMachine *WasmTM)
37 : ModulePass(ID), WasmTM(WasmTM) {}
38
39 bool runOnModule(Module &M) override;
40};
41} // namespace
42
43char WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy::ID = 0;
44INITIALIZE_PASS(WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy, DEBUG_TYPE,
45 "Coalesce features and strip atomics", true, false)
46
47ModulePass *llvm::createWebAssemblyCoalesceFeaturesAndStripAtomicsLegacyPass(
48 WebAssemblyTargetMachine &TM) {
49 return new WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy(&TM);
50}
51
52static std::string getFeatureString(const MCSubtargetInfo &STI,
53 const FeatureBitset &Features) {
54 std::string Ret;
55 for (const SubtargetFeatureKV &KV : STI.getAllProcessorFeatures()) {
56 if (Features[KV.Value])
57 Ret += (StringRef("+") + KV.key() + ",").str();
58 else
59 Ret += (StringRef("-") + KV.key() + ",").str();
60 }
61 // remove trailing ','
62 Ret.pop_back();
63 return Ret;
64}
65
66static std::pair<FeatureBitset, std::string>
67coalesceFeatures(const Module &M, WebAssemblyTargetMachine *WasmTM) {
68 // Union the features of all defined functions. Start with an empty set, so
69 // that if a feature is disabled in every function, we'll compute it as
70 // disabled. If any function lacks a target-features attribute, it'll
71 // default to the target CPU from the `TargetMachine`.
72 FeatureBitset Features;
73 bool AnyDefined = false;
74 for (auto &F : M) {
75 if (F.isDeclaration())
76 continue;
77
78 Features |= WasmTM->getSubtargetImpl(F)->getFeatureBits();
79 AnyDefined = true;
80 }
81
82 // If we have no defined functions, use the module-wide feature bits from the
83 // `TargetMachine`.
84 if (!AnyDefined)
85 Features = WasmTM->getMCSubtargetInfo().getFeatureBits();
86
87 return {Features, getFeatureString(STI: WasmTM->getMCSubtargetInfo(), Features)};
88}
89
90static void replaceFeatures(Function &F, const std::string &Features) {
91 F.removeFnAttr(Kind: "target-features");
92 F.removeFnAttr(Kind: "target-cpu");
93 F.addFnAttr(Kind: "target-features", Val: Features);
94}
95
96static bool stripAtomics(Module &M) {
97 // Detect whether any atomics will be lowered, since there is no way to tell
98 // whether the LowerAtomic pass lowers e.g. stores.
99 bool Stripped = false;
100 for (auto &F : M) {
101 for (auto &B : F) {
102 for (auto &I : B) {
103 if (I.isAtomic()) {
104 Stripped = true;
105 goto done;
106 }
107 }
108 }
109 }
110
111done:
112 if (!Stripped)
113 return false;
114
115 LowerAtomicPass Lowerer;
116 FunctionAnalysisManager FAM;
117 for (auto &F : M)
118 Lowerer.run(F, FAM);
119
120 return true;
121}
122
123static bool stripThreadLocals(Module &M) {
124 bool Stripped = false;
125 for (auto &GV : M.globals()) {
126 if (GV.isThreadLocal()) {
127 // replace `@llvm.threadlocal.address.pX(GV)` with `GV`.
128 for (Use &U : make_early_inc_range(Range: GV.uses())) {
129 if (IntrinsicInst *II = dyn_cast<IntrinsicInst>(Val: U.getUser())) {
130 if (II->getIntrinsicID() == Intrinsic::threadlocal_address &&
131 II->getArgOperand(i: 0) == &GV) {
132 II->replaceAllUsesWith(V: &GV);
133 II->eraseFromParent();
134 }
135 }
136 }
137
138 Stripped = true;
139 GV.setThreadLocal(false);
140 }
141 }
142 return Stripped;
143}
144
145static void recordFeatures(Module &M, const WebAssemblySubtarget *ST,
146 const FeatureBitset &Features, bool Stripped) {
147 for (const SubtargetFeatureKV &KV : ST->getAllProcessorFeatures()) {
148 if (Features[KV.Value]) {
149 // Mark features as used
150 std::string MDKey = (StringRef("wasm-feature-") + KV.key()).str();
151 M.addModuleFlag(Behavior: Module::ModFlagBehavior::Error, Key: MDKey,
152 Val: wasm::WASM_FEATURE_PREFIX_USED);
153 }
154 }
155 // Code compiled without atomics or bulk-memory may have had its atomics or
156 // thread-local data lowered to nonatomic operations or non-thread-local
157 // data. In that case, we mark the pseudo-feature "shared-mem" as disallowed
158 // to tell the linker that it would be unsafe to allow this code to be used
159 // in a module with shared memory.
160 if (Stripped) {
161 M.addModuleFlag(Behavior: Module::ModFlagBehavior::Error, Key: "wasm-feature-shared-mem",
162 Val: wasm::WASM_FEATURE_PREFIX_DISALLOWED);
163 }
164}
165
166static bool coalesceFeaturesAndStripAtomics(Module &M,
167 WebAssemblyTargetMachine *WasmTM) {
168 auto [Features, FeatureStr] = coalesceFeatures(M, WasmTM);
169
170 WasmTM->setTargetFeatureString(FeatureStr);
171 for (auto &F : M)
172 replaceFeatures(F, Features: FeatureStr);
173
174 bool StrippedAtomics = false;
175 bool StrippedTLS = false;
176
177 // In cooperative threading mode, thread locals are meaningful even without
178 // atomics.
179 const WebAssemblySubtarget *ST = WasmTM->getSubtargetImpl(
180 CPU: WasmTM->getTargetCPU(), FS: WasmTM->getTargetFeatureString(),
181 ABIName: WasmTM->getTargetABIName(M));
182 bool CooperativeThreading = ST->hasCooperativeMultithreading();
183
184 if (!Features[WebAssembly::FeatureAtomics]) {
185 StrippedAtomics = stripAtomics(M);
186 if (!CooperativeThreading)
187 StrippedTLS = stripThreadLocals(M);
188 }
189 if (!Features[WebAssembly::FeatureBulkMemory] && !StrippedTLS) {
190 StrippedTLS = stripThreadLocals(M);
191 }
192
193 if (StrippedAtomics && !StrippedTLS && !CooperativeThreading)
194 stripThreadLocals(M);
195 else if (StrippedTLS && !StrippedAtomics)
196 stripAtomics(M);
197
198 recordFeatures(M, ST, Features, Stripped: StrippedAtomics || StrippedTLS);
199
200 // Conservatively assume we have made some change
201 return true;
202}
203
204bool WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy::runOnModule(Module &M) {
205 return coalesceFeaturesAndStripAtomics(M, WasmTM);
206}
207
208PreservedAnalyses WebAssemblyCoalesceFeaturesAndStripAtomicsPass::run(
209 Module &M, ModuleAnalysisManager &MAM) {
210 return coalesceFeaturesAndStripAtomics(M, WasmTM: &TM) ? PreservedAnalyses::none()
211 : PreservedAnalyses::all();
212}
213