• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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