函数原型和函数的代码生成比较繁琐,相关代码不及表达式的代码生成来得优雅,不过却刚好可以用于演示一些重要概念。首先,我们来看看函数原型的代码生成过程:函数定义和外部函数声明都依赖于它。这部分代码一开始是这样的:
Function* PrototypeAST::codegen() { |
短短几行暗藏玄机。首先需要注意的是该函数的返回值类型是Function*
而不是Value*
。“函数原型”描述的是函数的对外接口(而不是某表达式计算出的值),返回代码生成过程中与之相对应的LLVM Function
自然也合情合理。
FunctionType::get
调用用于为给定的函数原型创建对应的FunctionType
对象。在Kaleidoscope中,函数的参数全部都是double
,因此第一行创建了一个包含“N”个LLVM double
的vector
。随后,FunctionType::get
方法以这“N”个double
为参数类型、以单个double
为返回值类型,创建出一个参数个数不可变(最后一个参数false
就是这个意思)的函数类型。注意,和常数一样,LLVM中的类型对象也是单例,应该用“get
”而不是“new
”来获取。
最后一行实际上创建的是与该函数原型相对应的函数。其中包含了类型、链接方式和函数名等信息,还指定了该函数待插入的模块。ExternalLinkage
表示该函数可能定义于当前模块之外,且/或可以被当前模块之外的函数调用。Name
是用户指定的函数名:如上述代码中的调用所示,既然将函数定义在“TheModule
”内,函数名自然也注册在“TheModule
”的符号表内。
// Set names for all arguments. |
最后,我们根据Prototype中给出的名称设置每个函数参数的名称。
此步骤并非严格必要,但保持名称一致会使IR更具可读性,并允许后续代码直接引用其名称的参数,而不必在Prototype AST中查找它们。
在这一点上,我们有一个没有身体的功能原型。 这是LLVM
IR表示函数声明的方式。 对于Kaleidoscope中的外部陈述,这是我们需要的。 但是对于函数定义,我们需要codegen并附加一个函数体。
Function *FunctionAST::codegen() { |
对于函数定义,我们首先在TheModule的符号表中搜索此函数的现有版本,以防已经使用extern
语句创建了一个。
如果Module::getFunction
返回null
,则不存在先前版本,因此我们将从Prototype中编译一个。在任何一种情况下,我们都要在开始之前断言函数是空的(即还没有正文)。
// Create a new basic block to start insertion into. |
现在该开始设置Builder
对象了。第一行新建了一个名为entry
的基本块对象,稍后该对象将被插入TheFunction
。第二行告诉Builder
,后续的新指令应该插至刚刚新建的基本块的末尾处。LLVM基本块是用于定义控制流图(Control Flow Graph)的重要部件。当前我们还不涉及到控制流,所以所有的函数都只有一个基本块。接下来,我们将函数参数添加到NamedValues映射(首先清除它之后),以便VariableExprAST节点可以访问它们。
if (Value *RetVal = Body->codegen()) { |
一旦设置了插入点并填充了NamedValues映射,我们就会调用codegen()
方法来获取函数的根表达式。
如果没有发生错误,则会发出代码以将表达式计算到条目块中并返回计算的值。
假设没有错误,我们然后创建一个LLVM ret
指令,完成该功能。
构建函数后,我们调用LLVM提供的verifyFunction
。
此函数对生成的代码执行各种一致性检查,以确定我们的编译器是否正在执行所有操作。
使用它很重要:它可以捕获很多错误。 功能完成并验证后,我们将其返回。
// Error reading body, remove function.
TheFunction->eraseFromParent();
return nullptr;
}
这里留下的唯一一件事是处理错误案例。
为简单起见,我们仅通过删除使用eraseFromParent
方法生成的函数来处理此问题。
这允许用户重新定义之前错误输入的函数:如果我们没有删除它,它将存在于符号表中,带有正文,从而阻止将来重新定义。
但是,他的代码确实存在错误:如果FunctionAST::codegen()
方法找到现有的IR函数,则它不会根据定义自己的原型验证其签名。
这意味着较早的extern
声明将优先于函数定义的签名,这可能导致codegen失败,例如,如果函数参数的名称不同。
有很多方法可以解决这个问题,看看你能想出什么! 这是一个测试用例:
extern foo(a); # ok, defines foo.
def foo(b) b; # Error: Unknown variable name. (decl using 'a' takes precedence).