diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-10-19 20:14:10 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-10-19 20:22:47 -0700 |
| commit | dfb3231959bb340d260ddbec2b8eabfb5063c1bf (patch) | |
| tree | 1f91b36fc9ed4b0aaa35fd8f8657efb365f3ec74 /src | |
| parent | 4a76523b92bcf0e9b48438cb22f49e67e0ab3fa1 (diff) | |
| download | zig-dfb3231959bb340d260ddbec2b8eabfb5063c1bf.tar.gz zig-dfb3231959bb340d260ddbec2b8eabfb5063c1bf.zip | |
stage2: implement switching on unions
* AstGen: Move `refToIndex` and `indexToRef` to Zir
* ZIR: the switch_block_*_* instruction tags are collapsed into one
switch_block tag which uses 4 bits for flags, and reduces the
scalar_cases_len field from 32 to 28 bits.
This freed up more ZIR tags, 2 of which are now used for
`switch_cond` and `switch_cond_ref` for producing the switch
condition value. For example, for union values it returns the
corresponding enum value.
* switching with multiple cases and ranges is not yet supported because
I want to change the ZIR encoding to store index pointers into the
extra array rather than storing prong indexes. This will avoid O(N^2)
iteration over prongs.
* AstGen now adds a `switch_cond` on the operand and then passes the
result of that to the `switch_block` instruction.
* Sema: partially implement `switch_capture_*` instructions.
* Sema: `unionToTag` notices if the enum type has only one possible value.
Diffstat (limited to 'src')
| -rw-r--r-- | src/AstGen.zig | 102 | ||||
| -rw-r--r-- | src/Sema.zig | 331 | ||||
| -rw-r--r-- | src/Zir.zig | 272 | ||||
| -rw-r--r-- | src/print_zir.zig | 125 | ||||
| -rw-r--r-- | src/value.zig | 2 |
5 files changed, 390 insertions, 442 deletions
diff --git a/src/AstGen.zig b/src/AstGen.zig index 83d82f4083..d1f65b75ba 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -11,6 +11,8 @@ const StringIndexAdapter = std.hash_map.StringIndexAdapter; const StringIndexContext = std.hash_map.StringIndexContext; const Zir = @import("Zir.zig"); +const refToIndex = Zir.refToIndex; +const indexToRef = Zir.indexToRef; const trace = @import("tracy.zig").trace; const BuiltinFn = @import("BuiltinFn.zig"); @@ -57,6 +59,7 @@ fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 { Zir.Inst.Ref => @enumToInt(@field(extra, field.name)), i32 => @bitCast(u32, @field(extra, field.name)), Zir.Inst.Call.Flags => @bitCast(u32, @field(extra, field.name)), + Zir.Inst.SwitchBlock.Bits => @bitCast(u32, @field(extra, field.name)), else => @compileError("bad field type"), }); } @@ -2133,17 +2136,8 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner .slice_sentinel, .import, .switch_block, - .switch_block_multi, - .switch_block_else, - .switch_block_else_multi, - .switch_block_under, - .switch_block_under_multi, - .switch_block_ref, - .switch_block_ref_multi, - .switch_block_ref_else, - .switch_block_ref_else_multi, - .switch_block_ref_under, - .switch_block_ref_under_multi, + .switch_cond, + .switch_cond_ref, .switch_capture, .switch_capture_ref, .switch_capture_multi, @@ -5127,11 +5121,12 @@ fn fieldAccess( rl: ResultLoc, node: Ast.Node.Index, ) InnerError!Zir.Inst.Ref { - if (rl == .ref) { - return addFieldAccess(.field_ptr, gz, scope, .ref, node); - } else { - const access = try addFieldAccess(.field_val, gz, scope, .none, node); - return rvalue(gz, rl, access, node); + switch (rl) { + .ref => return addFieldAccess(.field_ptr, gz, scope, .ref, node), + else => { + const access = try addFieldAccess(.field_val, gz, scope, .none, node); + return rvalue(gz, rl, access, node); + }, } } @@ -6028,11 +6023,13 @@ fn switchExpr( } const operand_rl: ResultLoc = if (any_payload_is_ref) .ref else .none; - const operand = try expr(parent_gz, scope, operand_rl, operand_node); + const raw_operand = try expr(parent_gz, scope, operand_rl, operand_node); + const cond_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_cond_ref else .switch_cond; + const cond = try parent_gz.addUnNode(cond_tag, raw_operand, operand_node); // We need the type of the operand to use as the result location for all the prong items. const typeof_tag: Zir.Inst.Tag = if (any_payload_is_ref) .typeof_elem else .typeof; - const operand_ty_inst = try parent_gz.addUnNode(typeof_tag, operand, operand_node); - const item_rl: ResultLoc = .{ .ty = operand_ty_inst }; + const cond_ty_inst = try parent_gz.addUnNode(typeof_tag, cond, operand_node); + const item_rl: ResultLoc = .{ .ty = cond_ty_inst }; // These contain the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti. // This is the optional else prong body. @@ -6050,7 +6047,7 @@ fn switchExpr( defer block_scope.instructions.deinit(gpa); // This gets added to the parent block later, after the item expressions. - const switch_block = try parent_gz.addBlock(undefined, switch_node); + const switch_block = try parent_gz.addBlock(.switch_block, switch_node); // We re-use this same scope for all cases, including the special prong, if any. var case_scope = parent_gz.makeSubBlock(&block_scope.base); @@ -6203,44 +6200,32 @@ fn switchExpr( // Now that the item expressions are generated we can add this. try parent_gz.instructions.append(gpa, switch_block); - const ref_bit: u4 = @boolToInt(any_payload_is_ref); - const multi_bit: u4 = @boolToInt(multi_cases_len != 0); - const special_prong_bits: u4 = @enumToInt(special_prong); - comptime { - assert(@enumToInt(Zir.SpecialProng.none) == 0b00); - assert(@enumToInt(Zir.SpecialProng.@"else") == 0b01); - assert(@enumToInt(Zir.SpecialProng.under) == 0b10); - } - const zir_tags = astgen.instructions.items(.tag); - zir_tags[switch_block] = switch ((ref_bit << 3) | (special_prong_bits << 1) | multi_bit) { - 0b0_00_0 => .switch_block, - 0b0_00_1 => .switch_block_multi, - 0b0_01_0 => .switch_block_else, - 0b0_01_1 => .switch_block_else_multi, - 0b0_10_0 => .switch_block_under, - 0b0_10_1 => .switch_block_under_multi, - 0b1_00_0 => .switch_block_ref, - 0b1_00_1 => .switch_block_ref_multi, - 0b1_01_0 => .switch_block_ref_else, - 0b1_01_1 => .switch_block_ref_else_multi, - 0b1_10_0 => .switch_block_ref_under, - 0b1_10_1 => .switch_block_ref_under_multi, - else => unreachable, - }; - const payload_index = astgen.extra.items.len; - const zir_datas = astgen.instructions.items(.data); - zir_datas[switch_block].pl_node.payload_index = @intCast(u32, payload_index); - // Documentation for this: `Zir.Inst.SwitchBlock` and `Zir.Inst.SwitchBlockMulti`. - try astgen.extra.ensureUnusedCapacity(gpa, @as(usize, 2) + // operand, scalar_cases_len + try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).Struct.fields.len + @boolToInt(multi_cases_len != 0) + special_case_payload.items.len + scalar_cases_payload.items.len + multi_cases_payload.items.len); - astgen.extra.appendAssumeCapacity(@enumToInt(operand)); - astgen.extra.appendAssumeCapacity(scalar_cases_len); + + const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{ + .operand = cond, + .bits = Zir.Inst.SwitchBlock.Bits{ + .is_ref = any_payload_is_ref, + .has_multi_cases = multi_cases_len != 0, + .has_else = special_prong == .@"else", + .has_under = special_prong == .under, + .scalar_cases_len = @intCast(u28, scalar_cases_len), + }, + }); + + const zir_datas = astgen.instructions.items(.data); + const zir_tags = astgen.instructions.items(.tag); + + zir_datas[switch_block].pl_node.payload_index = payload_index; + if (multi_cases_len != 0) { astgen.extra.appendAssumeCapacity(multi_cases_len); } + const strat = rl.strategy(&block_scope); switch (strat.tag) { .break_operand => { @@ -10622,21 +10607,6 @@ fn advanceSourceCursor(astgen: *AstGen, source: []const u8, end: usize) void { astgen.source_column = column; } -const ref_start_index: u32 = Zir.Inst.Ref.typed_value_map.len; - -fn indexToRef(inst: Zir.Inst.Index) Zir.Inst.Ref { - return @intToEnum(Zir.Inst.Ref, ref_start_index + inst); -} - -fn refToIndex(inst: Zir.Inst.Ref) ?Zir.Inst.Index { - const ref_int = @enumToInt(inst); - if (ref_int >= ref_start_index) { - return ref_int - ref_start_index; - } else { - return null; - } -} - fn scanDecls(astgen: *AstGen, namespace: *Scope.Namespace, members: []const Ast.Node.Index) !void { const gpa = astgen.gpa; const tree = astgen.tree; diff --git a/src/Sema.zig b/src/Sema.zig index 12da483e24..f34b078af5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -550,18 +550,9 @@ pub fn analyzeBody( .slice_sentinel => try sema.zirSliceSentinel(block, inst), .slice_start => try sema.zirSliceStart(block, inst), .str => try sema.zirStr(block, inst), - .switch_block => try sema.zirSwitchBlock(block, inst, false, .none), - .switch_block_multi => try sema.zirSwitchBlockMulti(block, inst, false, .none), - .switch_block_else => try sema.zirSwitchBlock(block, inst, false, .@"else"), - .switch_block_else_multi => try sema.zirSwitchBlockMulti(block, inst, false, .@"else"), - .switch_block_under => try sema.zirSwitchBlock(block, inst, false, .under), - .switch_block_under_multi => try sema.zirSwitchBlockMulti(block, inst, false, .under), - .switch_block_ref => try sema.zirSwitchBlock(block, inst, true, .none), - .switch_block_ref_multi => try sema.zirSwitchBlockMulti(block, inst, true, .none), - .switch_block_ref_else => try sema.zirSwitchBlock(block, inst, true, .@"else"), - .switch_block_ref_else_multi => try sema.zirSwitchBlockMulti(block, inst, true, .@"else"), - .switch_block_ref_under => try sema.zirSwitchBlock(block, inst, true, .under), - .switch_block_ref_under_multi => try sema.zirSwitchBlockMulti(block, inst, true, .under), + .switch_block => try sema.zirSwitchBlock(block, inst), + .switch_cond => try sema.zirSwitchCond(block, inst, false), + .switch_cond_ref => try sema.zirSwitchCond(block, inst, true), .switch_capture => try sema.zirSwitchCapture(block, inst, false, false), .switch_capture_ref => try sema.zirSwitchCapture(block, inst, false, true), .switch_capture_multi => try sema.zirSwitchCapture(block, inst, true, false), @@ -5433,11 +5424,80 @@ fn zirSwitchCapture( const zir_datas = sema.code.instructions.items(.data); const capture_info = zir_datas[inst].switch_capture; const switch_info = zir_datas[capture_info.switch_inst].pl_node; - const src = switch_info.src(); + const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index); + const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = switch_info.src_node }; + const switch_src = switch_info.src(); + const operand_is_ref = switch_extra.data.bits.is_ref; + const cond_inst = Zir.refToIndex(switch_extra.data.operand).?; + const cond_info = sema.code.instructions.items(.data)[cond_inst].un_node; + const operand_ptr = sema.resolveInst(cond_info.operand); + const operand_ptr_ty = sema.typeOf(operand_ptr); + const operand_ty = if (operand_is_ref) operand_ptr_ty.childType() else operand_ptr_ty; + + if (is_multi) { + return sema.fail(block, switch_src, "TODO implement Sema for switch capture multi", .{}); + } + const scalar_prong = switch_extra.data.getScalarProng(sema.code, switch_extra.end, capture_info.prong_index); + const item = sema.resolveInst(scalar_prong.item); + // Previous switch validation ensured this will succeed + const item_val = sema.resolveConstValue(block, .unneeded, item) catch unreachable; - _ = is_ref; - _ = is_multi; - return sema.fail(block, src, "TODO implement Sema for zirSwitchCapture", .{}); + switch (operand_ty.zigTypeTag()) { + .Union => { + const union_obj = operand_ty.cast(Type.Payload.Union).?.data; + const enum_ty = union_obj.tag_ty; + + const field_index_usize = enum_ty.enumTagFieldIndex(item_val).?; + const field_index = @intCast(u32, field_index_usize); + const field = union_obj.fields.values()[field_index]; + + // TODO handle multiple union tags which have compatible types + + if (is_ref) { + assert(operand_is_ref); + + const field_ty_ptr = try Type.ptr(sema.arena, .{ + .pointee_type = field.ty, + .@"addrspace" = .generic, + .mutable = operand_ptr_ty.ptrIsMutable(), + }); + + if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| { + return sema.addConstant( + field_ty_ptr, + try Value.Tag.field_ptr.create(sema.arena, .{ + .container_ptr = op_ptr_val, + .field_index = field_index, + }), + ); + } + try sema.requireRuntimeBlock(block, operand_src); + return block.addStructFieldPtr(operand_ptr, field_index, field.ty); + } + + const operand = if (operand_is_ref) + try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src) + else + operand_ptr; + + if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| { + return sema.addConstant( + field.ty, + operand_val.castTag(.@"union").?.data.val, + ); + } + try sema.requireRuntimeBlock(block, operand_src); + return block.addStructFieldVal(operand, field_index, field.ty); + }, + .ErrorSet => { + return sema.fail(block, operand_src, "TODO implement Sema for zirSwitchCapture for error sets", .{}); + }, + else => { + return sema.fail(block, operand_src, "switch on type '{}' provides no capture value", .{ + operand_ty, + }); + }, + } } fn zirSwitchCaptureElse( @@ -5452,96 +5512,108 @@ fn zirSwitchCaptureElse( const zir_datas = sema.code.instructions.items(.data); const capture_info = zir_datas[inst].switch_capture; const switch_info = zir_datas[capture_info.switch_inst].pl_node; + const switch_extra = sema.code.extraData(Zir.Inst.SwitchBlock, switch_info.payload_index).data; const src = switch_info.src(); + const operand_is_ref = switch_extra.bits.is_ref; + assert(!is_ref or operand_is_ref); - _ = is_ref; return sema.fail(block, src, "TODO implement Sema for zirSwitchCaptureElse", .{}); } -fn zirSwitchBlock( +fn zirSwitchCond( sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool, - special_prong: Zir.SpecialProng, ) CompileError!Air.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); - - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node }; - const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); + const operand_ptr = sema.resolveInst(inst_data.operand); + const operand = if (is_ref) try sema.analyzeLoad(block, src, operand_ptr, src) else operand_ptr; + const operand_ty = sema.typeOf(operand); - const operand_ptr = sema.resolveInst(extra.data.operand); - const operand = if (is_ref) - try sema.analyzeLoad(block, src, operand_ptr, operand_src) - else - operand_ptr; + switch (operand_ty.zigTypeTag()) { + .Type, + .Void, + .Bool, + .Int, + .Float, + .ComptimeFloat, + .ComptimeInt, + .EnumLiteral, + .Pointer, + .Fn, + .ErrorSet, + .Enum, + => { + if ((try sema.typeHasOnePossibleValue(block, src, operand_ty))) |opv| { + return sema.addConstant(operand_ty, opv); + } + return operand; + }, - return sema.analyzeSwitch( - block, - operand, - extra.end, - special_prong, - extra.data.cases_len, - 0, - inst, - inst_data.src_node, - ); + .Union => { + const enum_ty = operand_ty.unionTagType() orelse { + const msg = msg: { + const msg = try sema.errMsg(block, src, "switch on untagged union", .{}); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, operand_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(msg); + }; + return sema.unionToTag(block, enum_ty, operand, src); + }, + + .ErrorUnion, + .NoReturn, + .Array, + .Struct, + .Undefined, + .Null, + .Optional, + .BoundFn, + .Opaque, + .Vector, + .Frame, + .AnyFrame, + => return sema.fail(block, src, "switch on type '{}'", .{operand_ty}), + } } -fn zirSwitchBlockMulti( - sema: *Sema, - block: *Block, - inst: Zir.Inst.Index, - is_ref: bool, - special_prong: Zir.SpecialProng, -) CompileError!Air.Inst.Ref { +fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); + const gpa = sema.gpa; const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); - const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = inst_data.src_node }; - const extra = sema.code.extraData(Zir.Inst.SwitchBlockMulti, inst_data.payload_index); + const src_node_offset = inst_data.src_node; + const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset }; + const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset }; + const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); const operand_ptr = sema.resolveInst(extra.data.operand); - const operand = if (is_ref) + const operand = if (extra.data.bits.is_ref) try sema.analyzeLoad(block, src, operand_ptr, operand_src) else operand_ptr; - return sema.analyzeSwitch( - block, - operand, - extra.end, - special_prong, - extra.data.scalar_cases_len, - extra.data.multi_cases_len, - inst, - inst_data.src_node, - ); -} + var header_extra_index: usize = extra.end; -fn analyzeSwitch( - sema: *Sema, - block: *Block, - operand: Air.Inst.Ref, - extra_end: usize, - special_prong: Zir.SpecialProng, - scalar_cases_len: usize, - multi_cases_len: usize, - switch_inst: Zir.Inst.Index, - src_node_offset: i32, -) CompileError!Air.Inst.Ref { - const gpa = sema.gpa; + const scalar_cases_len = extra.data.bits.scalar_cases_len; + const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: { + const multi_cases_len = sema.code.extra[header_extra_index]; + header_extra_index += 1; + break :blk multi_cases_len; + } else 0; + const special_prong = extra.data.bits.specialProng(); const special: struct { body: []const Zir.Inst.Index, end: usize } = switch (special_prong) { - .none => .{ .body = &.{}, .end = extra_end }, + .none => .{ .body = &.{}, .end = header_extra_index }, .under, .@"else" => blk: { - const body_len = sema.code.extra[extra_end]; - const extra_body_start = extra_end + 1; + const body_len = sema.code.extra[header_extra_index]; + const extra_body_start = header_extra_index + 1; break :blk .{ .body = sema.code.extra[extra_body_start..][0..body_len], .end = extra_body_start + body_len, @@ -5549,9 +5621,6 @@ fn analyzeSwitch( }, }; - const src: LazySrcLoc = .{ .node_offset = src_node_offset }; - const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset }; - const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset }; const operand_ty = sema.typeOf(operand); // Validate usage of '_' prongs. @@ -5945,7 +6014,7 @@ fn analyzeSwitch( .data = undefined, }); var label: Block.Label = .{ - .zir_block = switch_inst, + .zir_block = inst, .merges = .{ .results = .{}, .br_list = .{}, @@ -8934,8 +9003,9 @@ fn zirStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool) const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node }; const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); - const field_index = union_obj.fields.getIndex(field_name) orelse + const field_index_usize = union_obj.fields.getIndex(field_name) orelse return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); + const field_index = @intCast(u32, field_index_usize); if (is_ref) { return sema.fail(block, src, "TODO: Sema.zirStructInit is_ref=true union", .{}); @@ -8943,12 +9013,10 @@ fn zirStructInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool) const init_inst = sema.resolveInst(item.data.init); if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| { + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); return sema.addConstant( resolved_ty, - try Value.Tag.@"union".create(sema.arena, .{ - .tag = try Value.Tag.int_u64.create(sema.arena, field_index), - .val = val, - }), + try Value.Tag.@"union".create(sema.arena, .{ .tag = tag_val, .val = val }), ); } return sema.fail(block, src, "TODO: Sema.zirStructInit for runtime-known union values", .{}); @@ -9152,8 +9220,8 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src); const val = try sema.resolveConstValue(block, operand_src, type_info); const union_val = val.cast(Value.Payload.Union).?.data; - const TypeInfoTag = std.meta.Tag(std.builtin.TypeInfo); - const tag_index = @intCast(std.meta.Tag(TypeInfoTag), union_val.tag.toUnsignedInt()); + const tag_ty = type_info_ty.unionTagType().?; + const tag_index = tag_ty.enumTagFieldIndex(union_val.tag).?; switch (@intToEnum(std.builtin.TypeId, tag_index)) { .Type => return Air.Inst.Ref.type_type, .Void => return Air.Inst.Ref.void_type, @@ -10819,10 +10887,39 @@ fn fieldVal( try Value.Tag.@"error".create(arena, .{ .name = name }), ); }, - .Struct, .Opaque, .Union => { + .Union => { if (child_type.getNamespace()) |namespace| { - if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| { - return sema.analyzeLoad(block, src, inst, src); + if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| { + return inst; + } + } + if (child_type.unionTagType()) |enum_ty| { + if (enum_ty.enumFieldIndex(field_name)) |field_index_usize| { + const field_index = @intCast(u32, field_index_usize); + return sema.addConstant( + enum_ty, + try Value.Tag.enum_field_index.create(sema.arena, field_index), + ); + } + } + return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name); + }, + .Enum => { + if (child_type.getNamespace()) |namespace| { + if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| { + return inst; + } + } + const field_index_usize = child_type.enumFieldIndex(field_name) orelse + return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name); + const field_index = @intCast(u32, field_index_usize); + const enum_val = try Value.Tag.enum_field_index.create(arena, field_index); + return sema.addConstant(try child_type.copy(arena), enum_val); + }, + .Struct, .Opaque => { + if (child_type.getNamespace()) |namespace| { + if (try sema.namespaceLookupVal(block, src, namespace, field_name)) |inst| { + return inst; } } // TODO add note: declared here @@ -10836,35 +10933,6 @@ fn fieldVal( kw_name, child_type, field_name, }); }, - .Enum => { - if (child_type.getNamespace()) |namespace| { - if (try sema.namespaceLookupRef(block, src, namespace, field_name)) |inst| { - return sema.analyzeLoad(block, src, inst, src); - } - } - const field_index = child_type.enumFieldIndex(field_name) orelse { - const msg = msg: { - const msg = try sema.errMsg( - block, - src, - "enum '{}' has no member named '{s}'", - .{ child_type, field_name }, - ); - errdefer msg.destroy(sema.gpa); - try sema.mod.errNoteNonLazy( - child_type.declSrcLoc(), - msg, - "enum declared here", - .{}, - ); - break :msg msg; - }; - return sema.failWithOwnedErrorMsg(msg); - }; - const field_index_u32 = @intCast(u32, field_index); - const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32); - return sema.addConstant(try child_type.copy(arena), enum_val); - }, else => return sema.fail(block, src, "type '{}' has no members", .{child_type}), } }, @@ -11244,6 +11312,17 @@ fn namespaceLookupRef( return try sema.analyzeDeclRef(decl); } +fn namespaceLookupVal( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + namespace: *Namespace, + decl_name: []const u8, +) CompileError!?Air.Inst.Ref { + const decl = (try sema.namespaceLookup(block, src, namespace, decl_name)) orelse return null; + return try sema.analyzeDeclVal(block, src, decl); +} + fn structFieldPtr( sema: *Sema, block: *Block, @@ -11370,10 +11449,9 @@ fn unionFieldVal( const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty); const union_obj = union_ty.cast(Type.Payload.Union).?.data; - const field_index_big = union_obj.fields.getIndex(field_name) orelse + const field_index_usize = union_obj.fields.getIndex(field_name) orelse return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name); - const field_index = @intCast(u32, field_index_big); - + const field_index = @intCast(u32, field_index_usize); const field = union_obj.fields.values()[field_index]; if (try sema.resolveMaybeUndefVal(block, src, union_byval)) |union_val| { @@ -12960,15 +13038,18 @@ fn wrapErrorUnion( fn unionToTag( sema: *Sema, block: *Block, - dest_ty: Type, + enum_ty: Type, un: Air.Inst.Ref, un_src: LazySrcLoc, ) !Air.Inst.Ref { + if ((try sema.typeHasOnePossibleValue(block, un_src, enum_ty))) |opv| { + return sema.addConstant(enum_ty, opv); + } if (try sema.resolveMaybeUndefVal(block, un_src, un)) |un_val| { - return sema.addConstant(dest_ty, un_val.unionTag()); + return sema.addConstant(enum_ty, un_val.unionTag()); } try sema.requireRuntimeBlock(block, un_src); - return block.addTyOp(.get_union_tag, dest_ty, un); + return block.addTyOp(.get_union_tag, enum_ty, un); } fn resolvePeerTypes( diff --git a/src/Zir.zig b/src/Zir.zig index e45aac1a6f..4d8cd57947 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -72,6 +72,7 @@ pub fn extraData(code: Zir, comptime T: type, index: usize) struct { data: T, en Inst.Ref => @intToEnum(Inst.Ref, code.extra[i]), i32 => @bitCast(i32, code.extra[i]), Inst.Call.Flags => @bitCast(Inst.Call.Flags, code.extra[i]), + Inst.SwitchBlock.Bits => @bitCast(Inst.SwitchBlock.Bits, code.extra[i]), else => @compileError("bad field type"), }; i += 1; @@ -618,39 +619,16 @@ pub const Inst = struct { enum_literal, /// A switch expression. Uses the `pl_node` union field. /// AST node is the switch, payload is `SwitchBlock`. - /// All prongs of target handled. switch_block, - /// Same as switch_block, except one or more prongs have multiple items. - /// Payload is `SwitchBlockMulti` - switch_block_multi, - /// Same as switch_block, except has an else prong. - switch_block_else, - /// Same as switch_block_else, except one or more prongs have multiple items. - /// Payload is `SwitchBlockMulti` - switch_block_else_multi, - /// Same as switch_block, except has an underscore prong. - switch_block_under, - /// Same as switch_block, except one or more prongs have multiple items. - /// Payload is `SwitchBlockMulti` - switch_block_under_multi, - /// Same as `switch_block` but the target is a pointer to the value being switched on. - switch_block_ref, - /// Same as `switch_block_multi` but the target is a pointer to the value being switched on. - /// Payload is `SwitchBlockMulti` - switch_block_ref_multi, - /// Same as `switch_block_else` but the target is a pointer to the value being switched on. - switch_block_ref_else, - /// Same as `switch_block_else_multi` but the target is a pointer to the - /// value being switched on. - /// Payload is `SwitchBlockMulti` - switch_block_ref_else_multi, - /// Same as `switch_block_under` but the target is a pointer to the value - /// being switched on. - switch_block_ref_under, - /// Same as `switch_block_under_multi` but the target is a pointer to - /// the value being switched on. - /// Payload is `SwitchBlockMulti` - switch_block_ref_under_multi, + /// Produces the value that will be switched on. For example, for + /// integers, it returns the integer with no modifications. For tagged unions, it + /// returns the active enum tag. + /// Uses the `un_node` union field. + switch_cond, + /// Same as `switch_cond`, except the input operand is a pointer to + /// what will be switched on. + /// Uses the `un_node` union field. + switch_cond_ref, /// Produces the capture value for a switch prong. /// Uses the `switch_capture` field. switch_capture, @@ -1109,17 +1087,8 @@ pub const Inst = struct { .switch_capture_else, .switch_capture_else_ref, .switch_block, - .switch_block_multi, - .switch_block_else, - .switch_block_else_multi, - .switch_block_under, - .switch_block_under_multi, - .switch_block_ref, - .switch_block_ref_multi, - .switch_block_ref_else, - .switch_block_ref_else_multi, - .switch_block_ref_under, - .switch_block_ref_under_multi, + .switch_cond, + .switch_cond_ref, .validate_struct_init, .validate_array_init, .struct_init_empty, @@ -1367,17 +1336,8 @@ pub const Inst = struct { .ensure_err_payload_void = .un_tok, .enum_literal = .str_tok, .switch_block = .pl_node, - .switch_block_multi = .pl_node, - .switch_block_else = .pl_node, - .switch_block_else_multi = .pl_node, - .switch_block_under = .pl_node, - .switch_block_under_multi = .pl_node, - .switch_block_ref = .pl_node, - .switch_block_ref_multi = .pl_node, - .switch_block_ref_else = .pl_node, - .switch_block_ref_else_multi = .pl_node, - .switch_block_ref_under = .pl_node, - .switch_block_ref_under_multi = .pl_node, + .switch_cond = .un_node, + .switch_cond_ref = .un_node, .switch_capture = .switch_capture, .switch_capture_ref = .switch_capture, .switch_capture_multi = .switch_capture, @@ -2466,37 +2426,17 @@ pub const Inst = struct { index: u32, }; - /// This form is supported when there are no ranges, and exactly 1 item per block. - /// Depending on zir tag and len fields, extra fields trail - /// this one in the extra array. - /// 0. else_body { // If the tag has "_else" or "_under" in it. - /// body_len: u32, - /// body member Index for every body_len - /// } - /// 1. cases: { - /// item: Ref, - /// body_len: u32, - /// body member Index for every body_len - /// } for every cases_len - pub const SwitchBlock = struct { - operand: Ref, - cases_len: u32, - }; - - /// This form is required when there exists a block which has more than one item, - /// or a range. - /// Depending on zir tag and len fields, extra fields trail - /// this one in the extra array. - /// 0. else_body { // If the tag has "_else" or "_under" in it. + /// 0. multi_cases_len: u32 // If has_multi_cases is set. + /// 1. else_body { // If has_else or has_under is set. /// body_len: u32, /// body member Index for every body_len /// } - /// 1. scalar_cases: { // for every scalar_cases_len + /// 2. scalar_cases: { // for every scalar_cases_len /// item: Ref, /// body_len: u32, /// body member Index for every body_len /// } - /// 2. multi_cases: { // for every multi_cases_len + /// 3. multi_cases: { // for every multi_cases_len /// items_len: u32, /// ranges_len: u32, /// body_len: u32, @@ -2507,10 +2447,78 @@ pub const Inst = struct { /// } /// body member Index for every body_len /// } - pub const SwitchBlockMulti = struct { + pub const SwitchBlock = struct { operand: Ref, - scalar_cases_len: u32, - multi_cases_len: u32, + bits: Bits, + + pub const Bits = packed struct { + /// If true, one or more prongs have multiple items. + has_multi_cases: bool, + /// If true, there is an else prong. This is mutually exclusive with `has_under`. + has_else: bool, + /// If true, there is an underscore prong. This is mutually exclusive with `has_else`. + has_under: bool, + /// If true, the `operand` is a pointer to the value being switched on. + is_ref: bool, + scalar_cases_len: u28, + + pub fn specialProng(bits: Bits) SpecialProng { + const has_else: u2 = @boolToInt(bits.has_else); + const has_under: u2 = @boolToInt(bits.has_under); + return switch ((has_else << 1) | has_under) { + 0b00 => .none, + 0b01 => .under, + 0b10 => .@"else", + 0b11 => unreachable, + }; + } + }; + + pub const ScalarProng = struct { + item: Ref, + body: []const Index, + }; + + /// TODO performance optimization: instead of having this helper method + /// change the definition of switch_capture instruction to store extra_index + /// instead of prong_index. This way, Sema won't be doing O(N^2) iterations + /// over the switch prongs. + pub fn getScalarProng( + self: SwitchBlock, + zir: Zir, + extra_end: usize, + prong_index: usize, + ) ScalarProng { + var extra_index: usize = extra_end; + + if (self.bits.has_multi_cases) { + extra_index += 1; + } + + if (self.bits.specialProng() != .none) { + const body_len = zir.extra[extra_index]; + extra_index += 1; + const body = zir.extra[extra_index..][0..body_len]; + extra_index += body.len; + } + + var scalar_i: usize = 0; + while (true) : (scalar_i += 1) { + const item = @intToEnum(Ref, zir.extra[extra_index]); + extra_index += 1; + const body_len = zir.extra[extra_index]; + extra_index += 1; + const body = zir.extra[extra_index..][0..body_len]; + extra_index += body.len; + + if (scalar_i < prong_index) continue; + + return .{ + .item = item, + .body = body, + }; + } + } }; pub const Field = struct { @@ -2934,7 +2942,7 @@ pub const Inst = struct { /// Trailing: for each `imports_len` there is an Item pub const Imports = struct { - imports_len: Zir.Inst.Index, + imports_len: Inst.Index, pub const Item = struct { /// null terminated string index @@ -3077,7 +3085,7 @@ pub fn declIteratorInner(zir: Zir, extra_index: usize, decls_len: u32) DeclItera /// The iterator would have to allocate memory anyway to iterate. So here we populate /// an ArrayList as the result. -pub fn findDecls(zir: Zir, list: *std.ArrayList(Zir.Inst.Index), decl_sub_index: u32) !void { +pub fn findDecls(zir: Zir, list: *std.ArrayList(Inst.Index), decl_sub_index: u32) !void { const block_inst = zir.extra[decl_sub_index + 6]; list.clearRetainingCapacity(); @@ -3086,8 +3094,8 @@ pub fn findDecls(zir: Zir, list: *std.ArrayList(Zir.Inst.Index), decl_sub_index: fn findDeclsInner( zir: Zir, - list: *std.ArrayList(Zir.Inst.Index), - inst: Zir.Inst.Index, + list: *std.ArrayList(Inst.Index), + inst: Inst.Index, ) Allocator.Error!void { const tags = zir.instructions.items(.tag); const datas = zir.instructions.items(.data); @@ -3148,19 +3156,7 @@ fn findDeclsInner( try zir.findDeclsBody(list, then_body); try zir.findDeclsBody(list, else_body); }, - .switch_block => return findDeclsSwitch(zir, list, inst, .none), - .switch_block_else => return findDeclsSwitch(zir, list, inst, .@"else"), - .switch_block_under => return findDeclsSwitch(zir, list, inst, .under), - .switch_block_ref => return findDeclsSwitch(zir, list, inst, .none), - .switch_block_ref_else => return findDeclsSwitch(zir, list, inst, .@"else"), - .switch_block_ref_under => return findDeclsSwitch(zir, list, inst, .under), - - .switch_block_multi => return findDeclsSwitchMulti(zir, list, inst, .none), - .switch_block_else_multi => return findDeclsSwitchMulti(zir, list, inst, .@"else"), - .switch_block_under_multi => return findDeclsSwitchMulti(zir, list, inst, .under), - .switch_block_ref_multi => return findDeclsSwitchMulti(zir, list, inst, .none), - .switch_block_ref_else_multi => return findDeclsSwitchMulti(zir, list, inst, .@"else"), - .switch_block_ref_under_multi => return findDeclsSwitchMulti(zir, list, inst, .under), + .switch_block => return findDeclsSwitch(zir, list, inst), .suspend_block => @panic("TODO iterate suspend block"), @@ -3170,71 +3166,34 @@ fn findDeclsInner( fn findDeclsSwitch( zir: Zir, - list: *std.ArrayList(Zir.Inst.Index), - inst: Zir.Inst.Index, - special_prong: SpecialProng, + list: *std.ArrayList(Inst.Index), + inst: Inst.Index, ) Allocator.Error!void { const inst_data = zir.instructions.items(.data)[inst].pl_node; const extra = zir.extraData(Inst.SwitchBlock, inst_data.payload_index); - const special: struct { - body: []const Inst.Index, - end: usize, - } = switch (special_prong) { - .none => .{ .body = &.{}, .end = extra.end }, - .under, .@"else" => blk: { - const body_len = zir.extra[extra.end]; - const extra_body_start = extra.end + 1; - break :blk .{ - .body = zir.extra[extra_body_start..][0..body_len], - .end = extra_body_start + body_len, - }; - }, - }; - try zir.findDeclsBody(list, special.body); + var extra_index: usize = extra.end; - var extra_index: usize = special.end; - var scalar_i: usize = 0; - while (scalar_i < extra.data.cases_len) : (scalar_i += 1) { + const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: { + const multi_cases_len = zir.extra[extra_index]; extra_index += 1; + break :blk multi_cases_len; + } else 0; + + const special_prong = extra.data.bits.specialProng(); + if (special_prong != .none) { const body_len = zir.extra[extra_index]; extra_index += 1; const body = zir.extra[extra_index..][0..body_len]; - extra_index += body_len; + extra_index += body.len; try zir.findDeclsBody(list, body); } -} -fn findDeclsSwitchMulti( - zir: Zir, - list: *std.ArrayList(Zir.Inst.Index), - inst: Zir.Inst.Index, - special_prong: SpecialProng, -) Allocator.Error!void { - const inst_data = zir.instructions.items(.data)[inst].pl_node; - const extra = zir.extraData(Inst.SwitchBlockMulti, inst_data.payload_index); - const special: struct { - body: []const Inst.Index, - end: usize, - } = switch (special_prong) { - .none => .{ .body = &.{}, .end = extra.end }, - .under, .@"else" => blk: { - const body_len = zir.extra[extra.end]; - const extra_body_start = extra.end + 1; - break :blk .{ - .body = zir.extra[extra_body_start..][0..body_len], - .end = extra_body_start + body_len, - }; - }, - }; - - try zir.findDeclsBody(list, special.body); - - var extra_index: usize = special.end; { + const scalar_cases_len = extra.data.bits.scalar_cases_len; var scalar_i: usize = 0; - while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) { + while (scalar_i < scalar_cases_len) : (scalar_i += 1) { extra_index += 1; const body_len = zir.extra[extra_index]; extra_index += 1; @@ -3246,7 +3205,7 @@ fn findDeclsSwitchMulti( } { var multi_i: usize = 0; - while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) { + while (multi_i < multi_cases_len) : (multi_i += 1) { const items_len = zir.extra[extra_index]; extra_index += 1; const ranges_len = zir.extra[extra_index]; @@ -3353,3 +3312,18 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo { .total_params_len = total_params_len, }; } + +const ref_start_index: u32 = Inst.Ref.typed_value_map.len; + +pub fn indexToRef(inst: Inst.Index) Inst.Ref { + return @intToEnum(Inst.Ref, ref_start_index + inst); +} + +pub fn refToIndex(inst: Inst.Ref) ?Inst.Index { + const ref_int = @enumToInt(inst); + if (ref_int >= ref_start_index) { + return ref_int - ref_start_index; + } else { + return null; + } +} diff --git a/src/print_zir.zig b/src/print_zir.zig index f0f282f55d..0bbd7e1655 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -234,6 +234,8 @@ const Writer = struct { .@"await", .await_nosuspend, .fence, + .switch_cond, + .switch_cond_ref, => try self.writeUnNode(stream, inst), .ref, @@ -379,19 +381,7 @@ const Writer = struct { .error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon), .error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func), - .switch_block => try self.writePlNodeSwitchBr(stream, inst, .none), - .switch_block_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"), - .switch_block_under => try self.writePlNodeSwitchBr(stream, inst, .under), - .switch_block_ref => try self.writePlNodeSwitchBr(stream, inst, .none), - .switch_block_ref_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"), - .switch_block_ref_under => try self.writePlNodeSwitchBr(stream, inst, .under), - - .switch_block_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none), - .switch_block_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"), - .switch_block_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under), - .switch_block_ref_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .none), - .switch_block_ref_else_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .@"else"), - .switch_block_ref_under_multi => try self.writePlNodeSwitchBlockMulti(stream, inst, .under), + .switch_block => try self.writePlNodeSwitchBlock(stream, inst), .field_ptr, .field_val, @@ -1649,113 +1639,46 @@ const Writer = struct { try self.writeSrc(stream, inst_data.src()); } - fn writePlNodeSwitchBr( - self: *Writer, - stream: anytype, - inst: Zir.Inst.Index, - special_prong: Zir.SpecialProng, - ) !void { + fn writePlNodeSwitchBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); - const special: struct { - body: []const Zir.Inst.Index, - end: usize, - } = switch (special_prong) { - .none => .{ .body = &.{}, .end = extra.end }, - .under, .@"else" => blk: { - const body_len = self.code.extra[extra.end]; - const extra_body_start = extra.end + 1; - break :blk .{ - .body = self.code.extra[extra_body_start..][0..body_len], - .end = extra_body_start + body_len, - }; - }, - }; - - try self.writeInstRef(stream, extra.data.operand); - - self.indent += 2; - - if (special.body.len != 0) { - const prong_name = switch (special_prong) { - .@"else" => "else", - .under => "_", - else => unreachable, - }; - try stream.writeAll(",\n"); - try stream.writeByteNTimes(' ', self.indent); - try stream.print("{s} => ", .{prong_name}); - try self.writeBracedBody(stream, special.body); - } - - var extra_index: usize = special.end; - { - var scalar_i: usize = 0; - while (scalar_i < extra.data.cases_len) : (scalar_i += 1) { - const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); - extra_index += 1; - const body_len = self.code.extra[extra_index]; - extra_index += 1; - const body = self.code.extra[extra_index..][0..body_len]; - extra_index += body_len; - - try stream.writeAll(",\n"); - try stream.writeByteNTimes(' ', self.indent); - try self.writeInstRef(stream, item_ref); - try stream.writeAll(" => "); - try self.writeBracedBody(stream, body); - } - } - - self.indent -= 2; - try stream.writeAll(") "); - try self.writeSrc(stream, inst_data.src()); - } + var extra_index: usize = extra.end; - fn writePlNodeSwitchBlockMulti( - self: *Writer, - stream: anytype, - inst: Zir.Inst.Index, - special_prong: Zir.SpecialProng, - ) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Zir.Inst.SwitchBlockMulti, inst_data.payload_index); - const special: struct { - body: []const Zir.Inst.Index, - end: usize, - } = switch (special_prong) { - .none => .{ .body = &.{}, .end = extra.end }, - .under, .@"else" => blk: { - const body_len = self.code.extra[extra.end]; - const extra_body_start = extra.end + 1; - break :blk .{ - .body = self.code.extra[extra_body_start..][0..body_len], - .end = extra_body_start + body_len, - }; - }, - }; + const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: { + const multi_cases_len = self.code.extra[extra_index]; + extra_index += 1; + break :blk multi_cases_len; + } else 0; try self.writeInstRef(stream, extra.data.operand); + try self.writeFlag(stream, ", ref", extra.data.bits.is_ref); self.indent += 2; - if (special.body.len != 0) { + else_prong: { + const special_prong = extra.data.bits.specialProng(); const prong_name = switch (special_prong) { .@"else" => "else", .under => "_", - else => unreachable, + else => break :else_prong, }; + + const body_len = self.code.extra[extra_index]; + extra_index += 1; + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body.len; + try stream.writeAll(",\n"); try stream.writeByteNTimes(' ', self.indent); try stream.print("{s} => ", .{prong_name}); - try self.writeBracedBody(stream, special.body); + try self.writeBracedBody(stream, body); } - var extra_index: usize = special.end; { + const scalar_cases_len = extra.data.bits.scalar_cases_len; var scalar_i: usize = 0; - while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) { + while (scalar_i < scalar_cases_len) : (scalar_i += 1) { const item_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); extra_index += 1; const body_len = self.code.extra[extra_index]; @@ -1772,7 +1695,7 @@ const Writer = struct { } { var multi_i: usize = 0; - while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) { + while (multi_i < multi_cases_len) : (multi_i += 1) { const items_len = self.code.extra[extra_index]; extra_index += 1; const ranges_len = self.code.extra[extra_index]; diff --git a/src/value.zig b/src/value.zig index e59cc662a7..fce5e8afb1 100644 --- a/src/value.zig +++ b/src/value.zig @@ -116,7 +116,7 @@ pub const Value = extern union { decl_ref_mut, /// Pointer to a specific element of an array. elem_ptr, - /// Pointer to a specific field of a struct. + /// Pointer to a specific field of a struct or union. field_ptr, /// A slice of u8 whose memory is managed externally. bytes, |
