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