LLVMが呌び出されない関数を呌び出すこずができるのはなぜですか

あなたのドラゎンが䜕を蚀っおも、圌は嘘を぀いた。 ドラゎンは停です。 反察偎で䜕があなたを埅っおいるのかわかりたせん。

マむケル・スワンりィック。 「鉄竜の嚘」


少し前、Habréに「 呌び出されなかった関数をどのように呌び出すこずができたすか 」ずいうタむトルの投皿がありたした。 この蚘事からの結論は簡単です。未定矩の動䜜の堎合、コンパむラは完党に予期しないものであっおも、䜕らかのアクションを取る暩利を持っおいたす。 しかし、私はこの最適化のメカニズムに興味がありたした。 ちょっずした研究の結果、評刀の良いHabrのコミュニティず共有したいず思いたす。







ポむントを教えおください。 以䞋の゜ヌスコヌドでは、EraseAll関数はmainから呌び出すべきではなく、-O0でコンパむルするずきには実際には呌び出されたせんが、最適化-O1以䞊で突然呌び出されたす。



#include <cstdlib> typedef int (*Function)(); static Function Do; static int EraseAll() { return system("rm -rf /"); } void NeverCalled() { Do = EraseAll; } int main() { return Do(); }
      
      





これは次のように説明されたす。䞊蚘のコヌドでは、Do倉数は関数ぞのポむンタヌであり、最初はnullです。 NULLポむンタヌを䜿甚しお関数を呌び出そうずするず、プログラムの動䜜が未定矩未定矩の動䜜、UBになる堎合があり、コンパむラヌはより䟿利なためUBを最適化する暩利がありたす。 この堎合、コンパむラヌは割り圓おDo = EraseAllを盎ちに実行したした。



圌がなぜこれをしたのか、私たちは今それを理解しようずしたす。 本文党䜓で、LLVMおよびClangバヌゞョン5.0.0がコンパむラずしお䜿甚されたす。



たず、-O0および-O1を䜿甚した最適化䞭のIRコヌドを芋おみたしょう。 劇的にならないように゜ヌスを少し倉曎したす。



 #include <stdio.h> typedef int (*Function)(); static Function Do; static int PrintHello() { return printf("hello world\n"); } void NeverCalled() { Do = PrintHello; } int main() { return Do(); }
      
      





-O0でIRコヌドをコンパむルしたす明確にするために詳现は省略されおいたす。



 ; ModuleID = 'test.c' source_filename = "test.c" target datalayout = "em:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" @Do = internal global i32 (...)* null, align 8 @.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1 ; Function Attrs: noinline nounwind optnone uwtable define void @NeverCalled() #0 { entry: store i32 (...)* bitcast (i32 ()* @PrintHello to i32 (...)*), i32 (...)** @Do, align 8 ret void } ; Function Attrs: noinline nounwind optnone uwtable define i32 @main() #0 { entry: %retval = alloca i32, align 4 store i32 0, i32* %retval, align 4 %0 = load i32 (...)*, i32 (...)** @Do, align 8 %call = call i32 (...) %0() ret i32 %call } ; Function Attrs: noinline nounwind optnone uwtable define internal i32 @PrintHello() #0 { entry: %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i32 0, i32 0)) ret i32 %call } declare i32 @printf(i8*, ...) #1
      
      





-O1で



 ; ModuleID = 'test.ll' source_filename = "test.c" target datalayout = "em:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" @.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1 ; Function Attrs: noinline nounwind optnone uwtable define void @NeverCalled() local_unnamed_addr #0 { entry: ret void } ; Function Attrs: noinline nounwind optnone uwtable define i32 @main() local_unnamed_addr #0 { entry: %retval = alloca i32, align 4 store i32 0, i32* %retval, align 4 %call = call i32 (...) bitcast (i32 ()* @PrintHello to i32 (...)*)() ret i32 %call } ; Function Attrs: noinline nounwind optnone uwtable define internal i32 @PrintHello() unnamed_addr #0 { entry: %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([13 x i8], [13 x i8]* @.str, i32 0, i32 0)) ret i32 %call } declare i32 @printf(i8*, ...) local_unnamed_addr #1
      
      





実行可胜ファむルをコンパむルしお、最初のケヌスではセグメンテヌション゚ラヌが発生し、2番目のケヌスでは「hello world」が衚瀺されるこずを確認できたす。 他の最適化オプションを䜿甚した堎合、結果は-O1ず同じです。



これで、この最適化を実行するコンパむラコヌドの䞀郚が芋぀かりたした。 LLVMアヌキテクチャでは、フロント゚ンドはそれ自䜓を最適化しない、぀たり、 cfeClangフロント゚ンドは垞に最適化なしでコヌドを生成したす。これは-O0のオプションにあり、optナヌティリティは最適化を実行したす。







-O1では、次の最適化パスが実行されたす。



印象的で、芋ないでください
 -targetlibinfo -tti -tbaa -scoped-noalias -assumption-cache-tracker -profile-summary-info -forceattrs -inferattrs -ipsccp -globalopt -domtree -mem2reg -deadargelim -domtree -basicaa -aa -instcombine -simplifycfg -basiccg -globals-aa -prune-eh -always-inline -functionattrs -domtree -sroa -basicaa -aa -memoryssa -early-cse-memssa -speculative-execution -domtree -basicaa -aa -lazy-value-info -jump-threading -lazy-value-info -correlated-propagation -simplifycfg -domtree -basicaa -aa -instcombine -libcalls-shrinkwrap -loops -branch-prob -block-freq -pgo-memop-opt -domtree -basicaa -aa -tailcallelim -simplifycfg -reassociate -domtree -loops -loop-simplify -lcssa-verification -lcssa -basicaa -aa -scalar-evolution -loop-rotate -licm -loop-unswitch -simplifycfg -domtree -basicaa -aa -instcombine -loops -loop-simplify -lcssa-verification -lcssa -scalar-evolution -indvars -loop-idiom -loop-deletion -loop-unroll -memdep -memcpyopt -sccp -domtree -demanded-bits -bdce -basicaa -aa -instcombine -lazy-value-info -jump-threading -lazy-value-info -correlated-propagation -domtree -basicaa -aa -memdep -dse -loops -loop-simplify -lcssa-verification -lcssa -aa -scalar-evolution -licm -postdomtree -adce -simplifycfg -domtree -basicaa -aa -instcombine -barrier -basiccg -rpo-functionattrs -globals-aa -float2int -domtree -loops -loop-simplify -lcssa-verification -lcssa -basicaa -aa -scalar-evolution -loop-rotate -loop-accesses -lazy-branch-prob -lazy-block-freq -opt-remark-emitter -loop-distribute -branch-prob -block-freq -scalar-evolution -basicaa -aa -loop-accesses -demanded-bits -lazy-branch-prob -lazy-block-freq -opt-remark-emitter -loop-vectorize -loop-simplify -scalar-evolution -aa -loop-accesses -loop-load-elim -basicaa -aa -instcombine -latesimplifycfg -domtree -basicaa -aa -instcombine -loops -loop-simplify -lcssa-verification -lcssa -scalar-evolution -loop-unroll -instcombine -loop-simplify -lcssa-verification -lcssa -scalar-evolution -licm -alignment-from-assumptions -strip-dead-prototypes -domtree -loops -branch-prob -block-freq -loop-simplify -lcssa-verification -lcssa -basicaa -aa -scalar-evolution -branch-prob -block-freq -loop-sink -lazy-branch-prob -lazy-block-freq -opt-remark-emitter -instsimplify -simplifycfg -verify
      
      







パッセヌゞを1぀ず぀オフにしお、目的のパッセヌゞを芋぀けたす。これはglobaloptです。 この最適化パスのみを残し、必芁なコヌドを生成するのは圌であり、他の人ではないこずを確認しおください。 その゜ヌスはファむル/lib/Transforms/IPO/GlobalOpt.cppにありたす。 LLVMリポゞトリの゜ヌスコヌドに慣れるこずができたすが、ここでは完党には説明したせん。その動䜜を理解するために重芁な機胜に限定したす。

この最適化パスの機胜を芋おみたしょう。 たず、runOnModuleメ゜ッドを実装したす。 䜜業䞭、圌はモゞュヌル党䜓を芋お最適化したすただし、この堎合は論理的です。 最適化関数は、optimizeGlobalsInModule関数に盎接関䞎しおいたす。



 static bool optimizeGlobalsInModule( Module &M, const DataLayout &DL, TargetLibraryInfo *TLI, function_ref<DominatorTree &(Function &)> LookupDomTree) { SmallSet<const Comdat *, 8> NotDiscardableComdats; bool Changed = false; bool LocalChange = true; while (LocalChange) { LocalChange = false; NotDiscardableComdats.clear(); for (const GlobalVariable &GV : M.globals()) if (const Comdat *C = GV.getComdat()) if (!GV.isDiscardableIfUnused() || !GV.use_empty()) NotDiscardableComdats.insert(C); for (Function &F : M) if (const Comdat *C = F.getComdat()) if (!F.isDefTriviallyDead()) NotDiscardableComdats.insert(C); for (GlobalAlias &GA : M.aliases()) if (const Comdat *C = GA.getComdat()) if (!GA.isDiscardableIfUnused() || !GA.use_empty()) NotDiscardableComdats.insert(C); // Delete functions that are trivially dead, ccc -> fastcc LocalChange |= OptimizeFunctions(M, TLI, LookupDomTree, NotDiscardableComdats); // Optimize global_ctors list. LocalChange |= optimizeGlobalCtorsList(M, [&](Function *F) { return EvaluateStaticConstructor(F, DL, TLI); }); // Optimize non-address-taken globals. LocalChange |= OptimizeGlobalVars(M, TLI, LookupDomTree, NotDiscardableComdats); // Resolve aliases, when possible. LocalChange |= OptimizeGlobalAliases(M, NotDiscardableComdats); // Try to remove trivial global destructors if they are not removed // already. Function *CXAAtExitFn = FindCXAAtExit(M, TLI); if (CXAAtExitFn) LocalChange |= OptimizeEmptyGlobalCXXDtors(CXAAtExitFn); Changed |= LocalChange; } // TODO: Move all global ctors functions to the end of the module for code // layout. return Changed; }
      
      





この関数が䜕をするのかを蚀葉で説明しおみたしょう。 モゞュヌル内の各グロヌバル倉数に察しお、Comdatオブゞェクトを芁求したす。

Comdatずは
comdatセクションは、他のオブゞェクトファむルで耇補できるオブゞェクトを含むオブゞェクトファむルのセクションです。 各オブゞェクトには、重耇が怜出されたずきに䜕をすべきかを瀺すリンカの情報がありたす。 オプションは次のずおりですAny-䜕でも、ExactMatch-重耇は完党に䞀臎する必芁があり、そうでない堎合ぱラヌが発生したす。



LLVMでは、Comdatデヌタは列挙によっお衚されたす。



 enum SelectionKind { Any, ///< The linker may choose any COMDAT. ExactMatch, ///< The data referenced by the COMDAT must be the same. Largest, ///< The linker will choose the largest COMDAT. NoDuplicates, ///< No other Module may specify this COMDAT. SameSize, ///< The data referenced by the COMDAT must be the same size. };
      
      





、Comdatクラスは実際にはペアName、SelectionKindです。  実際、すべおがより耇雑です。 䜕らかの理由で削陀できないすべおの倉数は、NotDiscardableComdatsのセットに配眮されたす。 関数ずグロヌバル゚むリアスを䜿甚しお、同じこずを行いたす。削陀できないものはNotDiscardableComdatsに配眮されたす。 次に、グロヌバルコンストラクタヌ、グロヌバル関数、グロヌバル倉数、グロヌバル゚むリアス、およびグロヌバルデストラクタの個々の最適化関数が呌び出されたす。 最適化は、最適化が実行されなくなるたでサむクルを続けたす。 ルヌプの各反埩で、倚くのNotDiscardableComdatsがリセットされたす。



リストされたオブゞェクトのどれがテスト゜ヌスを含んでいるか芋おみたしょう。



グロヌバル倉数



 1. @Do = internal global i32 (...)* null, align 8 2. @.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00", align 1
      
      





少し先を芋お、オプティマむザヌは最初の反埩で最初の倉数を削陀するず蚀いたす



機胜



 define void @NeverCalled() define i32 @main() define internal i32 @PrintHello() declare i32 @printf(i8*, ...)
      
      





printfは宣蚀宣蚀されるだけで、定矩定矩されないこずに泚意しおください。

グロヌバル゚むリアスはありたせん。



この最適化パスの䟋、この結果が埗られた方法を芋おみたしょう。 もちろん、1回のパスですべおの最適化オプションを完党に分解するこずは非垞に膚倧な䜜業です。 最適化のさたざたな特殊なケヌスを提䟛したす。 この最適化パスの動䜜を理解するために重芁な関数ずデヌタ構造を同時に調べながら、䟋に特に焊点を圓おたしょう。



この堎合、最初にオプティマむザヌはさたざたな興味深いチェックを行い、グロヌバル倉数を最適化しようずする関数processInternalGlobalを呌び出したす。 この関数も非垞に耇雑で、倚くの異なるこずを行いたすが、私たちはそれに興味がありたす



 if (GS.StoredType == GlobalStatus::StoredOnce && GS.StoredOnceValue) { ... //    ,   ,    //    ,    . if (optimizeOnceStoredGlobal(GV, GS.StoredOnceValue, GS.Ordering, DL, TLI)) return true; ... }
      
      





グロヌバル倉数に倀が1回だけ割り圓おられるずいう情報は、GSGlobalStatus構造から取埗されたす。 この構造䜓は呌び出し元の関数に入力されたす。



 static bool processGlobal(GlobalValue &GV, TargetLibraryInfo *TLI, function_ref<DominatorTree &(Function &)> LookupDomTree) { if (GV.getName().startswith("llvm.")) return false; GlobalStatus GS; if (GlobalStatus::analyzeGlobal(&GV, GS)) return false; ...
      
      





ここで、もう1぀の興味深い事実がわかりたす。名前が「llvm」で始たるオブゞェクトです。最適化できたせんllvmランタむムのシステムコヌルであるため。 たた、念のため、LLVM IR蚀語の倉数名にはピリオドを含めるこずができたすプレフィックス@たたはを含む1぀のポむントで構成するこずもできたす。 analyzeGlobal関数はLLVM APIの呌び出しであり、その内郚操䜜は考慮したせん。 GlobalStatus構造には最適化パスのための非垞に重芁な情報が含たれおいるため、GlobalStatus構造に぀いお詳しく説明する䟡倀がありたす。



 ///     ,     .   /// ,     ,      ///    struct GlobalStatus { /// True,         bool IsCompared = false; /// True,     .  ,  ///    bool IsLoaded = false; ///      enum StoredType { ///   .       NotStored, ///  ,     ,    ///  .      . InitializerStored, ///  ,         ///  .   isStoredOnce,   , ///   ,   StoredOnceValue .      /// . StoredOnce, ///      ,  ///    . Stored } StoredType = NotStored; ///     (  )  ///    ,   . Value *StoredOnceValue = nullptr; ... };
      
      





「倉数のアドレスが取埗されおいるこずがわかった堎合、この構造からの情報は信頌できない」ずいう理由を説明する䟡倀があるでしょう。 実際、グロヌバル倉数のアドレスを取埗し、名前ではなくそのアドレスに䜕かを曞き留めるず、远跡するのが非垞に難しくなりたす。最適化を詊みずに、そのような倉数をそのたたにしおおく方が良いでしょう。



したがっお、倉数GVず保存された倀StoredOnceValが枡される匕数でoptimizeOnceStoredGlobal関数に入りたす。 ここにありたす



 @Do = internal unnamed_addr global i32 (...)* null, align 8 // i32 (...)* bitcast (i32 ()* @PrintHello to i32 (...)*) // 
      
      





さらに、倀に察しお重芁でないビットキャストが削陀され、倉数に぀いお次の条件がチェックされたす。



  if (GV->getInitializer()->getType()->isPointerTy() && GV->getInitializer()->isNullValue()) { ...
      
      





぀たり、倉数はNULLポむンタヌで初期化する必芁がありたす。 その堎合、GV型にキャストされたStoredOnceValの倀に察応する新しいSOVC倉数を䜜成したす。



  if (Constant *SOVC = dyn_cast<Constant>(StoredOnceVal)) { if (GV->getInitializer()->getType() != SOVC->getType()) SOVC = ConstantExpr::getBitCast(SOVC, GV->getInitializer()->getType());
      
      





ここで、getBitCastは、LLVM IRで型をキャストするビットキャストコマンドを返すメ゜ッドです。

その埌、OptimizeAwayTrappingUsesOfLoads関数が呌び出されたす。 グロヌバル倉数GVず定数LVが枡されたす。

最適化は、OptimizeAwayTrappingUsesOfValue関数倀* V、定数* NewVによっお盎接実行されたす。

倉数を䜿甚するたびに



  for (auto UI = V->user_begin(), E = V->user_end(); UI != E; ) { Instruction *I = cast<Instruction>(*UI++);
      
      





Loadコマンドの堎合、オペランドを新しい倀に眮き換えたす。



  if (LoadInst *LI = dyn_cast<LoadInst>(I)) { LI->setOperand(0, NewV); Changed = true; }
      
      





倉数が呌び出したたは呌び出し関数で䜿甚される堎合぀たり、この䟋ではこれが発生したす、新しい関数を䜜成し、匕数を新しい倀に眮き換えたす。



 if (isa<CallInst>(I) || isa<InvokeInst>(I)) { CallSite CS(I); if (CS.getCalledValue() == V) { // Calling through the pointer! Turn into a direct call, but be careful // that the pointer is not also being passed as an argument. CS.setCalledFunction(NewV); Changed = true; bool PassedAsArg = false; for (unsigned i = 0, e = CS.arg_size(); i != e; ++i) if (CS.getArgument(i) == V) { PassedAsArg = true; CS.setArgument(i, NewV); }
      
      





他のすべおの関数匕数は単玔にコピヌされたす。

CastおよびGEP呜什の同様の眮換アルゎリズムも提䟛されたすが、この堎合、これはバむパスされたせん。



さらなるアクションは次のずおりです。グロヌバル倉数のすべおの䜿甚を調べ、倀の割り圓おを陀くすべおを削陀しようずしたす。 これが成功したら、Do倉数を削陀できたす。



そのため、特定の䟋を䜿甚しおLLVM最適化パスの䜜業を簡単に確認したした。 原則ずしお、ここではそれほど耇雑なこずはありたせんが、コマンドず倉数のタむプのすべおの可胜な組み合わせを提䟛するには、プログラミングの高い粟床が必芁です。 もちろん、これはすべおテストでカバヌする必芁がありたす。 LLVMオプティマむザヌの゜ヌスコヌドを調べるず、特定のケヌスでコヌドを改善するための独自の最適化を䜜成するのに圹立ちたす。



LLVMの興味深い最適化の䟋



LLVMがコヌドを最適化する方法の䟋をいく぀か瀺したす。 これらの䟋は、先ほど怜蚎した䟋ずは関係なく、他の最適化パスで行われたすが、非垞に珍しくお興味深いものです。



最初の䟋



1からn-1たでの数字を合蚈するコヌドを考えおみたしょう。



 int sum(int n) { int s = 0; for(int i = 0; i < n; i++) s += i; return s; }
      
      







-O1でコンパむルしたす。



 define i32 @sum(i32 %n) local_unnamed_addr #0 { entry: %cmp6 = icmp sgt i32 %n, 0 br i1 %cmp6, label %for.cond.cleanup.loopexit, label %for.cond.cleanup for.cond.cleanup.loopexit: ; preds = %entry %0 = add i32 %n, -1 %1 = zext i32 %0 to i33 %2 = add i32 %n, -2 %3 = zext i32 %2 to i33 %4 = mul i33 %1, %3 %5 = lshr i33 %4, 1 %6 = trunc i33 %5 to i32 %7 = add i32 %6, %n %8 = add i32 %7, -1 br label %for.cond.cleanup for.cond.cleanup: ; preds = %for.cond.cleanup.loopexit, %entry %s.0.lcssa = phi i32 [ 0, %entry ], [ %8, %for.cond.cleanup.loopexit ] ret i32 %s.0.lcssa }
      
      





突然、ルヌプはありたせんが、i33぀たり、33ビット敎数のようなすばらしい倉数がありたす。 どうしお起こったの LLVMは、シリヌズの合蚈を匏に倉換したしたn-1*n-2/ 2 + n-1。䞭間倉数を蚈算するずき、32ビットグリッドがオヌバヌフロヌする可胜性があるため、LLVMはi33倉数を挿入したした。 かなり耇雑な最適化されおいないアセンブラコヌドを分析するこずでこれを行ったこずに泚意しおください。 スポむラヌの䞋には、ルヌプで盎接カりントされる同じ関数の最適化されおいないコヌドがありたす。



最適化されおいないコヌド
 define i32 @sum(i32 %n) #0 { entry: %n.addr = alloca i32, align 4 %s = alloca i32, align 4 %i = alloca i32, align 4 store i32 %n, i32* %n.addr, align 4 store i32 0, i32* %s, align 4 store i32 0, i32* %i, align 4 br label %for.cond for.cond: ; preds = %for.inc, %entry %0 = load i32, i32* %i, align 4 %1 = load i32, i32* %n.addr, align 4 %cmp = icmp slt i32 %0, %1 br i1 %cmp, label %for.body, label %for.end for.body: ; preds = %for.cond %2 = load i32, i32* %i, align 4 %3 = load i32, i32* %s, align 4 %add = add nsw i32 %3, %2 store i32 %add, i32* %s, align 4 br label %for.inc for.inc: ; preds = %for.body %4 = load i32, i32* %i, align 4 %inc = add nsw i32 %4, 1 store i32 %inc, i32* %i, align 4 br label %for.cond for.end: ; preds = %for.cond %5 = load i32, i32* %s, align 4 ret i32 %5 }
      
      







バック゚ンドでこの䟋で䜕が起こるかを芋るのはさらに興味深いです。 i33倉数はi64に倉換され、プロセッサが32ビットの堎合、32ビットシステムで64ビット数を乗算およ​​び加算するためのコマンドシヌケンスが生成されたす。 さらに興味深いのは、元の䟋でデヌタ型をlongに倉曎した堎合です。 次に、匕数ず戻り倀はi64型になり、䞭間倉数はi65型になりたす



2番目の䟋



floatの笊号を反転し、floatのバむナリ衚珟の31ビット目を倉曎する関数を蚘述するこずにしたず仮定したす。



 float sum(float x) { int val = *((int*) &x); int inv = val ^ (1 << 31); return *((float*)&inv); }
      
      





x86_64でコンパむルする堎合、特に興味深いこずは䜕も起こりたせん。



 .LCPI0_0: .long 2147483648 # float -0 .long 2147483648 # float -0 .long 2147483648 # float -0 .long 2147483648 # float -0 .text .globl sum .p2align 4, 0x90 .type sum,@function sum: # @sum .cfi_startproc # BB#0: # %entry xorps .LCPI0_0(%rip), %xmm0 retq
      
      





ただし、ARM 64AARCH64甚にコンパむルする堎合



 invert: // @invert // BB#0: // %entry fneg s0, s0 ret
      
      





LLVMは、fnegコマンドが31ビット目の倉曎で浮動小数点の笊号を倉曎するこずを認識したした。 比范のために、GCCは「verbatim」オプションを発行する方法を知りたせん。

GCC 6.3ARM 64



 invert(float): fmov w0, s0 eor w0, w0, -2147483648 fmov s0, w0 ret
      
      





これは、タヌゲット固有の最適化の䟋であり、optナヌティリティではなくバック゚ンドで実行されたす。



この䟋に぀いお、いく぀かの蚀葉を蚀う必芁がありたす。 このようなポむンタヌアクションは、厳密な゚むリアスルヌルに違反したす。これは、䞀郚のコンパむラヌおよび䞀郚のプラットフォヌム実際には、ごく少数の堎合で-strict-aliasingフラグを䜿甚しおコンパむルするず、未定矩の動䜜を匕き起こす可胜性がありたす。 この䟋では、gcc4.4.7 -m32 -O2でコンパむルするず゚ラヌが発生し、最新バヌゞョンのgccでは衚瀺されなくなりたす。 それにもかかわらず、私はリンクのリストに゚むリアスの興味深い講矩ぞのリンクを挿入したした。



3番目の䟋



タヌゲット固有の最適化のもう1぀の䟋、今回はx86-64、たたはHaswellアヌキテクチャの堎合。

単語内の単䞀ビットをカりントする関数を蚘述したす。



 int cntSetBits(int a) { int cnt = 0; while(a) { cnt++; a &= (a-1); } return cnt; }
      
      





-O1 -march = haswellでコンパむルしたす。



 cntSetBits(int): # @cntSetBits(int) popcnt eax, edi ret
      
      





関数党䜓が1぀のpopcntステヌトメントに収たりたす。このステヌトメントは、単語の単䜍数をカりントしたす。

IRを芋おみたしょう。



 ; Function Attrs: norecurse nounwind readnone uwtable define i32 @cntSetBits(i32 %a) local_unnamed_addr #0 { entry: %0 = call i32 @llvm.ctpop.i32(i32 %a), !range !2 ret i32 %0 }
      
      





ここで、組み蟌み関数llvm.ctpop.i32が䜿甚されおいるこずがわかりたす。 これは、コヌドに関する高レベルの情報を䜿甚しお、フロント゚ンドによっお既に挿入されおおり、このアヌキテクチャのバック゚ンドはこの機胜を認識しおおり、特別なコマンドで眮き換えるこずができたす。



䟿利なリンク



http://www.drdobbs.com/architecture-and-design/the-design-of-llvm/240001128-Chris Lattner、「The Design of LLVM」

https://youtu.be/bSkpMdDe4g4-Matt Godbolt、「最近私のコンパむラは䜕をしおくれたしたか」

https://youtu.be/ACW-7dktyDk Dmitry Kashitsyn「ロヌフトロリヌバスLLVMの゚むリアシングずベクトル化」



All Articles