Commit 623d5f442c
Changed files (4)
src/AstGen.zig
@@ -1257,6 +1257,18 @@ fn blockExprStmts(
.break_inline,
.condbr,
.condbr_inline,
+ .switch_br,
+ .switch_br_range,
+ .switch_br_else,
+ .switch_br_else_range,
+ .switch_br_underscore,
+ .switch_br_underscore_range,
+ .switch_br_ref,
+ .switch_br_ref_range,
+ .switch_br_ref_else,
+ .switch_br_ref_else_range,
+ .switch_br_ref_underscore,
+ .switch_br_ref_underscore_range,
.compile_error,
.ret_node,
.ret_tok,
@@ -2536,20 +2548,19 @@ fn switchExpr(
rl: ResultLoc,
switch_node: ast.Node.Index,
) InnerError!zir.Inst.Ref {
- if (true) @panic("TODO update for zir-memory-layout");
const tree = parent_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 node_tags = tree.nodes.items(.tag);
+ if (true) @panic("TODO rework for zir-memory-layout branch");
+
const switch_token = main_tokens[switch_node];
const target_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];
- const switch_src = token_starts[switch_token];
-
var block_scope: GenZir = .{
.parent = scope,
.decl = scope.ownerDecl().?,
@@ -2627,7 +2638,7 @@ fn switchExpr(
const msg = msg: {
const msg = try mod.errMsg(
scope,
- switch_src,
+ parent_gz.nodeSrcLoc(switch_node),
"else and '_' prong in switch expression",
.{},
);
src/Module.zig
@@ -1532,6 +1532,7 @@ pub const SrcLoc = struct {
.node_offset_bin_op,
.node_offset_bin_lhs,
.node_offset_bin_rhs,
+ .node_offset_switch_operand,
=> src_loc.container.decl.container.file_scope,
};
}
@@ -1663,6 +1664,7 @@ pub const SrcLoc = struct {
const token_starts = tree.tokens.items(.start);
return token_starts[tok_index];
},
+ .node_offset_switch_operand => @panic("TODO"),
}
}
};
@@ -1795,6 +1797,11 @@ pub const LazySrcLoc = union(enum) {
/// which points to a binary expression AST node. Next, nagivate to the RHS.
/// The Decl is determined contextually.
node_offset_bin_rhs: i32,
+ /// The source location points to the operand of a switch expression, found
+ /// by taking this AST node index offset from the containing Decl AST node,
+ /// which points to a switch expression AST node. Next, nagivate to the operand.
+ /// The Decl is determined contextually.
+ node_offset_switch_operand: i32,
/// Upgrade to a `SrcLoc` based on the `Decl` or file in the provided scope.
pub fn toSrcLoc(lazy: LazySrcLoc, scope: *Scope) SrcLoc {
@@ -1828,6 +1835,7 @@ pub const LazySrcLoc = union(enum) {
.node_offset_bin_op,
.node_offset_bin_lhs,
.node_offset_bin_rhs,
+ .node_offset_switch_operand,
=> .{
.container = .{ .decl = scope.srcDecl().? },
.lazy = lazy,
@@ -1867,6 +1875,7 @@ pub const LazySrcLoc = union(enum) {
.node_offset_bin_op,
.node_offset_bin_lhs,
.node_offset_bin_rhs,
+ .node_offset_switch_operand,
=> .{
.container = .{ .decl = decl },
.lazy = lazy,
src/Sema.zig
@@ -229,10 +229,6 @@ pub fn analyzeBody(
.typeof => try sema.zirTypeof(block, inst),
.typeof_peer => try sema.zirTypeofPeer(block, inst),
.xor => try sema.zirBitwise(block, inst, .xor),
- // TODO
- //.switchbr => try sema.zirSwitchBr(block, inst, false),
- //.switchbr_ref => try sema.zirSwitchBr(block, inst, true),
- //.switch_range => try sema.zirSwitchRange(block, inst),
// Instructions that we know to *always* be noreturn based solely on their tag.
// These functions match the return type of analyzeBody so that we can
@@ -246,6 +242,18 @@ 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, .full),
+ .switch_br_range => return sema.zirSwitchBrRange(block, inst, false, .full),
+ .switch_br_else => return sema.zirSwitchBr(block, inst, false, .@"else"),
+ .switch_br_else_range => return sema.zirSwitchBrRange(block, inst, false, .@"else"),
+ .switch_br_underscore => return sema.zirSwitchBr(block, inst, false, .under),
+ .switch_br_underscore_range => return sema.zirSwitchBrRange(block, inst, false, .under),
+ .switch_br_ref => return sema.zirSwitchBr(block, inst, true, .full),
+ .switch_br_ref_range => return sema.zirSwitchBrRange(block, inst, true, .full),
+ .switch_br_ref_else => return sema.zirSwitchBr(block, inst, true, .@"else"),
+ .switch_br_ref_else_range => return sema.zirSwitchBrRange(block, inst, true, .@"else"),
+ .switch_br_ref_underscore => return sema.zirSwitchBr(block, inst, true, .under),
+ .switch_br_ref_underscore_range => return sema.zirSwitchBrRange(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
@@ -2197,54 +2205,82 @@ fn zirSliceSentinel(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) Inne
return sema.analyzeSlice(block, src, array_ptr, start, end, sentinel, sentinel_src);
}
-fn zirSwitchRange(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst {
+const ElseProng = enum { full, @"else", under };
+
+fn zirSwitchBr(
+ sema: *Sema,
+ block: *Scope.Block,
+ inst: zir.Inst.Index,
+ is_ref: bool,
+ else_prong: ElseProng,
+) InnerError!zir.Inst.Index {
const tracy = trace(@src());
defer tracy.end();
- const src: LazySrcLoc = .todo;
- const bin_inst = sema.code.instructions.items(.data)[inst].bin;
- const start = try sema.resolveInst(bin_inst.lhs);
- const end = try sema.resolveInst(bin_inst.rhs);
+ 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.SwitchBr, inst_data.payload_index);
- switch (start.ty.zigTypeTag()) {
- .Int, .ComptimeInt => {},
- else => return sema.mod.constVoid(sema.arena, .unneeded),
- }
- switch (end.ty.zigTypeTag()) {
- .Int, .ComptimeInt => {},
- else => return sema.mod.constVoid(sema.arena, .unneeded),
- }
- // .switch_range must be inside a comptime scope
- const start_val = start.value().?;
- const end_val = end.value().?;
- if (start_val.compare(.gte, end_val)) {
- return sema.mod.fail(&block.base, src, "range start value must be smaller than the end value", .{});
- }
- return sema.mod.constVoid(sema.arena, .unneeded);
+ const operand_ptr = try sema.resolveInst(extra.data.operand);
+ const operand = if (is_ref)
+ try sema.analyzeLoad(block, src, operand_ptr, operand_src)
+ else
+ operand_ptr;
+
+ return sema.analyzeSwitch(block, operand, extra.end, else_prong, extra.data.cases_len, 0, 0);
}
-fn zirSwitchBr(
+fn zirSwitchBrRange(
sema: *Sema,
- parent_block: *Scope.Block,
+ block: *Scope.Block,
inst: zir.Inst.Index,
- ref: bool,
-) InnerError!zir.Inst.Ref {
+ is_ref: bool,
+ else_prong: ElseProng,
+) InnerError!zir.Inst.Index {
const tracy = trace(@src());
defer tracy.end();
- if (true) @panic("TODO rework with zir-memory-layout in mind");
+ 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.SwitchBrRange, inst_data.payload_index);
- const target_ptr = try sema.resolveInst(inst.positionals.target);
- const target = if (ref)
- try sema.analyzeLoad(parent_block, inst.base.src, target_ptr, inst.positionals.target.src)
+ const operand_ptr = try sema.resolveInst(extra.data.operand);
+ const operand = if (is_ref)
+ try sema.analyzeLoad(block, src, operand_ptr, operand_src)
else
- target_ptr;
- try sema.validateSwitch(parent_block, target, inst);
+ operand_ptr;
+
+ return sema.analyzeSwitch(
+ block,
+ operand,
+ extra.end,
+ else_prong,
+ extra.data.scalar_cases_len,
+ extra.data.multi_cases_len,
+ extra.data.range_cases_len,
+ );
+}
- if (try sema.resolveDefinedValue(parent_block, inst.base.src, target)) |target_val| {
+fn analyzeSwitch(
+ sema: *Sema,
+ parent_block: *Scope.Block,
+ operand: *Inst,
+ extra_end: usize,
+ else_prong: ElseProng,
+ scalar_cases_len: usize,
+ multi_cases_len: usize,
+ range_cases_len: usize,
+) InnerError!zir.Inst.Index {
+ if (true) @panic("TODO rework for zir-memory-layout branch");
+
+ try sema.validateSwitch(parent_block, operand, inst);
+
+ if (try sema.resolveDefinedValue(parent_block, inst.base.src, operand)) |target_val| {
for (inst.positionals.cases) |case| {
const resolved = try sema.resolveInst(case.item);
- const casted = try sema.coerce(block, target.ty, resolved, resolved_src);
+ const casted = try sema.coerce(block, operand.ty, resolved, resolved_src);
const item = try sema.resolveConstValue(parent_block, case_src, casted);
if (target_val.eql(item)) {
@@ -2280,7 +2316,7 @@ fn zirSwitchBr(
case_block.instructions.items.len = 0;
const resolved = try sema.resolveInst(case.item);
- const casted = try sema.coerce(block, target.ty, resolved, resolved_src);
+ const casted = try sema.coerce(block, operand.ty, resolved, resolved_src);
const item = try sema.resolveConstValue(parent_block, case_src, casted);
_ = try sema.analyzeBody(&case_block, case.body);
@@ -2298,29 +2334,29 @@ fn zirSwitchBr(
.instructions = try sema.arena.dupe(*Inst, case_block.instructions.items),
};
- return mod.addSwitchBr(parent_block, inst.base.src, target, cases, else_body);
+ return mod.addSwitchBr(parent_block, inst.base.src, operand, cases, else_body);
}
-fn validateSwitch(sema: *Sema, block: *Scope.Block, target: *Inst, inst: zir.Inst.Index) InnerError!void {
+fn validateSwitch(sema: *Sema, block: *Scope.Block, operand: *Inst, inst: zir.Inst.Index) InnerError!void {
// validate usage of '_' prongs
- if (inst.positionals.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) {
+ if (inst.positionals.special_prong == .underscore and operand.ty.zigTypeTag() != .Enum) {
return sema.mod.fail(&block.base, inst.base.src, "'_' prong only allowed when switching on non-exhaustive enums", .{});
// TODO notes "'_' prong here" inst.positionals.cases[last].src
}
- // check that target type supports ranges
+ // check that operand type supports ranges
if (inst.positionals.range) |range_inst| {
- switch (target.ty.zigTypeTag()) {
+ switch (operand.ty.zigTypeTag()) {
.Int, .ComptimeInt => {},
else => {
- return sema.mod.fail(&block.base, target.src, "ranges not allowed when switching on type {}", .{target.ty});
+ return sema.mod.fail(&block.base, operand.src, "ranges not allowed when switching on type {}", .{operand.ty});
// TODO notes "range used here" range_inst.src
},
}
}
// validate for duplicate items/missing else prong
- switch (target.ty.zigTypeTag()) {
+ switch (operand.ty.zigTypeTag()) {
.Enum => return sema.mod.fail(&block.base, inst.base.src, "TODO validateSwitch .Enum", .{}),
.ErrorSet => return sema.mod.fail(&block.base, inst.base.src, "TODO validateSwitch .ErrorSet", .{}),
.Union => return sema.mod.fail(&block.base, inst.base.src, "TODO validateSwitch .Union", .{}),
@@ -2331,9 +2367,9 @@ fn validateSwitch(sema: *Sema, block: *Scope.Block, target: *Inst, inst: zir.Ins
for (inst.positionals.items) |item| {
const maybe_src = if (item.castTag(.switch_range)) |range| blk: {
const start_resolved = try sema.resolveInst(range.positionals.lhs);
- const start_casted = try sema.coerce(block, target.ty, start_resolved);
+ const start_casted = try sema.coerce(block, operand.ty, start_resolved);
const end_resolved = try sema.resolveInst(range.positionals.rhs);
- const end_casted = try sema.coerce(block, target.ty, end_resolved);
+ const end_casted = try sema.coerce(block, operand.ty, end_resolved);
break :blk try range_set.add(
try sema.resolveConstValue(block, range_start_src, start_casted),
@@ -2342,7 +2378,7 @@ fn validateSwitch(sema: *Sema, block: *Scope.Block, target: *Inst, inst: zir.Ins
);
} else blk: {
const resolved = try sema.resolveInst(item);
- const casted = try sema.coerce(block, target.ty, resolved);
+ const casted = try sema.coerce(block, operand.ty, resolved);
const value = try sema.resolveConstValue(block, item_src, casted);
break :blk try range_set.add(value, value, item.src);
};
@@ -2353,12 +2389,12 @@ fn validateSwitch(sema: *Sema, block: *Scope.Block, target: *Inst, inst: zir.Ins
}
}
- if (target.ty.zigTypeTag() == .Int) {
+ if (operand.ty.zigTypeTag() == .Int) {
var arena = std.heap.ArenaAllocator.init(sema.gpa);
defer arena.deinit();
- const start = try target.ty.minInt(&arena, mod.getTarget());
- const end = try target.ty.maxInt(&arena, mod.getTarget());
+ const start = try operand.ty.minInt(&arena, mod.getTarget());
+ const end = try operand.ty.maxInt(&arena, mod.getTarget());
if (try range_set.spans(start, end)) {
if (inst.positionals.special_prong == .@"else") {
return sema.mod.fail(&block.base, inst.base.src, "unreachable else prong, all cases already handled", .{});
@@ -2396,7 +2432,7 @@ fn validateSwitch(sema: *Sema, block: *Scope.Block, target: *Inst, inst: zir.Ins
},
.EnumLiteral, .Void, .Fn, .Pointer, .Type => {
if (inst.positionals.special_prong != .@"else") {
- return sema.mod.fail(&block.base, inst.base.src, "else prong required when switching on type '{}'", .{target.ty});
+ return sema.mod.fail(&block.base, inst.base.src, "else prong required when switching on type '{}'", .{operand.ty});
}
var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(sema.gpa);
@@ -2404,7 +2440,7 @@ fn validateSwitch(sema: *Sema, block: *Scope.Block, target: *Inst, inst: zir.Ins
for (inst.positionals.items) |item| {
const resolved = try sema.resolveInst(item);
- const casted = try sema.coerce(block, target.ty, resolved);
+ const casted = try sema.coerce(block, operand.ty, resolved);
const val = try sema.resolveConstValue(block, item_src, casted);
if (try seen_values.fetchPut(val, item.src)) |prev| {
@@ -2429,7 +2465,7 @@ fn validateSwitch(sema: *Sema, block: *Scope.Block, target: *Inst, inst: zir.Ins
.ComptimeFloat,
.Float,
=> {
- return sema.mod.fail(&block.base, target.src, "invalid switch target type '{}'", .{target.ty});
+ return sema.mod.fail(&block.base, operand.src, "invalid switch operand type '{}'", .{operand.ty});
},
}
}
src/zir.zig
@@ -585,39 +585,35 @@ pub const Inst = struct {
/// An enum literal 8 or fewer bytes. No source location.
/// Uses the `small_str` field.
enum_literal_small,
- // /// A switch expression.
- // /// lhs is target, SwitchBr[rhs]
- // /// All prongs of target handled.
- // switch_br,
- // /// Same as switch_br, except has a range field.
- // switch_br_range,
- // /// Same as switch_br, except has an else prong.
- // switch_br_else,
- // /// Same as switch_br_else, except has a range field.
- // switch_br_else_range,
- // /// Same as switch_br, except has an underscore prong.
- // switch_br_underscore,
- // /// Same as switch_br, except has a range field.
- // switch_br_underscore_range,
- // /// Same as `switch_br` but the target is a pointer to the value being switched on.
- // switch_br_ref,
- // /// Same as `switch_br_range` but the target is a pointer to the value being switched on.
- // switch_br_ref_range,
- // /// 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_range` but the target is a pointer to the
- // /// value being switched on.
- // switch_br_ref_else_range,
- // /// Same as `switch_br_underscore` but the target is a pointer to the value
- // /// being switched on.
- // switch_br_ref_underscore,
- // /// Same as `switch_br_underscore_range` but the target is a pointer to
- // /// the value being switched on.
- // switch_br_ref_underscore_range,
- // /// A range in a switch case, `lhs...rhs`.
- // /// Only checks that `lhs >= rhs` if they are ints, everything else is
- // /// validated by the switch_br instruction.
- // switch_range,
+ /// 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 has a range field.
+ switch_br_range,
+ /// Same as switch_br, except has an else prong.
+ switch_br_else,
+ /// Same as switch_br_else, except has a range field.
+ switch_br_else_range,
+ /// Same as switch_br, except has an underscore prong.
+ switch_br_underscore,
+ /// Same as switch_br, except has a range field.
+ switch_br_underscore_range,
+ /// Same as `switch_br` but the target is a pointer to the value being switched on.
+ switch_br_ref,
+ /// Same as `switch_br_range` but the target is a pointer to the value being switched on.
+ switch_br_ref_range,
+ /// 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_range` but the target is a pointer to the
+ /// value being switched on.
+ switch_br_ref_else_range,
+ /// Same as `switch_br_underscore` but the target is a pointer to the value
+ /// being switched on.
+ switch_br_ref_underscore,
+ /// Same as `switch_br_underscore_range` but the target is a pointer to
+ /// the value being switched on.
+ switch_br_ref_underscore_range,
/// Returns whether the instruction is one of the control flow "noreturn" types.
/// Function calls do not count.
@@ -760,6 +756,18 @@ pub const Inst = struct {
.@"unreachable",
.repeat,
.repeat_inline,
+ .switch_br,
+ .switch_br_range,
+ .switch_br_else,
+ .switch_br_else_range,
+ .switch_br_underscore,
+ .switch_br_underscore_range,
+ .switch_br_ref,
+ .switch_br_ref_range,
+ .switch_br_ref_else,
+ .switch_br_ref_else_range,
+ .switch_br_ref_underscore,
+ .switch_br_ref_underscore_range,
=> true,
};
}
@@ -1322,22 +1330,53 @@ pub const Inst = struct {
rhs: Ref,
};
- /// Stored in extra. Depending on zir tag and len fields, extra fields trail
+ /// 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. range: Ref // If the tag has "_range" in it.
- /// 1. else_body: Ref // If the tag has "_else" or "_underscore" in it.
- /// 2. items: list of all individual items and ranges.
- /// 3. cases: {
+ /// 0. else_body { // If the tag has "_else" or "_underscore" in it.
+ /// body_len: u32,
+ /// body member Index for every body_len
+ /// }
+ /// 1. cases: {
/// item: Ref,
/// body_len: u32,
- /// body member Ref for every body_len
+ /// body member Index for every body_len
/// } for every cases_len
pub const SwitchBr = struct {
- /// TODO investigate, why do we need to store this? is it redundant?
- items_len: u32,
+ 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 "_underscore" in it.
+ /// body_len: u32,
+ /// body member Index for every body_len
+ /// }
+ /// 1. 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
+ /// items_len: u32,
+ /// item: Ref for every items_len
+ /// block_index: u32, // index in extra to a `Block`
+ /// }
+ /// 3. range_cases: { // for every range_cases_len
+ /// item_start: Ref,
+ /// item_end: Ref,
+ /// block_index: u32, // index in extra to a `Block`
+ /// }
+ pub const SwitchBrRange = struct {
+ operand: Ref,
+ scalar_cases_len: u32,
+ multi_cases_len: u32,
+ range_cases_len: u32,
+ };
+
pub const Field = struct {
lhs: Ref,
/// Offset into `string_bytes`.
@@ -1503,6 +1542,22 @@ const Writer = struct {
.condbr_inline,
=> try self.writePlNodeCondBr(stream, inst),
+ .switch_br,
+ .switch_br_else,
+ .switch_br_underscore,
+ .switch_br_ref,
+ .switch_br_ref_else,
+ .switch_br_ref_underscore,
+ => try self.writePlNodeSwitchBr(stream, inst),
+
+ .switch_br_range,
+ .switch_br_else_range,
+ .switch_br_underscore_range,
+ .switch_br_ref_range,
+ .switch_br_ref_else_range,
+ .switch_br_ref_underscore_range,
+ => try self.writePlNodeSwitchBrRange(stream, inst),
+
.compile_log,
.typeof_peer,
=> try self.writePlNodeMultiOp(stream, inst),
@@ -1708,6 +1763,23 @@ const Writer = struct {
try self.writeSrc(stream, inst_data.src());
}
+ fn writePlNodeSwitchBr(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.SwitchBr, inst_data.payload_index);
+
+ try self.writeInstRef(stream, extra.data.operand);
+ try stream.writeAll(", TODO) ");
+ try self.writeSrc(stream, inst_data.src());
+ }
+
+ fn writePlNodeSwitchBrRange(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+ const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+ const extra = self.code.extraData(Inst.SwitchBrRange, inst_data.payload_index);
+ try self.writeInstRef(stream, extra.data.operand);
+ try stream.writeAll(", TODO) ");
+ try self.writeSrc(stream, inst_data.src());
+ }
+
fn writePlNodeMultiOp(self: *Writer, stream: anytype, inst: Inst.Index) !void {
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
const extra = self.code.extraData(Inst.MultiOp, inst_data.payload_index);