Commit ed2a5081e1
Changed files (4)
src
codegen
test
behavior
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) {