stage2: rework AstGen for switch expressions

The switch_br ZIR instructions are now switch_block instructions. This
avoids a pointless block always surrounding a switchbr in emitted ZIR
code.

Introduce typeof_elem ZIR instruction for getting the type of the
element of a pointer value in 1 instruction.

Change typeof to be un_node, not un_tok.

Introduce switch_capture ZIR instructions for obtaining the capture
value of switch prongs.

Introduce Sema.resolveBody for when you want to extract a *Inst out of a
block and you know that there is only going to be 1 break from it.

What's not working yet: AstGen does not correctly elide
store instructions when it turns out that the result location does not
need to be used as a pointer.

Also Sema validation code for duplicate switch items is not yet
implemented.
This commit is contained in:
Andrew Kelley 2021-03-30 21:22:30 -07:00
parent 195ddab2be
commit 2a1dd174cd
4 changed files with 675 additions and 179 deletions

View File

@ -82,47 +82,3 @@ Performance optimizations to look into:
}
}
fn switchCaseExpr(
gz: *GenZir,
scope: *Scope,
rl: ResultLoc,
block: *zir.Inst.Block,
case: ast.full.SwitchCase,
target: zir.Inst.Ref,
) !void {
const tree = gz.tree();
const node_datas = tree.nodes.items(.data);
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);
const case_src = token_starts[case.ast.arrow_token];
const sub_scope = blk: {
const payload_token = case.payload_token orelse break :blk scope;
const ident = if (token_tags[payload_token] == .asterisk)
payload_token + 1
else
payload_token;
const is_ptr = ident != payload_token;
const value_name = tree.tokenSlice(ident);
if (mem.eql(u8, value_name, "_")) {
if (is_ptr) {
return mod.failTok(scope, payload_token, "pointer modifier invalid on discard", .{});
}
break :blk scope;
}
return mod.failTok(scope, ident, "TODO implement switch value payload", .{});
};
const case_body = try expr(gz, sub_scope, rl, case.ast.target_expr);
if (!case_body.tag.isNoReturn()) {
_ = try addZIRInst(mod, sub_scope, case_src, zir.Inst.Break, .{
.block = block,
.operand = case_body,
}, .{});
}
}

View File

@ -22,6 +22,7 @@ const Scope = Module.Scope;
const GenZir = Scope.GenZir;
const InnerError = Module.InnerError;
const Decl = Module.Decl;
const LazySrcLoc = Module.LazySrcLoc;
const BuiltinFn = @import("BuiltinFn.zig");
instructions: std.MultiArrayList(zir.Inst) = .{},
@ -1215,6 +1216,7 @@ fn blockExprStmts(
.negate,
.negate_wrap,
.typeof,
.typeof_elem,
.xor,
.optional_type,
.optional_type_from_ptr_elem,
@ -1243,6 +1245,24 @@ fn blockExprStmts(
.slice_sentinel,
.import,
.typeof_peer,
.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_capture,
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
.switch_capture_else,
.switch_capture_else_ref,
=> break :b false,
// ZIR instructions that are always either `noreturn` or `void`.
@ -1257,18 +1277,6 @@ fn blockExprStmts(
.break_inline,
.condbr,
.condbr_inline,
.switch_br,
.switch_br_multi,
.switch_br_else,
.switch_br_else_multi,
.switch_br_under,
.switch_br_under_multi,
.switch_br_ref,
.switch_br_ref_multi,
.switch_br_ref_else,
.switch_br_ref_else_multi,
.switch_br_ref_under,
.switch_br_ref_under_multi,
.compile_error,
.ret_node,
.ret_tok,
@ -1543,7 +1551,7 @@ fn assignOp(
const lhs_ptr = try lvalExpr(gz, scope, node_datas[infix_node].lhs);
const lhs = try gz.addUnNode(.load, lhs_ptr, infix_node);
const lhs_type = try gz.addUnTok(.typeof, lhs, infix_node);
const lhs_type = try gz.addUnNode(.typeof, lhs, infix_node);
const rhs = try expr(gz, scope, .{ .ty = lhs_type }, node_datas[infix_node].rhs);
const result = try gz.addPlNode(op_inst_tag, infix_node, zir.Inst.Bin{
@ -2548,15 +2556,28 @@ fn switchExpr(
rl: ResultLoc,
switch_node: ast.Node.Index,
) InnerError!zir.Inst.Ref {
const astgen = parent_gz.astgen;
const mod = astgen.mod;
const gpa = mod.gpa;
const tree = parent_gz.tree();
const node_datas = tree.nodes.items(.data);
const node_tags = tree.nodes.items(.tag);
const main_tokens = tree.nodes.items(.main_token);
const token_tags = tree.tokens.items(.tag);
const operand_node = node_datas[switch_node].lhs;
const extra = tree.extraData(node_datas[switch_node].rhs, ast.Node.SubRange);
const case_nodes = tree.extra_data[extra.start..extra.end];
// We perform two passes over the AST. This first pass is to collect information
// 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 scalar_cases_len: u32 = 0;
var multi_cases_len: u32 = 0;
var special_prong: zir.SpecialProng = .none;
var special_node: ast.Node.Index = 0;
var else_src: ?LazySrcLoc = null;
var underscore_src: ?LazySrcLoc = null;
for (case_nodes) |case_node| {
const case = switch (node_tags[case_node]) {
.switch_case_one => tree.switchCaseOne(case_node),
@ -2568,22 +2589,346 @@ fn switchExpr(
any_payload_is_ref = true;
}
}
// Check for else/`_` prong.
if (case.ast.values.len == 0) {
const case_src = parent_gz.tokSrcLoc(case.ast.arrow_token - 1);
if (else_src) |src| {
const msg = msg: {
const msg = try mod.errMsg(
scope,
case_src,
"multiple else prongs in switch expression",
.{},
);
errdefer msg.destroy(gpa);
try mod.errNote(scope, src, msg, "previous else prong is here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
} else if (underscore_src) |some_underscore| {
const msg = msg: {
const msg = try mod.errMsg(
scope,
parent_gz.nodeSrcLoc(switch_node),
"else and '_' prong in switch expression",
.{},
);
errdefer msg.destroy(gpa);
try mod.errNote(scope, case_src, msg, "else prong is here", .{});
try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
}
special_node = case_node;
special_prong = .@"else";
else_src = case_src;
continue;
} else 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]]), "_"))
{
const case_src = parent_gz.tokSrcLoc(case.ast.arrow_token - 1);
if (underscore_src) |src| {
const msg = msg: {
const msg = try mod.errMsg(
scope,
case_src,
"multiple '_' prongs in switch expression",
.{},
);
errdefer msg.destroy(gpa);
try mod.errNote(scope, src, msg, "previous '_' prong is here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
} else if (else_src) |some_else| {
const msg = msg: {
const msg = try mod.errMsg(
scope,
parent_gz.nodeSrcLoc(switch_node),
"else and '_' prong in switch expression",
.{},
);
errdefer msg.destroy(gpa);
try mod.errNote(scope, some_else, msg, "else prong is here", .{});
try mod.errNote(scope, case_src, msg, "'_' prong is here", .{});
break :msg msg;
};
return mod.failWithOwnedErrorMsg(scope, msg);
}
special_node = case_node;
special_prong = .under;
underscore_src = case_src;
continue;
}
if (case.ast.values.len == 1 and
getRangeNode(node_tags, node_datas, case.ast.values[0]) == null)
{
scalar_cases_len += 1;
} else {
multi_cases_len += 1;
}
}
const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{
.rl = .ref,
.tag = .switch_br_ref,
} else .{
.rl = .none,
.tag = .switch_br,
};
const operand = try expr(parent_gz, scope, rl_and_tag.rl, operand_node);
const operand_rl: ResultLoc = if (any_payload_is_ref) .ref else .none;
const operand = try expr(parent_gz, scope, operand_rl, 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 result = try parent_gz.addPlNode(.switch_br, switch_node, zir.Inst.SwitchBr{
.operand = operand,
.cases_len = 0,
});
return rvalue(parent_gz, scope, rl, result, switch_node);
// Contains the data that goes into the `extra` array for the SwitchBr/SwitchBrMulti.
// This is the header as well as the optional else prong body, as well as all the
// scalar cases.
// At the end we will memcpy this into place.
var scalar_cases_payload = std.ArrayListUnmanaged(u32){};
defer scalar_cases_payload.deinit(gpa);
// Same deal, but this is only the `extra` data for the multi cases.
var multi_cases_payload = std.ArrayListUnmanaged(u32){};
defer multi_cases_payload.deinit(gpa);
var block_scope: GenZir = .{
.parent = scope,
.astgen = astgen,
.force_comptime = parent_gz.force_comptime,
.instructions = .{},
};
block_scope.setBreakResultLoc(rl);
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);
// We re-use this same scope for all cases, including the special prong, if any.
var case_scope: GenZir = .{
.parent = &block_scope.base,
.astgen = astgen,
.force_comptime = parent_gz.force_comptime,
.instructions = .{},
};
defer case_scope.instructions.deinit(gpa);
// Do the else/`_` first because it goes first in the payload.
var capture_val_scope: Scope.LocalVal = undefined;
if (special_node != 0) {
const case = switch (node_tags[special_node]) {
.switch_case_one => tree.switchCaseOne(special_node),
.switch_case => tree.switchCase(special_node),
else => unreachable,
};
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;
if (mem.eql(u8, tree.tokenSlice(ident), "_")) {
if (is_ptr) {
return mod.failTok(&case_scope.base, payload_token, "pointer modifier invalid on discard", .{});
}
break :blk &case_scope.base;
}
const capture_tag: zir.Inst.Tag = if (is_ptr)
.switch_capture_else_ref
else
.switch_capture_else;
const capture = try case_scope.add(.{
.tag = capture_tag,
.data = .{ .switch_capture = .{
.switch_inst = switch_block,
.prong_index = undefined,
} },
});
const capture_name = try mod.identifierTokenString(&parent_gz.base, payload_token);
capture_val_scope = .{
.parent = &case_scope.base,
.gen_zir = &case_scope,
.name = capture_name,
.inst = capture,
.src = parent_gz.tokSrcLoc(payload_token),
};
break :blk &capture_val_scope.base;
};
block_scope.break_count += 1;
const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
if (!astgen.refIsNoReturn(case_result)) {
_ = try case_scope.addBreak(.@"break", switch_block, case_result);
}
// Documentation for this: `zir.Inst.SwitchBr` and `zir.Inst.SwitchBrMulti`.
try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
3 + // operand, scalar_cases_len, else body len
@boolToInt(multi_cases_len != 0) +
case_scope.instructions.items.len);
scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand));
scalar_cases_payload.appendAssumeCapacity(scalar_cases_len);
if (multi_cases_len != 0) {
scalar_cases_payload.appendAssumeCapacity(multi_cases_len);
}
scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len));
scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items);
} else {
// Documentation for this: `zir.Inst.SwitchBr` and `zir.Inst.SwitchBrMulti`.
try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
2 + // operand, scalar_cases_len
@boolToInt(multi_cases_len != 0));
scalar_cases_payload.appendAssumeCapacity(@enumToInt(operand));
scalar_cases_payload.appendAssumeCapacity(scalar_cases_len);
if (multi_cases_len != 0) {
scalar_cases_payload.appendAssumeCapacity(multi_cases_len);
}
}
// In this pass we generate all the item and prong expressions except the special case.
for (case_nodes) |case_node| {
if (case_node == special_node)
continue;
const case = switch (node_tags[case_node]) {
.switch_case_one => tree.switchCaseOne(case_node),
.switch_case => tree.switchCase(case_node),
else => unreachable,
};
// Reset the scope.
case_scope.instructions.shrinkRetainingCapacity(0);
const is_multi_case = case.ast.values.len != 1 or
getRangeNode(node_tags, node_datas, case.ast.values[0]) != null;
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;
if (mem.eql(u8, tree.tokenSlice(ident), "_")) {
if (is_ptr) {
return mod.failTok(&case_scope.base, payload_token, "pointer modifier invalid on discard", .{});
}
break :blk &case_scope.base;
}
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_cases_len else scalar_cases_len;
const capture = try case_scope.add(.{
.tag = capture_tag,
.data = .{ .switch_capture = .{
.switch_inst = switch_block,
.prong_index = capture_index,
} },
});
const capture_name = try mod.identifierTokenString(&parent_gz.base, payload_token);
capture_val_scope = .{
.parent = &case_scope.base,
.gen_zir = &case_scope,
.name = capture_name,
.inst = capture,
.src = parent_gz.tokSrcLoc(payload_token),
};
break :blk &capture_val_scope.base;
};
if (is_multi_case) {
// items_len, ranges_len, body_len
const header_index = multi_cases_payload.items.len;
try multi_cases_payload.resize(gpa, multi_cases_payload.items.len + 3);
// items
var items_len: u32 = 0;
for (case.ast.values) |item_node| {
if (getRangeNode(node_tags, node_datas, item_node) != null) continue;
items_len += 1;
const item_inst = try comptimeExpr(parent_gz, scope, item_rl, item_node);
try multi_cases_payload.append(gpa, @enumToInt(item_inst));
}
// ranges
var ranges_len: u32 = 0;
for (case.ast.values) |item_node| {
const range = getRangeNode(node_tags, node_datas, item_node) orelse continue;
ranges_len += 1;
const first = try comptimeExpr(parent_gz, scope, item_rl, node_datas[range].lhs);
const last = try comptimeExpr(parent_gz, scope, item_rl, node_datas[range].rhs);
try multi_cases_payload.appendSlice(gpa, &[_]u32{
@enumToInt(first), @enumToInt(last),
});
}
block_scope.break_count += 1;
const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
if (!astgen.refIsNoReturn(case_result)) {
_ = try case_scope.addBreak(.@"break", switch_block, case_result);
}
multi_cases_payload.items[header_index + 0] = items_len;
multi_cases_payload.items[header_index + 1] = ranges_len;
multi_cases_payload.items[header_index + 2] = @intCast(u32, case_scope.instructions.items.len);
try multi_cases_payload.appendSlice(gpa, case_scope.instructions.items);
} else {
const item_node = case.ast.values[0];
const item_inst = try comptimeExpr(parent_gz, scope, item_rl, item_node);
block_scope.break_count += 1;
const case_result = try expr(&case_scope, sub_scope, block_scope.break_result_loc, case.ast.target_expr);
if (!astgen.refIsNoReturn(case_result)) {
_ = try case_scope.addBreak(.@"break", switch_block, case_result);
}
try scalar_cases_payload.ensureCapacity(gpa, scalar_cases_payload.items.len +
2 + case_scope.instructions.items.len);
scalar_cases_payload.appendAssumeCapacity(@enumToInt(item_inst));
scalar_cases_payload.appendAssumeCapacity(@intCast(u32, case_scope.instructions.items.len));
scalar_cases_payload.appendSliceAssumeCapacity(case_scope.instructions.items);
}
}
// 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 zir_datas = astgen.instructions.items(.data);
zir_datas[switch_block].pl_node.payload_index = @intCast(u32, astgen.extra.items.len);
try astgen.extra.ensureCapacity(gpa, astgen.extra.items.len +
scalar_cases_payload.items.len + multi_cases_payload.items.len);
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items);
astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items);
const strat = rl.strategy(&block_scope);
assert(strat.tag == .break_operand); // TODO
assert(!strat.elide_store_to_block_ptr_instructions); // TODO
assert(rl != .ref); // TODO
const switch_block_ref = astgen.indexToRef(switch_block);
return rvalue(parent_gz, scope, rl, switch_block_ref, switch_node);
}
fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref {
@ -3021,7 +3366,7 @@ fn typeOf(
return gz.astgen.mod.failTok(scope, builtin_token, "expected at least 1 argument, found 0", .{});
}
if (params.len == 1) {
const result = try gz.addUnTok(.typeof, try expr(gz, scope, .none, params[0]), node);
const result = try gz.addUnNode(.typeof, try expr(gz, scope, .none, params[0]), node);
return rvalue(gz, scope, rl, result, node);
}
const arena = gz.astgen.arena;

View File

@ -84,6 +84,15 @@ pub fn rootAsType(sema: *Sema, root_block: *Scope.Block) !Type {
return sema.resolveType(root_block, .unneeded, zir_inst_ref);
}
/// Returns only the result from the body that is specified.
/// Only appropriate to call when it is determined at comptime that this body
/// has no peers.
fn resolveBody(sema: *Sema, block: *Scope.Block, body: []const zir.Inst.Index) InnerError!*Inst {
const break_inst = try sema.analyzeBody(block, body);
const operand_ref = sema.code.instructions.items(.data)[break_inst].@"break".operand;
return sema.resolveInst(operand_ref);
}
/// ZIR instructions which are always `noreturn` return this. This matches the
/// return type of `analyzeBody` so that we can tail call them.
/// Only appropriate to return when the instruction is known to be NoReturn
@ -229,7 +238,26 @@ pub fn analyzeBody(
.str => try sema.zirStr(block, inst),
.sub => try sema.zirArithmetic(block, inst),
.subwrap => try sema.zirArithmetic(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_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),
.switch_capture_multi_ref => try sema.zirSwitchCapture(block, inst, true, true),
.switch_capture_else => try sema.zirSwitchCaptureElse(block, inst, false),
.switch_capture_else_ref => try sema.zirSwitchCaptureElse(block, inst, true),
.typeof => try sema.zirTypeof(block, inst),
.typeof_elem => try sema.zirTypeofElem(block, inst),
.typeof_peer => try sema.zirTypeofPeer(block, inst),
.xor => try sema.zirBitwise(block, inst, .xor),
@ -245,18 +273,6 @@ pub fn analyzeBody(
.ret_tok => return sema.zirRetTok(block, inst, false),
.@"unreachable" => return sema.zirUnreachable(block, inst),
.repeat => return sema.zirRepeat(block, inst),
.switch_br => return sema.zirSwitchBr(block, inst, false, .none),
.switch_br_multi => return sema.zirSwitchBrMulti(block, inst, false, .none),
.switch_br_else => return sema.zirSwitchBr(block, inst, false, .@"else"),
.switch_br_else_multi => return sema.zirSwitchBrMulti(block, inst, false, .@"else"),
.switch_br_under => return sema.zirSwitchBr(block, inst, false, .under),
.switch_br_under_multi => return sema.zirSwitchBrMulti(block, inst, false, .under),
.switch_br_ref => return sema.zirSwitchBr(block, inst, true, .none),
.switch_br_ref_multi => return sema.zirSwitchBrMulti(block, inst, true, .none),
.switch_br_ref_else => return sema.zirSwitchBr(block, inst, true, .@"else"),
.switch_br_ref_else_multi => return sema.zirSwitchBrMulti(block, inst, true, .@"else"),
.switch_br_ref_under => return sema.zirSwitchBr(block, inst, true, .under),
.switch_br_ref_under_multi => return sema.zirSwitchBrMulti(block, inst, true, .under),
// Instructions that we know can *never* be noreturn based solely on
// their tag. We avoid needlessly checking if they are noreturn and
@ -1034,7 +1050,7 @@ fn analyzeBlockBody(
}
assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1] == coerced_operand);
// Here we depend on the br instruction having been over-allocated (if necessary)
// inide analyzeBreak so that it can be converted into a br_block_flat instruction.
// inside zirBreak so that it can be converted into a br_block_flat instruction.
const br_src = br.base.src;
const br_ty = br.base.ty;
const br_block_flat = @ptrCast(*Inst.BrBlockFlat, br);
@ -1063,22 +1079,15 @@ fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerEr
_ = try block.addNoOp(src, Type.initTag(.void), .breakpoint);
}
fn zirBreak(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
fn zirBreak(sema: *Sema, start_block: *Scope.Block, inst: zir.Inst.Index) InnerError!zir.Inst.Index {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].@"break";
const src = sema.src;
const operand = try sema.resolveInst(inst_data.operand);
return sema.analyzeBreak(block, sema.src, inst_data.block_inst, operand);
}
const zir_block = inst_data.block_inst;
fn analyzeBreak(
sema: *Sema,
start_block: *Scope.Block,
src: LazySrcLoc,
zir_block: zir.Inst.Index,
operand: *Inst,
) InnerError!zir.Inst.Index {
var block = start_block;
while (true) {
if (block.label) |*label| {
@ -1103,7 +1112,7 @@ fn analyzeBreak(
try start_block.instructions.append(sema.gpa, &br.base);
try label.merges.results.append(sema.gpa, operand);
try label.merges.br_list.append(sema.gpa, br);
return always_noreturn;
return inst;
}
}
block = block.parent.?;
@ -2208,15 +2217,38 @@ fn zirSliceSentinel(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) Inne
return sema.analyzeSlice(block, src, array_ptr, start, end, sentinel, sentinel_src);
}
const SpecialProng = enum { none, @"else", under };
fn zirSwitchCapture(
sema: *Sema,
block: *Scope.Block,
inst: zir.Inst.Index,
is_multi: bool,
is_ref: bool,
) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
fn zirSwitchBr(
@panic("TODO implement Sema for zirSwitchCapture");
}
fn zirSwitchCaptureElse(
sema: *Sema,
block: *Scope.Block,
inst: zir.Inst.Index,
is_ref: bool,
special_prong: SpecialProng,
) InnerError!zir.Inst.Index {
) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
@panic("TODO implement Sema for zirSwitchCaptureElse");
}
fn zirSwitchBlock(
sema: *Sema,
block: *Scope.Block,
inst: zir.Inst.Index,
is_ref: bool,
special_prong: zir.SpecialProng,
) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
@ -2238,17 +2270,18 @@ fn zirSwitchBr(
special_prong,
extra.data.cases_len,
0,
inst,
inst_data.src_node,
);
}
fn zirSwitchBrMulti(
fn zirSwitchBlockMulti(
sema: *Sema,
block: *Scope.Block,
inst: zir.Inst.Index,
is_ref: bool,
special_prong: SpecialProng,
) InnerError!zir.Inst.Index {
special_prong: zir.SpecialProng,
) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
@ -2270,6 +2303,7 @@ fn zirSwitchBrMulti(
special_prong,
extra.data.scalar_cases_len,
extra.data.multi_cases_len,
inst,
inst_data.src_node,
);
}
@ -2279,11 +2313,12 @@ fn analyzeSwitch(
block: *Scope.Block,
operand: *Inst,
extra_end: usize,
special_prong: SpecialProng,
special_prong: zir.SpecialProng,
scalar_cases_len: usize,
multi_cases_len: usize,
switch_inst: zir.Inst.Index,
src_node_offset: i32,
) InnerError!zir.Inst.Index {
) InnerError!*Inst {
const special: struct { body: []const zir.Inst.Index, end: usize } = switch (special_prong) {
.none => .{ .body = &.{}, .end = extra_end },
.under, .@"else" => blk: {
@ -2584,7 +2619,7 @@ fn analyzeSwitch(
const item = try sema.resolveInst(item_ref);
const item_val = try sema.resolveConstValue(block, item.src, item);
if (operand_val.eql(item_val)) {
return sema.analyzeBody(block, body);
return sema.resolveBody(block, body);
}
}
}
@ -2605,7 +2640,7 @@ fn analyzeSwitch(
const item = try sema.resolveInst(item_ref);
const item_val = try sema.resolveConstValue(block, item.src, item);
if (operand_val.eql(item_val)) {
return sema.analyzeBody(block, body);
return sema.resolveBody(block, body);
}
}
@ -2621,26 +2656,59 @@ fn analyzeSwitch(
if (Value.compare(operand_val, .gte, first_tv.val) and
Value.compare(operand_val, .lte, last_tv.val))
{
return sema.analyzeBody(block, body);
return sema.resolveBody(block, body);
}
}
extra_index += body_len;
}
}
return sema.analyzeBody(block, special.body);
return sema.resolveBody(block, special.body);
}
if (scalar_cases_len + multi_cases_len == 0) {
return sema.analyzeBody(block, special.body);
return sema.resolveBody(block, special.body);
}
try sema.requireRuntimeBlock(block, src);
const block_inst = try sema.arena.create(Inst.Block);
block_inst.* = .{
.base = .{
.tag = Inst.Block.base_tag,
.ty = undefined, // Set after analysis.
.src = src,
},
.body = undefined,
};
var child_block: Scope.Block = .{
.parent = block,
.sema = sema,
.src_decl = block.src_decl,
.instructions = .{},
// TODO @as here is working around a stage1 miscompilation bug :(
.label = @as(?Scope.Block.Label, Scope.Block.Label{
.zir_block = switch_inst,
.merges = .{
.results = .{},
.br_list = .{},
.block_inst = block_inst,
},
}),
.inlining = block.inlining,
.is_comptime = block.is_comptime,
};
const merges = &child_block.label.?.merges;
defer child_block.instructions.deinit(sema.gpa);
defer merges.results.deinit(sema.gpa);
defer merges.br_list.deinit(sema.gpa);
// TODO when reworking TZIR memory layout make multi cases get generated as cases,
// not as part of the "else" block.
const cases = try sema.arena.alloc(Inst.SwitchBr.Case, scalar_cases_len);
var case_block = block.makeSubBlock();
var case_block = child_block.makeSubBlock();
defer case_block.instructions.deinit(sema.gpa);
var extra_index: usize = special.end;
@ -2656,7 +2724,7 @@ fn analyzeSwitch(
case_block.instructions.shrinkRetainingCapacity(0);
const item = try sema.resolveInst(item_ref);
const item_val = try sema.resolveConstValue(block, item.src, item);
const item_val = try sema.resolveConstValue(&case_block, item.src, item);
_ = try sema.analyzeBody(&case_block, body);
@ -2687,7 +2755,7 @@ fn analyzeSwitch(
for (items) |item_ref| {
const item = try sema.resolveInst(item_ref);
_ = try sema.resolveConstValue(block, item.src, item);
_ = try sema.resolveConstValue(&child_block, item.src, item);
const cmp_ok = try case_block.addBinOp(item.src, bool_ty, .cmp_eq, operand, item);
if (any_ok) |some| {
@ -2707,8 +2775,8 @@ fn analyzeSwitch(
const item_first = try sema.resolveInst(first_ref);
const item_last = try sema.resolveInst(last_ref);
_ = try sema.resolveConstValue(block, item_first.src, item_first);
_ = try sema.resolveConstValue(block, item_last.src, item_last);
_ = try sema.resolveConstValue(&child_block, item_first.src, item_first);
_ = try sema.resolveConstValue(&child_block, item_last.src, item_last);
const range_src = item_first.src;
@ -2779,8 +2847,8 @@ fn analyzeSwitch(
.instructions = try sema.arena.dupe(*Inst, &[1]*Inst{&first_condbr.base}),
};
_ = try block.addSwitchBr(src, operand, cases, final_else_body);
return always_noreturn;
_ = try child_block.addSwitchBr(src, operand, cases, final_else_body);
return sema.analyzeBlockBody(block, &child_block, merges);
}
fn validateSwitchItem(
@ -3261,12 +3329,18 @@ fn zirCmp(
}
fn zirTypeof(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[inst].un_tok;
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
const src = inst_data.src();
const operand = try sema.resolveInst(inst_data.operand);
return sema.mod.constType(sema.arena, inst_data.src(), operand.ty);
return sema.mod.constType(sema.arena, src, operand.ty);
}
fn zirTypeofElem(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
const src = inst_data.src();
const operand_ptr = try sema.resolveInst(inst_data.operand);
const elem_ty = operand_ptr.ty.elemType();
return sema.mod.constType(sema.arena, src, elem_ty);
}
fn zirTypeofPeer(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
@ -3360,8 +3434,7 @@ fn zirBoolBr(
// comptime-known left-hand side. No need for a block here; the result
// is simply the rhs expression. Here we rely on there only being 1
// break instruction (`break_inline`).
const break_inst = try sema.analyzeBody(parent_block, body);
return sema.resolveInst(datas[break_inst].@"break".operand);
return sema.resolveBody(parent_block, body);
}
const block_inst = try sema.arena.create(Inst.Block);
@ -3392,8 +3465,7 @@ fn zirBoolBr(
});
_ = try lhs_block.addBr(src, block_inst, lhs_result);
const rhs_break_inst = try sema.analyzeBody(rhs_block, body);
const rhs_result = try sema.resolveInst(datas[rhs_break_inst].@"break".operand);
const rhs_result = try sema.resolveBody(rhs_block, body);
_ = try rhs_block.addBr(src, block_inst, rhs_result);
const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, then_block.instructions.items) };

View File

@ -514,6 +514,9 @@ pub const Inst = struct {
/// Returns the type of a value.
/// Uses the `un_tok` field.
typeof,
/// Given a value which is a pointer, returns the element type.
/// Uses the `un_node` field.
typeof_elem,
/// The builtin `@TypeOf` which returns the type after Peer Type Resolution
/// of one or more params.
/// Uses the `pl_node` field. AST node is the `@TypeOf` call. Payload is `MultiOp`.
@ -588,32 +591,55 @@ pub const Inst = struct {
/// A switch expression. Uses the `pl_node` union field.
/// AST node is the switch, payload is `SwitchBr`.
/// All prongs of target handled.
switch_br,
/// Same as switch_br, except one or more prongs have multiple items.
switch_br_multi,
/// Same as switch_br, except has an else prong.
switch_br_else,
/// Same as switch_br_else, except one or more prongs have multiple items.
switch_br_else_multi,
/// Same as switch_br, except has an underscore prong.
switch_br_under,
/// Same as switch_br, except one or more prongs have multiple items.
switch_br_under_multi,
/// Same as `switch_br` but the target is a pointer to the value being switched on.
switch_br_ref,
/// Same as `switch_br_multi` but the target is a pointer to the value being switched on.
switch_br_ref_multi,
/// Same as `switch_br_else` but the target is a pointer to the value being switched on.
switch_br_ref_else,
/// Same as `switch_br_else_multi` but the target is a pointer to the
switch_block,
/// Same as switch_block, except one or more prongs have multiple items.
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.
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.
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.
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.
switch_br_ref_else_multi,
/// Same as `switch_br_under` but the target is a pointer to the value
switch_block_ref_else_multi,
/// Same as `switch_block_under` but the target is a pointer to the value
/// being switched on.
switch_br_ref_under,
/// Same as `switch_br_under_multi` but the target is a pointer to
switch_block_ref_under,
/// Same as `switch_block_under_multi` but the target is a pointer to
/// the value being switched on.
switch_br_ref_under_multi,
switch_block_ref_under_multi,
/// Produces the capture value for a switch prong.
/// Uses the `switch_capture` field.
switch_capture,
/// Produces the capture value for a switch prong.
/// Result is a pointer to the value.
/// Uses the `switch_capture` field.
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 the else/'_' switch prong.
/// Uses the `switch_capture` field.
switch_capture_else,
/// Produces the capture value for the else/'_' switch prong.
/// Result is a pointer to the value.
/// Uses the `switch_capture` field.
switch_capture_else_ref,
/// Returns whether the instruction is one of the control flow "noreturn" types.
/// Function calls do not count.
@ -710,6 +736,7 @@ pub const Inst = struct {
.negate,
.negate_wrap,
.typeof,
.typeof_elem,
.xor,
.optional_type,
.optional_type_from_ptr_elem,
@ -743,6 +770,24 @@ pub const Inst = struct {
.set_eval_branch_quota,
.compile_log,
.elided,
.switch_capture,
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
.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,
=> false,
.@"break",
@ -756,18 +801,6 @@ pub const Inst = struct {
.@"unreachable",
.repeat,
.repeat_inline,
.switch_br,
.switch_br_multi,
.switch_br_else,
.switch_br_else_multi,
.switch_br_under,
.switch_br_under_multi,
.switch_br_ref,
.switch_br_ref_multi,
.switch_br_ref_else,
.switch_br_ref_else_multi,
.switch_br_ref_under,
.switch_br_ref_under_multi,
=> true,
};
}
@ -1223,6 +1256,10 @@ pub const Inst = struct {
block_inst: Index,
operand: Ref,
},
switch_capture: struct {
switch_inst: Index,
prong_index: u32,
},
// Make sure we don't accidentally add a field to make this union
// bigger than expected. Note that in Debug builds, Zig is allowed
@ -1394,6 +1431,8 @@ pub const Inst = struct {
};
};
pub const SpecialProng = enum { none, @"else", under };
const Writer = struct {
gpa: *Allocator,
arena: *Allocator,
@ -1461,12 +1500,13 @@ const Writer = struct {
.is_null_ptr,
.is_err,
.is_err_ptr,
.typeof,
.typeof_elem,
=> try self.writeUnNode(stream, inst),
.ref,
.ret_tok,
.ret_coerce,
.typeof,
.ensure_err_payload_void,
=> try self.writeUnTok(stream, inst),
@ -1542,21 +1582,19 @@ const Writer = struct {
.condbr_inline,
=> try self.writePlNodeCondBr(stream, inst),
.switch_br,
.switch_br_else,
.switch_br_under,
.switch_br_ref,
.switch_br_ref_else,
.switch_br_ref_under,
=> try self.writePlNodeSwitchBr(stream, inst),
.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_br_multi,
.switch_br_else_multi,
.switch_br_under_multi,
.switch_br_ref_multi,
.switch_br_ref_else_multi,
.switch_br_ref_under_multi,
=> try self.writePlNodeSwitchBrMulti(stream, inst),
.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),
.compile_log,
.typeof_peer,
@ -1588,10 +1626,19 @@ const Writer = struct {
.fn_type_cc => try self.writeFnTypeCc(stream, inst, false),
.fn_type_var_args => try self.writeFnType(stream, inst, true),
.fn_type_cc_var_args => try self.writeFnTypeCc(stream, inst, true),
.@"unreachable" => try self.writeUnreachable(stream, inst),
.enum_literal_small => try self.writeSmallStr(stream, inst),
.switch_capture,
.switch_capture_ref,
.switch_capture_multi,
.switch_capture_multi_ref,
.switch_capture_else,
.switch_capture_else_ref,
=> try self.writeSwitchCapture(stream, inst),
.bitcast,
.bitcast_ref,
.bitcast_result_ptr,
@ -1763,11 +1810,46 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}
fn writePlNodeSwitchBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
fn writePlNodeSwitchBr(
self: *Writer,
stream: anytype,
inst: Inst.Index,
special_prong: SpecialProng,
) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Inst.SwitchBr, 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 = 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);
var extra_index: usize = extra.end;
if (special.body.len != 0) {
const prong_name = switch (special_prong) {
.@"else" => "else",
.under => "_",
else => unreachable,
};
try stream.print(", {s} => {{\n", .{prong_name});
self.indent += 2;
try self.writeBody(stream, special.body);
self.indent -= 2;
try stream.writeByteNTimes(' ', self.indent);
try stream.writeAll("}");
}
var extra_index: usize = special.end;
{
var scalar_i: usize = 0;
while (scalar_i < extra.data.cases_len) : (scalar_i += 1) {
@ -1792,11 +1874,46 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}
fn writePlNodeSwitchBrMulti(self: *Writer, stream: anytype, inst: Inst.Index) !void {
fn writePlNodeSwitchBlockMulti(
self: *Writer,
stream: anytype,
inst: Inst.Index,
special_prong: SpecialProng,
) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Inst.SwitchBrMulti, 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 = 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);
var extra_index: usize = extra.end;
if (special.body.len != 0) {
const prong_name = switch (special_prong) {
.@"else" => "else",
.under => "_",
else => unreachable,
};
try stream.print(", {s} => {{\n", .{prong_name});
self.indent += 2;
try self.writeBody(stream, special.body);
self.indent -= 2;
try stream.writeByteNTimes(' ', self.indent);
try stream.writeAll("}");
}
var extra_index: usize = special.end;
{
var scalar_i: usize = 0;
while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) {
@ -2015,6 +2132,12 @@ const Writer = struct {
try stream.print("\"{}\")", .{std.zig.fmtEscapes(str)});
}
fn writeSwitchCapture(self: *Writer, stream: anytype, inst: 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 writeInstRef(self: *Writer, stream: anytype, ref: Inst.Ref) !void {
var i: usize = @enumToInt(ref);