一、介紹
Alias Analysis (又名 Pointer Analysis)是用于確定兩個指針是否指向內存中的同一對象,這里有很多不同的別名分析算法,分為幾種類型:流敏感vs流非敏感、上下文敏感vs上下文非敏感、域敏感vs域非敏感、基于一致性的vs基于子集的。傳統的別名分析用于回答must、may、no的問題,也即兩個指針總是指向同一對象,可能指向同一對象以及絕不會指向同一對象。(SSA—靜態單一賦值,將同一變量名用多個名表示,被賦值的變量名不會重復,便于尋找變量的產生與使用點)。
LLVM AliasAnalysis類是實現別名分析的基礎類,能夠提供簡單的別名分析信息,且能提供Mod/Ref信息,有利于進行更復雜的分析。本文介紹了該接口的實現與使用。
首先,我們來了解一下別名分析,以及別名分析該如何使用。
1.別名分析的作用
例如以下c代碼:
int foo (int __attribute__((address_space(0)))* a,
int __attribute__((address_space(1)))* b) {
*a = 42;
*b = 20;
return *a;
}
轉換成llvm如下:
define i32 @foo(i32 addrspace(0)* %a, i32 addrspace(1)* %b) #0 {
entry:
store i32 42, i32 addrspace(0)* %a, align 4
store i32 20, i32 addrspace(1)* %b, align 4
%0 = load i32, i32* %a, align 4
ret i32 %0
}
現在需要對foo進行優化,去掉不必要的load:
define i32 @foo(i32 addrspace(0)* %a, i32 addrspace(1)* %b) #0 {
entry:
store i32 42, i32 addrspace(0)* %a, align 4
store i32 20, i32 addrspace(1)* %b, align 4
ret i32 42
}
但是這個優化的前提是,a和b不能別名,否則會導致錯誤如下:
int i = 0;
int result = foo(&i, &i);
以上可以看到,以上調用會使a和b別名,本應該返回20,結果因為優化的緣故返回了42,導致錯誤。所以編譯器只有確定兩個指針不會產生別名時,才能進行以上優化。
2.使用方法
一種實現是利用 llvm::AAResultBase,如果我們的目標是TAR,則可以創建一個從AAResultBase<TARAAResult>繼承的類TARAAResult:
class TARAAResult : public AAResultBase<TARAAResult> {
public:
explicit TARAAResult() : AAResultBase() {}
TARAAResult(TARAAResult &&Arg) : AAResultBase(std::move(Arg)) {}
AliasResult alias(const MemoryLocation &LocA, const MemoryLocation &LocB);
};
alias函數的輸入是兩個MemoryLocation,返回AliasResult。返回結果顯示內存對象絕不別名、可能別名、部分別名或正好別名。
AliasResult TARAAResult::alias(const MemoryLocation &LocA,
const MemoryLocation &LocB) {
auto AsA = LocA.Ptr->getType()->getPointerAddressSpace();
auto AsB = LocB.Ptr->getType()->getPointerAddressSpace();
if (AsA != AsB) {
return NoAlias;
}
// Forward the query to the next analysis.
return AAResultBase::alias(LocA, LocB);
}
二、AliasAnalysis類總覽
AliasAnalysis定義了別名分析必須支持的接口,并且導出了兩個重要方法,AliasResult和ModRefResult輸出別名結果和mod/ref結果。
AliasAnalysis接口能夠用不同的方式輸出內存信息,例如,內存對象表示成開始地址和size,函數調用表示成實際的call和invoke指令。AliasAnalysis接口也提供了helper方法,允許你獲取任意指令的mod/ref信息。
1.指針表示
AliasAnalysis類提供了不同的方法,來query兩個內存對象是否別名,以及函數調用是否可以修改或讀內存對象。對于所有query,內存對象表示為開始地址(符號化的LLVM值)+size。
內存對象的表示對于正確的別名分析至關重要,例如以下c代碼:
int i;
char C[2];
char A[10];
/* ... */
for (i = 0; i != 10; ++i) {
C[0] = A[i]; /* One byte store */
C[1] = A[9-i]; /* One byte store */
}
對于以上代碼,basicaa pass將消除對C[0]和C[1]的store,因為他們都是訪問不同地址的單個字節,互不干擾,Loop Invariant Code Motion (LICM) pass會使用store motion移除循環中的store。
int i;
char C[2];
char A[10];
/* ... */
for (i = 0; i != 10; ++i) {
((short*)C)[0] = A[i]; /* Two byte store! */
C[1] = A[9-i]; /* One byte store */
}
相反,對于以上代碼,兩個對C的store會分開,因為訪問&C[0]元素是2個字節訪問;如果query中沒有size信息,第一個案例也會別名。
2.alias方法
alias方法用于確定兩個內存對象是否別名,它輸入兩個內存對象,輸出MustAlias, PartialAlias, MayAlias, 或 NoAlias。對于所有AliasAnalysis接口,alias方法要求兩個指針值在同一個函數中定義,或者至少1個值是常數。
NoAlias表示兩個內存對象沒有任何重疊區域;MayAlias表示兩個指針可能指向同一對象;PartialAlias表示兩個內存對象可能有重疊;MustAlias表示兩個內存對象總是從同一位置開始。
3.GetModRefInfo方法
GetModRefInfo方法返回信息是,指令是否可以讀或修改某個內存區域。Mod/Ref信息是保守的,如果一條指令可能讀或寫某區域,就返回ModRef。
4.其他AliasAnalysis方法
(1)pointsToConstantMemory方法
若能確定指針僅指向不變的內存區域(函數,全局常量,null指針)則返回true,該信息可以優化mod/ref信息:因為不變的內存區域是不能被修改的。
(2)doesNotAccessMemory和onlyReadsMemory方法
若確定函數從不讀寫內存,或者函數僅從常量內存讀,則doesNotAccessMemory返回true。
若確定函數僅從非易失性內存讀,則onlyReadsMemory返回true。注意,滿足doesNotAccessMemory方法的所有函數也都滿足onlyReadsMemory。
三、實現新的AliasAnalysis
1.不同的Pass類型
選擇使用哪種LLVM pass來做別名分析取決于你想要解決哪種問題。
- 做過程間分析,用Pass
- 做局部函數的分析,用FunctionPass子類
- 若不需要看程序,則選擇ImmutablePass
除了繼承以上pass,你也需要繼承AliasAnalysis接口,當然,也可以用RegisterAnalysisGroup模板去注冊AliasAnalysis實現。
2.進行初始化所需的調用
所寫的AliasAnalysis的子類需調用AliasAnalysis基類的兩個方法:getAnalysisUsage和InitializeAliasAnalysis。例如,實現你的getAnalysisUsage時,除了聲明pass依賴,還需顯式調用AliasAnalysis::getAnalysisUsage方法。
void getAnalysisUsage(AnalysisUsage &AU) const {
AliasAnalysis::getAnalysisUsage(AU);
// declare your dependencies here.
}
另外,在你的run方法中需調用InitializeAliasAnalysis方法(Pass—run;FunctionPass—runOnFunction;ImmutablePass—InitializePass)。
bool run(Module &M) {
InitializeAliasAnalysis(this);
// Perform analysis here...
return false;
}
3.需要覆蓋的方法
在你的AliasAnalysis子類中必須覆蓋getAdjustedAnalysisPointer方法,例如:
void *getAdjustedAnalysisPointer(const void* ID) override {
if (ID == &AliasAnalysis::ID)
return (AliasAnalysis*)this;
return this;
}
4.可指定的接口
所有AliasAnalysis虛方法都默認為其他別名分析提供一個鏈接,最終能返回正確結果(為alias query和mod/ref query返回May和Mod/Ref),根據你分析的功能,覆蓋相應的接口即可。
5.AliasAnalysis鏈接行為
除了-no-aa pass,每個分析pass都鏈接到另一個別名分析的實現(例如,可以使用"-basicaa -ds-aa -licm"來從多個別名分析達到最大優化)。別名分析會自動處理你未覆蓋的方法,對于已覆蓋的方法,若需返回AmayAlias或Mod/Ref結果,只需返回超類計算的結果。
AliasResult alias(const Value *V1, unsigned V1Size,
const Value *V2, unsigned V2Size) {
if (...)
return NoAlias;
...
// Couldn't determine a must or no-alias result.
return AliasAnalysis::alias(V1, V1Size, V2, V2Size);
}
除了分析查詢,如果你需要覆蓋某方法,需將更新通知傳給超類,這樣就允許所有別名分析進行更新。
6.更新代碼轉換后的分析結果
別名分析最初用于建立程序的靜態快照,但用戶也用于進行代碼轉換。所有別名分析都需要更新分析結果,以反映代碼轉換所做的變換。AliasAnalysis接口提供了4個方法來更新程序變化后的分析結果。
(1)deleteValue方法
當從程序刪除某個指令或值(包括不使用指針的值)時調用deleteValue方法。通常,別名分析會保留數據結構,這些數據結構包含程序中每個值的條目。調用此方法時,則刪除指定值的任何條目(如果存在)。
(2)copyValue方法
當程序引入新的值時調用copyValue方法。通常不會引入程序中不存在的值(編譯器安全轉換),所以這是引入新值的唯一方法,這個方法指示新值和拷貝值有相同的屬性。
(3)replaceWithNewValue方法
用新值替換舊值,該方法不能被別名分析實現所覆蓋。
(4)addEscapingUse方法
當指針的使用導致之前計算的分析結果無效時,調用addEscapingUse方法。該方法會提供保守的返回,或者重新計算分析結果。
總之,只要有新的指針使用,就需要調用該方法,除了一下3種情況:
- 指針的bitcast或getelementptr
- 通過指針來store,但并非存指針
- 通過指針load
四、使用別名分析結果
1.使用MemoryDependenceAnalysis Pass
memdep pass使用別名分析來提供內存使用指令的高級依賴信息,例如,告訴你哪個store提供給了load。它也使用緩存等技術提高效率,一般用于Dead Store Elimination, GVN 和 memcpy優化。
2.使用AliasSetTracker類
有些代碼轉換需要某個代碼區域內的活躍的別名集,而不是成對的別名信息。AliasSetTracker類可以根據AliasAnalysis接口提供的成對的別名分析信息,高效的構建所需的別名集。
首先需要調用add方法對AliasSetTracker進行初始化,添加該代碼區域內可能導致別名的指令。當別名集構建完成后,你可以利用AliasSetTrackerbegin() / end()方法迭代訪問該別名集。
AliasSetTracker構造的AliasSets是不相交的,計算mod/ref信息,并跟蹤記錄該集合所有指針是否是Must aliases。
例如,Loop Invariant Code Motion pass使用AliasSetTracker來計算每個循環嵌套的別名集,如果循環的某個AliasSet沒有被修改,則該集合所有的load指令可能被提出循環。如果所有別名集是store to 和must alias集,則store 在循環外部使用,這樣在循環時將內存位置放入進村器。只有當指針參數是循環不變的,才采用這些轉換。
3.直接使用AliasAnalysis接口
如果這些功能類都用不到,你可以直接使用AliasAnalysis類。盡量使用高級方法,以獲取更高的準確率和效率。
五、現有的別名分析實現
1.可用的AliasAnalysis實現
(1)-no-aa pass
不做別名分析。
(2)-basicaa pass
(3)-globalsmodref-aa pass
(4)-steens-aa pass
(5)-ds-aa pass
(6)-scev-aa pass
注意:basicaa和steens-aa這類標準的LLVM pass太耗時了,Anderson Analysis也很耗時耗內存,已經有一些工作在優化別名分析。
2.別名分析驅動的轉換
(1)-adce pass
(2)-licm pass
(3)-argpromotion pass
(4)-gvn -memcpyopt -dse pass
3.用于調試和評估的client
命令:% opt -ds-aa -aa-eval foo.bc -disable-output -stats
(1)-print-alias-sets pass
% opt -ds-aa -print-alias-sets -disable-output
(2)-aa-eval pass
4.內存依賴分析
正在將MemoryDependenceAnalysis遷移到MemorySSA。
參考:
https://llvm.org/docs/AliasAnalysis.html