From ed2a5081e1f379cf089f7700a2818db35faadc05 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 20 Oct 2021 14:10:37 -0700 Subject: [PATCH] stage2: LLVM backend: implement switch_br --- src/codegen/llvm.zig | 56 ++++++-- src/codegen/llvm/bindings.zig | 6 + test/behavior/switch.zig | 217 +++++++++++++++++++++++++++++++ test/behavior/switch_stage1.zig | 224 +------------------------------- 4 files changed, 268 insertions(+), 235 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index bcffc1f0a2..5dc0a251bd 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1993,23 +1993,55 @@ pub const FuncGen = struct { const then_block = self.context.appendBasicBlock(self.llvm_func, "Then"); const else_block = self.context.appendBasicBlock(self.llvm_func, "Else"); - { - const prev_block = self.builder.getInsertBlock(); - defer self.builder.positionBuilderAtEnd(prev_block); - - self.builder.positionBuilderAtEnd(then_block); - try self.genBody(then_body); - - self.builder.positionBuilderAtEnd(else_block); - try self.genBody(else_body); - } _ = self.builder.buildCondBr(cond, then_block, else_block); + + self.builder.positionBuilderAtEnd(then_block); + try self.genBody(then_body); + + self.builder.positionBuilderAtEnd(else_block); + try self.genBody(else_body); + + // No need to reset the insert cursor since this instruction is noreturn. return null; } fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - _ = inst; - return self.todo("implement llvm codegen for switch_br", .{}); + const pl_op = self.air.instructions.items(.data)[inst].pl_op; + const cond = try self.resolveInst(pl_op.operand); + const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload); + const else_block = self.context.appendBasicBlock(self.llvm_func, "Else"); + const llvm_switch = self.builder.buildSwitch(cond, else_block, switch_br.data.cases_len); + + var extra_index: usize = switch_br.end; + var case_i: u32 = 0; + + while (case_i < switch_br.data.cases_len) : (case_i += 1) { + const case = self.air.extraData(Air.SwitchBr.Case, extra_index); + const items = @bitCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]); + const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len]; + extra_index = case.end + case.data.items_len + case_body.len; + + const case_block = self.context.appendBasicBlock(self.llvm_func, "Case"); + + for (items) |item| { + const llvm_item = try self.resolveInst(item); + llvm_switch.addCase(llvm_item, case_block); + } + + self.builder.positionBuilderAtEnd(case_block); + try self.genBody(case_body); + } + + self.builder.positionBuilderAtEnd(else_block); + const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len]; + if (else_body.len != 0) { + try self.genBody(else_body); + } else { + _ = self.builder.buildUnreachable(); + } + + // No need to reset the insert cursor since this instruction is noreturn. + return null; } fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 1b7806d22a..c583d0cd00 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -178,6 +178,9 @@ pub const Value = opaque { pub const setInitializer = LLVMSetInitializer; extern fn LLVMSetInitializer(GlobalVar: *const Value, ConstantVal: *const Value) void; + + pub const addCase = LLVMAddCase; + extern fn LLVMAddCase(Switch: *const Value, OnVal: *const Value, Dest: *const BasicBlock) void; }; pub const Type = opaque { @@ -554,6 +557,9 @@ pub const Builder = opaque { pub const buildCondBr = LLVMBuildCondBr; extern fn LLVMBuildCondBr(*const Builder, If: *const Value, Then: *const BasicBlock, Else: *const BasicBlock) *const Value; + pub const buildSwitch = LLVMBuildSwitch; + extern fn LLVMBuildSwitch(*const Builder, V: *const Value, Else: *const BasicBlock, NumCases: c_uint) *const Value; + pub const buildPhi = LLVMBuildPhi; extern fn LLVMBuildPhi(*const Builder, Ty: *const Type, Name: [*:0]const u8) *const Value; diff --git a/test/behavior/switch.zig b/test/behavior/switch.zig index 9524e69540..67cda60958 100644 --- a/test/behavior/switch.zig +++ b/test/behavior/switch.zig @@ -2,3 +2,220 @@ const std = @import("std"); const expect = std.testing.expect; const expectError = std.testing.expectError; const expectEqual = std.testing.expectEqual; + +test "switch with numbers" { + try testSwitchWithNumbers(13); +} + +fn testSwitchWithNumbers(x: u32) !void { + const result = switch (x) { + 1, 2, 3, 4...8 => false, + 13 => true, + else => false, + }; + try expect(result); +} + +test "switch with all ranges" { + try expect(testSwitchWithAllRanges(50, 3) == 1); + try expect(testSwitchWithAllRanges(101, 0) == 2); + try expect(testSwitchWithAllRanges(300, 5) == 3); + try expect(testSwitchWithAllRanges(301, 6) == 6); +} + +fn testSwitchWithAllRanges(x: u32, y: u32) u32 { + return switch (x) { + 0...100 => 1, + 101...200 => 2, + 201...300 => 3, + else => y, + }; +} + +test "implicit comptime switch" { + const x = 3 + 4; + const result = switch (x) { + 3 => 10, + 4 => 11, + 5, 6 => 12, + 7, 8 => 13, + else => 14, + }; + + comptime { + try expect(result + 1 == 14); + } +} + +test "switch on enum" { + const fruit = Fruit.Orange; + nonConstSwitchOnEnum(fruit); +} +const Fruit = enum { + Apple, + Orange, + Banana, +}; +fn nonConstSwitchOnEnum(fruit: Fruit) void { + switch (fruit) { + Fruit.Apple => unreachable, + Fruit.Orange => {}, + Fruit.Banana => unreachable, + } +} + +test "switch statement" { + try nonConstSwitch(SwitchStatementFoo.C); +} +fn nonConstSwitch(foo: SwitchStatementFoo) !void { + const val = switch (foo) { + SwitchStatementFoo.A => @as(i32, 1), + SwitchStatementFoo.B => 2, + SwitchStatementFoo.C => 3, + SwitchStatementFoo.D => 4, + }; + try expect(val == 3); +} +const SwitchStatementFoo = enum { A, B, C, D }; + +test "switch with multiple expressions" { + const x = switch (returnsFive()) { + 1, 2, 3 => 1, + 4, 5, 6 => 2, + else => @as(i32, 3), + }; + try expect(x == 2); +} +fn returnsFive() i32 { + return 5; +} + +const Number = union(enum) { + One: u64, + Two: u8, + Three: f32, +}; + +const number = Number{ .Three = 1.23 }; + +fn returnsFalse() bool { + switch (number) { + Number.One => |x| return x > 1234, + Number.Two => |x| return x == 'a', + Number.Three => |x| return x > 12.34, + } +} +test "switch on const enum with var" { + try expect(!returnsFalse()); +} + +test "switch on type" { + try expect(trueIfBoolFalseOtherwise(bool)); + try expect(!trueIfBoolFalseOtherwise(i32)); +} + +fn trueIfBoolFalseOtherwise(comptime T: type) bool { + return switch (T) { + bool => true, + else => false, + }; +} + +test "switching on booleans" { + try testSwitchOnBools(); + comptime try testSwitchOnBools(); +} + +fn testSwitchOnBools() !void { + try expect(testSwitchOnBoolsTrueAndFalse(true) == false); + try expect(testSwitchOnBoolsTrueAndFalse(false) == true); + + try expect(testSwitchOnBoolsTrueWithElse(true) == false); + try expect(testSwitchOnBoolsTrueWithElse(false) == true); + + try expect(testSwitchOnBoolsFalseWithElse(true) == false); + try expect(testSwitchOnBoolsFalseWithElse(false) == true); +} + +fn testSwitchOnBoolsTrueAndFalse(x: bool) bool { + return switch (x) { + true => false, + false => true, + }; +} + +fn testSwitchOnBoolsTrueWithElse(x: bool) bool { + return switch (x) { + true => false, + else => true, + }; +} + +fn testSwitchOnBoolsFalseWithElse(x: bool) bool { + return switch (x) { + false => true, + else => false, + }; +} + +test "u0" { + var val: u0 = 0; + switch (val) { + 0 => try expect(val == 0), + } +} + +test "undefined.u0" { + var val: u0 = undefined; + switch (val) { + 0 => try expect(val == 0), + } +} + +test "switch with disjoint range" { + var q: u8 = 0; + switch (q) { + 0...125 => {}, + 127...255 => {}, + 126...126 => {}, + } +} + +test "switch variable for range and multiple prongs" { + const S = struct { + fn doTheTest() !void { + var u: u8 = 16; + try doTheSwitch(u); + comptime try doTheSwitch(u); + var v: u8 = 42; + try doTheSwitch(v); + comptime try doTheSwitch(v); + } + fn doTheSwitch(q: u8) !void { + switch (q) { + 0...40 => |x| try expect(x == 16), + 41, 42, 43 => |x| try expect(x == 42), + else => try expect(false), + } + } + }; + _ = S; +} + +var state: u32 = 0; +fn poll() void { + switch (state) { + 0 => { + state = 1; + }, + else => { + state += 1; + }, + } +} + +test "switch on global mutable var isn't constant-folded" { + while (state < 2) { + poll(); + } +} diff --git a/test/behavior/switch_stage1.zig b/test/behavior/switch_stage1.zig index 62afc74d83..5847acb7d2 100644 --- a/test/behavior/switch_stage1.zig +++ b/test/behavior/switch_stage1.zig @@ -3,86 +3,6 @@ const expect = std.testing.expect; const expectError = std.testing.expectError; const expectEqual = std.testing.expectEqual; -test "switch with numbers" { - try testSwitchWithNumbers(13); -} - -fn testSwitchWithNumbers(x: u32) !void { - const result = switch (x) { - 1, 2, 3, 4...8 => false, - 13 => true, - else => false, - }; - try expect(result); -} - -test "switch with all ranges" { - try expect(testSwitchWithAllRanges(50, 3) == 1); - try expect(testSwitchWithAllRanges(101, 0) == 2); - try expect(testSwitchWithAllRanges(300, 5) == 3); - try expect(testSwitchWithAllRanges(301, 6) == 6); -} - -fn testSwitchWithAllRanges(x: u32, y: u32) u32 { - return switch (x) { - 0...100 => 1, - 101...200 => 2, - 201...300 => 3, - else => y, - }; -} - -test "implicit comptime switch" { - const x = 3 + 4; - const result = switch (x) { - 3 => 10, - 4 => 11, - 5, 6 => 12, - 7, 8 => 13, - else => 14, - }; - - comptime { - try expect(result + 1 == 14); - } -} - -test "switch on enum" { - const fruit = Fruit.Orange; - nonConstSwitchOnEnum(fruit); -} -const Fruit = enum { - Apple, - Orange, - Banana, -}; -fn nonConstSwitchOnEnum(fruit: Fruit) void { - switch (fruit) { - Fruit.Apple => unreachable, - Fruit.Orange => {}, - Fruit.Banana => unreachable, - } -} - -test "switch statement" { - try nonConstSwitch(SwitchStatementFoo.C); -} -fn nonConstSwitch(foo: SwitchStatementFoo) !void { - const val = switch (foo) { - SwitchStatementFoo.A => @as(i32, 1), - SwitchStatementFoo.B => 2, - SwitchStatementFoo.C => 3, - SwitchStatementFoo.D => 4, - }; - try expect(val == 3); -} -const SwitchStatementFoo = enum { - A, - B, - C, - D, -}; - test "switch prong with variable" { try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 }); try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 }); @@ -125,49 +45,6 @@ fn testSwitchEnumPtrCapture() !void { } } -test "switch with multiple expressions" { - const x = switch (returnsFive()) { - 1, 2, 3 => 1, - 4, 5, 6 => 2, - else => @as(i32, 3), - }; - try expect(x == 2); -} -fn returnsFive() i32 { - return 5; -} - -const Number = union(enum) { - One: u64, - Two: u8, - Three: f32, -}; - -const number = Number{ .Three = 1.23 }; - -fn returnsFalse() bool { - switch (number) { - Number.One => |x| return x > 1234, - Number.Two => |x| return x == 'a', - Number.Three => |x| return x > 12.34, - } -} -test "switch on const enum with var" { - try expect(!returnsFalse()); -} - -test "switch on type" { - try expect(trueIfBoolFalseOtherwise(bool)); - try expect(!trueIfBoolFalseOtherwise(i32)); -} - -fn trueIfBoolFalseOtherwise(comptime T: type) bool { - return switch (T) { - bool => true, - else => false, - }; -} - test "switch handles all cases of number" { try testSwitchHandleAllCases(); comptime try testSwitchHandleAllCases(); @@ -237,57 +114,6 @@ test "capture value of switch with all unreachable prongs" { try expect(x == 1); } -test "switching on booleans" { - try testSwitchOnBools(); - comptime try testSwitchOnBools(); -} - -fn testSwitchOnBools() !void { - try expect(testSwitchOnBoolsTrueAndFalse(true) == false); - try expect(testSwitchOnBoolsTrueAndFalse(false) == true); - - try expect(testSwitchOnBoolsTrueWithElse(true) == false); - try expect(testSwitchOnBoolsTrueWithElse(false) == true); - - try expect(testSwitchOnBoolsFalseWithElse(true) == false); - try expect(testSwitchOnBoolsFalseWithElse(false) == true); -} - -fn testSwitchOnBoolsTrueAndFalse(x: bool) bool { - return switch (x) { - true => false, - false => true, - }; -} - -fn testSwitchOnBoolsTrueWithElse(x: bool) bool { - return switch (x) { - true => false, - else => true, - }; -} - -fn testSwitchOnBoolsFalseWithElse(x: bool) bool { - return switch (x) { - false => true, - else => false, - }; -} - -test "u0" { - var val: u0 = 0; - switch (val) { - 0 => try expect(val == 0), - } -} - -test "undefined.u0" { - var val: u0 = undefined; - switch (val) { - 0 => try expect(val == 0), - } -} - test "anon enum literal used in switch on union enum" { const Foo = union(enum) { a: i32, @@ -435,54 +261,6 @@ test "switch prongs with cases with identical payload types" { comptime try S.doTheTest(); } -test "switch with disjoint range" { - var q: u8 = 0; - switch (q) { - 0...125 => {}, - 127...255 => {}, - 126...126 => {}, - } -} - -test "switch variable for range and multiple prongs" { - const S = struct { - fn doTheTest() !void { - var u: u8 = 16; - try doTheSwitch(u); - comptime try doTheSwitch(u); - var v: u8 = 42; - try doTheSwitch(v); - comptime try doTheSwitch(v); - } - fn doTheSwitch(q: u8) !void { - switch (q) { - 0...40 => |x| try expect(x == 16), - 41, 42, 43 => |x| try expect(x == 42), - else => try expect(false), - } - } - }; - _ = S; -} - -var state: u32 = 0; -fn poll() void { - switch (state) { - 0 => { - state = 1; - }, - else => { - state += 1; - }, - } -} - -test "switch on global mutable var isn't constant-folded" { - while (state < 2) { - poll(); - } -} - test "switch on pointer type" { const S = struct { const X = struct { @@ -527,7 +305,7 @@ test "switch on error set with single else" { comptime try S.doTheTest(); } -test "while copies its payload" { +test "switch capture copies its payload" { const S = struct { fn doTheTest() !void { var tmp: union(enum) {