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