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 bool CooperativeThreading = ST->hasCooperativeMultithreading();
182
183 if (!Features[WebAssembly::FeatureAtomics]) {
184 StrippedAtomics = stripAtomics(M);
185 if (!CooperativeThreading)
186 StrippedTLS = stripThreadLocals(M);
187 }
188 if (!Features[WebAssembly::FeatureBulkMemory] && !StrippedTLS) {
189 StrippedTLS = stripThreadLocals(M);
190 }
191
192 if (StrippedAtomics && !StrippedTLS && !CooperativeThreading)
193 stripThreadLocals(M);
194 else if (StrippedTLS && !StrippedAtomics)
195 stripAtomics(M);
196
197 bool Stripped = StrippedAtomics || StrippedTLS;
198 if (!Stripped &&
199 (Features[WebAssembly::FeatureAtomics] ||
200 (CooperativeThreading && Features[WebAssembly::FeatureBulkMemory])) &&
201 !M.getModuleFlag(Key: "thread-model")) {
202 M.setThreadModel(ThreadModel::POSIX);
203 }
204
205 recordFeatures(M, ST, Features, Stripped);
206
207 // Conservatively assume we have made some change
208 return true;
209}
210
211bool WebAssemblyCoalesceFeaturesAndStripAtomicsLegacy::runOnModule(Module &M) {
212 return coalesceFeaturesAndStripAtomics(M, WasmTM);
213}
214
215PreservedAnalyses WebAssemblyCoalesceFeaturesAndStripAtomicsPass::run(
216 Module &M, ModuleAnalysisManager &MAM) {
217 return coalesceFeaturesAndStripAtomics(M, WasmTM: &TM) ? PreservedAnalyses::none()
218 : PreservedAnalyses::all();
219}
220