上一篇已经成功深入到指令层面了,这一篇就开始使用 IRBuilder 对函数流程进行修改,使得“不安全”版本的除法函数也增加对除数的零检查。因为除法指令分为整数除法、浮点数除法等等,而例如整数除法又可以细分为有符号数除法、无符号数除法,同时每种除法又根据类型不同分为32位除法、64位除法....。这里为了演示效果,我们只实现了32位的有符号除法。仅仅满足了测试代码的要求,如果要真的在生产中实现这个功能,细节上的很多问题还是要仔细分类处理的。
IRBuilder
参考 doc: IRBuilder
除法指令处理函数
添加一个processSignedDiv函数用来处理有符号除法:
我们先获取到指令并转成一个BinaryOperator,二元指令就是加减乘除这些指令的类型,它包含一个代表操作类型的操作码及两个操作数。这里取出它的第二个操作数即除数,然后我们构造两个常量0和1。
接下来我们构造一个IRBuilder,构造参数中的inst代表代码的插入点是inst这条指令之前。然后使用IRBuilder插入一条比较指令,比较除数与0,将这条指令的结果用作跳转条件。
这里我没有使用CreatePHI来创建PHI节点,而是使用了CreateSelect这条指令。其实它们的作用一样,只不过PHI节点更强大,可以分发处理多个分支的选择,而我们的需求比较简单,仅仅是二选一,所以这里就偷懒使用CreateSelect了。select指令根据cond指令的结果来进行判断,如果cond成立,即除数等于零时,选择1为结果,否则选择除数为结果。这样我们就成功进行了除法检查,然后再把这个“新”的,经过处理后的除数,设置到原来的除法指令中去,这样就把整个流程闭合住了。
一点额外的处理
记录处理指令
由于我们是遍历除法指令,然后添加比较指令,修改原除法指令的操作数,所以我们需要记录一下哪些除法指令是已经被处理过的了,否则就陷入无限循环之中了。添加set
测试
检查中间代码结果
编译生成新的插件模块,然后使用系统的clang编译器加载我们的插件编译示例代码为LLVM IR,可以看到我们新添加的cmp指令及select指令已经生效了:
编译并运行测试程序
使用编译器加载插件编译并运行测试程序,可以看到无论是“安全”版本的除法函数,还是“不安全”版本的除法函数,都正确处理了除零检查,没有发生崩溃的问题:
这样,我们这个功能就完成了。如果需要对无符号整数或其它位宽的整数,又或者浮点数进行相同的处理,只需要参考文档加更多的条件判断即可,万变不离其宗。