mirror of
https://github.com/ziglang/zig.git
synced 2026-01-21 23:05:24 +00:00
stage2: LLVM backend: implement switch_br
This commit is contained in:
parent
dfb3231959
commit
ed2a5081e1
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user