| author | |
| committer | |
| log | 8509e7111d80a07e778aa2a57d58d2bea6945014 |
| tree | e7eedaf980ada713a6c3d2da3e6153751d03bed6 |
| parent | a132190cad80669306705b72276e9641401426fb |
* 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 | 2109 | .negate, |
| 2110 | 2110 | .negate_wrap, |
| 2111 | 2111 | .typeof, |
| 2112 | .typeof_elem, | |
| 2113 | 2112 | .xor, |
| 2114 | 2113 | .optional_type, |
| 2115 | 2114 | .optional_payload_safe, |
| ... | ... | @@ -6028,8 +6027,7 @@ fn switchExpr( |
| 6028 | 6027 | const cond_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_cond_ref else .switch_cond; |
| 6029 | 6028 | const cond = try parent_gz.addUnNode(cond_tag, raw_operand, operand_node); |
| 6030 | 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; | |
| 6032 | const cond_ty_inst = try parent_gz.addUnNode(typeof_tag, cond, operand_node); | |
| 6030 | const cond_ty_inst = try parent_gz.addUnNode(.typeof, cond, operand_node); | |
| 6033 | 6031 | const item_rl: ResultLoc = .{ .ty = cond_ty_inst }; |
| 6034 | 6032 | |
| 6035 | 6033 | // These contain the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti. |
| ... | ... | @@ -6214,7 +6212,7 @@ fn switchExpr( |
| 6214 | 6212 | .has_multi_cases = multi_cases_len != 0, |
| 6215 | 6213 | .has_else = special_prong == .@"else", |
| 6216 | 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 | }); |
| 6220 | 6218 |
src/Sema.zig+22-22| ... | ... | @@ -608,7 +608,6 @@ pub fn analyzeBody( |
| 608 | 608 | .size_of => try sema.zirSizeOf(block, inst), |
| 609 | 609 | .bit_size_of => try sema.zirBitSizeOf(block, inst), |
| 610 | 610 | .typeof => try sema.zirTypeof(block, inst), |
| 611 | .typeof_elem => try sema.zirTypeofElem(block, inst), | |
| 612 | 611 | .log2_int_type => try sema.zirLog2IntType(block, inst), |
| 613 | 612 | .typeof_log2_int_type => try sema.zirTypeofLog2IntType(block, inst), |
| 614 | 613 | .xor => try sema.zirBitwise(block, inst, .xor), |
| ... | ... | @@ -2337,11 +2336,21 @@ fn validateUnionInit( |
| 2337 | 2336 | return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); |
| 2338 | 2337 | const field_index = @intCast(u32, field_index_big); |
| 2339 | 2338 | |
| 2340 | // TODO here we need to go back and see if we need to convert the union | |
| 2341 | // to a comptime-known value. This will involve editing the AIR code we have | |
| 2342 | // generated so far - in particular deleting some runtime pointer bitcast | |
| 2343 | // instructions which are not actually needed if the initialization expression | |
| 2344 | // ends up being comptime-known. | |
| 2339 | // Handle the possibility of the union value being comptime-known. | |
| 2340 | const union_ptr_inst = Air.refToIndex(sema.resolveInst(field_ptr_extra.lhs)).?; | |
| 2341 | switch (sema.air_instructions.items(.tag)[union_ptr_inst]) { | |
| 2342 | .constant => return, // In this case the tag has already been set. No validation to do. | |
| 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 | } | |
| 2345 | 2354 | |
| 2346 | 2355 | // Otherwise, we set the new union tag now. |
| 2347 | 2356 | const new_tag = try sema.addConstant( |
| ... | ... | @@ -4091,18 +4100,20 @@ fn analyzeCall( |
| 4091 | 4100 | zir_tags, |
| 4092 | 4101 | ); |
| 4093 | 4102 | } else res: { |
| 4103 | try sema.requireRuntimeBlock(block, call_src); | |
| 4104 | ||
| 4094 | 4105 | const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); |
| 4095 | 4106 | for (uncasted_args) |uncasted_arg, i| { |
| 4107 | const arg_src = call_src; // TODO: better source location | |
| 4096 | 4108 | if (i < fn_params_len) { |
| 4097 | 4109 | const param_ty = func_ty.fnParamType(i); |
| 4098 | const arg_src = call_src; // TODO: better source location | |
| 4110 | try sema.resolveTypeLayout(block, arg_src, param_ty); | |
| 4099 | 4111 | args[i] = try sema.coerce(block, param_ty, uncasted_arg, arg_src); |
| 4100 | 4112 | } else { |
| 4101 | 4113 | args[i] = uncasted_arg; |
| 4102 | 4114 | } |
| 4103 | 4115 | } |
| 4104 | 4116 | |
| 4105 | try sema.requireRuntimeBlock(block, call_src); | |
| 4106 | 4117 | try sema.resolveTypeLayout(block, call_src, func_ty_info.return_type); |
| 4107 | 4118 | |
| 4108 | 4119 | try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + |
| ... | ... | @@ -4173,6 +4184,7 @@ fn finishGenericCall( |
| 4173 | 4184 | const param_ty = new_fn_ty.fnParamType(runtime_i); |
| 4174 | 4185 | const arg_src = call_src; // TODO: better source location |
| 4175 | 4186 | const uncasted_arg = uncasted_args[total_i]; |
| 4187 | try sema.resolveTypeLayout(block, arg_src, param_ty); | |
| 4176 | 4188 | const casted_arg = try sema.coerce(block, param_ty, uncasted_arg, arg_src); |
| 4177 | 4189 | runtime_args[runtime_i] = casted_arg; |
| 4178 | 4190 | runtime_i += 1; |
| ... | ... | @@ -5548,7 +5560,7 @@ fn zirSwitchCapture( |
| 5548 | 5560 | ); |
| 5549 | 5561 | } |
| 5550 | 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 | } |
| 5553 | 5565 | |
| 5554 | 5566 | const operand = if (operand_is_ref) |
| ... | ... | @@ -5669,11 +5681,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 5669 | 5681 | const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset }; |
| 5670 | 5682 | const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); |
| 5671 | 5683 | |
| 5672 | const operand_ptr = 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; | |
| 5684 | const operand = sema.resolveInst(extra.data.operand); | |
| 5677 | 5685 | |
| 5678 | 5686 | var header_extra_index: usize = extra.end; |
| 5679 | 5687 | |
| ... | ... | @@ -8675,14 +8683,6 @@ fn zirTypeof(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. |
| 8675 | 8683 | return sema.addType(operand_ty); |
| 8676 | 8684 | } |
| 8677 | 8685 | |
| 8678 | fn 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 | ||
| 8686 | 8686 | fn zirTypeofLog2IntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 8687 | 8687 | const inst_data = sema.code.instructions.items(.data)[inst].un_node; |
| 8688 | 8688 | const src = inst_data.src(); |
src/Zir.zig+11-6| ... | ... | @@ -544,9 +544,6 @@ pub const Inst = struct { |
| 544 | 544 | /// Returns the type of a value. |
| 545 | 545 | /// Uses the `un_node` field. |
| 546 | 546 | typeof, |
| 547 | /// Given a value which is a pointer, returns the element type. | |
| 548 | /// Uses the `un_node` field. | |
| 549 | typeof_elem, | |
| 550 | 547 | /// Given a value, look at the type of it, which must be an integer type. |
| 551 | 548 | /// Returns the integer type for the RHS of a shift operation. |
| 552 | 549 | /// Uses the `un_node` field. |
| ... | ... | @@ -1045,7 +1042,6 @@ pub const Inst = struct { |
| 1045 | 1042 | .negate, |
| 1046 | 1043 | .negate_wrap, |
| 1047 | 1044 | .typeof, |
| 1048 | .typeof_elem, | |
| 1049 | 1045 | .xor, |
| 1050 | 1046 | .optional_type, |
| 1051 | 1047 | .optional_payload_safe, |
| ... | ... | @@ -1312,7 +1308,6 @@ pub const Inst = struct { |
| 1312 | 1308 | .negate = .un_node, |
| 1313 | 1309 | .negate_wrap = .un_node, |
| 1314 | 1310 | .typeof = .un_node, |
| 1315 | .typeof_elem = .un_node, | |
| 1316 | 1311 | .typeof_log2_int_type = .un_node, |
| 1317 | 1312 | .log2_int_type = .un_node, |
| 1318 | 1313 | .@"unreachable" = .@"unreachable", |
| ... | ... | @@ -2443,6 +2438,13 @@ pub const Inst = struct { |
| 2443 | 2438 | /// body member Index for every body_len |
| 2444 | 2439 | /// } |
| 2445 | 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 | 2448 | operand: Ref, |
| 2447 | 2449 | bits: Bits, |
| 2448 | 2450 | |
| ... | ... | @@ -2454,8 +2456,11 @@ pub const Inst = struct { |
| 2454 | 2456 | /// If true, there is an underscore prong. This is mutually exclusive with `has_else`. |
| 2455 | 2457 | has_under: bool, |
| 2456 | 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 | 2460 | is_ref: bool, |
| 2458 | scalar_cases_len: u28, | |
| 2461 | scalar_cases_len: ScalarCasesLen, | |
| 2462 | ||
| 2463 | pub const ScalarCasesLen = u28; | |
| 2459 | 2464 | |
| 2460 | 2465 | pub fn specialProng(bits: Bits) SpecialProng { |
| 2461 | 2466 | const has_else: u2 = @boolToInt(bits.has_else); |
src/print_zir.zig-1| ... | ... | @@ -184,7 +184,6 @@ const Writer = struct { |
| 184 | 184 | .is_non_err, |
| 185 | 185 | .is_non_err_ptr, |
| 186 | 186 | .typeof, |
| 187 | .typeof_elem, | |
| 188 | 187 | .struct_init_empty, |
| 189 | 188 | .type_info, |
| 190 | 189 | .size_of, |
src/type.zig+37-22| ... | ... | @@ -3391,34 +3391,49 @@ pub const Type = extern union { |
| 3391 | 3391 | } |
| 3392 | 3392 | } |
| 3393 | 3393 | |
| 3394 | /// Supports structs and unions. | |
| 3394 | 3395 | pub fn structFieldOffset(ty: Type, index: usize, target: Target) u64 { |
| 3395 | const fields = ty.structFields(); | |
| 3396 | if (ty.castTag(.@"struct")) |payload| { | |
| 3397 | const struct_obj = payload.data; | |
| 3398 | assert(struct_obj.status == .have_layout); | |
| 3399 | const is_packed = struct_obj.layout == .Packed; | |
| 3400 | if (is_packed) @panic("TODO packed structs"); | |
| 3401 | } | |
| 3396 | switch (ty.tag()) { | |
| 3397 | .@"struct" => { | |
| 3398 | const struct_obj = ty.castTag(.@"struct").?.data; | |
| 3399 | assert(struct_obj.status == .have_layout); | |
| 3400 | const is_packed = struct_obj.layout == .Packed; | |
| 3401 | if (is_packed) @panic("TODO packed structs"); | |
| 3402 | 3402 | |
| 3403 | var offset: u64 = 0; | |
| 3404 | var big_align: u32 = 0; | |
| 3405 | for (fields.values()) |field, i| { | |
| 3406 | if (!field.ty.hasCodeGenBits()) continue; | |
| 3403 | var offset: u64 = 0; | |
| 3404 | var big_align: u32 = 0; | |
| 3405 | for (struct_obj.fields.values()) |field, i| { | |
| 3406 | if (!field.ty.hasCodeGenBits()) continue; | |
| 3407 | 3407 | |
| 3408 | const field_align = a: { | |
| 3409 | if (field.abi_align.tag() == .abi_align_default) { | |
| 3410 | break :a field.ty.abiAlignment(target); | |
| 3408 | const field_align = a: { | |
| 3409 | if (field.abi_align.tag() == .abi_align_default) { | |
| 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 | 3430 | } else { |
| 3412 | break :a @intCast(u32, field.abi_align.toUnsignedInt()); | |
| 3431 | // {Payload, Tag} | |
| 3432 | return 0; | |
| 3413 | 3433 | } |
| 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); | |
| 3434 | }, | |
| 3435 | else => unreachable, | |
| 3419 | 3436 | } |
| 3420 | offset = std.mem.alignForwardGeneric(u64, offset, big_align); | |
| 3421 | return offset; | |
| 3422 | 3437 | } |
| 3423 | 3438 | |
| 3424 | 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 | 219 | poll(); |
| 220 | 220 | } |
| 221 | 221 | } |
| 222 | ||
| 223 | const SwitchProngWithVarEnum = union(enum) { | |
| 224 | One: i32, | |
| 225 | Two: f32, | |
| 226 | Meh: void, | |
| 227 | }; | |
| 228 | ||
| 229 | test "switch prong with variable" { | |
| 230 | try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 }); | |
| 231 | try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 }); | |
| 232 | try switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} }); | |
| 233 | } | |
| 234 | fn 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 | ||
| 249 | test "switch on enum using pointer capture" { | |
| 250 | try testSwitchEnumPtrCapture(); | |
| 251 | comptime try testSwitchEnumPtrCapture(); | |
| 252 | } | |
| 253 | ||
| 254 | fn 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 | 3 | const expectError = std.testing.expectError; |
| 4 | 4 | const expectEqual = std.testing.expectEqual; |
| 5 | 5 | |
| 6 | test "switch prong with variable" { | |
| 7 | try switchProngWithVarFn(SwitchProngWithVarEnum{ .One = 13 }); | |
| 8 | try switchProngWithVarFn(SwitchProngWithVarEnum{ .Two = 13.0 }); | |
| 9 | try switchProngWithVarFn(SwitchProngWithVarEnum{ .Meh = {} }); | |
| 10 | } | |
| 11 | const SwitchProngWithVarEnum = union(enum) { | |
| 12 | One: i32, | |
| 13 | Two: f32, | |
| 14 | Meh: void, | |
| 15 | }; | |
| 16 | fn 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 | ||
| 31 | test "switch on enum using pointer capture" { | |
| 32 | try testSwitchEnumPtrCapture(); | |
| 33 | comptime try testSwitchEnumPtrCapture(); | |
| 34 | } | |
| 35 | ||
| 36 | fn 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 | ||
| 48 | 6 | test "switch handles all cases of number" { |
| 49 | 7 | try testSwitchHandleAllCases(); |
| 50 | 8 | comptime try testSwitchHandleAllCases(); |