authorgravatar for timonkruiper@gmail.comTimon Kruiper <timonkruiper@gmail.com> 2021-01-08 19:28:34+01:00
committergravatar for timonkruiper@gmail.comTimon Kruiper <timonkruiper@gmail.com> 2021-01-08 19:30:52+01:00
log56c059077cdaf71220cb44f06902051a34ffd31d
tree6caa61bd74fdb294c293e025f9f76ae3580aac79
parent3715ed7b54d4382a4495eb041ff3f9ad987bacfb

stage2: add initial impl of control flow in LLVM backend

The following TZIR instrutions have been implemented in the backend: - all cmp operators (lt, lte, gt, gte, eq, neq) - block - br - condbr The following LLVMIR is generated for a simple assert function: ``` define void @assert(i1 %0) { Entry: %1 = alloca i1, align 1 store i1 %0, i1* %1, align 1 %2 = load i1, i1* %1, align 1 %3 = xor i1 %2, true br i1 %3, label %Then, label %Else Then: ; preds = %Entry call void @llvm.debugtrap() unreachable Else: ; preds = %Entry br label %Block Block: ; preds = %Else ret void } ``` See tests for more examples.

3 files changed, 256 insertions(+), 26 deletions(-)

src/codegen/llvm.zig+151-26
......@@ -5,6 +5,7 @@ const Compilation = @import("../Compilation.zig");
55const llvm = @import("llvm/bindings.zig");
66const link = @import("../link.zig");
77const log = std.log.scoped(.codegen);
8const math = std.math;
89
910const Module = @import("../Module.zig");
1011const TypedValue = @import("../TypedValue.zig");
......@@ -154,6 +155,8 @@ pub const LLVMIRModule = struct {
154155
155156 /// This stores the LLVM values used in a function, such that they can be
156157 /// referred to in other instructions. This table is cleared before every function is generated.
158 /// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks
159 /// in here, however if a block ends, the instructions can be thrown away.
157160 func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value) = .{},
158161
159162 /// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction.
......@@ -165,6 +168,18 @@ pub const LLVMIRModule = struct {
165168 /// to the top of the function.
166169 latest_alloca_inst: ?*const llvm.Value = null,
167170
171 llvm_func: *const llvm.Value = undefined,
172
173 /// This data structure is used to implement breaking to blocks.
174 blocks: std.AutoHashMapUnmanaged(*Inst.Block, struct {
175 parent_bb: *const llvm.BasicBlock,
176 break_bbs: *BreakBasicBlocks,
177 break_vals: *BreakValues,
178 }) = .{},
179
180 const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock);
181 const BreakValues = std.ArrayListUnmanaged(*const llvm.Value);
182
168183 pub fn create(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*LLVMIRModule {
169184 const self = try allocator.create(LLVMIRModule);
170185 errdefer allocator.destroy(self);
......@@ -252,6 +267,8 @@ pub const LLVMIRModule = struct {
252267 self.func_inst_table.deinit(self.gpa);
253268 self.gpa.free(self.object_path);
254269
270 self.blocks.deinit(self.gpa);
271
255272 allocator.destroy(self);
256273 }
257274
......@@ -349,32 +366,9 @@ pub const LLVMIRModule = struct {
349366 self.entry_block = self.context.appendBasicBlock(llvm_func, "Entry");
350367 self.builder.positionBuilderAtEnd(self.entry_block);
351368 self.latest_alloca_inst = null;
369 self.llvm_func = llvm_func;
352370
353 const instructions = func.body.instructions;
354 for (instructions) |inst| {
355 const opt_llvm_val: ?*const llvm.Value = switch (inst.tag) {
356 .add => try self.genAdd(inst.castTag(.add).?),
357 .alloc => try self.genAlloc(inst.castTag(.alloc).?),
358 .arg => try self.genArg(inst.castTag(.arg).?),
359 .bitcast => try self.genBitCast(inst.castTag(.bitcast).?),
360 .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?),
361 .call => try self.genCall(inst.castTag(.call).?),
362 .intcast => try self.genIntCast(inst.castTag(.intcast).?),
363 .load => try self.genLoad(inst.castTag(.load).?),
364 .not => try self.genNot(inst.castTag(.not).?),
365 .ret => try self.genRet(inst.castTag(.ret).?),
366 .retvoid => self.genRetVoid(inst.castTag(.retvoid).?),
367 .store => try self.genStore(inst.castTag(.store).?),
368 .sub => try self.genSub(inst.castTag(.sub).?),
369 .unreach => self.genUnreach(inst.castTag(.unreach).?),
370 .dbg_stmt => blk: {
371 // TODO: implement debug info
372 break :blk null;
373 },
374 else => |tag| return self.fail(src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}),
375 };
376 if (opt_llvm_val) |llvm_val| try self.func_inst_table.putNoClobber(self.gpa, inst, llvm_val);
377 }
371 try self.genBody(func.body);
378372 } else if (typed_value.val.castTag(.extern_fn)) |extern_fn| {
379373 _ = try self.resolveLLVMFunction(extern_fn.data, src);
380374 } else {
......@@ -382,6 +376,42 @@ pub const LLVMIRModule = struct {
382376 }
383377 }
384378
379 fn genBody(self: *LLVMIRModule, body: ir.Body) error{ OutOfMemory, CodegenFail }!void {
380 for (body.instructions) |inst| {
381 const opt_value = switch (inst.tag) {
382 .add => try self.genAdd(inst.castTag(.add).?),
383 .alloc => try self.genAlloc(inst.castTag(.alloc).?),
384 .arg => try self.genArg(inst.castTag(.arg).?),
385 .bitcast => try self.genBitCast(inst.castTag(.bitcast).?),
386 .block => try self.genBlock(inst.castTag(.block).?),
387 .br => try self.genBr(inst.castTag(.br).?),
388 .breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?),
389 .call => try self.genCall(inst.castTag(.call).?),
390 .cmp_eq => try self.genCmp(inst.castTag(.cmp_eq).?, .eq),
391 .cmp_gt => try self.genCmp(inst.castTag(.cmp_gt).?, .gt),
392 .cmp_gte => try self.genCmp(inst.castTag(.cmp_gte).?, .gte),
393 .cmp_lt => try self.genCmp(inst.castTag(.cmp_lt).?, .lt),
394 .cmp_lte => try self.genCmp(inst.castTag(.cmp_lte).?, .lte),
395 .cmp_neq => try self.genCmp(inst.castTag(.cmp_neq).?, .neq),
396 .condbr => try self.genCondBr(inst.castTag(.condbr).?),
397 .intcast => try self.genIntCast(inst.castTag(.intcast).?),
398 .load => try self.genLoad(inst.castTag(.load).?),
399 .not => try self.genNot(inst.castTag(.not).?),
400 .ret => try self.genRet(inst.castTag(.ret).?),
401 .retvoid => self.genRetVoid(inst.castTag(.retvoid).?),
402 .store => try self.genStore(inst.castTag(.store).?),
403 .sub => try self.genSub(inst.castTag(.sub).?),
404 .unreach => self.genUnreach(inst.castTag(.unreach).?),
405 .dbg_stmt => blk: {
406 // TODO: implement debug info
407 break :blk null;
408 },
409 else => |tag| return self.fail(inst.src, "TODO implement LLVM codegen for Zir instruction: {}", .{tag}),
410 };
411 if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val);
412 }
413 }
414
385415 fn genCall(self: *LLVMIRModule, inst: *Inst.Call) !?*const llvm.Value {
386416 if (inst.func.value()) |func_value| {
387417 const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn|
......@@ -436,6 +466,99 @@ pub const LLVMIRModule = struct {
436466 return null;
437467 }
438468
469 fn genCmp(self: *LLVMIRModule, inst: *Inst.BinOp, op: math.CompareOperator) !?*const llvm.Value {
470 const lhs = try self.resolveInst(inst.lhs);
471 const rhs = try self.resolveInst(inst.rhs);
472
473 if (!inst.base.ty.isInt())
474 if (inst.base.ty.tag() != .bool)
475 return self.fail(inst.base.src, "TODO implement 'genCmp' for type {}", .{inst.base.ty});
476
477 const is_signed = inst.base.ty.isSignedInt();
478 const operation = switch (op) {
479 .eq => .EQ,
480 .neq => .NE,
481 .lt => @as(llvm.IntPredicate, if (is_signed) .SLT else .ULT),
482 .lte => @as(llvm.IntPredicate, if (is_signed) .SLE else .ULE),
483 .gt => @as(llvm.IntPredicate, if (is_signed) .SGT else .UGT),
484 .gte => @as(llvm.IntPredicate, if (is_signed) .SGE else .UGE),
485 };
486
487 return self.builder.buildICmp(operation, lhs, rhs, "");
488 }
489
490 fn genBlock(self: *LLVMIRModule, inst: *Inst.Block) !?*const llvm.Value {
491 const parent_bb = self.context.createBasicBlock("Block");
492
493 // 5 breaks to a block seems like a reasonable default.
494 var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa, 5);
495 var break_vals = try BreakValues.initCapacity(self.gpa, 5);
496 try self.blocks.putNoClobber(self.gpa, inst, .{
497 .parent_bb = parent_bb,
498 .break_bbs = &break_bbs,
499 .break_vals = &break_vals,
500 });
501 defer {
502 self.blocks.removeAssertDiscard(inst);
503 break_bbs.deinit(self.gpa);
504 break_vals.deinit(self.gpa);
505 }
506
507 try self.genBody(inst.body);
508
509 self.llvm_func.appendExistingBasicBlock(parent_bb);
510 self.builder.positionBuilderAtEnd(parent_bb);
511
512 // If the block does not return a value, we dont have to create a phi node.
513 if (!inst.base.ty.hasCodeGenBits()) return null;
514
515 const phi_node = self.builder.buildPhi(try self.getLLVMType(inst.base.ty, inst.base.src), "");
516 phi_node.addIncoming(
517 break_vals.items.ptr,
518 break_bbs.items.ptr,
519 @intCast(c_uint, break_vals.items.len),
520 );
521 return phi_node;
522 }
523
524 fn genBr(self: *LLVMIRModule, inst: *Inst.Br) !?*const llvm.Value {
525 // Get the block that we want to break to.
526 var block = self.blocks.get(inst.block).?;
527 _ = self.builder.buildBr(block.parent_bb);
528
529 // If the break doesn't break a value, then we don't have to add
530 // the values to the lists.
531 if (!inst.operand.ty.hasCodeGenBits()) return null;
532
533 // For the phi node, we need the basic blocks and the values of the
534 // break instructions.
535 try block.break_bbs.append(self.gpa, self.builder.getInsertBlock());
536
537 const val = try self.resolveInst(inst.operand);
538 try block.break_vals.append(self.gpa, val);
539
540 return null;
541 }
542
543 fn genCondBr(self: *LLVMIRModule, inst: *Inst.CondBr) !?*const llvm.Value {
544 const condition_value = try self.resolveInst(inst.condition);
545
546 const then_block = self.context.appendBasicBlock(self.llvm_func, "Then");
547 const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");
548 {
549 const prev_block = self.builder.getInsertBlock();
550 defer self.builder.positionBuilderAtEnd(prev_block);
551
552 self.builder.positionBuilderAtEnd(then_block);
553 try self.genBody(inst.then_body);
554
555 self.builder.positionBuilderAtEnd(else_block);
556 try self.genBody(inst.else_body);
557 }
558 _ = self.builder.buildCondBr(condition_value, then_block, else_block);
559 return null;
560 }
561
439562 fn genNot(self: *LLVMIRModule, inst: *Inst.UnOp) !?*const llvm.Value {
440563 return self.builder.buildNot(try self.resolveInst(inst.operand), "");
441564 }
......@@ -509,6 +632,9 @@ pub const LLVMIRModule = struct {
509632 /// Use this instead of builder.buildAlloca, because this function makes sure to
510633 /// put the alloca instruction at the top of the function!
511634 fn buildAlloca(self: *LLVMIRModule, t: *const llvm.Type) *const llvm.Value {
635 const prev_block = self.builder.getInsertBlock();
636 defer self.builder.positionBuilderAtEnd(prev_block);
637
512638 if (self.latest_alloca_inst) |latest_alloc| {
513639 // builder.positionBuilder adds it before the instruction,
514640 // but we want to put it after the last alloca instruction.
......@@ -521,7 +647,6 @@ pub const LLVMIRModule = struct {
521647 self.builder.positionBuilder(self.entry_block, first_inst);
522648 }
523649 }
524 defer self.builder.positionBuilderAtEnd(self.entry_block);
525650
526651 const val = self.builder.buildAlloca(t, "");
527652 self.latest_alloca_inst = val;
src/codegen/llvm/bindings.zig+34
......@@ -24,6 +24,9 @@ pub const Context = opaque {
2424 pub const constString = LLVMConstStringInContext;
2525 extern fn LLVMConstStringInContext(C: *const Context, Str: [*]const u8, Length: c_uint, DontNullTerminate: LLVMBool) *const Value;
2626
27 pub const createBasicBlock = LLVMCreateBasicBlockInContext;
28 extern fn LLVMCreateBasicBlockInContext(C: *const Context, Name: [*:0]const u8) *const BasicBlock;
29
2730 pub const appendBasicBlock = LLVMAppendBasicBlockInContext;
2831 extern fn LLVMAppendBasicBlockInContext(C: *const Context, Fn: *const Value, Name: [*:0]const u8) *const BasicBlock;
2932
......@@ -38,6 +41,12 @@ pub const Value = opaque {
3841 pub const getFirstBasicBlock = LLVMGetFirstBasicBlock;
3942 extern fn LLVMGetFirstBasicBlock(Fn: *const Value) ?*const BasicBlock;
4043
44 pub const appendExistingBasicBlock = LLVMAppendExistingBasicBlock;
45 extern fn LLVMAppendExistingBasicBlock(Fn: *const Value, BB: *const BasicBlock) void;
46
47 pub const addIncoming = LLVMAddIncoming;
48 extern fn LLVMAddIncoming(PhiNode: *const Value, IncomingValues: [*]*const Value, IncomingBlocks: [*]*const BasicBlock, Count: c_uint) void;
49
4150 pub const getNextInstruction = LLVMGetNextInstruction;
4251 extern fn LLVMGetNextInstruction(Inst: *const Value) ?*const Value;
4352};
......@@ -183,6 +192,31 @@ pub const Builder = opaque {
183192
184193 pub const buildInBoundsGEP = LLVMBuildInBoundsGEP;
185194 extern fn LLVMBuildInBoundsGEP(B: *const Builder, Pointer: *const Value, Indices: [*]*const Value, NumIndices: c_uint, Name: [*:0]const u8) *const Value;
195
196 pub const buildICmp = LLVMBuildICmp;
197 extern fn LLVMBuildICmp(*const Builder, Op: IntPredicate, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
198
199 pub const buildBr = LLVMBuildBr;
200 extern fn LLVMBuildBr(*const Builder, Dest: *const BasicBlock) *const Value;
201
202 pub const buildCondBr = LLVMBuildCondBr;
203 extern fn LLVMBuildCondBr(*const Builder, If: *const Value, Then: *const BasicBlock, Else: *const BasicBlock) *const Value;
204
205 pub const buildPhi = LLVMBuildPhi;
206 extern fn LLVMBuildPhi(*const Builder, Ty: *const Type, Name: [*:0]const u8) *const Value;
207};
208
209pub const IntPredicate = extern enum {
210 EQ = 32,
211 NE = 33,
212 UGT = 34,
213 UGE = 35,
214 ULT = 36,
215 ULE = 37,
216 SGT = 38,
217 SGE = 39,
218 SLT = 40,
219 SLE = 41,
186220};
187221
188222pub const BasicBlock = opaque {
test/stage2/llvm.zig+71
......@@ -40,4 +40,75 @@ pub fn addCases(ctx: *TestContext) !void {
4040 \\}
4141 , "hello world!" ++ std.cstr.line_sep);
4242 }
43
44 {
45 var case = ctx.exeUsingLlvmBackend("simple if statement", linux_x64);
46
47 case.addCompareOutput(
48 \\fn add(a: i32, b: i32) i32 {
49 \\ return a + b;
50 \\}
51 \\
52 \\fn assert(ok: bool) void {
53 \\ if (!ok) unreachable;
54 \\}
55 \\
56 \\export fn main() c_int {
57 \\ assert(add(1,2) == 3);
58 \\ return 0;
59 \\}
60 , "");
61 }
62
63 {
64 var case = ctx.exeUsingLlvmBackend("blocks", linux_x64);
65
66 case.addCompareOutput(
67 \\fn assert(ok: bool) void {
68 \\ if (!ok) unreachable;
69 \\}
70 \\
71 \\fn foo(ok: bool) i32 {
72 \\ const val: i32 = blk: {
73 \\ var x: i32 = 1;
74 \\ if (!ok) break :blk x + 9;
75 \\ break :blk x + 19;
76 \\ };
77 \\ return val + 10;
78 \\}
79 \\
80 \\export fn main() c_int {
81 \\ assert(foo(false) == 20);
82 \\ assert(foo(true) == 30);
83 \\ return 0;
84 \\}
85 , "");
86 }
87
88 {
89 var case = ctx.exeUsingLlvmBackend("nested blocks", linux_x64);
90
91 case.addCompareOutput(
92 \\fn assert(ok: bool) void {
93 \\ if (!ok) unreachable;
94 \\}
95 \\
96 \\fn foo(ok: bool) i32 {
97 \\ var val: i32 = blk: {
98 \\ const val2: i32 = another: {
99 \\ if (!ok) break :blk 10;
100 \\ break :another 10;
101 \\ };
102 \\ break :blk val2 + 10;
103 \\ };
104 \\ return val;
105 \\}
106 \\
107 \\export fn main() c_int {
108 \\ assert(foo(false) == 10);
109 \\ assert(foo(true) == 20);
110 \\ return 0;
111 \\}
112 , "");
113 }
43114}