与 Swift 同乐:玩转编译器玩具(第 7 部分)
2023-09-14 07:03:13
踏入堆栈分配的世界
在本次旅程中,我们将着眼于变量,它们在计算机科学中扮演着至关重要的角色。变量就像存储数据的容器,允许我们在程序中引用和操作它们。然而,为了使用变量,我们需要将它们存储在计算机内存中。对于我们的编译器玩具,我们将使用堆栈——一种先进先出的数据结构——来管理变量的内存分配。
定义变量
要表示变量,我们需要扩展我们的 NamedValue
枚举,为变量添加 var
值:
enum NamedValue {
case int(Int)
case var(Int)
}
变量值类型为 Int
,表明变量可以存储整数。
分配内存
为了在堆栈上为变量分配内存,我们需要一个辅助函数 alloca
。alloca
函数的作用是为给定的变量类型分配内存。我们通过将变量类型作为参数传递给 alloca
函数来调用它:
func alloca(_ type: LLVMType) -> LLVMValue
alloca
函数返回一个 LLVMValue
,代表堆栈上分配的内存地址。
初始化变量
有了 alloca
函数,我们就可以为变量初始化内存了。为此,我们使用 store
指令将变量值存储在分配的内存地址中。store
指令有三个参数:要存储的值、存储位置和存储类型:
func store(_ value: LLVMValue, at: LLVMValue, type: LLVMType)
加载变量
为了从堆栈中加载变量的值,我们使用 load
指令。load
指令将值从指定的内存地址加载到寄存器中:
func load(_ from: LLVMValue, type: LLVMType) -> LLVMValue
load
指令返回一个 LLVMValue
,代表加载的值。
示例代码
让我们来看看一个使用堆栈分配变量的简单示例:
let nameValue = NamedValue.var(10)
let namePtr = alloca(LLVMInt32Type())
store(LLVMInt32(10), at: namePtr, type: LLVMInt32Type())
在上面示例中,我们首先声明一个名为 nameValue
的变量,其值为 10。然后,我们使用 alloca
函数为 nameValue
分配内存,并将结果存储在 namePtr
中。最后,我们使用 store
指令将值 10 存储在 namePtr
指向的内存地址中。
结语
通过支持变量和“=”运算符,我们的编译器玩具向前迈进了一大步。我们探索了如何使用堆栈分配内存、创建 Alloca,以及将变量存储在堆栈中。这些概念对于编写实际编译器至关重要,它们为我们处理更复杂的数据结构和操作奠定了基础。