有空再進行編輯,最近有點忙,抱歉html
使用的llvm4.0+Clang4.0的版本,依據的是上次發的llvm4.0和clang4.0源碼安裝的教程http://www.javashuo.com/article/p-dsjpastg-ma.html前端
其中Clang的源碼位於llvm-4.0.0.src/tools/clang/文件夾中,在本文中,咱們的base_dir就是此目錄,即base_dir=llvm-4.0.0.src/tools/clanglinux
Clang 是 LLVM 的一個編譯器前端,是使用C++開發的一個優秀軟件。所以分析clang的源碼,能夠從調試clang的main函數做爲入口開始。windows
使用命令進入gdb模式多線程
$gdb ./clang++app
設置輸入參數函數
(gdb) set args apxy.cu -o apxy --cuda-gpu-arch=sm_50 --cuda-path=/usr/local/cuda -L/usr/local/cuda/lib64/ -lcudart_static -ldl -lrt -pthread源碼分析
這裏有個很關鍵的點,gdb必須設置爲子線程模式,不然一直停留在父線程上,沒法進入想要的函數。(Notice:這個設置僅當次有效,若是須要長期有效,請修改配置文件)ui
(gdb) set follow-fork-mode childthis
在main函數上設置斷點
(gdb) b main
提示Breakpoint 1 at 0x1be2b27: file base_dir/tools/driver/driver.cpp, line 308.
所以Clang的main函數位於base_dir/tools/driver/driver.cpp文件中
使用n單步運行,到了
340 bool ClangCLMode = false;
(gdb)
341 if (TargetAndMode.second == "--driver-mode=cl" ||
的時候,從名字上看這個條件if語句用來判斷是不是ClangCLMode。咱們已知CL是Windows上的標準C++編譯器,而clang是一個多端開源編譯器,一樣支持Windows平臺,所以這一步是判斷環境是否爲windows的CL環境
而後一直沒有執行程序塊,猜測正確
而後繼續單步調試到
374 if (FirstArg != argv.end() && StringRef(*FirstArg).startswith("-cc1")) {
(gdb)
383 bool CanonicalPrefixes = true;
374行是判斷是否有一個參數是-cc1,這個很明顯是一個gcc中經常使用的參數,在編譯文件的時候,不少時候是一個默認的參數,這裏的if判斷應該爲真纔對。在使用clang++直接編譯普通cpp文件的時候,確實爲真,可是,在這裏,比較奇怪的是,程序塊並無執行,條件爲假,這個是調試過程當中一個很奇怪的地方。忽略這裏的問題,繼續向下。
(gdb)
456 std::unique_ptr<Compilation> C(TheDriver.BuildCompilation(argv));
這裏出現了第一個關鍵function,這個function使用咱們設置的args創建了Compilation,咱們作的就是編譯器的源碼分析,所以這裏應該是一個關鍵的部分。s進入這個函數。
Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) 函數頭是這樣的,位於base_dir/lib/Driver/driver.cpp中。
簡略的掃一遍代碼,發現,裏邊先是進行了InputArg的解析,而後根據這些args進行分析究竟是採用了什麼編譯參數。
單步到
// Perform the default argument translations.
DerivedArgList *TranslatedArgs = TranslateInputArgs(*UArgs);
// Owned by the host.
const ToolChain &TC = getToolChain(
*UArgs, computeTargetTriple(*this, DefaultTargetTriple, *UArgs));
// The compilation takes ownership of Args.
Compilation *C = new Compilation(*this, TC, UArgs.release(), TranslatedArgs);
按照註釋中的意思,這裏解析完了參數,創建了全部hostDevice上的Compilation,若是須要關注host端到底解析到了什麼參數,須要關注這以前的代碼
繼續單步向下,遇到
// Populate the tool chains for the offloading devices, if any.
CreateOffloadingDeviceToolChains(*C, Inputs);
這個function從註釋來看,應該是創建從設備(slave device或者說offloading device)
跟進去這個函數,發現函數頭是
void Driver::CreateOffloadingDeviceToolChains(Compilation &C, InputList &Inputs)
// We need to generate a CUDA toolchain if any of the inputs has a CUDA type.
從中間的代碼也能夠清晰的發現,這裏是創建NVIDIA CUDA代碼選項的函數
llvm::Triple CudaTriple(HostTriple.isArch64Bit() ? "nvptx64-nvidia-cuda" : "nvptx-nvidia-cuda");
這個函數退出後,回到BuildCompilation中
// Construct the list of abstract actions to perform for this compilation. On
// MachO targets this uses the driver-driver and universal actions.
if (TC.getTriple().isOSBinFormatMachO())
BuildUniversalActions(*C, C->getDefaultToolChain(), Inputs);
else
BuildActions(*C, C->getArgs(), Inputs, C->getActions());
根據註釋中的內容,這個地方對linux的代碼生成(以前已經判斷出了windows代碼的生成,若是是windows上代碼的生成,不會走到這裏),進行了一個區分,將其分紅了普通linux代碼和macOS,其中macOS 上,使用BuildUniversalActions函數創建Actions
執行完該函數,返回到main函數
457 int Res = 0;
(gdb)
458 SmallVector<std::pair<int, const Command *>, 4> FailingCommands;
(gdb)
459 if (C.get())
(gdb)
460 Res = TheDriver.ExecuteCompilation(*C, FailingCommands);
從函數名字上看這裏應該是執行了編譯過程,跟進去這個函數看一下。
int Driver::ExecuteCompilation(
Compilation &C,
SmallVectorImpl<std::pair<int, const Command *>> &FailingCommands) {
// Just print if -### was present.
if (C.getArgs().hasArg(options::OPT__HASH_HASH_HASH)) {
C.getJobs().Print(llvm::errs(), "\n", true);
return 0;
}
// If there were errors building the compilation, quit now.
if (Diags.hasErrorOccurred())
return 1;
// Set up response file names for each command, if necessary
for (auto &Job : C.getJobs())
setUpResponseFiles(C, Job);
C.ExecuteJobs(C.getJobs(), FailingCommands);
// Remove temp files.
// If the command succeeded, we are done.
if (FailingCommands.empty())
return 0;
從函數體上看,先獲取參數,以後執行Jobs,而後清理臨時文件,是一個很是正常的流程,奇怪的是,執行到這裏後if (FailingCommands.empty()),條件爲真,判斷失敗的FailingCommands是否爲空後,就直接返回到main函數了,中間經歷了比較長的過程,若是沒有設置set follow-fork-mode child這個選項,就一直無法進入真正的編譯過程,在以前的瞭解中,clang中應該有一個parseAST()的函數用來生成AST樹,可是若是不加set follow-fork-mode child這個選項,就是沒法到達這裏,即便在這裏添加斷點也無效。這個問題是使用gdb調試clang編譯CUDA源碼的主要難點。
set follow-fork-mode child是gdb中的一個選項,主要用法是
set follow-fork-mode [parent|child] 用來調試多線程程序中,當子線程創建時,是留在父進程仍是進入子進程。在默認狀況下,一直留在父進程,咱們的調試過程當中,就會致使程序沒法真正的進入編譯的進程中,致使調試失敗。能夠說,clang編譯CUDA程序的時候,先創建編譯器,而後再執行編譯器的時候,是經過fork子進程的方式來新啓動一個編譯器的。
跟進去子進程,從新進入main函數。
此次又將參數解析了一遍,而後運行到了
376 if (MarkEOLs) {
(gdb)
380 return ExecuteCC1Tool(argv, argv[1] + 4);
此次運行到了ExecuteCC1Tool這個函數中,這就和咱們以前想象的同樣了
以後進入了cc1_main這個函數中,此函數位於base_dir/tools/driver/cc1_main.cpp中
一進來就是新建一個編譯實例
173 std::unique_ptr<CompilerInstance> Clang(new CompilerInstance());
從這裏來看,實例化了一個CompilerInstance,這個CompilerInstance是整個編譯器中主要的成員,其類圖以下所示:
從代碼上看,在
197 bool Success = CompilerInvocation::CreateFromArgs(
綁定了調用過程,在
221 Success = ExecuteCompilerInvocation(Clang.get());
執行了編譯器的調用過程,ExecuteCompilerInvocation函數屬於clang類中,這個類位於base_dir\lib\FrontendTool\ExecuteCompilerInvocation.cpp中,而後在
246 std::unique_ptr<FrontendAction> Act(CreateFrontendAction(*Clang));
中建立了FrontedAction,在後邊的
249 bool Success = Clang->ExecuteAction(*Act);
執行了FrontendAction,進入了base_dir/lib/Frontend/CompilerInstance.cpp中的bool CompilerInstance::ExecuteAction(FrontendAction &Act)中,在執行到
914 if (getLangOpts().CUDA && !getFrontendOpts().AuxTriple.empty()) {
(gdb) p getLangOpts().CUDA
$1 = 1
這裏,咱們能夠確定的說,編譯器的編譯選項中是將其當作CUDA程序來編譯,繼續執行
同時,在917行的地方,一樣驗證了咱們的猜測
917 TO->HostTriple = getTarget().getTriple().str();
(gdb) p getTarget().getTriple().str()
$3 = "nvptx64-nvidia-cuda"
以後執行到line946開始作真正的源碼的分析等工做
for (const FrontendInputFile &FIF : getFrontendOpts().Inputs) {
// Reset the ID tables if we are reusing the SourceManager and parsing
// regular files.
if (hasSourceManager() && !Act.isModelParsingAction())
getSourceManager().clearIDTables();
if (Act.BeginSourceFile(*this, FIF)) {
Act.Execute();
Act.EndSourceFile();
}
}
前邊的應該都沒有作太多的工做,主要的應該在Act相關的BeginSourceFile、Execute、EndSourceFile這三個函數內
其中BeginSourceFile函數內判斷了輸入是AST樹仍是源碼,這裏的輸入是IK_CUDA
204 if (Input.getKind() == IK_AST) {
(gdb) p Input.getKind()
$1 = clang::IK_CUDA
後邊完成的是setVirtualFileSystem、createFileManager、createSourceManager、createPreprocessor、createASTContext、CreateWrappedASTConsumer
尤爲是在CreateWrappedASTConsumer中,這裏邊對前端Frontend的Plugin進行了檢測,若是檢測到了Plugin,須要進行Action的添加,這些Action被叫作AfterConsumers。
如今執行結束這個函數,返回到bool CompilerInstance::ExecuteAction中
if (Act.BeginSourceFile(*this, FIF)) {
Act.Execute();
Act.EndSourceFile();
}
進入Execute中,函數代碼位於base_dir/lib/Frontend/FrontendAction.cpp中,函數原型是
void ASTFrontendAction::ExecuteAction(),這個裏邊最重要的就是最後ParseAST( CI.getSema(), CI.getFrontendOpts().ShowStats,CI.getFrontendOpts().SkipFunctionBodies);
這裏就是創建AST樹的地方
以後的EndSourceFile負責EndSourceFileAction和clearOutputFiles