Commit ed2a5081e1

Andrew Kelley <andrew@ziglang.org>
2021-10-20 23:10:37
stage2: LLVM backend: implement switch_br
1 parent dfb3231
Changed files (4)
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;
 
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.buildCondBr(cond, then_block, else_block);
 
-            self.builder.positionBuilderAtEnd(then_block);
-            try self.genBody(then_body);
+        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(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 {
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();
+    }
+}
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) {