1 /*
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "transforms/passes/gc_intrusion_check.h"
17
18 #include "llvm_ark_interface.h"
19 #include "llvm_compiler_options.h"
20 #include "transforms/gc_utils.h"
21
22 #include <llvm/ADT/PostOrderIterator.h>
23 #include <llvm/IR/Statepoint.h>
24 #include <llvm/Pass.h>
25 #include <llvm/Support/Error.h>
26
27 #define DEBUG_TYPE "gc-intrusion-check"
28
29 // Basic classes
30 using llvm::ArrayRef;
31 using llvm::BasicBlock;
32 using llvm::DenseSet;
33 using llvm::Function;
34 using llvm::FunctionAnalysisManager;
35 using llvm::Use;
36 using llvm::Value;
37 // Instructions
38 using llvm::Argument;
39 using llvm::CastInst;
40 using llvm::Constant;
41 using llvm::GCRelocateInst;
42 using llvm::GCStatepointInst;
43 using llvm::Instruction;
44 using llvm::IntToPtrInst;
45 using llvm::LoadInst;
46 using llvm::PHINode;
47 using llvm::ZExtInst;
48 // Gc utils
49 using ark::llvmbackend::gc_utils::HasBeenGcRef;
50 using ark::llvmbackend::gc_utils::IsAllowedEscapedUser;
51 using ark::llvmbackend::gc_utils::IsDerived;
52 using ark::llvmbackend::gc_utils::IsGcRefType;
53
54 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
55 static llvm::cl::opt<bool> g_checkAnyEscaped("gcic-any-escaped", llvm::cl::Hidden, llvm::cl::init(true));
56
57 /// No derived references must be presented in deopt and gc-live bundles.
58 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
59 static llvm::cl::opt<bool> g_checkDerived("gcic-no-derived", llvm::cl::Hidden, llvm::cl::init(true));
60
61 /// Unreachable does not follow the gc.relocate instruction.
62 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
63 static llvm::cl::opt<bool> g_checkUnreachableRelocates("gcic-no-unreachable-relocate", llvm::cl::Hidden,
64 llvm::cl::init(false));
65
66 /// Non-movable object is not relocated
67 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
68 static llvm::cl::opt<bool> g_checkNonMovableRelocates("gcic-movable-relocates-only", llvm::cl::Hidden,
69 llvm::cl::init(true));
70
71 namespace ark::llvmbackend::passes {
72
CheckStatepoint(const Function & function,const GCStatepointInst & statepoint)73 void GcIntrusionCheck::CheckStatepoint(const Function &function, const GCStatepointInst &statepoint)
74 {
75 if (!g_checkDerived) {
76 return;
77 }
78 constexpr std::array BUNDLES = {llvm::LLVMContext::OB_deopt, llvm::LLVMContext::OB_gc_live};
79 constexpr std::array NAMES = {"deopt", "gc-live"};
80 for (unsigned i = 0; i < BUNDLES.size(); ++i) {
81 auto entries = GetBundle(statepoint, BUNDLES[i]);
82 for (auto &entry : entries) {
83 if (!IsDerived(entry)) {
84 continue;
85 }
86 llvm::errs() << "Post GC Intrusion check failed in function " << function.getName() << "\n";
87 llvm::errs() << "Incorrect bundle in instruction: " << statepoint << "\n";
88 llvm::errs() << NAMES[i] << " bundle contains a derived pointer\n\t" << *entry << "\n";
89 llvm::report_fatal_error("Post GC Intrusion check failed for statepoint");
90 }
91 }
92 }
93
CheckRelocate(const Function & function,const GCRelocateInst & relocate)94 void GcIntrusionCheck::CheckRelocate(const Function &function, const GCRelocateInst &relocate)
95 {
96 auto baseCst = llvm::dyn_cast<Constant>(relocate.getBasePtr());
97 auto derivedCst = llvm::dyn_cast<Constant>(relocate.getDerivedPtr());
98 if (baseCst != nullptr && baseCst->isNullValue()) {
99 llvm::errs() << "Post GC Intrusion check failed in function " << function.getName() << "\n";
100 llvm::errs() << "relocate instruction " << relocate << " has been generated for a null base reference\n";
101 llvm::report_fatal_error("Post GC Intrusion check failed for relocate");
102 }
103 if (derivedCst != nullptr && derivedCst->isNullValue()) {
104 llvm::errs() << "Post GC Intrusion check failed in function " << function.getName() << "\n";
105 llvm::errs() << "relocate instruction " << relocate << " has been generated for a null derived reference\n";
106 llvm::report_fatal_error("Post GC Intrusion check failed for relocate");
107 }
108 if (g_checkUnreachableRelocates && llvm::isa_and_nonnull<llvm::UnreachableInst>(relocate.getNextNode())) {
109 llvm::errs() << "Post GC Intrusion check failed in function " << function.getName() << "\n";
110 llvm::errs() << "relocate instruction " << relocate << " is followed by unreachable instruction\n";
111 llvm::report_fatal_error("Post GC Intrusion check failed for relocate");
112 }
113 if (g_checkNonMovableRelocates) {
114 if (baseCst != nullptr && gc_utils::IsNonMovable(baseCst)) {
115 llvm::errs() << "Post GC Intrusion check failed in function " << function.getName() << "\n";
116 llvm::errs() << "relocate instruction " << relocate << " relocates non-movable value " << *baseCst << "\n";
117 llvm::report_fatal_error("Post GC Intrusion check failed for relocate");
118 }
119 if (derivedCst != nullptr && gc_utils::IsNonMovable(derivedCst)) {
120 llvm::errs() << "Post GC Intrusion check failed in function " << function.getName() << "\n";
121 llvm::errs() << "relocate instruction " << relocate << " relocates non-movable value " << *derivedCst
122 << "\n";
123 llvm::report_fatal_error("Post GC Intrusion check failed for relocate");
124 }
125 }
126 }
127
CheckInstruction(const Function & function,const Instruction & inst)128 void GcIntrusionCheck::CheckInstruction(const Function &function, const Instruction &inst)
129 {
130 for (unsigned int i = 0; i < inst.getNumOperands(); i++) {
131 auto val = inst.getOperand(i);
132 // Support only instructions and arguments
133 if (!llvm::isa<Instruction>(val) && !llvm::isa<Argument>(val)) {
134 continue;
135 }
136
137 // Track only GC refs
138 if ((!HasBeenGcRef(val, g_checkAnyEscaped) && !IsHiddenGcRef(val)) || gc_utils::IsNonMovable(val)) {
139 continue;
140 }
141
142 // Strip any casts
143 for (auto cast = llvm::dyn_cast<CastInst>(val); cast != nullptr; cast = llvm::dyn_cast<CastInst>(val)) {
144 val = cast->getOperand(0);
145 }
146
147 // If it's a function argument, just let FindDefOrStatepoint exhaust all BBs
148 auto instVal = llvm::isa<Argument>(val) ? nullptr : llvm::cast<Instruction>(val);
149 const Instruction *start = &inst;
150 if (llvm::isa<PHINode>(start)) {
151 // For PHIs, we want not just any path, but a path that specifically goes
152 // from the associated BB. Easiest way is to check if we have
153 // a no-statepoint way to the incoming block terminator
154 auto phi = llvm::cast<PHINode>(start);
155 auto incBlock = phi->getIncomingBlock(i);
156 start = incBlock->getTerminator();
157 }
158
159 auto statepoint = FindDefOrStatepoint(start, instVal);
160 if (statepoint != nullptr) {
161 llvm::errs() << "Post GC Intrusion check failed in function " << function.getName() << "\n";
162 if (llvm::isa<Argument>(val)) {
163 llvm::errs() << "Function argument: ";
164 } else if (IsHiddenGcRef(val)) {
165 llvm::errs() << "Hidden definition: ";
166 } else {
167 llvm::errs() << "Definition: ";
168 }
169 llvm::errs() << *val << "\n";
170 llvm::errs() << "Statepoint in between: " << *statepoint << "\n";
171 llvm::errs() << "User: " << inst << "\n";
172 for (auto cast = llvm::dyn_cast<CastInst>(inst.getOperand(i)); cast != nullptr;
173 cast = llvm::dyn_cast<CastInst>(cast->getOperand(0))) {
174 llvm::errs() << " Casted from: " << *cast << "\n";
175 }
176 llvm::report_fatal_error("Post GC Intrusion check failed for value input");
177 }
178 }
179 }
180
FindDefOrStatepoint(const Instruction * start,const Instruction * def)181 const Instruction *GcIntrusionCheck::FindDefOrStatepoint(const Instruction *start, const Instruction *def)
182 {
183 DenseSet<const BasicBlock *> blockVisited;
184 return FindDefOrStatepointRecursive(start, def, &blockVisited);
185 }
186
FindDefOrStatepointRecursive(const Instruction * start,const Instruction * def,DenseSet<const BasicBlock * > * visited)187 const Instruction *GcIntrusionCheck::FindDefOrStatepointRecursive(const Instruction *start, const Instruction *def,
188 DenseSet<const BasicBlock *> *visited)
189 {
190 // The idea here is as follows: for invariant to be true we need to know that no paths between
191 // definition and user contain statepoint instructions
192 auto block = start->getParent();
193 start = llvm::isa<GCStatepointInst>(start) ? start->getPrevNode() : start;
194 for (auto inst = start; inst != nullptr; inst = inst->getPrevNode()) {
195 if (inst == def) {
196 return nullptr;
197 }
198 if (llvm::isa<GCStatepointInst>(inst)) {
199 return inst;
200 }
201 }
202 // We've reached the end of BB and haven't found anything. Look in both successors
203 for (auto pred : llvm::predecessors(block)) {
204 if (visited->find(pred) == visited->end()) {
205 visited->insert(pred);
206 auto result = FindDefOrStatepointRecursive(pred->getTerminator(), def, visited);
207 if (result != nullptr) {
208 return result;
209 }
210 }
211 }
212 return nullptr;
213 }
214
215 /// Return true if a loaded value is casted to addrspace (271).
IsHiddenGcRef(Value * ref)216 bool GcIntrusionCheck::IsHiddenGcRef(Value *ref)
217 {
218 // Checking for escaped i32 pointer
219 constexpr auto POINTER_WIDTH_BITS = 32;
220 if (!llvm::isa<LoadInst>(ref) || !ref->getType()->isIntegerTy(POINTER_WIDTH_BITS)) {
221 return false;
222 }
223 for (auto user : ref->users()) {
224 auto zext = llvm::dyn_cast<ZExtInst>(user);
225 if (zext == nullptr) {
226 continue;
227 }
228 for (auto zextUser : zext->users()) {
229 if (llvm::isa<IntToPtrInst>(zextUser) && IsGcRefType(zextUser->getType())) {
230 return true;
231 }
232 }
233 }
234 return false;
235 }
236
GetBundle(const GCStatepointInst & call,uint32_t bundleId)237 ArrayRef<Use> GcIntrusionCheck::GetBundle(const GCStatepointInst &call, uint32_t bundleId)
238 {
239 const auto &bundle = call.getOperandBundle(bundleId);
240 if (!bundle) {
241 return llvm::None;
242 }
243 return bundle->Inputs;
244 }
245
246 } // namespace ark::llvmbackend::passes
247
248 namespace ark::llvmbackend::passes {
249
ShouldInsert(const ark::llvmbackend::LLVMCompilerOptions * options)250 bool GcIntrusionCheck::ShouldInsert(const ark::llvmbackend::LLVMCompilerOptions *options)
251 {
252 return options->gcIntrusionChecks;
253 }
254
run(Function & function,FunctionAnalysisManager &)255 llvm::PreservedAnalyses GcIntrusionCheck::run(Function &function, FunctionAnalysisManager & /*AM*/)
256 {
257 llvm::ReversePostOrderTraversal<Function *> rpo(&function);
258 for (auto block : rpo) {
259 for (auto &inst : *block) {
260 if (llvm::isa<GCStatepointInst>(&inst)) {
261 CheckStatepoint(function, llvm::cast<GCStatepointInst>(inst));
262 CheckInstruction(function, inst);
263 } else if (llvm::isa<GCRelocateInst>(&inst)) {
264 CheckRelocate(function, llvm::cast<GCRelocateInst>(inst));
265 } else if (!IsAllowedEscapedUser(&inst)) {
266 CheckInstruction(function, inst);
267 }
268 }
269 }
270
271 return llvm::PreservedAnalyses::all();
272 }
273
274 } // namespace ark::llvmbackend::passes
275