关于llvm kaleidoscope: 记一次Debug血泪之路
簡而言之,慎(bu)用(yong)全局變量!
這次debug基本上花了我一周的時間,我基本上是晚上9點30下自習(xí)回然后調(diào)試到11點30,如此反復(fù)一周直到今天周五終于解決了,,以前都聽說前輩們 說盡量不要使用全局變量,我只當個笑話順而過,今天我可能走了前輩們的老路,我實在忍不住要告誡各位請慎用全局變量,如果不當笑話對待這點那這篇文章目的就達到了,后面可以省略了。
以下是可以被省略的正文。上學(xué)期到這學(xué)期始我林林總總寫過幾個編譯器前端,有l(wèi)exyacc自底向上自動生成的也有手寫詞法分析自頂向下的遞歸下降分析,但是還從來沒做過后端,一來是感覺自己差點火候二來也太懶感覺量大繁瑣。這學(xué)期開學(xué)偶然在知乎聽說llvm有成熟的代碼生成優(yōu)化以及到到目標機器的代碼生成,想來自己看了那么多theory還從來沒有實踐過真正的編譯器,說不遺憾肯定是假的,然后我翻了一遍llvm documentation發(fā)現(xiàn)有個極簡編譯器kaleidoscope的demo,于是準備擼起袖子實現(xiàn)一遍,官方demo是linux下的,我用visual
studio 2015實現(xiàn)了一遍,然后稍微把它面向?qū)ο罅艘环栴}就在這里的“稍微”,我只把詞法分析語法分析用面向?qū)ο筮M行表示,其余部分用demo里的靜態(tài)/全局變量/函數(shù)。直到LLVM IR代碼生成都是熟悉的味道熟悉的套路,但是到了chapter4添加了一個優(yōu)化器和JIT解釋器就遇到了九天神坑,首先一大堆LINK ERRORs,好在都在接受范圍內(nèi),編譯了一大堆依賴項后編譯通過了。當我輸入一個表達式"1+2"就出現(xiàn)了nullptr異常,然后我從startup開始很自然的進入parser.parserDriver();
int main() {
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
llvm::InitializeNativeTargetAsmParser();
kaleidoscope::Parser parser;
fprintf(stderr, ">> ");
parser.getNextToken();
theJIT = llvm::make_unique<llvm::orc::KaleidoscopeJIT>();
initializeModuleAndPassManager();
parser.parserDriver();
return 0;
}
進入parserDriver后大概長這個樣子
/// top ::= definition | external | expression | ';'
void kaleidoscope::Parser::parserDriver() {
while (true) {
fprintf(stderr, ">> ");
switch (currentToken) {
case Token::TokenEOF:
return;
case ';': // ignore top-level semicolons.
getNextToken();
break;
case Token::TokenDef:
handleFunctionDefinition();
break;
case Token::TokenExtern:
handleExtern();
break;
default:
handleTopLevelExpression();
break;
}
}
}
這里由于我輸入的是"1+2"在kaleidoscope的定義里面應(yīng)該屬于toplevelexpression,繼續(xù)跟蹤handleTopLevelExpression(),
void kaleidoscope::Parser::handleTopLevelExpression() {
if (auto funcAST = parseTopLevelExpr()) {
if (funcAST->codegen()) {
//omit these codes...
}
}
else {
getNextToken();
}
}
我也不清楚到底是解析表達式錯誤還是代碼生成,先進parserTopLevelExpr下了斷點看了一下函數(shù)正常返回,排除解析錯誤那接下來就是代碼生成
llvm::Function * FunctionAST::codegen() {
auto & p = *(this->funcProto);
funcPrototypeMap[this->funcProto->getFunctionName()] = std::move(this->funcProto);
llvm::Function * theFunction = getSpecifiedFunction(p.getFunctionName());
//omit...
}
逐語句跑了一遍發(fā)現(xiàn)是第三行引發(fā)的異常,斷點顯式getFunctionName沒有問題,那肯定就是函數(shù)問題了,繼續(xù)跟蹤getSpecifiedFunction
static llvm::Function * getSpecifiedFunction(std::string name) {
// First, see if the function has already been added to the current module.
if (auto *F = theModule->getFunction(name))
return F;
// If not, check whether we can codegen the declaration from some existing
// prototype.
auto FI = funcPrototypeMap.find(name);
if (FI != funcPrototypeMap.end())
return FI->second->codegen();
// If no existing prototype exists, return null.
return nullptr;
}
逐語句發(fā)現(xiàn)是對llvm::Module::getFunction的問題,getFunction在module的符號表查詢指定的函數(shù)如果不存在就返回null
Function *Module::getFunction(StringRef Name) const {
return dyn_cast_or_null<Function>(getNamedValue(Name));
}
繼續(xù)getNamedValue()
GlobalValue *Module::getNamedValue(StringRef Name) const {
return cast_or_null<GlobalValue>(getValueSymbolTable().lookup(Name));
}
問題就在這里了,異常顯示的this is nullptr也就是說getValueSymbolTable返回的是nullptr理所當然后面的lookup成員函數(shù)調(diào)用出錯,進入getValueSymbolTable
/// Get the symbol table of global variable and function identifiers
const ValueSymbolTable &getValueSymbolTable() const { return *ValSymTab; }
這是Module類的一個函數(shù),返回private ValueSymbolTable *ValSymTab;而ValSymTab在module初始化的時候會分配內(nèi)存
Module::Module(StringRef MID, LLVMContext &C)
: Context(C), Materializer(), ModuleID(MID), SourceFileName(MID), DL("") {
ValSymTab = new ValueSymbolTable();
NamedMDSymTab = new StringMap<NamedMDNode *>();
Context.addModule(this);
}
但是這里ValSymTab指向的內(nèi)存卻是沒有分配。我想應(yīng)該是堆不夠的問題,我相信我的電腦,沒有為什么,然后剩下的可能就是theModule變量出現(xiàn)了問題。縱觀整個解決方案,用到static std::unique_ptr<llvm::Module> theModule;也就initializeModuleAndPassManager(),getSpecifiedFunction(std::string name),我 先是構(gòu)跟蹤n了itializeModuleAndPassManager(),回到main,斷點顯式theJIT沒有問題(這里多說一句,在windows下KaleidoscopeJIT里面的if
(auto Sym = CompileLayer.findSymbolIn(H, Name, true))應(yīng)該改成false否則會出現(xiàn)問題,這也是個坑),進入initializeModuleAndPassManager()
int main() {
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
llvm::InitializeNativeTargetAsmParser();
kaleidoscope::Parser parser;
fprintf(stderr, ">> ");
parser.getNextToken();
theJIT = llvm::make_unique<llvm::orc::KaleidoscopeJIT>();
initializeModuleAndPassManager();
parser.parserDriver();
return 0;
}
斷點顯示變量都沒問題了,都正常被賦值了,wtf?難道我之前跟蹤錯了?詭異的事情發(fā)生了,我順手給theModule加了個監(jiān)控,雖然在這個initializeModuleAndPassManager里面theModule沒有任何問題,但是進入parser.parserDriver后突然監(jiān)控顯示變量就出現(xiàn)問題了,我真是一臉懵逼,parser.parserDriver()根本沒有對theModule的操作啊,為什么無緣無故變量的值會變,我都不知道看了多少遍源碼,終于發(fā)現(xiàn)AST.h里面的theModule是按照官方demo的寫法是static變量,我隱約記得全局static變量只能在文件內(nèi)使用,而我在codegen的文件內(nèi)直接引用了它,雖然不明白為什么會過編譯但所幸發(fā)現(xiàn)了問題,去掉static后LINKERROR報錯顯示這幾個變量重定義,因為多次include
.h文件變量會多次定義,最后放到.cpp編譯通過輸入"1+2"顯示"evaluate to 3.00000"莫名感動。
回想起這慘痛的debug經(jīng)歷深感慚愧,感覺寫多了業(yè)務(wù)邏輯代碼腦子里好像少了一種思考的東西,忘記了很多基礎(chǔ),遇到問題就無腦baidu google,稍微解決不了就換庫換包, 一直順風(fēng)順水沒怎么自己努力解決過問題,沒想過有些東西換上千百次都不會變。痛定思痛,文以記之。
總結(jié)
以上是生活随笔為你收集整理的关于llvm kaleidoscope: 记一次Debug血泪之路的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一头扎进Node系列 - 目录
- 下一篇: 续航超600公里 吉利雷达纯电皮卡内饰发