authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-25 15:06:47-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-25 15:11:21-07:00
log8509e7111d80a07e778aa2a57d58d2bea6945014
treee7eedaf980ada713a6c3d2da3e6153751d03bed6
parenta132190cad80669306705b72276e9641401426fb

stage2: fix switch on tagged union capture-by-pointer

* AstGen: always use `typeof` and never `typeof_elem` on the `switch_cond`/`switch_cond_ref` instruction because both variants return a value and not a pointer. - Delete the `typeof_elem` ZIR instruction since it is no longer needed. * Sema: validateUnionInit now recognizes a comptime mutable value and no longer emits a compile error saying "cannot evaluate constant expression" - Still to-do is detecting comptime union values in a function that is not being executed at compile-time. - This is still to-do for structs too. * Sema: when emitting a call AIR instruction, call resolveTypeLayout on all the parameter types as well as the return type. * `Type.structFieldOffset` now works for unions in addition to structs.

7 files changed, 115 insertions(+), 97 deletions(-)

src/AstGen.zig+2-4
...@@ -2109,7 +2109,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner...@@ -2109,7 +2109,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
2109 .negate,2109 .negate,
2110 .negate_wrap,2110 .negate_wrap,
2111 .typeof,2111 .typeof,
2112 .typeof_elem,
2113 .xor,2112 .xor,
2114 .optional_type,2113 .optional_type,
2115 .optional_payload_safe,2114 .optional_payload_safe,
...@@ -6028,8 +6027,7 @@ fn switchExpr(...@@ -6028,8 +6027,7 @@ fn switchExpr(
6028 const cond_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_cond_ref else .switch_cond;6027 const cond_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_cond_ref else .switch_cond;
6029 const cond = try parent_gz.addUnNode(cond_tag, raw_operand, operand_node);6028 const cond = try parent_gz.addUnNode(cond_tag, raw_operand, operand_node);
6030 // We need the type of the operand to use as the result location for all the prong items.6029 // We need the type of the operand to use as the result location for all the prong items.
6031 const typeof_tag: Zir.Inst.Tag = if (any_payload_is_ref) .typeof_elem else .typeof;6030 const cond_ty_inst = try parent_gz.addUnNode(.typeof, cond, operand_node);
6032 const cond_ty_inst = try parent_gz.addUnNode(typeof_tag, cond, operand_node);
6033 const item_rl: ResultLoc = .{ .ty = cond_ty_inst };6031 const item_rl: ResultLoc = .{ .ty = cond_ty_inst };
60346032
6035 // These contain the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti.6033 // These contain the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti.
...@@ -6214,7 +6212,7 @@ fn switchExpr(...@@ -6214,7 +6212,7 @@ fn switchExpr(
6214 .has_multi_cases = multi_cases_len != 0,6212 .has_multi_cases = multi_cases_len != 0,
6215 .has_else = special_prong == .@"else",6213 .has_else = special_prong == .@"else",
6216 .has_under = special_prong == .under,6214 .has_under = special_prong == .under,
6217 .scalar_cases_len = @intCast(u28, scalar_cases_len),6215 .scalar_cases_len = @intCast(Zir.Inst.SwitchBlock.Bits.ScalarCasesLen, scalar_cases_len),
6218 },6216 },
6219 });6217 });
62206218
src/Sema.zig+22-22
...@@ -608,7 +608,6 @@ pub fn analyzeBody(...@@ -608,7 +608,6 @@ pub fn analyzeBody(
608 .size_of => try sema.zirSizeOf(block, inst),608 .size_of => try sema.zirSizeOf(block, inst),
609 .bit_size_of => try sema.zirBitSizeOf(block, inst),609 .bit_size_of => try sema.zirBitSizeOf(block, inst),
610 .typeof => try sema.zirTypeof(block, inst),610 .typeof => try sema.zirTypeof(block, inst),
611 .typeof_elem => try sema.zirTypeofElem(block, inst),
612 .log2_int_type => try sema.zirLog2IntType(block, inst),611 .log2_int_type => try sema.zirLog2IntType(block, inst),
613 .typeof_log2_int_type => try sema.zirTypeofLog2IntType(block, inst),612 .typeof_log2_int_type => try sema.zirTypeofLog2IntType(block, inst),
614 .xor => try sema.zirBitwise(block, inst, .xor),613 .xor => try sema.zirBitwise(block, inst, .xor),
...@@ -2337,11 +2336,21 @@ fn validateUnionInit(...@@ -2337,11 +2336,21 @@ fn validateUnionInit(
2337 return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name);2336 return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name);
2338 const field_index = @intCast(u32, field_index_big);2337 const field_index = @intCast(u32, field_index_big);
23392338
2340 // TODO here we need to go back and see if we need to convert the union2339 // Handle the possibility of the union value being comptime-known.
2341 // to a comptime-known value. This will involve editing the AIR code we have2340 const union_ptr_inst = Air.refToIndex(sema.resolveInst(field_ptr_extra.lhs)).?;
2342 // generated so far - in particular deleting some runtime pointer bitcast2341 switch (sema.air_instructions.items(.tag)[union_ptr_inst]) {
2343 // instructions which are not actually needed if the initialization expression2342 .constant => return, // In this case the tag has already been set. No validation to do.
2344 // ends up being comptime-known.2343 .bitcast => {
2344 // TODO here we need to go back and see if we need to convert the union
2345 // to a comptime-known value. In such case, we must delete all the instructions
2346 // added to the current block starting with the bitcast.
2347 // If the bitcast result ptr is an alloc, the alloc should be replaced with
2348 // a constant decl_ref.
2349 // Otherwise, the bitcast should be preserved and a store instruction should be
2350 // emitted to store the constant union value through the bitcast.
2351 },
2352 else => unreachable,
2353 }
23452354
2346 // Otherwise, we set the new union tag now.2355 // Otherwise, we set the new union tag now.
2347 const new_tag = try sema.addConstant(2356 const new_tag = try sema.addConstant(
...@@ -4091,18 +4100,20 @@ fn analyzeCall(...@@ -4091,18 +4100,20 @@ fn analyzeCall(
4091 zir_tags,4100 zir_tags,
4092 );4101 );
4093 } else res: {4102 } else res: {
4103 try sema.requireRuntimeBlock(block, call_src);
4104
4094 const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len);4105 const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len);
4095 for (uncasted_args) |uncasted_arg, i| {4106 for (uncasted_args) |uncasted_arg, i| {
4107 const arg_src = call_src; // TODO: better source location
4096 if (i < fn_params_len) {4108 if (i < fn_params_len) {
4097 const param_ty = func_ty.fnParamType(i);4109 const param_ty = func_ty.fnParamType(i);
4098 const arg_src = call_src; // TODO: better source location4110 try sema.resolveTypeLayout(block, arg_src, param_ty);
4099 args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src);4111 args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src);
4100 } else {4112 } else {
4101 args[i] = uncasted_arg;4113 args[i] = uncasted_arg;
4102 }4114 }
4103 }4115 }
41044116
4105 try sema.requireRuntimeBlock(block, call_src);
4106 try sema.resolveTypeLayout(block, call_src, func_ty_info.return_type);4117 try sema.resolveTypeLayout(block, call_src, func_ty_info.return_type);
41074118
4108 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +4119 try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len +
...@@ -4173,6 +4184,7 @@ fn finishGenericCall(...@@ -4173,6 +4184,7 @@ fn finishGenericCall(
4173 const param_ty = new_fn_ty.fnParamType(runtime_i);4184 const param_ty = new_fn_ty.fnParamType(runtime_i);
4174 const arg_src = call_src; // TODO: better source location4185 const arg_src = call_src; // TODO: better source location
4175 const uncasted_arg = uncasted_args[total_i];4186 const uncasted_arg = uncasted_args[total_i];
4187 try sema.resolveTypeLayout(block, arg_src, param_ty);
4176 const casted_arg = try sema.coerce(block, param_ty, uncasted_arg, arg_src);4188 const casted_arg = try sema.coerce(block, param_ty, uncasted_arg, arg_src);
4177 runtime_args[runtime_i] = casted_arg;4189 runtime_args[runtime_i] = casted_arg;
4178 runtime_i += 1;4190 runtime_i += 1;
...@@ -5548,7 +5560,7 @@ fn zirSwitchCapture(...@@ -5548,7 +5560,7 @@ fn zirSwitchCapture(
5548 );5560 );
5549 }5561 }
5550 try sema.requireRuntimeBlock(block, operand_src);5562 try sema.requireRuntimeBlock(block, operand_src);
5551 return block.addStructFieldPtr(operand_ptr, field_index, field.ty);5563 return block.addStructFieldPtr(operand_ptr, field_index, field_ty_ptr);
5552 }5564 }
55535565
5554 const operand = if (operand_is_ref)5566 const operand = if (operand_is_ref)
...@@ -5669,11 +5681,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError...@@ -5669,11 +5681,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
5669 const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset };5681 const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset };
5670 const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);5682 const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
56715683
5672 const operand_ptr = sema.resolveInst(extra.data.operand);5684 const operand = sema.resolveInst(extra.data.operand);
5673 const operand = if (extra.data.bits.is_ref)
5674 try sema.analyzeLoad(block, src, operand_ptr, operand_src)
5675 else
5676 operand_ptr;
56775685
5678 var header_extra_index: usize = extra.end;5686 var header_extra_index: usize = extra.end;
56795687
...@@ -8675,14 +8683,6 @@ fn zirTypeof(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air....@@ -8675,14 +8683,6 @@ fn zirTypeof(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
8675 return sema.addType(operand_ty);8683 return sema.addType(operand_ty);
8676}8684}
86778685
8678fn zirTypeofElem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
8679 _ = block;
8680 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
8681 const operand_ptr = sema.resolveInst(inst_data.operand);
8682 const elem_ty = sema.typeOf(operand_ptr).elemType();
8683 return sema.addType(elem_ty);
8684}
8685
8686fn zirTypeofLog2IntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {8686fn zirTypeofLog2IntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
8687 const inst_data = sema.code.instructions.items(.data)[inst].un_node;8687 const inst_data = sema.code.instructions.items(.data)[inst].un_node;
8688 const src = inst_data.src();8688 const src = inst_data.src();
src/Zir.zig+11-6
...@@ -544,9 +544,6 @@ pub const Inst = struct {...@@ -544,9 +544,6 @@ pub const Inst = struct {
544 /// Returns the type of a value.544 /// Returns the type of a value.
545 /// Uses the `un_node` field.545 /// Uses the `un_node` field.
546 typeof,546 typeof,
547 /// Given a value which is a pointer, returns the element type.
548 /// Uses the `un_node` field.
549 typeof_elem,
550 /// Given a value, look at the type of it, which must be an integer type.547 /// Given a value, look at the type of it, which must be an integer type.
551 /// Returns the integer type for the RHS of a shift operation.548 /// Returns the integer type for the RHS of a shift operation.
552 /// Uses the `un_node` field.549 /// Uses the `un_node` field.
...@@ -1045,7 +1042,6 @@ pub const Inst = struct {...@@ -1045,7 +1042,6 @@ pub const Inst = struct {
1045 .negate,1042 .negate,
1046 .negate_wrap,1043 .negate_wrap,
1047 .typeof,1044 .typeof,
1048 .typeof_elem,
1049 .xor,1045 .xor,
1050 .optional_type,1046 .optional_type,
1051 .optional_payload_safe,1047 .optional_payload_safe,
...@@ -1312,7 +1308,6 @@ pub const Inst = struct {...@@ -1312,7 +1308,6 @@ pub const Inst = struct {
1312 .negate = .un_node,1308 .negate = .un_node,
1313 .negate_wrap = .un_node,1309 .negate_wrap = .un_node,
1314 .typeof = .un_node,1310 .typeof = .un_node,
1315 .typeof_elem = .un_node,
1316 .typeof_log2_int_type = .un_node,1311 .typeof_log2_int_type = .un_node,
1317 .log2_int_type = .un_node,1312 .log2_int_type = .un_node,
1318 .@"unreachable" = .@"unreachable",1313 .@"unreachable" = .@"unreachable",
...@@ -2443,6 +2438,13 @@ pub const Inst = struct {...@@ -2443,6 +2438,13 @@ pub const Inst = struct {
2443 /// body member Index for every body_len2438 /// body member Index for every body_len
2444 /// }2439 /// }
2445 pub const SwitchBlock = struct {2440 pub const SwitchBlock = struct {
2441 /// This is always a `switch_cond` or `switch_cond_ref` instruction.
2442 /// If it is a `switch_cond_ref` instruction, bits.is_ref is always true.
2443 /// If it is a `switch_cond` instruction, bits.is_ref is always false.
2444 /// Both `switch_cond` and `switch_cond_ref` return a value, not a pointer,
2445 /// that is useful for the case items, but cannot be used for capture values.
2446 /// For the capture values, Sema is expected to find the operand of this operand
2447 /// and use that.
2446 operand: Ref,2448 operand: Ref,
2447 bits: Bits,2449 bits: Bits,
24482450
...@@ -2454,8 +2456,11 @@ pub const Inst = struct {...@@ -2454,8 +2456,11 @@ pub const Inst = struct {
2454 /// If true, there is an underscore prong. This is mutually exclusive with `has_else`.2456 /// If true, there is an underscore prong. This is mutually exclusive with `has_else`.
2455 has_under: bool,2457 has_under: bool,
2456 /// If true, the `operand` is a pointer to the value being switched on.2458 /// If true, the `operand` is a pointer to the value being switched on.
2459 /// TODO this flag is redundant with the tag of operand and can be removed.
2457 is_ref: bool,2460 is_ref: bool,
2458 scalar_cases_len: u28,2461 scalar_cases_len: ScalarCasesLen,
2462
2463 pub const ScalarCasesLen = u28;
24592464
2460 pub fn specialProng(bits: Bits) SpecialProng {2465 pub fn specialProng(bits: Bits) SpecialProng {
2461 const has_else: u2 = @boolToInt(bits.has_else);2466 const has_else: u2 = @boolToInt(bits.has_else);
src/print_zir.zig-1
...@@ -184,7 +184,6 @@ const Writer = struct {...@@ -184,7 +184,6 @@ const Writer = struct {
184 .is_non_err,184 .is_non_err,
185 .is_non_err_ptr,185 .is_non_err_ptr,
186 .typeof,186 .typeof,
187 .typeof_elem,
188 .struct_init_empty,187 .struct_init_empty,
189 .type_info,188 .type_info,
190 .size_of,189 .size_of,
src/type.zig+37-22
...@@ -3391,34 +3391,49 @@ pub const Type = extern union {...@@ -3391,34 +3391,49 @@ pub const Type = extern union {
3391 }3391 }
3392 }3392 }
33933393
3394 /// Supports structs and unions.
3394 pub fn structFieldOffset(ty: Type, index: usize, target: Target) u64 {3395 pub fn structFieldOffset(ty: Type, index: usize, target: Target) u64 {
3395 const fields = ty.structFields();3396 switch (ty.tag()) {
3396 if (ty.castTag(.@"struct")) |payload| {3397 .@"struct" => {
3397 const struct_obj = payload.data;3398 const struct_obj = ty.castTag(.@"struct").?.data;
3398 assert(struct_obj.status == .have_layout);3399 assert(struct_obj.status == .have_layout);
3399 const is_packed = struct_obj.layout == .Packed;3400 const is_packed = struct_obj.layout == .Packed;
3400 if (is_packed) @panic("TODO packed structs");3401 if (is_packed) @panic("TODO packed structs");
3401 }
34023402
3403 var offset: u64 = 0;3403 var offset: u64 = 0;
3404 var big_align: u32 = 0;3404 var big_align: u32 = 0;
3405 for (fields.values()) |field, i| {3405 for (struct_obj.fields.values()) |field, i| {
3406 if (!field.ty.hasCodeGenBits()) continue;3406 if (!field.ty.hasCodeGenBits()) continue;
34073407
3408 const field_align = a: {3408 const field_align = a: {
3409 if (field.abi_align.tag() == .abi_align_default) {3409 if (field.abi_align.tag() == .abi_align_default) {
3410 break :a field.ty.abiAlignment(target);3410 break :a field.ty.abiAlignment(target);
3411 } else {
3412 break :a @intCast(u32, field.abi_align.toUnsignedInt());
3413 }
3414 };
3415 big_align = @maximum(big_align, field_align);
3416 offset = std.mem.alignForwardGeneric(u64, offset, field_align);
3417 if (i == index) return offset;
3418 offset += field.ty.abiSize(target);
3419 }
3420 offset = std.mem.alignForwardGeneric(u64, offset, big_align);
3421 return offset;
3422 },
3423 .@"union" => return 0,
3424 .union_tagged => {
3425 const union_obj = ty.castTag(.union_tagged).?.data;
3426 const layout = union_obj.getLayout(target, true);
3427 if (layout.tag_align >= layout.payload_align) {
3428 // {Tag, Payload}
3429 return std.mem.alignForwardGeneric(u64, layout.tag_size, layout.payload_align);
3411 } else {3430 } else {
3412 break :a @intCast(u32, field.abi_align.toUnsignedInt());3431 // {Payload, Tag}
3432 return 0;
3413 }3433 }
3414 };3434 },
3415 big_align = @maximum(big_align, field_align);3435 else => unreachable,
3416 offset = std.mem.alignForwardGeneric(u64, offset, field_align);
3417 if (i == index) return offset;
3418 offset += field.ty.abiSize(target);
3419 }3436 }
3420 offset = std.mem.alignForwardGeneric(u64, offset, big_align);
3421 return offset;
3422 }3437 }
34233438
3424 pub fn declSrcLoc(ty: Type) Module.SrcLoc {3439 pub fn declSrcLoc(ty: Type) Module.SrcLoc {
test/behavior/switch.zig+43
...@@ -219,3 +219,46 @@ test "switch on global mutable var isn't constant-folded" {...@@ -219,3 +219,46 @@ test "switch on global mutable var isn't constant-folded" {
219 poll();219 poll();
220 }220 }
221}221}
222
223const SwitchProngWithVarEnum = union(enum) {
224 One: i32,
225 Two: f32,
226 Meh: void,
227};
228
229test "switch prong with variable" {
230 try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 });
231 try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 });
232 try switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} });
233}
234fn switchProngWithVarFn(a: SwitchProngWithVarEnum) !void {
235 switch (a) {
236 SwitchProngWithVarEnum.One => |x| {
237 try expect(x == 13);
238 },
239 SwitchProngWithVarEnum.Two => |x| {
240 try expect(x == 13.0);
241 },
242 SwitchProngWithVarEnum.Meh => |x| {
243 const v: void = x;
244 _ = v;
245 },
246 }
247}
248
249test "switch on enum using pointer capture" {
250 try testSwitchEnumPtrCapture();
251 comptime try testSwitchEnumPtrCapture();
252}
253
254fn testSwitchEnumPtrCapture() !void {
255 var value = SwitchProngWithVarEnum{ .One = 1234 };
256 switch (value) {
257 SwitchProngWithVarEnum.One => |*x| x.* += 1,
258 else => unreachable,
259 }
260 switch (value) {
261 SwitchProngWithVarEnum.One => |x| try expect(x == 1235),
262 else => unreachable,
263 }
264}
test/behavior/switch_stage1.zig-42
...@@ -3,48 +3,6 @@ const expect = std.testing.expect;...@@ -3,48 +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 prong with variable" {
7 try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 });
8 try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 });
9 try switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} });
10}
11const SwitchProngWithVarEnum = union(enum) {
12 One: i32,
13 Two: f32,
14 Meh: void,
15};
16fn switchProngWithVarFn(a: SwitchProngWithVarEnum) !void {
17 switch (a) {
18 SwitchProngWithVarEnum.One => |x| {
19 try expect(x == 13);
20 },
21 SwitchProngWithVarEnum.Two => |x| {
22 try expect(x == 13.0);
23 },
24 SwitchProngWithVarEnum.Meh => |x| {
25 const v: void = x;
26 _ = v;
27 },
28 }
29}
30
31test "switch on enum using pointer capture" {
32 try testSwitchEnumPtrCapture();
33 comptime try testSwitchEnumPtrCapture();
34}
35
36fn testSwitchEnumPtrCapture() !void {
37 var value = SwitchProngWithVarEnum{ .One = 1234 };
38 switch (value) {
39 SwitchProngWithVarEnum.One => |*x| x.* += 1,
40 else => unreachable,
41 }
42 switch (value) {
43 SwitchProngWithVarEnum.One => |x| try expect(x == 1235),
44 else => unreachable,
45 }
46}
47
48test "switch handles all cases of number" {6test "switch handles all cases of number" {
49 try testSwitchHandleAllCases();7 try testSwitchHandleAllCases();
50 comptime try testSwitchHandleAllCases();8 comptime try testSwitchHandleAllCases();