• 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 <algorithm>
17 #include <sstream>
18 
19 #include "transforms/passes/gc_intrusion.h"
20 
21 #include "transforms/gc_utils.h"
22 #include "transforms/transform_utils.h"
23 
24 #include <llvm/ADT/DenseMap.h>
25 #include <llvm/ADT/DenseSet.h>
26 #include <llvm/ADT/SetVector.h>
27 #include <llvm/ADT/PostOrderIterator.h>
28 #include <llvm/ADT/SmallVector.h>
29 #include <llvm/IR/CFG.h>
30 #include <llvm/IR/InlineAsm.h>
31 #include <llvm/IR/IRBuilder.h>
32 #include <llvm/IR/MDBuilder.h>
33 #include <llvm/IR/Statepoint.h>
34 #include <llvm/IR/Verifier.h>
35 #include <llvm/Pass.h>
36 #include <llvm/Transforms/Utils/BasicBlockUtils.h>
37 
38 #define DEBUG_TYPE "gc-intrusion"
39 
40 using llvm::ArrayRef;
41 using llvm::BasicBlock;
42 using llvm::DenseMap;
43 using llvm::DenseSet;
44 using llvm::Function;
45 using llvm::FunctionAnalysisManager;
46 using llvm::Optional;
47 using llvm::SetVector;
48 using llvm::SmallVector;
49 using llvm::Use;
50 using llvm::Value;
51 
52 using llvm::Argument;
53 using llvm::CallInst;
54 using llvm::CastInst;
55 using llvm::Instruction;
56 using llvm::PHINode;
57 
58 using ark::llvmbackend::gc_utils::IsDerived;
59 using ark::llvmbackend::gc_utils::IsGcRefType;
60 
61 // NOLINTNEXTLINE(fuchsia-statically-constructed-objects)
62 static llvm::cl::opt<bool> g_moveCmps("gc-intrusion-move-cmps", llvm::cl::Hidden, llvm::cl::init(true),
63                                       llvm::cl::desc("Move comparisons closer to their usages"));
64 
65 namespace ark::llvmbackend::passes {
66 
67 class GcRefLiveness {
68     using BlockValuesMap = DenseMap<BasicBlock *, SetVector<Value *>>;
69 
70 public:
GcRefLiveness(Function * function)71     explicit GcRefLiveness(Function *function)
72     {
73         LLVM_DEBUG(llvm::dbgs() << "Running GC references liveness analysis for: " << function->getName() << "\n");
74         ComputeLiveSets(function);
75         LLVM_DEBUG(Dump());
76     }
77 
GetLiveInsForBlock(BasicBlock * block) const78     const SetVector<Value *> &GetLiveInsForBlock(BasicBlock *block) const
79     {
80         ASSERT(liveIns_.find(block) != liveIns_.end());
81         return liveIns_.find(block)->second;
82     }
83 
GetLiveOutsForBlock(BasicBlock * block) const84     const SetVector<Value *> &GetLiveOutsForBlock(BasicBlock *block) const
85     {
86         ASSERT(liveOuts_.find(block) != liveOuts_.end());
87         return liveOuts_.find(block)->second;
88     }
89 
90     void ReplaceValue(Value *from, Value *to);
91 
92 private:
93     void PopulateLiveInByUsers(BasicBlock &block);
94 
95     void PopulateLiveOutByPhis(BasicBlock &block);
96 
97     bool PropagateLiveInsLiveOuts(BasicBlock *block);
98 
99     void ComputeLiveSets(Function *function);
100 
101     void Dump() const;
102 
103     void DumpBlockValuesMap(const BlockValuesMap &map, const char *header = nullptr) const;
104 
105     BlockValuesMap liveIns_;
106     BlockValuesMap liveOuts_;
107 };
108 
DumpBlockValuesMap(const BlockValuesMap & map,const char * header) const109 void GcRefLiveness::DumpBlockValuesMap(const BlockValuesMap &map, [[maybe_unused]] const char *header) const
110 {
111     LLVM_DEBUG(llvm::dbgs() << header << "\n");
112     for (const auto &elt : map) {
113         LLVM_DEBUG(llvm::dbgs() << "\t");
114         LLVM_DEBUG(elt.first->printAsOperand(llvm::dbgs(), false));
115         LLVM_DEBUG(llvm::dbgs() << ":");
116         for ([[maybe_unused]] const auto &var : elt.second) {
117             LLVM_DEBUG(llvm::dbgs() << " "; var->printAsOperand(llvm::dbgs(), false));
118         }
119         LLVM_DEBUG(llvm::dbgs() << "\n");
120     }
121 }
122 
123 #ifndef NDEBUG
Dump() const124 void GcRefLiveness::Dump() const
125 {
126     DumpBlockValuesMap(liveIns_, "LiveIns:");
127     DumpBlockValuesMap(liveOuts_, "LiveOuts:");
128 }
129 #endif
130 
ReplaceValue(Value * from,Value * to)131 void GcRefLiveness::ReplaceValue(Value *from, Value *to)
132 {
133     for (auto &entry : liveIns_) {
134         if (entry.second.remove(from)) {
135             entry.second.insert(to);
136         }
137     }
138     for (auto &entry : liveOuts_) {
139         if (entry.second.remove(from)) {
140             entry.second.insert(to);
141         }
142     }
143 }
144 
PopulateLiveInByUsers(BasicBlock & block)145 void GcRefLiveness::PopulateLiveInByUsers(BasicBlock &block)
146 {
147     for (auto &inst : block) {
148         if (!IsGcRefType(inst.getType()) || IsDerived(&inst) || gc_utils::IsNonMovable(&inst)) {
149             continue;
150         }
151         for (auto user : inst.users()) {
152             auto userBlock = llvm::cast<Instruction>(user)->getParent();
153             // Definitions and phi inputs are not live-ins
154             if (userBlock == &block || llvm::isa<PHINode>(user)) {
155                 continue;
156             }
157             liveIns_[userBlock].insert(&inst);
158         }
159     }
160 }
161 
PopulateLiveOutByPhis(BasicBlock & block)162 void GcRefLiveness::PopulateLiveOutByPhis(BasicBlock &block)
163 {
164     for (auto &phi : block.phis()) {
165         if (!IsGcRefType(phi.getType())) {
166             continue;
167         }
168         ASSERT(!IsDerived(&phi));
169         for (auto &incoming : phi.incoming_values()) {
170             // GcRegType has been already checked
171             if (llvm::isa<Argument>(incoming) || llvm::isa<Instruction>(incoming)) {
172                 liveOuts_[phi.getIncomingBlock(incoming)].insert(llvm::cast<Value>(&incoming));
173             }
174         }
175     }
176 }
177 
PropagateLiveInsLiveOuts(BasicBlock * block)178 bool GcRefLiveness::PropagateLiveInsLiveOuts(BasicBlock *block)
179 {
180     // Fill live-outs based on live-ins of successors
181     auto &blockOuts = liveOuts_[block];
182     for (auto succ : llvm::successors(block)) {
183         for (auto &succIn : liveIns_[succ]) {
184             blockOuts.insert(succIn);
185         }
186     }
187 
188     // Fill live-ins based on live-outs that is not defined in block
189     auto &blockIns = liveIns_[block];
190     auto updated = false;
191     for (auto out : blockOuts) {
192         auto inst = llvm::dyn_cast<Instruction>(out);
193         // If defined in block, it is not a live-in
194         if (inst != nullptr) {
195             if (inst->getParent() == block || gc_utils::IsNonMovable(inst)) {
196                 continue;
197             }
198         }
199         updated |= blockIns.insert(out);
200     }
201     return updated;
202 }
ComputeLiveSets(Function * function)203 void GcRefLiveness::ComputeLiveSets(Function *function)
204 {
205     SetVector<BasicBlock *> worklist;
206     // Push live-ins of arguments using its uses.
207     for (auto &arg : function->args()) {
208         // Arguments are considered as always non-derived.
209         if (IsGcRefType(arg.getType())) {
210             for (auto user : arg.users()) {
211                 liveIns_[llvm::cast<Instruction>(user)->getParent()].insert(&arg);
212             }
213         }
214     }
215     // Push live-ins of definitions using its uses.
216     for (auto &block : *function) {
217         worklist.insert(&block);
218         PopulateLiveInByUsers(block);
219         PopulateLiveOutByPhis(block);
220     }
221 
222     DumpBlockValuesMap(liveIns_, "LiveIns init:");
223 
224     // Iteratively propagate live-ins and live-outs.
225     while (!worklist.empty()) {
226         auto block = worklist.pop_back_val();
227         auto updated = PropagateLiveInsLiveOuts(block);
228         // If live-ins have been updated, we reconsider all predecessors
229         if (updated) {
230             worklist.insert(pred_begin(block), pred_end(block));
231         }
232     }
233 }
234 
run(Function & function,FunctionAnalysisManager &)235 llvm::PreservedAnalyses GcIntrusion::run(Function &function, FunctionAnalysisManager & /*AM*/)
236 {
237     LLVM_DEBUG(llvm::dbgs() << "Function: " << function.getName() << "\n" << function << "\n");
238 
239     if (!gc_utils::IsFunctionSupplemental(function)) {
240         function.setCallingConv(llvm::CallingConv::ArkMethod);
241     }
242 
243     if (!gc_utils::IsGcFunction(function) || gc_utils::IsFunctionSupplemental(function)) {
244         return llvm::PreservedAnalyses::all();
245     }
246 
247     if (g_moveCmps) {
248         MoveComparisons(&function);
249     }
250     HoistForRelocation(&function);
251 
252     // Set up auxiliary structures
253     GcRefLiveness liveness(&function);
254     DenseSet<BasicBlock *> visited;
255     llvm::ReversePostOrderTraversal<Function *> rpo(&function);
256     GcIntrusionContext gcContext;
257     uint32_t rpoCounter = 0;
258     uint64_t orderCounter = 0;
259     for (auto block : rpo) {
260         gcContext.rpoMap[block] = rpoCounter++;
261         for (auto &inst : *block) {
262             // Leave space before calls for possible new instructions
263             if (llvm::isa<CallInst>(inst)) {
264                 orderCounter++;
265             }
266             gcContext.orderMap[&inst] = orderCounter++;
267         }
268     }
269     SetVector<Value *> alive;
270     // Iterate through instructions and replace each instruction where GC can happen with statepoint
271     for (auto block : rpo) {
272         RewriteWithGcInBlock(block, &liveness, &alive, &visited, &gcContext);
273     }
274 
275     // Update phis because we had no information on backedges
276     for (auto &block : function) {
277         UpdatePhiInputs(&block, &gcContext.relocs);
278     }
279     return llvm::PreservedAnalyses::none();
280 }
281 
RewriteWithGcInBlock(BasicBlock * block,GcRefLiveness * liveness,SetVector<Value * > * alive,DenseSet<BasicBlock * > * visited,GcIntrusionContext * gcContext)282 void GcIntrusion::RewriteWithGcInBlock(BasicBlock *block, GcRefLiveness *liveness, SetVector<Value *> *alive,
283                                        DenseSet<BasicBlock *> *visited, GcIntrusionContext *gcContext)
284 {
285     auto &liveOuts = liveness->GetLiveOutsForBlock(block);
286 
287     alive->insert(liveOuts.begin(), liveOuts.end());
288     auto inst = &(*block->rbegin());
289     while (inst != nullptr && !llvm::isa<PHINode>(inst)) {
290         alive->remove(inst);
291         for (auto &ops : inst->operands()) {
292             if (IsGcRefType(ops->getType()) && (llvm::isa<Argument>(ops) || llvm::isa<Instruction>(ops)) &&
293                 !IsDerived(ops) && !gc_utils::IsNonMovable(ops)) {
294                 alive->insert(ops);
295             }
296         }
297 
298         auto call = llvm::dyn_cast<CallInst>(inst);
299         inst = inst->getPrevNode();
300         if (call != nullptr && !call->isInlineAsm() && call->getIntrinsicID() == llvm::Intrinsic::not_intrinsic &&
301             (call->getDebugLoc() || call->hasFnAttr("safepoint"))) {
302             RewriteWithGc(call, liveness, alive, gcContext);
303         }
304     }
305     PropagateRelocs(liveness, visited, block, gcContext);
306     visited->insert(block);
307     alive->clear();
308 }
309 
310 /*static*/
ComesBefore(Value * a,Value * b,InstructionOrderMap * orderMap)311 bool GcIntrusion::ComesBefore(Value *a, Value *b, InstructionOrderMap *orderMap)
312 {
313     return orderMap->lookup(a) <= orderMap->lookup(b);
314 }
315 
GetStatepointId(const Instruction & inst)316 uint32_t GcIntrusion::GetStatepointId(const Instruction &inst)
317 {
318     if (inst.getDebugLoc()) {
319         return inst.getDebugLoc().getLine();
320     }
321     auto call = llvm::dyn_cast<llvm::CallBase>(&inst);
322     if (call != nullptr && call->hasFnAttr("safepoint")) {
323         return 0;
324     }
325     llvm_unreachable("Unknown statepoint-id metadata");
326 }
327 
328 /// Copy relocation info about BLOCK live-ins for BLOCK from its unique predecessor
CopySinglePredRelocs(GcRefLiveness * liveness,BasicBlock * block,GcIntrusionContext * gcContext)329 void GcIntrusion::CopySinglePredRelocs(GcRefLiveness *liveness, BasicBlock *block, GcIntrusionContext *gcContext)
330 {
331     LLVM_DEBUG(llvm::dbgs() << "Propagate single predecessor liveins for ");
332     LLVM_DEBUG(block->printAsOperand(llvm::dbgs(), false));
333     LLVM_DEBUG(llvm::dbgs() << "\n");
334     for (auto var : liveness->GetLiveInsForBlock(block)) {
335         auto pred = block->getUniquePredecessor();
336         auto varIter = gcContext->relocs.find(var);
337         // No relocations have been met
338         if (varIter == gcContext->relocs.end()) {
339             continue;
340         }
341         auto &varRelocs = varIter->second;
342         if (varRelocs.find(pred) != varRelocs.end()) {
343             LLVM_DEBUG(llvm::dbgs() << "\t");
344             LLVM_DEBUG(var->printAsOperand(llvm::dbgs(), false));
345             LLVM_DEBUG(llvm::dbgs() << " :");
346 
347             auto alt = varRelocs[pred];
348             varRelocs.insert({block, alt});
349             ReplaceDominatedUses(var, alt, block, gcContext);
350 
351             LLVM_DEBUG(llvm::dbgs() << " ");
352             LLVM_DEBUG(alt->printAsOperand(llvm::dbgs(), false));
353             LLVM_DEBUG(llvm::dbgs() << " \n");
354         }
355     }
356 }
357 
ReplaceWithPhi(Value * var,BasicBlock * block,GcIntrusionContext * gcContext)358 void GcIntrusion::ReplaceWithPhi(Value *var, BasicBlock *block, GcIntrusionContext *gcContext)
359 {
360     ASSERT(var != nullptr);
361     auto &varBlocks = gcContext->relocs.FindAndConstruct(var).second;
362 
363     PHINode *phi = PHINode::Create(var->getType(), pred_size(block), "", &(*block->begin()));
364     if (var->hasName()) {
365         phi->setName(var->getName() + ".rel.phi");
366     }
367 
368     for (auto pred : predecessors(block)) {
369         if (varBlocks.find(pred) != varBlocks.end()) {
370             phi->addIncoming(varBlocks[pred], pred);
371         } else {
372             phi->addIncoming(var, pred);
373         }
374     }
375     LLVM_DEBUG(llvm::dbgs() << *phi << " \n");
376     varBlocks.insert({block, phi});
377     ReplaceDominatedUses(var, phi, block, gcContext);
378 }
379 
PropagateRelocs(GcRefLiveness * liveness,DenseSet<BasicBlock * > * visited,BasicBlock * block,GcIntrusionContext * gcContext)380 void GcIntrusion::PropagateRelocs(GcRefLiveness *liveness, DenseSet<BasicBlock *> *visited, BasicBlock *block,
381                                   GcIntrusionContext *gcContext)
382 {
383     if (pred_empty(block)) {
384         return;
385     }
386     if (pred_size(block) == 1) {
387         CopySinglePredRelocs(liveness, block, gcContext);
388         return;
389     }
390 
391     LLVM_DEBUG(llvm::dbgs() << "Update liveins in ");
392     LLVM_DEBUG(block->printAsOperand(llvm::dbgs(), false));
393     LLVM_DEBUG(llvm::dbgs() << "\n");
394     for (auto var : liveness->GetLiveInsForBlock(block)) {
395         bool backEdge = false;
396         bool valid = true;
397         for (auto pred : predecessors(block)) {
398             backEdge |= visited->find(pred) == visited->end();
399             // Variable is not a live-out of one of predecessors. No need to merge.
400             valid &= liveness->GetLiveOutsForBlock(pred).contains(var);
401         }
402         if (!valid) {
403             continue;
404         }
405 
406         Value *unique = nullptr;
407         LLVM_DEBUG(llvm::dbgs() << "\t");
408         LLVM_DEBUG(var->printAsOperand(llvm::dbgs(), false));
409         LLVM_DEBUG(llvm::dbgs() << " : ");
410 
411         if (!backEdge && (unique = GetUniqueLiveOut(&gcContext->relocs, block, var)) != nullptr) {
412             auto &varBlocks = gcContext->relocs.FindAndConstruct(var).second;
413             // It is not a backedge and all predecessors has the same live-out.  Just update uses in
414             // the BLOCK.
415             LLVM_DEBUG(unique->printAsOperand(llvm::dbgs(), false));
416             LLVM_DEBUG(llvm::dbgs() << "\n");
417             varBlocks.insert({block, unique});
418             ReplaceDominatedUses(var, unique, block, gcContext);
419         } else {
420             // Otherwise we need to create a phi instruction
421             ReplaceWithPhi(var, block, gcContext);
422         }
423     }
424 }
425 
GetUniqueLiveOut(SSAVarRelocs * relocs,BasicBlock * block,Value * var) const426 Value *GcIntrusion::GetUniqueLiveOut(SSAVarRelocs *relocs, BasicBlock *block, Value *var) const
427 {
428     auto varEnt = relocs->find(var);
429     // No info about relocated values has been collected. Var is unique by itself
430     if (varEnt == relocs->end()) {
431         return var;
432     }
433     auto &varBlocks = varEnt->second;
434     Value *unique = nullptr;
435     for (auto pred : predecessors(block)) {
436         if (varBlocks.find(pred) != varBlocks.end()) {
437             if (unique == nullptr) {
438                 unique = varBlocks[pred];
439             } else if (unique != varBlocks[pred]) {
440                 return nullptr;
441             }
442         } else {
443             if (unique == nullptr) {
444                 unique = var;
445             } else if (unique != var) {
446                 return nullptr;
447             }
448         }
449     }
450     return unique;
451 }
452 
ReplaceWithRelocated(CallInst * call,CallInst * gcCall,Value * inst,Value * relocated,GcIntrusionContext * gcContext)453 void GcIntrusion::ReplaceWithRelocated(CallInst *call, CallInst *gcCall, Value *inst, Value *relocated,
454                                        GcIntrusionContext *gcContext)
455 {
456     if (inst->hasName()) {
457         relocated->setName(inst->getName() + ".relocated");
458     }
459     gcContext->relocs.FindAndConstruct(inst).second.insert({call->getParent(), relocated});
460     // Since relocates don't use each other, it doesn't matter if they all have the same order
461     gcContext->orderMap.FindAndConstruct(relocated).second = gcContext->orderMap.lookup(call);
462     bool needInsert = gcContext->sortedUses.count(inst) != 0;
463     ReplaceDominatedUses(inst, relocated, call->getParent(), gcContext);
464     if (needInsert) {
465         for (auto useIter = inst->use_begin(), useEnd = inst->use_end(); useIter != useEnd;) {
466             auto &gcUse = *useIter++;
467             if (gcUse.getUser() != gcCall) {
468                 break;
469             }
470             gcContext->sortedUses.FindAndConstruct(inst).second.push_back(&gcUse);
471         }
472     }
473 }
474 
RewriteWithGc(CallInst * call,GcRefLiveness * liveness,SetVector<Value * > * refs,GcIntrusionContext * gcContext)475 void GcIntrusion::RewriteWithGc(CallInst *call, GcRefLiveness *liveness, SetVector<Value *> *refs,
476                                 GcIntrusionContext *gcContext)
477 {
478     llvm::IRBuilder<> builder(call);
479 
480     std::vector<Value *> callArgs(call->arg_begin(), call->arg_end());
481     std::vector<Value *> gced(refs->begin(), refs->end());
482     const auto &bundle = call->getOperandBundle(llvm::LLVMContext::OB_deopt);
483     CallInst *gcCall = nullptr;
484     if (bundle && !bundle->Inputs.empty()) {
485         ASSERT(!call->hasFnAttr("inline-info"));
486         gcCall = builder.CreateGCStatepointCall(GetStatepointId(*call), 0,
487                                                 llvm::FunctionCallee(call->getFunctionType(), call->getCalledOperand()),
488                                                 0, callArgs, llvm::None, bundle->Inputs, gced);
489     } else {
490         auto inlineInfo = GetDeoptsFromInlineInfo(builder, call);
491         Optional<ArrayRef<Value *>> deoptArgs = inlineInfo.empty() ? llvm::None : llvm::makeArrayRef(inlineInfo);
492         gcCall = builder.CreateGCStatepointCall(GetStatepointId(*call), 0,
493                                                 llvm::FunctionCallee(call->getFunctionType(), call->getCalledOperand()),
494                                                 callArgs, deoptArgs, gced);
495     }
496     gcCall->setCallingConv(call->getCallingConv());
497     gcContext->orderMap.FindAndConstruct(gcCall).second = gcContext->orderMap.lookup(call) - 1;
498 
499     for (size_t i = 0; i < callArgs.size(); i++) {
500         if (call->getParamAttr(i, llvm::Attribute::SExt).isValid()) {
501             gcCall->addParamAttr(llvm::GCStatepointInst::CallArgsBeginPos + i, llvm::Attribute::SExt);
502         }
503         if (call->getParamAttr(i, llvm::Attribute::ZExt).isValid()) {
504             gcCall->addParamAttr(llvm::GCStatepointInst::CallArgsBeginPos + i, llvm::Attribute::ZExt);
505         }
506     }
507 
508     if (call->hasFnAttr("use-ark-spills")) {
509         gcCall->addFnAttr(call->getFnAttr("use-ark-spills"));
510     }
511 
512     for (size_t i = 0; i < gced.size(); ++i) {
513         auto inst = gced[i];
514         auto relocated = builder.CreateGCRelocate(gcCall, i, i, inst->getType());
515         ReplaceWithRelocated(call, gcCall, inst, relocated, gcContext);
516     }
517 
518     if (!call->getType()->isVoidTy()) {
519         auto ret = builder.CreateGCResult(gcCall, call->getType());
520         liveness->ReplaceValue(call, ret);
521         BasicBlock::iterator ii(call);
522         if (gcContext->relocs.find(call) != gcContext->relocs.end()) {
523             gcContext->relocs.insert({ret, gcContext->relocs.lookup(call)});
524             gcContext->relocs.erase(call);
525         }
526         gcContext->sortedUses.erase(call);
527         ReplaceInstWithValue(call->getParent()->getInstList(), ii, ret);
528     } else {
529         call->eraseFromParent();
530     }
531 }
532 
533 /// Create deopts from `inline-info` attribute
GetDeoptsFromInlineInfo(llvm::IRBuilder<> & builder,CallInst * call)534 std::vector<Value *> GcIntrusion::GetDeoptsFromInlineInfo(llvm::IRBuilder<> &builder, CallInst *call)
535 {
536     if (!call->hasFnAttr("inline-info")) {
537         return {};
538     }
539 
540     std::vector<Value *> inlineInfo;
541     auto attr = call->getFnAttr("inline-info");
542     ASSERT(attr.isStringAttribute());
543     ASSERT(!attr.getValueAsString().empty());
544     std::stringstream stream(attr.getValueAsString().str());
545 
546     constexpr auto RECORD_SIZE = 4U;
547     constexpr auto POISON_LINE_NUMBER = 0;
548 
549     auto debugLoc = call->getDebugLoc().get();
550     /* If llvm can't merge debug location properly, then it set column = 0 */
551     if (debugLoc != nullptr && debugLoc->getColumn() == 0) {
552         debugLoc = nullptr;
553     }
554 
555     std::vector<size_t> savedBpc;
556     for (size_t id = 0; stream >> id;) {
557         auto bpc = debugLoc != nullptr ? debugLoc->getLine() : POISON_LINE_NUMBER;
558         savedBpc.push_back(bpc);
559         inlineInfo.push_back(builder.getInt32(id)); /* Method ID */
560         inlineInfo.push_back(nullptr);              /* BPC */
561         inlineInfo.push_back(builder.getInt32(0));  /* need_regmap */
562         inlineInfo.push_back(builder.getInt32(0));  /* vregs_total */
563         debugLoc = debugLoc != nullptr ? debugLoc->getInlinedAt() : debugLoc;
564         if (stream.peek() == ',') {
565             stream.ignore();
566         }
567     }
568     auto depth = savedBpc.size();
569 
570     /* Sequence of bpc is inverted, we need reverse it */
571     for (size_t i = 0; i < inlineInfo.size(); i += RECORD_SIZE) {
572         auto bpc = savedBpc.back();
573         inlineInfo[i + 1U] = builder.getInt32(bpc);
574         savedBpc.pop_back();
575     }
576 
577     inlineInfo.resize(inlineInfo.size() + depth + 1);
578     std::rotate(inlineInfo.rbegin(), inlineInfo.rbegin() + depth + 1, inlineInfo.rend());
579     inlineInfo.front() = builder.getInt32(depth);
580     for (size_t i = 0; i < depth; ++i) {
581         inlineInfo[i + 1] = builder.getInt32(1 + depth + i * RECORD_SIZE);
582     }
583 
584     return inlineInfo;
585 }
586 
CreateSortedUseList(BasicBlock * block,Value * from,GcIntrusionContext * gcContext)587 void GcIntrusion::CreateSortedUseList(BasicBlock *block, Value *from, GcIntrusionContext *gcContext)
588 {
589     auto comp = [gcContext](const Use *u1, const Use *u2) -> bool {
590         auto *uinst1 = llvm::cast<Instruction>(u1->getUser());
591         auto *uinst2 = llvm::cast<Instruction>(u2->getUser());
592         if (uinst1->getParent() == uinst2->getParent()) {
593             return ComesBefore(uinst1, uinst2, &(gcContext->orderMap));
594         }
595         return gcContext->rpoMap.lookup(uinst1->getParent()) > gcContext->rpoMap.lookup(uinst2->getParent());
596     };
597     auto &useList = gcContext->sortedUses.FindAndConstruct(from).second;
598     for (auto &use : from->uses()) {
599         if (gcContext->rpoMap.lookup(llvm::cast<Instruction>(use.getUser())->getParent()) >=
600             gcContext->rpoMap.lookup(block)) {
601             auto it = std::upper_bound(useList.begin(), useList.end(), &use, comp);
602             useList.insert(it, &use);
603         }
604     }
605 }
606 
607 /// Replace uses of FROM to TO in scope of BLOCK.
ReplaceDominatedUses(Value * from,Value * to,BasicBlock * block,GcIntrusionContext * gcContext)608 void GcIntrusion::ReplaceDominatedUses(Value *from, Value *to, BasicBlock *block, GcIntrusionContext *gcContext)
609 {
610     if (gcContext->sortedUses.count(from) == 0) {
611         CreateSortedUseList(block, from, gcContext);
612     }
613 
614     auto &sortedList = gcContext->sortedUses.FindAndConstruct(from).second;
615     // needDominance = false assumes that FROM definitely dominates TO
616     bool needDominance = llvm::isa<Instruction>(to) && llvm::cast<Instruction>(to)->getParent() == block;
617     while (!sortedList.empty()) {
618         auto *use = *sortedList.rbegin();
619         auto *uinst = llvm::cast<Instruction>(use->getUser());
620         // Drop any uses left from previous blocks
621         if (gcContext->rpoMap.lookup(uinst->getParent()) < gcContext->rpoMap.lookup(block)) {
622             sortedList.pop_back();
623             continue;
624         }
625         // If no more uses in this block
626         if (uinst->getParent() != block) {
627             return;
628         }
629         // Do not replace inputs in instruction itself
630         if (uinst == to) {
631             return;
632         }
633 
634         if (needDominance) {
635             /* Phi use cannot be dominated */
636             if (llvm::isa<PHINode>(uinst)) {
637                 return;
638             }
639             if (!llvm::isa<PHINode>(to) && !ComesBefore(to, uinst, &(gcContext->orderMap))) {
640                 return;
641             }
642         }
643         use->set(to);
644         sortedList.pop_back();
645     }
646 }
647 
648 /// Update input values of phi based on relocations made for GC
UpdatePhiInputs(BasicBlock * block,SSAVarRelocs * relocs)649 void GcIntrusion::UpdatePhiInputs(BasicBlock *block, SSAVarRelocs *relocs)
650 {
651     for (auto &phi : block->phis()) {
652         for (uint32_t i = 0; i < phi.getNumOperands(); ++i) {
653             auto inBlock = phi.getIncomingBlock(i);
654             auto inValue = phi.getIncomingValue(i);
655             auto relBlocks = relocs->find(inValue);
656             if (relBlocks != relocs->end() && relBlocks->second.find(inBlock) != relBlocks->second.end()) {
657                 phi.setIncomingValue(i, relBlocks->second[inBlock]);
658             }
659         }
660     }
661 }
662 
HoistForRelocation(Function * function)663 void GcIntrusion::HoistForRelocation(Function *function)
664 {
665     SmallVector<Instruction *> toMove;
666     llvm::ReversePostOrderTraversal<Function *> rpo(function);
667     for (auto block : rpo) {
668         for (auto &inst : *block) {
669             if ((inst.getOpcode() == Instruction::IntToPtr || inst.getOpcode() == Instruction::AddrSpaceCast) &&
670                 IsGcRefType(inst.getType())) {
671                 toMove.push_back(&inst);
672             }
673         }
674     }
675 
676     SmallVector<Instruction *> casts;
677     for (auto inst : toMove) {
678         // We need to traverse a chain of casts in a reverse order
679         for (auto cast = llvm::cast<CastInst>(inst); cast != nullptr;
680              cast = llvm::dyn_cast<CastInst>(cast->getOperand(0))) {
681             casts.push_back(cast);
682         }
683         // It is proven that inst introduces a GC reference. So, we need to
684         // fixup other usages than casts.
685         FixupEscapedUsages(inst, casts);
686 
687         // Place the first cast right after a definition
688         auto last = casts.pop_back_val();
689         auto def = llvm::cast<Instruction>(last->getOperand(0));
690         if (llvm::isa<PHINode>(def)) {
691             // We believe in phis (actually no)
692             last->moveBefore(*def->getParent(), def->getParent()->getFirstInsertionPt());
693         } else {
694             last->moveAfter(def);
695         }
696         // Put the rest casts after the first cast
697         while (!casts.empty()) {
698             auto curr = casts.pop_back_val();
699             curr->moveAfter(last);
700             last = curr;
701         }
702     }
703 
704     if (llvm::verifyFunction(*function, &llvm::errs())) {
705         LLVM_DEBUG(function->print(llvm::dbgs()));
706         llvm_unreachable("Function verification failed: HoistForRelocation");
707     }
708 }
709 
FixupEscapedUsages(Instruction * inst,const llvm::SmallVector<Instruction * > & casts)710 void GcIntrusion::FixupEscapedUsages(Instruction *inst, const llvm::SmallVector<Instruction *> &casts)
711 {
712     if (casts.empty()) {
713         return;
714     }
715     llvm::IRBuilder<> builder(inst);
716     auto def = casts.back()->getOperand(0);
717     for (auto uiter = def->user_begin(); uiter != def->user_end();) {
718         auto user = *uiter++;
719         if (gc_utils::IsAllowedEscapedUser(user)) {
720             continue;
721         }
722         auto phi = llvm::dyn_cast<PHINode>(user);
723 
724         if (phi == nullptr) {
725             builder.SetInsertPoint(llvm::cast<Instruction>(user));
726             auto fixed = CreateBackwardCasts(&builder, inst, casts);
727             user->replaceUsesOfWith(def, fixed);
728             continue;
729         }
730 
731         for (size_t i = 0; i < phi->getNumIncomingValues(); ++i) {
732             auto ival = phi->getIncomingValue(i);
733             if (ival != def) {
734                 continue;
735             }
736             auto iblock = phi->getIncomingBlock(i);
737             builder.SetInsertPoint(iblock->getTerminator());
738             auto fixed = CreateBackwardCasts(&builder, inst, casts);
739             phi->setIncomingValueForBlock(iblock, fixed);
740         }
741     }
742 }
743 
744 /// Creates a sequence of casts opposite to a given one.
CreateBackwardCasts(llvm::IRBuilder<> * builder,Value * from,const llvm::SmallVector<Instruction * > & casts)745 Value *GcIntrusion::CreateBackwardCasts(llvm::IRBuilder<> *builder, Value *from,
746                                         const llvm::SmallVector<Instruction *> &casts)
747 {
748     Value *last = from;
749     for (auto cast : casts) {
750         auto targetType = cast->getOperand(0)->getType();
751         switch (cast->getOpcode()) {
752             case Instruction::IntToPtr:
753                 last = builder->CreatePtrToInt(last, targetType);
754                 break;
755             case Instruction::ZExt:
756                 last = builder->CreateTrunc(last, targetType);
757                 break;
758             case Instruction::AddrSpaceCast:
759                 last = builder->CreateAddrSpaceCast(last, targetType);
760                 break;
761             default:
762                 llvm_unreachable("Unsupported backward cast to fix escaped usages");
763                 break;
764         }
765     }
766     return last;
767 }
768 
MoveComparisons(Function * function)769 bool GcIntrusion::MoveComparisons(Function *function)
770 {
771     bool changed = false;
772     // clang-format off
773     auto getConditionInst = [](Instruction *instruction) -> Instruction* {
774         if (auto *branchInst = llvm::dyn_cast<llvm::BranchInst>(instruction)) {
775             if (branchInst->isConditional()) {
776                 return llvm::dyn_cast<Instruction>(branchInst->getCondition());
777             }
778         }
779         return nullptr;
780     };
781     // clang-format on
782     for (auto &basicBlock : *function) {
783         Instruction *terminator = basicBlock.getTerminator();
784         if (auto *cond = getConditionInst(terminator)) {
785             if (llvm::isa<llvm::ICmpInst>(cond) && cond->hasOneUse()) {
786                 changed = true;
787                 cond->moveBefore(terminator);
788             }
789         }
790     }
791     return changed;
792 }
793 
794 }  // namespace ark::llvmbackend::passes
795