authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-20 14:10:37-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-20 14:10:37-07:00
loged2a5081e1f379cf089f7700a2818db35faadc05
treedb42e8cdfddcd712d7f3f01fd4bcd015f9a20d6d
parentdfb3231959bb340d260ddbec2b8eabfb5063c1bf

stage2: LLVM backend: implement switch_br


4 files changed, 267 insertions(+), 234 deletions(-)

src/codegen/llvm.zig+43-11
...@@ -1993,23 +1993,55 @@ pub const FuncGen = struct {...@@ -1993,23 +1993,55 @@ pub const FuncGen = struct {
19931993
1994 const then_block = self.context.appendBasicBlock(self.llvm_func, "Then");1994 const then_block = self.context.appendBasicBlock(self.llvm_func, "Then");
1995 const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");1995 const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");
1996 {1996 _ = self.builder.buildCondBr(cond, then_block, else_block);
1997 const prev_block = self.builder.getInsertBlock();
1998 defer self.builder.positionBuilderAtEnd(prev_block);
19991997
2000 self.builder.positionBuilderAtEnd(then_block);1998 self.builder.positionBuilderAtEnd(then_block);
2001 try self.genBody(then_body);1999 try self.genBody(then_body);
20022000
2003 self.builder.positionBuilderAtEnd(else_block);2001 self.builder.positionBuilderAtEnd(else_block);
2004 try self.genBody(else_body);2002 try self.genBody(else_body);
2005 }2003
2006 _ = self.builder.buildCondBr(cond, then_block, else_block);2004 // No need to reset the insert cursor since this instruction is noreturn.
2007 return null;2005 return null;
2008 }2006 }
20092007
2010 fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {2008 fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
2011 _ = inst;2009 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
2012 return self.todo("implement llvm codegen for switch_br", .{});2010 const cond = try self.resolveInst(pl_op.operand);
2011 const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
2012 const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");
2013 const llvm_switch = self.builder.buildSwitch(cond, else_block, switch_br.data.cases_len);
2014
2015 var extra_index: usize = switch_br.end;
2016 var case_i: u32 = 0;
2017
2018 while (case_i < switch_br.data.cases_len) : (case_i += 1) {
2019 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
2020 const items = @bitCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
2021 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
2022 extra_index = case.end + case.data.items_len + case_body.len;
2023
2024 const case_block = self.context.appendBasicBlock(self.llvm_func, "Case");
2025
2026 for (items) |item| {
2027 const llvm_item = try self.resolveInst(item);
2028 llvm_switch.addCase(llvm_item, case_block);
2029 }
2030
2031 self.builder.positionBuilderAtEnd(case_block);
2032 try self.genBody(case_body);
2033 }
2034
2035 self.builder.positionBuilderAtEnd(else_block);
2036 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
2037 if (else_body.len != 0) {
2038 try self.genBody(else_body);
2039 } else {
2040 _ = self.builder.buildUnreachable();
2041 }
2042
2043 // No need to reset the insert cursor since this instruction is noreturn.
2044 return null;
2013 }2045 }
20142046
2015 fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {2047 fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
src/codegen/llvm/bindings.zig+6
...@@ -178,6 +178,9 @@ pub const Value = opaque {...@@ -178,6 +178,9 @@ pub const Value = opaque {
178178
179 pub const setInitializer = LLVMSetInitializer;179 pub const setInitializer = LLVMSetInitializer;
180 extern fn LLVMSetInitializer(GlobalVar: *const Value, ConstantVal: *const Value) void;180 extern fn LLVMSetInitializer(GlobalVar: *const Value, ConstantVal: *const Value) void;
181
182 pub const addCase = LLVMAddCase;
183 extern fn LLVMAddCase(Switch: *const Value, OnVal: *const Value, Dest: *const BasicBlock) void;
181};184};
182185
183pub const Type = opaque {186pub const Type = opaque {
...@@ -554,6 +557,9 @@ pub const Builder = opaque {...@@ -554,6 +557,9 @@ pub const Builder = opaque {
554 pub const buildCondBr = LLVMBuildCondBr;557 pub const buildCondBr = LLVMBuildCondBr;
555 extern fn LLVMBuildCondBr(*const Builder, If: *const Value, Then: *const BasicBlock, Else: *const BasicBlock) *const Value;558 extern fn LLVMBuildCondBr(*const Builder, If: *const Value, Then: *const BasicBlock, Else: *const BasicBlock) *const Value;
556559
560 pub const buildSwitch = LLVMBuildSwitch;
561 extern fn LLVMBuildSwitch(*const Builder, V: *const Value, Else: *const BasicBlock, NumCases: c_uint) *const Value;
562
557 pub const buildPhi = LLVMBuildPhi;563 pub const buildPhi = LLVMBuildPhi;
558 extern fn LLVMBuildPhi(*const Builder, Ty: *const Type, Name: [*:0]const u8) *const Value;564 extern fn LLVMBuildPhi(*const Builder, Ty: *const Type, Name: [*:0]const u8) *const Value;
559565
test/behavior/switch.zig+217
...@@ -2,3 +2,220 @@ const std = @import("std");...@@ -2,3 +2,220 @@ const std = @import("std");
2const expect = std.testing.expect;2const expect = std.testing.expect;
3const expectError = std.testing.expectError;3const expectError = std.testing.expectError;
4const expectEqual = std.testing.expectEqual;4const expectEqual = std.testing.expectEqual;
5
6test "switch with numbers" {
7 try testSwitchWithNumbers(13);
8}
9
10fn testSwitchWithNumbers(x: u32) !void {
11 const result = switch (x) {
12 1, 2, 3, 4...8 => false,
13 13 => true,
14 else => false,
15 };
16 try expect(result);
17}
18
19test "switch with all ranges" {
20 try expect(testSwitchWithAllRanges(50, 3) == 1);
21 try expect(testSwitchWithAllRanges(101, 0) == 2);
22 try expect(testSwitchWithAllRanges(300, 5) == 3);
23 try expect(testSwitchWithAllRanges(301, 6) == 6);
24}
25
26fn testSwitchWithAllRanges(x: u32, y: u32) u32 {
27 return switch (x) {
28 0...100 => 1,
29 101...200 => 2,
30 201...300 => 3,
31 else => y,
32 };
33}
34
35test "implicit comptime switch" {
36 const x = 3 + 4;
37 const result = switch (x) {
38 3 => 10,
39 4 => 11,
40 5, 6 => 12,
41 7, 8 => 13,
42 else => 14,
43 };
44
45 comptime {
46 try expect(result + 1 == 14);
47 }
48}
49
50test "switch on enum" {
51 const fruit = Fruit.Orange;
52 nonConstSwitchOnEnum(fruit);
53}
54const Fruit = enum {
55 Apple,
56 Orange,
57 Banana,
58};
59fn nonConstSwitchOnEnum(fruit: Fruit) void {
60 switch (fruit) {
61 Fruit.Apple => unreachable,
62 Fruit.Orange => {},
63 Fruit.Banana => unreachable,
64 }
65}
66
67test "switch statement" {
68 try nonConstSwitch(SwitchStatementFoo.C);
69}
70fn nonConstSwitch(foo: SwitchStatementFoo) !void {
71 const val = switch (foo) {
72 SwitchStatementFoo.A => @as(i32, 1),
73 SwitchStatementFoo.B => 2,
74 SwitchStatementFoo.C => 3,
75 SwitchStatementFoo.D => 4,
76 };
77 try expect(val == 3);
78}
79const SwitchStatementFoo = enum { A, B, C, D };
80
81test "switch with multiple expressions" {
82 const x = switch (returnsFive()) {
83 1, 2, 3 => 1,
84 4, 5, 6 => 2,
85 else => @as(i32, 3),
86 };
87 try expect(x == 2);
88}
89fn returnsFive() i32 {
90 return 5;
91}
92
93const Number = union(enum) {
94 One: u64,
95 Two: u8,
96 Three: f32,
97};
98
99const number = Number{ .Three = 1.23 };
100
101fn returnsFalse() bool {
102 switch (number) {
103 Number.One => |x| return x > 1234,
104 Number.Two => |x| return x == 'a',
105 Number.Three => |x| return x > 12.34,
106 }
107}
108test "switch on const enum with var" {
109 try expect(!returnsFalse());
110}
111
112test "switch on type" {
113 try expect(trueIfBoolFalseOtherwise(bool));
114 try expect(!trueIfBoolFalseOtherwise(i32));
115}
116
117fn trueIfBoolFalseOtherwise(comptime T: type) bool {
118 return switch (T) {
119 bool => true,
120 else => false,
121 };
122}
123
124test "switching on booleans" {
125 try testSwitchOnBools();
126 comptime try testSwitchOnBools();
127}
128
129fn testSwitchOnBools() !void {
130 try expect(testSwitchOnBoolsTrueAndFalse(true) == false);
131 try expect(testSwitchOnBoolsTrueAndFalse(false) == true);
132
133 try expect(testSwitchOnBoolsTrueWithElse(true) == false);
134 try expect(testSwitchOnBoolsTrueWithElse(false) == true);
135
136 try expect(testSwitchOnBoolsFalseWithElse(true) == false);
137 try expect(testSwitchOnBoolsFalseWithElse(false) == true);
138}
139
140fn testSwitchOnBoolsTrueAndFalse(x: bool) bool {
141 return switch (x) {
142 true => false,
143 false => true,
144 };
145}
146
147fn testSwitchOnBoolsTrueWithElse(x: bool) bool {
148 return switch (x) {
149 true => false,
150 else => true,
151 };
152}
153
154fn testSwitchOnBoolsFalseWithElse(x: bool) bool {
155 return switch (x) {
156 false => true,
157 else => false,
158 };
159}
160
161test "u0" {
162 var val: u0 = 0;
163 switch (val) {
164 0 => try expect(val == 0),
165 }
166}
167
168test "undefined.u0" {
169 var val: u0 = undefined;
170 switch (val) {
171 0 => try expect(val == 0),
172 }
173}
174
175test "switch with disjoint range" {
176 var q: u8 = 0;
177 switch (q) {
178 0...125 => {},
179 127...255 => {},
180 126...126 => {},
181 }
182}
183
184test "switch variable for range and multiple prongs" {
185 const S = struct {
186 fn doTheTest() !void {
187 var u: u8 = 16;
188 try doTheSwitch(u);
189 comptime try doTheSwitch(u);
190 var v: u8 = 42;
191 try doTheSwitch(v);
192 comptime try doTheSwitch(v);
193 }
194 fn doTheSwitch(q: u8) !void {
195 switch (q) {
196 0...40 => |x| try expect(x == 16),
197 41, 42, 43 => |x| try expect(x == 42),
198 else => try expect(false),
199 }
200 }
201 };
202 _ = S;
203}
204
205var state: u32 = 0;
206fn poll() void {
207 switch (state) {
208 0 => {
209 state = 1;
210 },
211 else => {
212 state += 1;
213 },
214 }
215}
216
217test "switch on global mutable var isn't constant-folded" {
218 while (state < 2) {
219 poll();
220 }
221}
test/behavior/switch_stage1.zig+1-223
...@@ -3,86 +3,6 @@ const expect = std.testing.expect;...@@ -3,86 +3,6 @@ const expect = std.testing.expect;
3const expectError = std.testing.expectError;3const expectError = std.testing.expectError;
4const expectEqual = std.testing.expectEqual;4const expectEqual = std.testing.expectEqual;
55
6test "switch with numbers" {
7 try testSwitchWithNumbers(13);
8}
9
10fn testSwitchWithNumbers(x: u32) !void {
11 const result = switch (x) {
12 1, 2, 3, 4...8 => false,
13 13 => true,
14 else => false,
15 };
16 try expect(result);
17}
18
19test "switch with all ranges" {
20 try expect(testSwitchWithAllRanges(50, 3) == 1);
21 try expect(testSwitchWithAllRanges(101, 0) == 2);
22 try expect(testSwitchWithAllRanges(300, 5) == 3);
23 try expect(testSwitchWithAllRanges(301, 6) == 6);
24}
25
26fn testSwitchWithAllRanges(x: u32, y: u32) u32 {
27 return switch (x) {
28 0...100 => 1,
29 101...200 => 2,
30 201...300 => 3,
31 else => y,
32 };
33}
34
35test "implicit comptime switch" {
36 const x = 3 + 4;
37 const result = switch (x) {
38 3 => 10,
39 4 => 11,
40 5, 6 => 12,
41 7, 8 => 13,
42 else => 14,
43 };
44
45 comptime {
46 try expect(result + 1 == 14);
47 }
48}
49
50test "switch on enum" {
51 const fruit = Fruit.Orange;
52 nonConstSwitchOnEnum(fruit);
53}
54const Fruit = enum {
55 Apple,
56 Orange,
57 Banana,
58};
59fn nonConstSwitchOnEnum(fruit: Fruit) void {
60 switch (fruit) {
61 Fruit.Apple => unreachable,
62 Fruit.Orange => {},
63 Fruit.Banana => unreachable,
64 }
65}
66
67test "switch statement" {
68 try nonConstSwitch(SwitchStatementFoo.C);
69}
70fn nonConstSwitch(foo: SwitchStatementFoo) !void {
71 const val = switch (foo) {
72 SwitchStatementFoo.A => @as(i32, 1),
73 SwitchStatementFoo.B => 2,
74 SwitchStatementFoo.C => 3,
75 SwitchStatementFoo.D => 4,
76 };
77 try expect(val == 3);
78}
79const SwitchStatementFoo = enum {
80 A,
81 B,
82 C,
83 D,
84};
85
86test "switch prong with variable" {6test "switch prong with variable" {
87 try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 });7 try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 });
88 try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 });8 try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 });
...@@ -125,49 +45,6 @@ fn testSwitchEnumPtrCapture() !void {...@@ -125,49 +45,6 @@ fn testSwitchEnumPtrCapture() !void {
125 }45 }
126}46}
12747
128test "switch with multiple expressions" {
129 const x = switch (returnsFive()) {
130 1, 2, 3 => 1,
131 4, 5, 6 => 2,
132 else => @as(i32, 3),
133 };
134 try expect(x == 2);
135}
136fn returnsFive() i32 {
137 return 5;
138}
139
140const Number = union(enum) {
141 One: u64,
142 Two: u8,
143 Three: f32,
144};
145
146const number = Number{ .Three = 1.23 };
147
148fn returnsFalse() bool {
149 switch (number) {
150 Number.One => |x| return x > 1234,
151 Number.Two => |x| return x == 'a',
152 Number.Three => |x| return x > 12.34,
153 }
154}
155test "switch on const enum with var" {
156 try expect(!returnsFalse());
157}
158
159test "switch on type" {
160 try expect(trueIfBoolFalseOtherwise(bool));
161 try expect(!trueIfBoolFalseOtherwise(i32));
162}
163
164fn trueIfBoolFalseOtherwise(comptime T: type) bool {
165 return switch (T) {
166 bool => true,
167 else => false,
168 };
169}
170
171test "switch handles all cases of number" {48test "switch handles all cases of number" {
172 try testSwitchHandleAllCases();49 try testSwitchHandleAllCases();
173 comptime try testSwitchHandleAllCases();50 comptime try testSwitchHandleAllCases();
...@@ -237,57 +114,6 @@ test "capture value of switch with all unreachable prongs" {...@@ -237,57 +114,6 @@ test "capture value of switch with all unreachable prongs" {
237 try expect(x == 1);114 try expect(x == 1);
238}115}
239116
240test "switching on booleans" {
241 try testSwitchOnBools();
242 comptime try testSwitchOnBools();
243}
244
245fn testSwitchOnBools() !void {
246 try expect(testSwitchOnBoolsTrueAndFalse(true) == false);
247 try expect(testSwitchOnBoolsTrueAndFalse(false) == true);
248
249 try expect(testSwitchOnBoolsTrueWithElse(true) == false);
250 try expect(testSwitchOnBoolsTrueWithElse(false) == true);
251
252 try expect(testSwitchOnBoolsFalseWithElse(true) == false);
253 try expect(testSwitchOnBoolsFalseWithElse(false) == true);
254}
255
256fn testSwitchOnBoolsTrueAndFalse(x: bool) bool {
257 return switch (x) {
258 true => false,
259 false => true,
260 };
261}
262
263fn testSwitchOnBoolsTrueWithElse(x: bool) bool {
264 return switch (x) {
265 true => false,
266 else => true,
267 };
268}
269
270fn testSwitchOnBoolsFalseWithElse(x: bool) bool {
271 return switch (x) {
272 false => true,
273 else => false,
274 };
275}
276
277test "u0" {
278 var val: u0 = 0;
279 switch (val) {
280 0 => try expect(val == 0),
281 }
282}
283
284test "undefined.u0" {
285 var val: u0 = undefined;
286 switch (val) {
287 0 => try expect(val == 0),
288 }
289}
290
291test "anon enum literal used in switch on union enum" {117test "anon enum literal used in switch on union enum" {
292 const Foo = union(enum) {118 const Foo = union(enum) {
293 a: i32,119 a: i32,
...@@ -435,54 +261,6 @@ test "switch prongs with cases with identical payload types" {...@@ -435,54 +261,6 @@ test "switch prongs with cases with identical payload types" {
435 comptime try S.doTheTest();261 comptime try S.doTheTest();
436}262}
437263
438test "switch with disjoint range" {
439 var q: u8 = 0;
440 switch (q) {
441 0...125 => {},
442 127...255 => {},
443 126...126 => {},
444 }
445}
446
447test "switch variable for range and multiple prongs" {
448 const S = struct {
449 fn doTheTest() !void {
450 var u: u8 = 16;
451 try doTheSwitch(u);
452 comptime try doTheSwitch(u);
453 var v: u8 = 42;
454 try doTheSwitch(v);
455 comptime try doTheSwitch(v);
456 }
457 fn doTheSwitch(q: u8) !void {
458 switch (q) {
459 0...40 => |x| try expect(x == 16),
460 41, 42, 43 => |x| try expect(x == 42),
461 else => try expect(false),
462 }
463 }
464 };
465 _ = S;
466}
467
468var state: u32 = 0;
469fn poll() void {
470 switch (state) {
471 0 => {
472 state = 1;
473 },
474 else => {
475 state += 1;
476 },
477 }
478}
479
480test "switch on global mutable var isn't constant-folded" {
481 while (state < 2) {
482 poll();
483 }
484}
485
486test "switch on pointer type" {264test "switch on pointer type" {
487 const S = struct {265 const S = struct {
488 const X = struct {266 const X = struct {
...@@ -527,7 +305,7 @@ test "switch on error set with single else" {...@@ -527,7 +305,7 @@ test "switch on error set with single else" {
527 comptime try S.doTheTest();305 comptime try S.doTheTest();
528}306}
529307
530test "while copies its payload" {308test "switch capture copies its payload" {
531 const S = struct {309 const S = struct {
532 fn doTheTest() !void {310 fn doTheTest() !void {
533 var tmp: union(enum) {311 var tmp: union(enum) {