https://github.com/leejaymin/llvm8-tutorials-jemin/tree/master
해당 GitHub를 참고하여 Loop를 분석하는 LLVM Pass를 제작해 보았다.
각 Loop의 시작 라벨과 CIV를 출력하는 Pass
코드를 부분적으로 살펴보도록 하자
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
LoopInfo &LI = FAM.getResult<LoopAnalysis>(F);
ScalarEvolution &SE = FAM.getResult<ScalarEvolutionAnalysis>(F);
for (auto *L : LI)
analyzeLoop(L, SE);
return PreservedAnalyses::all();
}
먼저 run 함수다. Function Pass 이다.
LoopInfo &LI = FAM.getResult<LoopAnalysis>(F);
루프 구조를 파악하기 위한 루프 분석 정보를 가져온다. LoopInfo는 루프의 헤더, 바디, 중첩 구조 등의 정보를 제공한다.
ScalarEvolution &SE = FAM.getResult<ScalarEvolutionAnalysis>(F);
루프 내의 변수가 어떻게 진화하는지 분석해주는 도구다.
반복 횟수 측정, 루프 종료 조건 계산, 일정 패턴 탐지 등에 사용된다.
for (auto *L : LI)
현재 함수에서 발견된 루프들을 순회한다.
void analyzeLoop(Loop *L, ScalarEvolution &SE) {
BasicBlock *header = L->getHeader();
errs() << "loop : " << getBlockLabel(header) << "\n";
if (PHINode *civ = findCIV(L, SE))
errs() << "CIV : " << getValueLabel(civ) << "\n";
for (auto *sub : L->getSubLoops())
analyzeLoop(sub, SE);
}
*header는 루프 헤더의 베이직 블럭 포인터다.
getBlockLabel함수를 호출하여 라벨을 출력한다.
findCIV(L, SE)함수를 이용하여 루프에서 카운터 역할을 하는 PHI노드(CIV)를 찾는다.
getValueLabel()을 통하여 해당 주소에 있는 IR을 출력한다.
마지막으로 for (auto *sub : L->getSubLoops()) 재귀적으로 루프를 탐색하며 위 과정을 반복한다.
CIV(Canonical Induction Variable)란? : 루프 내에서 일정한 패턴으로 증감하는 PHI노드를 의미
PHINode* findCIV(Loop *L, ScalarEvolution &SE) {
BasicBlock *header = L->getHeader();
for (auto &I : *header) {
if (auto *phi = dyn_cast<PHINode>(&I)) {
const SCEV *scev = SE.getSCEV(phi);
if (auto *addRec = dyn_cast<SCEVAddRecExpr>(scev))
if (addRec->getLoop() == L)
return phi;
}
}
return nullptr;
}
ScalarEvolution 분석기를 이용해 해당 PHI노드에 대한 SCEV 표현식을 가져옴SCEVAddRecExpr인지 확인std::string getBlockLabel(BasicBlock *BB) {
std::string s;
raw_string_ostream os(s);
BB->printAsOperand(os, /*PrintType=*/false);
os.flush();
// if (!s.empty() && s[0] == '%')
// return s.substr(1) +;
return s;
}
std::string getValueLabel(Value *V) {
std::string s;
raw_string_ostream os(s);
V->print(os);
os.flush();
return s;
}
마지막으로 BasicBlock의 라벨을 문자열로 반환하는 함수와 LLVM IR의 Value 객체를 문자열화하는 함수다.
#include "llvm/IR/PassManager.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/ScalarEvolution.h"
#include "llvm/Analysis/ScalarEvolutionExpressions.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/PassPlugin.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
#include <sstream>
using namespace llvm;
namespace {
class LoopAnalysisPass : public PassInfoMixin<LoopAnalysisPass> {
public:
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
LoopInfo &LI = FAM.getResult<LoopAnalysis>(F);
ScalarEvolution &SE = FAM.getResult<ScalarEvolutionAnalysis>(F);
for (auto *L : LI)
analyzeLoop(L, SE);
return PreservedAnalyses::all();
}
private:
void analyzeLoop(Loop *L, ScalarEvolution &SE) {
BasicBlock *header = L->getHeader();
errs() << "loop : " << getBlockLabel(header) << "\n";
if (PHINode *civ = findCIV(L, SE))
errs() << "CIV : " << getValueLabel(civ) << "\n";
for (auto *sub : L->getSubLoops())
analyzeLoop(sub, SE);
}
PHINode* findCIV(Loop *L, ScalarEvolution &SE) {
BasicBlock *header = L->getHeader();
for (auto &I : *header) {
if (auto *phi = dyn_cast<PHINode>(&I)) {
const SCEV *scev = SE.getSCEV(phi);
if (auto *addRec = dyn_cast<SCEVAddRecExpr>(scev))
if (addRec->getLoop() == L)
return phi;
}
}
return nullptr;
}
std::string getBlockLabel(BasicBlock *BB) {
std::string s;
raw_string_ostream os(s);
BB->printAsOperand(os, /*PrintType=*/false);
os.flush();
// if (!s.empty() && s[0] == '%')
// return s.substr(1) +;
return s;
}
std::string getValueLabel(Value *V) {
std::string s;
raw_string_ostream os(s);
V->print(os);
os.flush();
return s;
}
};
} // namespace
extern "C" ::llvm::PassPluginLibraryInfo llvmGetPassPluginInfo() {
return {
LLVM_PLUGIN_API_VERSION,
"LoopAnalysisPass", LLVM_VERSION_STRING,
[](PassBuilder &PB) {
PB.registerPipelineParsingCallback(
[](StringRef Name,
FunctionPassManager &FPM,
ArrayRef<PassBuilder::PipelineElement>) {
if (Name == "loopanalysis") {
FPM.addPass(LoopAnalysisPass());
return true;
}
return false;
});
}
};
}
#include <stdio.h>
int main() {
int x, y, z;
scanf("%d %d %d ", &x, &y, &z);
for(int i=0;i<x;i++) {
for(int j=0;j<y;j++) {
for(int k=0;k<z;k++) {
printf("i: %d, j: %d, k: %d\n", i, j, k);
}
}
}
return 0;
}
최적화 가능성이 있어 side effect가 있는 함수로 진행



코드가 길어 루프의 헤더 블럭만 가져왔다. 3중 for문 이고, for문의 시작 label은 각 10, 20, 40이다. PHI노드를 보면 SSA에 0을 저장하는 것을 볼 수 있다.이는 for문에서 변수를 0으로 초기화 하는 부분이며, 이를 통해 해당 PHI노드가 CIV라는 것을 유추할 수 있다.

실행 결과 앞서 분석한 것과 같은 결과임을 볼 수 있다.