Commit 5d5db833f2

Timon Kruiper <timonkruiper@gmail.com>
2021-01-05 18:35:42
stage2: hoist alloca instructions to top of function in LLVM backend
This way the generated code only has to setup the stack size at the beginning of a function and this improves codegen. Fixes #7689 ``` fn foo() void {} export fn hello(z: i8) void { var x: i16 = undefined; foo(); var y: i32 = 1; y += z; } ``` llvm-ir: ``` define void @hello(i8 %0) { Entry: %1 = alloca i8, align 1 %2 = alloca i16, align 2 %3 = alloca i32, align 4 store i8 %0, i8* %1, align 1 %4 = load i8, i8* %1, align 1 store i16 undef, i16* %2, align 2 call void @foo() store i32 1, i32* %3, align 4 %5 = load i32, i32* %3, align 4 %6 = sext i8 %4 to i32 %7 = add nsw i32 %5, %6 store i32 %7, i32* %3, align 4 ret void } ```
1 parent 1149cd5
src/llvm_backend.zig
@@ -148,6 +148,9 @@ pub const LLVMIRModule = struct {
     gpa: *Allocator,
     err_msg: ?*Compilation.ErrorMsg = null,
 
+    // TODO: The fields below should really move into a different struct,
+    //       because they are only valid when generating a function
+
     /// This stores the LLVM values used in a function, such that they can be
     /// referred to in other instructions. This table is cleared before every function is generated.
     func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value) = .{},
@@ -156,6 +159,11 @@ pub const LLVMIRModule = struct {
     args: []*const llvm.Value = &[_]*const llvm.Value{},
     arg_index: usize = 0,
 
+    entry_block: *const llvm.BasicBlock = undefined,
+    /// This fields stores the last alloca instruction, such that we can append more alloca instructions
+    /// to the top of the function.
+    latest_alloca_inst: ?*const llvm.Value = null,
+
     pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*LLVMIRModule {
         const self = try allocator.create(LLVMIRModule);
         errdefer allocator.destroy(self);
@@ -332,8 +340,9 @@ pub const LLVMIRModule = struct {
                 bb.deleteBasicBlock();
             }
 
-            const entry_block = llvm_func.appendBasicBlock("Entry");
-            self.builder.positionBuilderAtEnd(entry_block);
+            self.entry_block = llvm_func.appendBasicBlock("Entry");
+            self.builder.positionBuilderAtEnd(self.entry_block);
+            self.latest_alloca_inst = null;
 
             const instructions = func.body.instructions;
             for (instructions) |inst| {
@@ -476,7 +485,7 @@ pub const LLVMIRModule = struct {
         const arg_val = self.args[self.arg_index];
         self.arg_index += 1;
 
-        const ptr_val = self.builder.buildAlloca(try self.getLLVMType(inst.base.ty, inst.base.src), "");
+        const ptr_val = self.buildAlloca(try self.getLLVMType(inst.base.ty, inst.base.src));
         _ = self.builder.buildStore(arg_val, ptr_val);
         return self.builder.buildLoad(ptr_val, "");
     }
@@ -488,7 +497,29 @@ pub const LLVMIRModule = struct {
 
         // TODO: figure out a way to get the name of the var decl.
         // TODO: set alignment and volatile
-        return self.builder.buildAlloca(try self.getLLVMType(pointee_type, inst.base.src), "");
+        return self.buildAlloca(try self.getLLVMType(pointee_type, inst.base.src));
+    }
+
+    /// Use this instead of builder.buildAlloca, because this function makes sure to
+    /// put the alloca instruction at the top of the function!
+    fn buildAlloca(self: *LLVMIRModule, t: *const llvm.Type) *const llvm.Value {
+        if (self.latest_alloca_inst) |latest_alloc| {
+            // builder.positionBuilder adds it before the instruction,
+            // but we want to put it after the last alloca instruction.
+            self.builder.positionBuilder(self.entry_block, latest_alloc.getNextInstruction().?);
+        } else {
+            // There might have been other instructions emitted before the
+            // first alloca has been generated. However the alloca should still
+            // be first in the function.
+            if (self.entry_block.getFirstInstruction()) |first_inst| {
+                self.builder.positionBuilder(self.entry_block, first_inst);
+            }
+        }
+        defer self.builder.positionBuilderAtEnd(self.entry_block);
+
+        const val = self.builder.buildAlloca(t, "");
+        self.latest_alloca_inst = val;
+        return val;
     }
 
     fn genStore(self: *LLVMIRModule, inst: *Inst.BinOp) !?*const llvm.Value {
src/llvm_bindings.zig
@@ -17,6 +17,9 @@ pub const Value = opaque {
     pub const getFirstBasicBlock = LLVMGetFirstBasicBlock;
     extern fn LLVMGetFirstBasicBlock(Fn: *const Value) ?*const BasicBlock;
 
+    pub const getNextInstruction = LLVMGetNextInstruction;
+    extern fn LLVMGetNextInstruction(Inst: *const Value) ?*const Value;
+
     // Helper functions
     // TODO: Do we want to put these functions here? It allows for convienient function calls
     //       on Value: llvm_fn.addFnAttr("noreturn")
@@ -138,6 +141,9 @@ pub const Builder = opaque {
     pub const disposeBuilder = LLVMDisposeBuilder;
     extern fn LLVMDisposeBuilder(Builder: *const Builder) void;
 
+    pub const positionBuilder = LLVMPositionBuilder;
+    extern fn LLVMPositionBuilder(Builder: *const Builder, Block: *const BasicBlock, Instr: *const Value) void;
+
     pub const positionBuilderAtEnd = LLVMPositionBuilderAtEnd;
     extern fn LLVMPositionBuilderAtEnd(Builder: *const Builder, Block: *const BasicBlock) void;
 
@@ -196,6 +202,9 @@ pub const Builder = opaque {
 pub const BasicBlock = opaque {
     pub const deleteBasicBlock = LLVMDeleteBasicBlock;
     extern fn LLVMDeleteBasicBlock(BB: *const BasicBlock) void;
+
+    pub const getFirstInstruction = LLVMGetFirstInstruction;
+    extern fn LLVMGetFirstInstruction(BB: *const BasicBlock) ?*const Value;
 };
 
 pub const TargetMachine = opaque {