简而言之,慎(bu)用(yong)全局变量

这次debug基本上花了我一周的时间,我基本上是晚上9点30下自习回然后调试到11点30,如此反复一周直到今天周五终于解决了,,以前都听说前辈们 说尽量不要使用全局变量,我只当个笑话顺而过,今天我可能走了前辈们的老路,我实在忍不住要告诫各位请慎用全局变量,如果不当笑话对待这点那这篇文章目的就达到了,后面可以省略了。

以下是可以被省略的正文。上学期到这学期始我林林总总写过几个编译器前端,有lexyacc自底向上自动生成的也有手写词法分析自顶向下的递归下降分析,但是还从来没做过后端,一来是感觉自己差点火候二来也太懒感觉量大繁琐。这学期开学偶然在知乎听说llvm有成熟的代码生成优化以及到到目标机器的代码生成,想来自己看了那么多theory还从来没有实践过真正的编译器,说不遗憾肯定是假的,然后我翻了一遍llvm documentation发现有个极简编译器kaleidoscope的demo,于是准备撸起袖子实现一遍,官方demo是linux下的,我用visual
studio 2015实现了一遍,然后稍微把它面向对象了一番。问题就在这里的“稍微”,我只把词法分析语法分析用面向对象进行表示,其余部分用demo里的静态/全局变量/函数。直到LLVM IR代码生成都是熟悉的味道熟悉的套路,但是到了chapter4添加了一个优化器和JIT解释器就遇到了九天神坑,首先一大堆LINK ERRORs,好在都在接受范围内,编译了一大堆依赖项后编译通过了。当我输入一个表达式"1+2"就出现了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的定义里面应该属于toplevelexpression,继续跟踪handleTopLevelExpression(),

void kaleidoscope::Parser::handleTopLevelExpression() {
if (auto funcAST = parseTopLevelExpr()) {
if (funcAST->codegen()) { //omit these codes...
}
}
else {
getNextToken();
}
}

我也不清楚到底是解析表达式错误还是代码生成,先进parserTopLevelExpr下了断点看了一下函数正常返回,排除解析错误那接下来就是代码生成

llvm::Function * FunctionAST::codegen() {
auto & p = *(this->funcProto);
funcPrototypeMap[this->funcProto->getFunctionName()] = std::move(this->funcProto);
llvm::Function * theFunction = getSpecifiedFunction(p.getFunctionName());
//omit...
}

逐语句跑了一遍发现是第三行引发的异常,断点显式getFunctionName没有问题,那肯定就是函数问题了,继续跟踪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;
}

逐语句发现是对llvm::Module::getFunction的问题,getFunction在module的符号表查询指定的函数如果不存在就返回null

Function *Module::getFunction(StringRef Name) const {
return dyn_cast_or_null<Function>(getNamedValue(Name));
}

继续getNamedValue()

GlobalValue *Module::getNamedValue(StringRef Name) const {
return cast_or_null<GlobalValue>(getValueSymbolTable().lookup(Name));
}

问题就在这里了,异常显示的this is nullptr也就是说getValueSymbolTable返回的是nullptr理所当然后面的lookup成员函数调用出错,进入getValueSymbolTable

  /// Get the symbol table of global variable and function identifiers
const ValueSymbolTable &getValueSymbolTable() const { return *ValSymTab; }

这是Module类的一个函数,返回private  ValueSymbolTable *ValSymTab;而ValSymTab在module初始化的时候会分配内存

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指向的内存却是没有分配。我想应该是堆不够的问题,我相信我的电脑,没有为什么,然后剩下的可能就是theModule变量出现了问题。纵观整个解决方案,用到static std::unique_ptr<llvm::Module> theModule;也就initializeModuleAndPassManager(),getSpecifiedFunction(std::string name),我 先是构跟踪n了itializeModuleAndPassManager(),回到main,断点显式theJIT没有问题(这里多说一句,在windows下KaleidoscopeJIT里面的if
(auto Sym = CompileLayer.findSymbolIn(H, Name, true))应该改成false否则会出现问题,这也是个坑),进入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?难道我之前跟踪错了?诡异的事情发生了,我顺手给theModule加了个监控,虽然在这个initializeModuleAndPassManager里面theModule没有任何问题,但是进入parser.parserDriver后突然监控显示变量就出现问题了,我真是一脸懵逼,parser.parserDriver()根本没有对theModule的操作啊,为什么无缘无故变量的值会变,我都不知道看了多少遍源码,终于发现AST.h里面的theModule是按照官方demo的写法是static变量,我隐约记得全局static变量只能在文件内使用,而我在codegen的文件内直接引用了它,虽然不明白为什么会过编译但所幸发现了问题,去掉static后LINKERROR报错显示这几个变量重定义,因为多次include
.h文件变量会多次定义,最后放到.cpp编译通过输入"1+2"显示"evaluate to 3.00000"莫名感动。

回想起这惨痛的debug经历深感惭愧,感觉写多了业务逻辑代码脑子里好像少了一种思考的东西,忘记了很多基础,遇到问题就无脑baidu google,稍微解决不了就换库换包, 一直顺风顺水没怎么自己努力解决过问题,没想过有些东西换上千百次都不会变。痛定思痛,文以记之。

关于llvm kaleidoscope: 记一次Debug血泪之路的更多相关文章

  1. 记一次debug记录:Uncaught SyntaxError: Unexpected token ILLEGAL

    在使用FIS3搭建项目的时候,遇到了一些问题,这里记录下. 这里是发布搭建代码: // 代码发布时 fis.media('qa') .match('*.{js,css,png}', { useHash ...

  2. 记一次Debug过程

    刚刚加入新公司,就迎来第一场战斗,微服务拉入拉出测试. 简单的说,对于接入eureka 和 vi(携程开源的) 应用,在使用发布系统进行发布的时候,会经过这么一个流程   UP —— STARTING ...

  3. 不要使用Android Studio的Git Commit了---->记一次debug

    今天下午写了一些代码,吃晚饭时分用Android Studio commit了一下,不知道有没有选择Commit and push,结果刚才代码出bug我想回滚到上个版本的时候,发现Android S ...

  4. 平板点餐软件编程体会---记我的Android编程之路

    前言 想开发一个平板点餐系统,研究下陈江根大侠分享的一个很高水准的实例,只是个单机版无实用意义. (如需运行源码请回复联系邮箱) 实现 Mysql 数据库+Tomcat WEb服务器,使用Servle ...

  5. 记一次踩坑之路之Ubuntu未导入镜像前配置docke/docker-composer

    更新 apt 包索引与升级 sudo apt-get update sudo apt-get upgrade 安装 apt 依赖包,用于通过HTTPS来获取仓库: sudo apt-get insta ...

  6. u-boot 2011.09 开启debug 调试

    以前做过,现在刚才又想不起来了,这个错误非常的严重. 在这里记一下. debug 调试信息的开启在 include/common.h 有如下宏定义: #ifdef DEBUG #define debu ...

  7. [DEBUG] QAT Nginx for docker 部署时"--with-ld-opt"出错

    layout: post title: [DEBUG] QAT Nginx for docker 部署时"--with-ld-opt"出错 subtitle: 记一次debug经历 ...

  8. jmeter sampler maven项目排错记

    eclipse 创建的maven项目,引入jar包之后出现红色叹号,一直找不到原因,连main方法都无法运行,提示找不到类: 错误: 找不到或无法加载主类 soapsampler.SoapSample ...

  9. slf4j-api、slf4j-log4j12、log4j之间关系

    1. slf4j-api slf4j:Simple Logging Facade for Java,为java提供的简单日志Facade.Facade门面,更底层一点说就是接口.它允许用户以自己的喜好 ...

随机推荐

  1. head first python菜鸟学习笔记(第七章) ——web应用之为数据建模

    问题1. #意思是从athletelist.py中导入AthleteListfrom athletelist import AthleteList 源程序代码 import pickle from a ...

  2. 【JDK1.8】JDK1.8集合源码阅读——TreeMap(二)

    一.前言 在前一篇博客中,我们对TreeMap的继承关系进行了分析,在这一篇里,我们将分析TreeMap的数据结构,深入理解它的排序能力是如何实现的.这一节要有一定的数据结构基础,在阅读下面的之前,推 ...

  3. C#设计模式之十八中介者模式(Mediator Pattern)【行为型】

    一.引言 今天我们开始讲“行为型”设计模式的第五个模式,该模式是[中介者模式],英文名称是:Mediator Pattern.还是老套路,先从名字上来看看.“中介者模式”我第一次看到这个名称,我的理解 ...

  4. jsp加java连接数据库,进行信息输入,并进行初步的拦截判断。

    图形大概这样 按照图片要求设计添加新课程界面.(0.5分) 在后台数据库中建立相应的表结构存储课程信息.(0.5分) 实现新课程添加的功能. 要求判断任课教师为王建民.刘立嘉.刘丹.王辉.杨子光五位教 ...

  5. Code Kata:超级偶数数列 javascript实现

    超级偶数(SuperEven)是指每一位都是偶数的正整数,例如: 0,2,4,6,8,20,22,24,26,28,40,...,88,200,202,... 要求写一个函数,输入项数n,返回数列第n ...

  6. google软件测试之道读后感(二)

    这几天又翻了几页这本书,觉得妙语连珠,关键语录摘抄如下,并补充自己的一些思考: "如果你想要求一个团队去尝试新的事物或者做某些改进,给他们提供一个联系人会更好一些,这个联系人来源于更大的社区 ...

  7. java日期详解

    [TOC] 一.简介 java中的日期处理一直是个问题,没有很好的方式去处理,所以才有第三方框架的地位比如joda. 文章主要对java日期处理的详解,用1.8可以不用joda. 1. 相关概念 首先 ...

  8. css走过的坑

    css盒模型 1.内联元素 设置宽高无效.margin左右有效上下无效.padding都有效 会被当做字体所以内联之间有间隙 父级元素要设置font-size:0; 内联元素:a.b.button.e ...

  9. 【译】使用Jwt身份认证保护 Asp.Net Core Web Api

    原文出自Rui Figueiredo的博客,原文链接<Secure a Web Api in ASP.NET Core> 摘要:这边文章阐述了如何使用 Json Web Token (Jw ...

  10. Python3 词汇助手 有道翻译助手 有道导出文件格式转换

    根据有道翻译软件的功能,结合实际用途,基于Python3.6写了一个有道翻译助手软件. 测试文件及源代码已上传至:https://github.com/MMMMMichael/Translation- ...