Merge pull request #15880 from mlugg/feat/better-switch-zir-2

Simplify and compact switch ZIR, and resolve union payload captures with PTR
This commit is contained in:
Andrew Kelley 2023-06-13 08:45:12 -07:00 committed by GitHub
commit df6319418a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1332 additions and 802 deletions

View File

@ -2610,13 +2610,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.slice_length,
.import,
.switch_block,
.switch_cond,
.switch_cond_ref,
.switch_capture,
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
.switch_capture_tag,
.switch_block_ref,
.struct_init_empty,
.struct_init,
.struct_init_ref,
@ -2960,7 +2954,7 @@ fn deferStmt(
try gz.astgen.instructions.append(gz.astgen.gpa, .{
.tag = .extended,
.data = .{ .extended = .{
.opcode = .errdefer_err_code,
.opcode = .value_placeholder,
.small = undefined,
.operand = undefined,
} },
@ -6715,6 +6709,7 @@ fn switchExpr(
// for the following variables, make note of the special prong AST node index,
// and bail out with a compile error if there are multiple special prongs present.
var any_payload_is_ref = false;
var any_has_tag_capture = false;
var scalar_cases_len: u32 = 0;
var multi_cases_len: u32 = 0;
var inline_cases_len: u32 = 0;
@ -6725,8 +6720,12 @@ fn switchExpr(
for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
if (case.payload_token) |payload_token| {
if (token_tags[payload_token] == .asterisk) {
const ident = if (token_tags[payload_token] == .asterisk) blk: {
any_payload_is_ref = true;
break :blk payload_token + 1;
} else payload_token;
if (token_tags[ident + 1] == .comma) {
any_has_tag_capture = true;
}
}
// Check for else/`_` prong.
@ -6835,13 +6834,7 @@ fn switchExpr(
const operand_lc = LineColumn{ astgen.source_line - parent_gz.decl_line, astgen.source_column };
const raw_operand = try expr(parent_gz, scope, operand_ri, 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);
// Sema expects a dbg_stmt immediately after switch_cond(_ref)
try emitDbgStmt(parent_gz, operand_lc);
// We need the type of the operand to use as the result location for all the prong items.
const cond_ty_inst = try parent_gz.addUnNode(.typeof, cond, operand_node);
const item_ri: ResultInfo = .{ .rl = .{ .ty = cond_ty_inst } };
const item_ri: ResultInfo = .{ .rl = .none };
// This contains the data that goes into the `extra` array for the SwitchBlock/SwitchBlockMulti,
// except the first cases_nodes.len slots are a table that indexes payloads later in the array, with
@ -6860,13 +6853,30 @@ fn switchExpr(
block_scope.instructions_top = GenZir.unstacked_top;
block_scope.setBreakResultInfo(ri);
// Sema expects a dbg_stmt immediately before switch_block(_ref)
try emitDbgStmt(parent_gz, operand_lc);
// This gets added to the parent block later, after the item expressions.
const switch_block = try parent_gz.makeBlockInst(.switch_block, switch_node);
const switch_tag: Zir.Inst.Tag = if (any_payload_is_ref) .switch_block_ref else .switch_block;
const switch_block = try parent_gz.makeBlockInst(switch_tag, 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);
case_scope.instructions_top = GenZir.unstacked_top;
// If any prong has an inline tag capture, allocate a shared dummy instruction for it
const tag_inst = if (any_has_tag_capture) tag_inst: {
const inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
try astgen.instructions.append(astgen.gpa, .{
.tag = .extended,
.data = .{ .extended = .{
.opcode = .value_placeholder,
.small = undefined,
.operand = undefined,
} }, // TODO rename opcode
});
break :tag_inst inst;
} else undefined;
// In this pass we generate all the item and prong expressions.
var multi_case_index: u32 = 0;
var scalar_case_index: u32 = 0;
@ -6880,17 +6890,22 @@ fn switchExpr(
var dbg_var_inst: Zir.Inst.Ref = undefined;
var dbg_var_tag_name: ?u32 = null;
var dbg_var_tag_inst: Zir.Inst.Ref = undefined;
var capture_inst: Zir.Inst.Index = 0;
var tag_inst: Zir.Inst.Index = 0;
var has_tag_capture = false;
var capture_val_scope: Scope.LocalVal = undefined;
var tag_scope: Scope.LocalVal = undefined;
var capture: Zir.Inst.SwitchBlock.ProngInfo.Capture = .none;
const sub_scope = blk: {
const payload_token = case.payload_token orelse break :blk &case_scope.base;
const ident = if (token_tags[payload_token] == .asterisk)
payload_token + 1
else
payload_token;
const is_ptr = ident != payload_token;
capture = if (is_ptr) .by_ref else .by_val;
const ident_slice = tree.tokenSlice(ident);
var payload_sub_scope: *Scope = undefined;
if (mem.eql(u8, ident_slice, "_")) {
@ -6899,53 +6914,18 @@ fn switchExpr(
}
payload_sub_scope = &case_scope.base;
} else {
if (case_node == special_node) {
const capture_tag: Zir.Inst.Tag = if (is_ptr)
.switch_capture_ref
else
.switch_capture;
capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
try astgen.instructions.append(gpa, .{
.tag = capture_tag,
.data = .{
.switch_capture = .{
.switch_inst = switch_block,
// Max int communicates that this is the else/underscore prong.
.prong_index = std.math.maxInt(u32),
},
},
});
} else {
const is_multi_case_bits: u2 = @boolToInt(is_multi_case);
const is_ptr_bits: u2 = @boolToInt(is_ptr);
const capture_tag: Zir.Inst.Tag = switch ((is_multi_case_bits << 1) | is_ptr_bits) {
0b00 => .switch_capture,
0b01 => .switch_capture_ref,
0b10 => .switch_capture_multi,
0b11 => .switch_capture_multi_ref,
};
const capture_index = if (is_multi_case) multi_case_index else scalar_case_index;
capture_inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
try astgen.instructions.append(gpa, .{
.tag = capture_tag,
.data = .{ .switch_capture = .{
.switch_inst = switch_block,
.prong_index = capture_index,
} },
});
}
const capture_name = try astgen.identAsString(ident);
try astgen.detectLocalShadowing(&case_scope.base, capture_name, ident, ident_slice, .capture);
capture_val_scope = .{
.parent = &case_scope.base,
.gen_zir = &case_scope,
.name = capture_name,
.inst = indexToRef(capture_inst),
.inst = indexToRef(switch_block),
.token_src = payload_token,
.id_cat = .capture,
};
dbg_var_name = capture_name;
dbg_var_inst = indexToRef(capture_inst);
dbg_var_inst = indexToRef(switch_block);
payload_sub_scope = &capture_val_scope.base;
}
@ -6961,14 +6941,9 @@ fn switchExpr(
}
const tag_name = try astgen.identAsString(tag_token);
try astgen.detectLocalShadowing(payload_sub_scope, tag_name, tag_token, tag_slice, .@"switch tag capture");
tag_inst = @intCast(Zir.Inst.Index, astgen.instructions.len);
try astgen.instructions.append(gpa, .{
.tag = .switch_capture_tag,
.data = .{ .un_tok = .{
.operand = cond,
.src_tok = case_scope.tokenIndexToRelative(tag_token),
} },
});
assert(any_has_tag_capture);
has_tag_capture = true;
tag_scope = .{
.parent = payload_sub_scope,
@ -7034,8 +7009,6 @@ fn switchExpr(
case_scope.instructions_top = parent_gz.instructions.items.len;
defer case_scope.unstack();
if (capture_inst != 0) try case_scope.instructions.append(gpa, capture_inst);
if (tag_inst != 0) try case_scope.instructions.append(gpa, tag_inst);
try case_scope.addDbgBlockBegin();
if (dbg_var_name) |some| {
try case_scope.addDbgVar(.dbg_var_val, some, dbg_var_inst);
@ -7053,10 +7026,42 @@ fn switchExpr(
}
const case_slice = case_scope.instructionsSlice();
const body_len = astgen.countBodyLenAfterFixups(case_slice);
// Since we use the switch_block instruction itself to refer to the
// capture, which will not be added to the child block, we need to
// handle ref_table manually, and the same for the inline tag
// capture instruction.
const refs_len = refs: {
var n: usize = 0;
var check_inst = switch_block;
while (astgen.ref_table.get(check_inst)) |ref_inst| {
n += 1;
check_inst = ref_inst;
}
if (has_tag_capture) {
check_inst = tag_inst;
while (astgen.ref_table.get(check_inst)) |ref_inst| {
n += 1;
check_inst = ref_inst;
}
}
break :refs n;
};
const body_len = refs_len + astgen.countBodyLenAfterFixups(case_slice);
try payloads.ensureUnusedCapacity(gpa, body_len);
const inline_bit = @as(u32, @boolToInt(case.inline_token != null)) << 31;
payloads.items[body_len_index] = body_len | inline_bit;
payloads.items[body_len_index] = @bitCast(u32, Zir.Inst.SwitchBlock.ProngInfo{
.body_len = @intCast(u28, body_len),
.capture = capture,
.is_inline = case.inline_token != null,
.has_tag_capture = has_tag_capture,
});
if (astgen.ref_table.fetchRemove(switch_block)) |kv| {
appendPossiblyRefdBodyInst(astgen, payloads, kv.value);
}
if (has_tag_capture) {
if (astgen.ref_table.fetchRemove(tag_inst)) |kv| {
appendPossiblyRefdBodyInst(astgen, payloads, kv.value);
}
}
appendBodyWithFixupsArrayList(astgen, payloads, case_slice);
}
}
@ -7065,14 +7070,16 @@ fn switchExpr(
try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.SwitchBlock).Struct.fields.len +
@boolToInt(multi_cases_len != 0) +
@boolToInt(any_has_tag_capture) +
payloads.items.len - case_table_end);
const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.SwitchBlock{
.operand = cond,
.operand = raw_operand,
.bits = Zir.Inst.SwitchBlock.Bits{
.has_multi_cases = multi_cases_len != 0,
.has_else = special_prong == .@"else",
.has_under = special_prong == .under,
.any_has_tag_capture = any_has_tag_capture,
.scalar_cases_len = @intCast(Zir.Inst.SwitchBlock.Bits.ScalarCasesLen, scalar_cases_len),
},
});
@ -7081,6 +7088,10 @@ fn switchExpr(
astgen.extra.appendAssumeCapacity(multi_cases_len);
}
if (any_has_tag_capture) {
astgen.extra.appendAssumeCapacity(tag_inst);
}
const zir_datas = astgen.instructions.items(.data);
const zir_tags = astgen.instructions.items(.tag);
@ -7103,7 +7114,7 @@ fn switchExpr(
end_index += 3 + items_len + 2 * ranges_len;
}
const body_len = @truncate(u31, payloads.items[body_len_index]);
const body_len = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, payloads.items[body_len_index]).body_len;
end_index += body_len;
switch (strat.tag) {

View File

@ -1993,31 +1993,6 @@ fn walkInstruction(
.expr = .{ .switchIndex = switch_index },
};
},
.switch_cond => {
const un_node = data[inst_index].un_node;
const operand = try self.walkRef(
file,
parent_scope,
parent_src,
un_node.operand,
need_type,
);
const operand_index = self.exprs.items.len;
try self.exprs.append(self.arena, operand.expr);
// const ast_index = self.ast_nodes.items.len;
// const sep = "=" ** 200;
// log.debug("{s}", .{sep});
// log.debug("SWITCH COND", .{});
// log.debug("ast index = {}", .{ast_index});
// log.debug("ast previous = {}", .{self.ast_nodes.items[ast_index - 1]});
// log.debug("{s}", .{sep});
return DocData.WalkResult{
.typeRef = operand.typeRef,
.expr = .{ .typeOf = operand_index },
};
},
.typeof => {
const un_node = data[inst_index].un_node;

View File

@ -2471,12 +2471,23 @@ pub const SrcLoc = struct {
}
} else unreachable;
},
.node_offset_switch_prong_capture => |node_off| {
.node_offset_switch_prong_capture,
.node_offset_switch_prong_tag_capture,
=> |node_off| {
const tree = try src_loc.file_scope.getTree(gpa);
const case_node = src_loc.declRelativeToNodeIndex(node_off);
const case = tree.fullSwitchCase(case_node).?;
const start_tok = case.payload_token.?;
const token_tags = tree.tokens.items(.tag);
const start_tok = switch (src_loc.lazy) {
.node_offset_switch_prong_capture => case.payload_token.?,
.node_offset_switch_prong_tag_capture => blk: {
var tok = case.payload_token.?;
if (token_tags[tok] == .asterisk) tok += 1;
tok += 2; // skip over comma
break :blk tok;
},
else => unreachable,
};
const end_tok = switch (token_tags[start_tok]) {
.asterisk => start_tok + 1,
else => start_tok,
@ -2957,6 +2968,9 @@ pub const LazySrcLoc = union(enum) {
/// The source location points to the capture of a switch_prong.
/// The Decl is determined contextually.
node_offset_switch_prong_capture: i32,
/// The source location points to the tag capture of a switch_prong.
/// The Decl is determined contextually.
node_offset_switch_prong_tag_capture: i32,
/// The source location points to the align expr of a function type
/// expression, found by taking this AST node index offset from the containing
/// Decl AST node, which points to a function type AST node. Next, navigate to
@ -3130,6 +3144,7 @@ pub const LazySrcLoc = union(enum) {
.node_offset_switch_special_prong,
.node_offset_switch_range,
.node_offset_switch_prong_capture,
.node_offset_switch_prong_tag_capture,
.node_offset_fn_type_align,
.node_offset_fn_type_addrspace,
.node_offset_fn_type_section,
@ -5867,10 +5882,26 @@ fn lockAndClearFileCompileError(mod: *Module, file: *File) void {
}
pub const SwitchProngSrc = union(enum) {
/// The item for a scalar prong.
scalar: u32,
/// A given single item for a multi prong.
multi: Multi,
/// A given range item for a multi prong.
range: Multi,
/// The item for the special prong.
special,
/// The main capture for a scalar prong.
scalar_capture: u32,
/// The main capture for a multi prong.
multi_capture: u32,
/// The main capture for the special prong.
special_capture,
/// The tag capture for a scalar prong.
scalar_tag_capture: u32,
/// The tag capture for a multi prong.
multi_tag_capture: u32,
/// The tag capture for the special prong.
special_tag_capture,
pub const Multi = struct {
prong: u32,
@ -5886,6 +5917,7 @@ pub const SwitchProngSrc = union(enum) {
mod: *Module,
decl: *Decl,
switch_node_offset: i32,
/// Ignored if `prong_src` is not `.range`
range_expand: RangeExpand,
) LazySrcLoc {
@setCold(true);
@ -5906,43 +5938,76 @@ pub const SwitchProngSrc = union(enum) {
var multi_i: u32 = 0;
var scalar_i: u32 = 0;
for (case_nodes) |case_node| {
const case_node = for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
if (case.ast.values.len == 0)
continue;
if (case.ast.values.len == 1 and
node_tags[case.ast.values[0]] == .identifier and
mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_"))
{
continue;
const is_special = special: {
if (case.ast.values.len == 0) break :special true;
if (case.ast.values.len == 1 and node_tags[case.ast.values[0]] == .identifier) {
break :special mem.eql(u8, tree.tokenSlice(main_tokens[case.ast.values[0]]), "_");
}
break :special false;
};
if (is_special) {
switch (prong_src) {
.special, .special_capture, .special_tag_capture => break case_node,
else => continue,
}
}
const is_multi = case.ast.values.len != 1 or
node_tags[case.ast.values[0]] == .switch_range;
switch (prong_src) {
.scalar => |i| if (!is_multi and i == scalar_i) return LazySrcLoc.nodeOffset(
.scalar,
.scalar_capture,
.scalar_tag_capture,
=> |i| if (!is_multi and i == scalar_i) break case_node,
.multi_capture,
.multi_tag_capture,
=> |i| if (is_multi and i == multi_i) break case_node,
.multi,
.range,
=> |m| if (is_multi and m.prong == multi_i) break case_node,
.special,
.special_capture,
.special_tag_capture,
=> {},
}
if (is_multi) {
multi_i += 1;
} else {
scalar_i += 1;
}
} else unreachable;
const case = tree.fullSwitchCase(case_node).?;
switch (prong_src) {
.scalar, .special => return LazySrcLoc.nodeOffset(
decl.nodeIndexToRelative(case.ast.values[0]),
),
.multi_capture => |i| if (is_multi and i == multi_i) {
return LazySrcLoc{ .node_offset_switch_prong_capture = decl.nodeIndexToRelative(case_node) };
},
.multi => |s| if (is_multi and s.prong == multi_i) {
.multi => |m| {
var item_i: u32 = 0;
for (case.ast.values) |item_node| {
if (node_tags[item_node] == .switch_range) continue;
if (item_i == s.item) return LazySrcLoc.nodeOffset(
if (item_i == m.item) return LazySrcLoc.nodeOffset(
decl.nodeIndexToRelative(item_node),
);
item_i += 1;
} else unreachable;
}
unreachable;
},
.range => |s| if (is_multi and s.prong == multi_i) {
.range => |m| {
var range_i: u32 = 0;
for (case.ast.values) |range| {
if (node_tags[range] != .switch_range) continue;
if (range_i == s.item) switch (range_expand) {
if (range_i == m.item) switch (range_expand) {
.none => return LazySrcLoc.nodeOffset(
decl.nodeIndexToRelative(range),
),
@ -5954,15 +6019,16 @@ pub const SwitchProngSrc = union(enum) {
),
};
range_i += 1;
} else unreachable;
}
unreachable;
},
.scalar_capture, .multi_capture, .special_capture => {
return .{ .node_offset_switch_prong_capture = decl.nodeIndexToRelative(case_node) };
},
.scalar_tag_capture, .multi_tag_capture, .special_tag_capture => {
return .{ .node_offset_switch_prong_tag_capture = decl.nodeIndexToRelative(case_node) };
},
}
if (is_multi) {
multi_i += 1;
} else {
scalar_i += 1;
}
} else unreachable;
}
};

File diff suppressed because it is too large Load Diff

View File

@ -667,38 +667,9 @@ pub const Inst = struct {
/// A switch expression. Uses the `pl_node` union field.
/// AST node is the switch, payload is `SwitchBlock`.
switch_block,
/// 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.
/// If the `prong_index` field is max int, it means this is the capture
/// for the else/`_` prong.
switch_capture,
/// Produces the capture value for a switch prong.
/// Result is a pointer to the value.
/// Uses the `switch_capture` field.
/// If the `prong_index` field is max int, it means this is the capture
/// for the else/`_` prong.
switch_capture_ref,
/// Produces the capture value for a switch prong.
/// The prong is one of the multi cases.
/// Uses the `switch_capture` field.
switch_capture_multi,
/// Produces the capture value for a switch prong.
/// The prong is one of the multi cases.
/// Result is a pointer to the value.
/// Uses the `switch_capture` field.
switch_capture_multi_ref,
/// Produces the capture value for an inline switch prong tag capture.
/// Uses the `un_tok` field.
switch_capture_tag,
/// A switch expression. Uses the `pl_node` union field.
/// AST node is the switch, payload is `SwitchBlock`. Operand is a pointer.
switch_block_ref,
/// Given a
/// *A returns *A
/// *E!A returns *A
@ -1144,14 +1115,8 @@ pub const Inst = struct {
.typeof_log2_int_type,
.resolve_inferred_alloc,
.set_eval_branch_quota,
.switch_capture,
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
.switch_capture_tag,
.switch_block,
.switch_cond,
.switch_cond_ref,
.switch_block_ref,
.array_base_ptr,
.field_base_ptr,
.validate_array_init_ty,
@ -1438,14 +1403,8 @@ pub const Inst = struct {
.slice_length,
.import,
.typeof_log2_int_type,
.switch_capture,
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
.switch_capture_tag,
.switch_block,
.switch_cond,
.switch_cond_ref,
.switch_block_ref,
.array_base_ptr,
.field_base_ptr,
.struct_init_empty,
@ -1696,13 +1655,7 @@ pub const Inst = struct {
.err_union_code_ptr = .un_node,
.enum_literal = .str_tok,
.switch_block = .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,
.switch_capture_multi_ref = .switch_capture,
.switch_capture_tag = .un_tok,
.switch_block_ref = .pl_node,
.array_base_ptr = .un_node,
.field_base_ptr = .un_node,
.validate_array_init_ty = .pl_node,
@ -2028,9 +1981,10 @@ pub const Inst = struct {
/// Implements the `@inComptime` builtin.
/// `operand` is `src_node: i32`.
in_comptime,
/// Used as a placeholder for the capture of an `errdefer`.
/// This is replaced by Sema with the captured value.
errdefer_err_code,
/// Used as a placeholder instruction which is just a dummy index for Sema to replace
/// with a specific value. For instance, this is used for the capture of an `errdefer`.
/// This should never appear in a body.
value_placeholder,
pub const InstData = struct {
opcode: Extended,
@ -2269,10 +2223,6 @@ pub const Inst = struct {
operand: Ref,
payload_index: u32,
},
switch_capture: struct {
switch_inst: Index,
prong_index: u32,
},
dbg_stmt: LineColumn,
/// Used for unary operators which reference an inst,
/// with an AST node source location.
@ -2342,7 +2292,6 @@ pub const Inst = struct {
bool_br,
@"unreachable",
@"break",
switch_capture,
dbg_stmt,
inst_node,
str_op,
@ -2681,37 +2630,53 @@ pub const Inst = struct {
};
/// 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. tag_capture_inst: u32 // If any_has_tag_capture is set. Index of instruction prongs use to refer to the inline tag capture.
/// 2. else_body { // If has_else or has_under is set.
/// info: ProngInfo,
/// body member Index for every info.body_len
/// }
/// 2. scalar_cases: { // for every scalar_cases_len
/// 3. scalar_cases: { // for every scalar_cases_len
/// item: Ref,
/// body_len: u32,
/// body member Index for every body_len
/// info: ProngInfo,
/// body member Index for every info.body_len
/// }
/// 3. multi_cases: { // for every multi_cases_len
/// 4. multi_cases: { // for every multi_cases_len
/// items_len: u32,
/// ranges_len: u32,
/// body_len: u32,
/// info: ProngInfo,
/// item: Ref // for every items_len
/// ranges: { // for every ranges_len
/// item_first: Ref,
/// item_last: Ref,
/// }
/// body member Index for every body_len
/// body member Index for every info.body_len
/// }
///
/// When analyzing a case body, the switch instruction itself refers to the
/// captured payload. Whether this is captured by reference or by value
/// depends on whether the `byref` bit is set for the corresponding body.
pub const SwitchBlock = struct {
/// This is always a `switch_cond` or `switch_cond_ref` instruction.
/// If it is a `switch_cond_ref` instruction, bits.is_ref is always true.
/// If it is a `switch_cond` instruction, bits.is_ref is always false.
/// Both `switch_cond` and `switch_cond_ref` return a value, not a pointer,
/// that is useful for the case items, but cannot be used for capture values.
/// For the capture values, Sema is expected to find the operand of this operand
/// and use that.
/// The operand passed to the `switch` expression. If this is a
/// `switch_block`, this is the operand value; if `switch_block_ref` it
/// is a pointer to the operand. `switch_block_ref` is always used if
/// any prong has a byref capture.
operand: Ref,
bits: Bits,
/// These are stored in trailing data in `extra` for each prong.
pub const ProngInfo = packed struct(u32) {
body_len: u28,
capture: Capture,
is_inline: bool,
has_tag_capture: bool,
pub const Capture = enum(u2) {
none,
by_val,
by_ref,
};
};
pub const Bits = packed struct {
/// If true, one or more prongs have multiple items.
has_multi_cases: bool,
@ -2719,9 +2684,11 @@ pub const Inst = struct {
has_else: bool,
/// If true, there is an underscore prong. This is mutually exclusive with `has_else`.
has_under: bool,
/// If true, at least one prong has an inline tag capture.
any_has_tag_capture: bool,
scalar_cases_len: ScalarCasesLen,
pub const ScalarCasesLen = u29;
pub const ScalarCasesLen = u28;
pub fn specialProng(bits: Bits) SpecialProng {
const has_else: u2 = @boolToInt(bits.has_else);
@ -2735,103 +2702,10 @@ pub const Inst = struct {
}
};
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 = @truncate(u31, 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 = @truncate(u31, 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 MultiProng = struct {
items: []const Ref,
body: []const Index,
};
pub fn getMultiProng(
self: SwitchBlock,
zir: Zir,
extra_end: usize,
prong_index: usize,
) MultiProng {
// +1 for self.bits.has_multi_cases == true
var extra_index: usize = extra_end + 1;
if (self.bits.specialProng() != .none) {
const body_len = @truncate(u31, 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 (scalar_i < self.bits.scalar_cases_len) : (scalar_i += 1) {
extra_index += 1;
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
extra_index += body_len;
}
var multi_i: u32 = 0;
while (true) : (multi_i += 1) {
const items_len = zir.extra[extra_index];
extra_index += 1;
const ranges_len = zir.extra[extra_index];
extra_index += 1;
const body_len = @truncate(u31, zir.extra[extra_index]);
extra_index += 1;
const items = zir.refSlice(extra_index, items_len);
extra_index += items_len;
// Each range has a start and an end.
extra_index += 2 * ranges_len;
const body = zir.extra[extra_index..][0..body_len];
extra_index += body_len;
if (multi_i < prong_index) continue;
return .{
.items = items,
.body = body,
};
}
}
};
pub const Field = struct {

View File

@ -222,8 +222,6 @@ const Writer = struct {
.bit_reverse,
.@"resume",
.@"await",
.switch_cond,
.switch_cond_ref,
.array_base_ptr,
.field_base_ptr,
.validate_struct_init_ty,
@ -235,7 +233,6 @@ const Writer = struct {
.ref,
.ret_implicit,
.closure_capture,
.switch_capture_tag,
=> try self.writeUnTok(stream, inst),
.bool_br_and,
@ -389,7 +386,9 @@ 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.writeSwitchBlock(stream, inst),
.switch_block,
.switch_block_ref,
=> try self.writeSwitchBlock(stream, inst),
.field_ptr,
.field_ptr_init,
@ -436,12 +435,6 @@ const Writer = struct {
.@"unreachable" => try self.writeUnreachable(stream, inst),
.switch_capture,
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
=> try self.writeSwitchCapture(stream, inst),
.dbg_stmt => try self.writeDbgStmt(stream, inst),
.dbg_block_begin,
@ -469,7 +462,7 @@ const Writer = struct {
.breakpoint,
.c_va_start,
.in_comptime,
.errdefer_err_code,
.value_placeholder,
=> try self.writeExtNode(stream, extended),
.builtin_src => {
@ -1903,8 +1896,19 @@ const Writer = struct {
break :blk multi_cases_len;
} else 0;
const tag_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_has_tag_capture) blk: {
const tag_capture_inst = self.code.extra[extra_index];
extra_index += 1;
break :blk tag_capture_inst;
} else undefined;
try self.writeInstRef(stream, extra.data.operand);
if (extra.data.bits.any_has_tag_capture) {
try stream.writeAll(", tag_capture=");
try self.writeInstIndex(stream, tag_capture_inst);
}
self.indent += 2;
else_prong: {
@ -1915,15 +1919,20 @@ const Writer = struct {
else => break :else_prong,
};
const body_len = @truncate(u31, self.code.extra[extra_index]);
const inline_text = if (self.code.extra[extra_index] >> 31 != 0) "inline " else "";
const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, self.code.extra[extra_index]);
const capture_text = switch (info.capture) {
.none => "",
.by_val => "by_val ",
.by_ref => "by_ref ",
};
const inline_text = if (info.is_inline) "inline " else "";
extra_index += 1;
const body = self.code.extra[extra_index..][0..body_len];
const body = self.code.extra[extra_index..][0..info.body_len];
extra_index += body.len;
try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
try stream.print("{s}{s} => ", .{ inline_text, prong_name });
try stream.print("{s}{s}{s} => ", .{ capture_text, inline_text, prong_name });
try self.writeBracedBody(stream, body);
}
@ -1933,15 +1942,19 @@ const Writer = struct {
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 = @truncate(u31, self.code.extra[extra_index]);
const is_inline = self.code.extra[extra_index] >> 31 != 0;
const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, self.code.extra[extra_index]);
extra_index += 1;
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body_len;
const body = self.code.extra[extra_index..][0..info.body_len];
extra_index += info.body_len;
try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
if (is_inline) try stream.writeAll("inline ");
switch (info.capture) {
.none => {},
.by_val => try stream.writeAll("by_val "),
.by_ref => try stream.writeAll("by_ref "),
}
if (info.is_inline) try stream.writeAll("inline ");
try self.writeInstRef(stream, item_ref);
try stream.writeAll(" => ");
try self.writeBracedBody(stream, body);
@ -1954,15 +1967,19 @@ const Writer = struct {
extra_index += 1;
const ranges_len = self.code.extra[extra_index];
extra_index += 1;
const body_len = @truncate(u31, self.code.extra[extra_index]);
const is_inline = self.code.extra[extra_index] >> 31 != 0;
const info = @bitCast(Zir.Inst.SwitchBlock.ProngInfo, self.code.extra[extra_index]);
extra_index += 1;
const items = self.code.refSlice(extra_index, items_len);
extra_index += items_len;
try stream.writeAll(",\n");
try stream.writeByteNTimes(' ', self.indent);
if (is_inline) try stream.writeAll("inline ");
switch (info.capture) {
.none => {},
.by_val => try stream.writeAll("by_val "),
.by_ref => try stream.writeAll("by_ref "),
}
if (info.is_inline) try stream.writeAll("inline ");
for (items, 0..) |item_ref, item_i| {
if (item_i != 0) try stream.writeAll(", ");
@ -1984,8 +2001,8 @@ const Writer = struct {
try self.writeInstRef(stream, item_last);
}
const body = self.code.extra[extra_index..][0..body_len];
extra_index += body_len;
const body = self.code.extra[extra_index..][0..info.body_len];
extra_index += info.body_len;
try stream.writeAll(" => ");
try self.writeBracedBody(stream, body);
}
@ -2437,12 +2454,6 @@ const Writer = struct {
try self.writeSrc(stream, src);
}
fn writeSwitchCapture(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].switch_capture;
try self.writeInstIndex(stream, inst_data.switch_inst);
try stream.print(", {d})", .{inst_data.prong_index});
}
fn writeDbgStmt(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].dbg_stmt;
try stream.print("{d}, {d})", .{ inst_data.line + 1, inst_data.column + 1 });

View File

@ -1,5 +1,6 @@
const builtin = @import("builtin");
const std = @import("std");
const assert = std.debug.assert;
const expect = std.testing.expect;
const expectError = std.testing.expectError;
const expectEqual = std.testing.expectEqual;
@ -717,3 +718,70 @@ test "comptime inline switch" {
try expectEqual(u32, value);
}
test "switch capture peer type resolution" {
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
const U = union(enum) {
a: u32,
b: u64,
fn innerVal(u: @This()) u64 {
switch (u) {
.a, .b => |x| return x,
}
}
};
try expectEqual(@as(u64, 100), U.innerVal(.{ .a = 100 }));
try expectEqual(@as(u64, 200), U.innerVal(.{ .b = 200 }));
}
test "switch capture peer type resolution for in-memory coercible payloads" {
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
const T1 = c_int;
const T2 = @Type(@typeInfo(T1));
comptime assert(T1 != T2);
const U = union(enum) {
a: T1,
b: T2,
fn innerVal(u: @This()) c_int {
switch (u) {
.a, .b => |x| return x,
}
}
};
try expectEqual(@as(c_int, 100), U.innerVal(.{ .a = 100 }));
try expectEqual(@as(c_int, 200), U.innerVal(.{ .b = 200 }));
}
test "switch pointer capture peer type resolution" {
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
const T1 = c_int;
const T2 = @Type(@typeInfo(T1));
comptime assert(T1 != T2);
const U = union(enum) {
a: T1,
b: T2,
fn innerVal(u: *@This()) *c_int {
switch (u.*) {
.a, .b => |*ptr| return ptr,
}
}
};
var ua: U = .{ .a = 100 };
var ub: U = .{ .b = 200 };
ua.innerVal().* = 111;
ub.innerVal().* = 222;
try expectEqual(U{ .a = 111 }, ua);
try expectEqual(U{ .b = 222 }, ub);
}

View File

@ -1,21 +0,0 @@
const Union = union(enum) {
A: usize,
B: isize,
};
comptime {
var u = Union{ .A = 8 };
switch (u) {
.A, .B => |e| {
_ = e;
unreachable;
},
}
}
// error
// backend=stage2
// target=native
//
// :8:20: error: capture group with incompatible types
// :8:10: note: type 'usize' here
// :8:14: note: type 'isize' here

View File

@ -0,0 +1,27 @@
export fn f() void {
const U = union(enum) { a: u32, b: *u8 };
var u: U = undefined;
switch (u) {
.a, .b => |val| _ = val,
}
}
export fn g() void {
const U = union(enum) { a: u64, b: u32 };
var u: U = undefined;
switch (u) {
.a, .b => |*ptr| _ = ptr,
}
}
// error
// backend=stage2
// target=native
//
// :5:20: error: capture group with incompatible types
// :5:20: note: incompatible types: 'u32' and '*u8'
// :5:10: note: type 'u32' here
// :5:14: note: type '*u8' here
// :13:20: error: capture group with incompatible types
// :13:14: note: pointer type child 'u32' cannot cast into resolved pointer type child 'u64'
// :13:20: note: this coercion is only possible when capturing by value

View File

@ -9,7 +9,7 @@ export fn entry() void {
}
fn foo(a: *const Payload) void {
switch (a.*) {
Payload.A => {},
.A => {},
else => unreachable,
}
}