diff --git a/BRANCH_TODO b/BRANCH_TODO index 40b9449ada..ada445f848 100644 --- a/BRANCH_TODO +++ b/BRANCH_TODO @@ -35,3 +35,94 @@ Performance optimizations to look into: var decl and assignment instructions, etc. - make it set sema.src where appropriate * look into not emitting redundant dbg stmts to TZIR + * make decl references in ZIR be u32 indexes to the Decl dependencies array hash map + instead of duplicating *Decl entries in zir.Code. + + if (maybe_src) |previous_src| { + return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{}); + // TODO notes "previous value is here" previous_src + } + + const item = try sema.resolveInst(item_ref); + const value = try sema.resolveConstValue(block, item.src, item); + const maybe_src = try range_set.add(value, value, item.src); + try sema.validateSwitchDupeValue(parent_block, maybe_src, item.src); + + + const first = try sema.resolveInst(item_first); + const last = try sema.resolveInst(item_last); + const maybe_src = try range_set.add( + try sema.resolveConstValue(block, range_first_src, first_casted), + try sema.resolveConstValue(block, range_last_src, last_casted), + item.src, + ); + }; + + + const item = try sema.resolveInst(item_ref); + if ((try sema.resolveConstValue(block, item.src, item)).toBool()) { + true_count += 1; + } else { + false_count += 1; + } + if (true_count + false_count > 2) { + return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{}); + } + + + + for (inst.positionals.items) |item| { + const resolved = try sema.resolveInst(item); + 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| { + return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{}); + // TODO notes "previous value here" prev.value + } + } + + + + + + +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, + }, .{}); + } +} diff --git a/src/AstGen.zig b/src/AstGen.zig index d91a0966ef..5afc49ca3b 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -1258,17 +1258,17 @@ fn blockExprStmts( .condbr, .condbr_inline, .switch_br, - .switch_br_range, + .switch_br_multi, .switch_br_else, - .switch_br_else_range, - .switch_br_underscore, - .switch_br_underscore_range, + .switch_br_else_multi, + .switch_br_under, + .switch_br_under_multi, .switch_br_ref, - .switch_br_ref_range, + .switch_br_ref_multi, .switch_br_ref_else, - .switch_br_ref_else_range, - .switch_br_ref_underscore, - .switch_br_ref_underscore_range, + .switch_br_ref_else_multi, + .switch_br_ref_under, + .switch_br_ref_under_multi, .compile_error, .ret_node, .ret_tok, @@ -2550,35 +2550,12 @@ fn switchExpr( ) InnerError!zir.Inst.Ref { 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 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]; - var block_scope: GenZir = .{ - .parent = scope, - .decl = scope.ownerDecl().?, - .arena = parent_gz.astgen.arena, - .force_comptime = parent_gz.force_comptime, - .instructions = .{}, - }; - block_scope.setBreakResultLoc(rl); - defer block_scope.instructions.deinit(mod.gpa); - - var items = std.ArrayList(zir.Inst.Ref).init(mod.gpa); - defer items.deinit(); - - // First we gather all the switch items and check else/'_' prongs. - var else_src: ?usize = null; - var underscore_src: ?usize = null; - var first_range: ?*zir.Inst = null; - var simple_case_count: usize = 0; var any_payload_is_ref = false; for (case_nodes) |case_node| { const case = switch (node_tags[case_node]) { @@ -2591,284 +2568,22 @@ fn switchExpr( any_payload_is_ref = true; } } - // Check for else/_ prong, those are handled last. - if (case.ast.values.len == 0) { - const case_src = token_starts[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(mod.gpa); - try mod.errNote(scope, src, msg, "previous else prong is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - 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 = token_starts[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(mod.gpa); - try mod.errNote(scope, src, msg, "previous '_' prong is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - underscore_src = case_src; - continue; - } - - if (else_src) |some_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(mod.gpa); - try mod.errNote(scope, some_else, msg, "else prong is here", .{}); - try mod.errNote(scope, some_underscore, msg, "'_' prong is here", .{}); - break :msg msg; - }; - return mod.failWithOwnedErrorMsg(scope, msg); - } - } - - if (case.ast.values.len == 1 and - getRangeNode(node_tags, node_datas, case.ast.values[0]) == null) - { - simple_case_count += 1; - } - - // Generate all the switch items as comptime expressions. - for (case.ast.values) |item| { - if (getRangeNode(node_tags, node_datas, item)) |range| { - const start = try comptimeExpr(&block_scope, &block_scope.base, .none, node_datas[range].lhs); - const end = try comptimeExpr(&block_scope, &block_scope.base, .none, node_datas[range].rhs); - const range_src = token_starts[main_tokens[range]]; - const range_inst = try addZIRBinOp(mod, &block_scope.base, range_src, .switch_range, start, end); - try items.append(range_inst); - } else { - const item_inst = try comptimeExpr(&block_scope, &block_scope.base, .none, item); - try items.append(item_inst); - } - } } - var special_prong: zir.Inst.SwitchBr.SpecialProng = .none; - if (else_src != null) special_prong = .@"else"; - if (underscore_src != null) special_prong = .underscore; - var cases = try block_scope.arena.alloc(zir.Inst.SwitchBr.Case, simple_case_count); - const rl_and_tag: struct { rl: ResultLoc, tag: zir.Inst.Tag } = if (any_payload_is_ref) .{ .rl = .ref, - .tag = .switchbr_ref, + .tag = .switch_br_ref, } else .{ .rl = .none, - .tag = .switchbr, + .tag = .switch_br, }; - const target = try expr(&block_scope, &block_scope.base, rl_and_tag.rl, target_node); - const switch_inst = try addZirInstT(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, rl_and_tag.tag, .{ - .target = target, - .cases = cases, - .items = try block_scope.arena.dupe(zir.Inst.Ref, items.items), - .else_body = undefined, // populated below - .range = first_range, - .special_prong = special_prong, + const operand = try expr(parent_gz, scope, rl_and_tag.rl, operand_node); + + const result = try parent_gz.addPlNode(.switch_br, switch_node, zir.Inst.SwitchBr{ + .operand = operand, + .cases_len = 0, }); - const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ - .instructions = try block_scope.arena.dupe(zir.Inst.Ref, block_scope.instructions.items), - }); - - var case_scope: GenZir = .{ - .parent = scope, - .decl = block_scope.decl, - .arena = block_scope.arena, - .force_comptime = block_scope.force_comptime, - .instructions = .{}, - }; - defer case_scope.instructions.deinit(mod.gpa); - - var else_scope: GenZir = .{ - .parent = scope, - .decl = case_scope.decl, - .arena = case_scope.arena, - .force_comptime = case_scope.force_comptime, - .instructions = .{}, - }; - defer else_scope.instructions.deinit(mod.gpa); - - // Now generate all but the special cases. - var special_case: ?ast.full.SwitchCase = null; - var items_index: usize = 0; - var case_index: usize = 0; - for (case_nodes) |case_node| { - const case = switch (node_tags[case_node]) { - .switch_case_one => tree.switchCaseOne(case_node), - .switch_case => tree.switchCase(case_node), - else => unreachable, - }; - const case_src = token_starts[main_tokens[case_node]]; - case_scope.instructions.shrinkRetainingCapacity(0); - - // Check for else/_ prong, those are handled last. - if (case.ast.values.len == 0) { - special_case = case; - 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]]), "_")) - { - special_case = case; - continue; - } - - // If this is a simple one item prong then it is handled by the switchbr. - if (case.ast.values.len == 1 and - getRangeNode(node_tags, node_datas, case.ast.values[0]) == null) - { - const item = items.items[items_index]; - items_index += 1; - try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target); - - cases[case_index] = .{ - .item = item, - .body = .{ .instructions = try parent_gz.astgen.arena.dupe(zir.Inst.Ref, case_scope.instructions.items) }, - }; - case_index += 1; - continue; - } - - // Check if the target matches any of the items. - // 1, 2, 3..6 will result in - // target == 1 or target == 2 or (target >= 3 and target <= 6) - // TODO handle multiple items as switch prongs rather than along with ranges. - var any_ok: ?*zir.Inst = null; - for (case.ast.values) |item| { - if (getRangeNode(node_tags, node_datas, item)) |range| { - const range_src = token_starts[main_tokens[range]]; - const range_inst = items.items[items_index].castTag(.switch_range).?; - items_index += 1; - - // target >= start and target <= end - const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, range_inst.positionals.lhs); - const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, range_inst.positionals.rhs); - const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_and, range_start_ok, range_end_ok); - - if (any_ok) |some| { - any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .bool_or, some, range_ok); - } else { - any_ok = range_ok; - } - continue; - } - - const item_inst = items.items[items_index]; - items_index += 1; - const cpm_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .cmp_eq, target, item_inst); - - if (any_ok) |some| { - any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .bool_or, some, cpm_ok); - } else { - any_ok = cpm_ok; - } - } - - const condbr = try addZIRInstSpecial(mod, &case_scope.base, case_src, zir.Inst.CondBr, .{ - .condition = any_ok.?, - .then_body = undefined, // populated below - .else_body = undefined, // populated below - }, .{}); - const cond_block = try addZIRInstBlock(mod, &else_scope.base, case_src, .block, .{ - .instructions = try parent_gz.astgen.arena.dupe(zir.Inst.Ref, case_scope.instructions.items), - }); - - // reset cond_scope for then_body - case_scope.instructions.items.len = 0; - try switchCaseExpr(mod, &case_scope.base, block_scope.break_result_loc, block, case, target); - condbr.positionals.then_body = .{ - .instructions = try parent_gz.astgen.arena.dupe(zir.Inst.Ref, case_scope.instructions.items), - }; - - // reset cond_scope for else_body - case_scope.instructions.items.len = 0; - _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{ - .block = cond_block, - }, .{}); - condbr.positionals.else_body = .{ - .instructions = try parent_gz.astgen.arena.dupe(zir.Inst.Ref, case_scope.instructions.items), - }; - } - - // Finally generate else block or a break. - if (special_case) |case| { - try switchCaseExpr(mod, &else_scope.base, block_scope.break_result_loc, block, case, target); - } else { - // Not handling all possible cases is a compile error. - _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreachable_unsafe); - } - switch_inst.positionals.else_body = .{ - .instructions = try block_scope.arena.dupe(zir.Inst.Ref, else_scope.instructions.items), - }; - - return &block.base; -} - -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, - }, .{}); - } + return rvalue(parent_gz, scope, rl, result, switch_node); } fn ret(gz: *GenZir, scope: *Scope, node: ast.Node.Index) InnerError!zir.Inst.Ref { diff --git a/src/Module.zig b/src/Module.zig index 3309a10b30..91790aa1b6 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -888,7 +888,7 @@ pub const Scope = struct { pub fn addSwitchBr( block: *Scope.Block, src: LazySrcLoc, - target: *ir.Inst, + operand: *ir.Inst, cases: []ir.Inst.SwitchBr.Case, else_body: ir.Body, ) !*ir.Inst { @@ -899,7 +899,7 @@ pub const Scope = struct { .ty = Type.initTag(.noreturn), .src = src, }, - .target = target, + .target = operand, .cases = cases, .else_body = else_body, }; @@ -1533,6 +1533,8 @@ pub const SrcLoc = struct { .node_offset_bin_lhs, .node_offset_bin_rhs, .node_offset_switch_operand, + .node_offset_switch_special_prong, + .node_offset_switch_range, => src_loc.container.decl.container.file_scope, }; } @@ -1665,6 +1667,8 @@ pub const SrcLoc = struct { return token_starts[tok_index]; }, .node_offset_switch_operand => @panic("TODO"), + .node_offset_switch_special_prong => @panic("TODO"), + .node_offset_switch_range => @panic("TODO"), } } }; @@ -1802,6 +1806,17 @@ pub const LazySrcLoc = union(enum) { /// which points to a switch expression AST node. Next, nagivate to the operand. /// The Decl is determined contextually. node_offset_switch_operand: i32, + /// The source location points to the else/`_` prong 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 else/`_` prong. + /// The Decl is determined contextually. + node_offset_switch_special_prong: i32, + /// The source location points to all the ranges 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 any of the + /// range nodes. The error applies to all of them. + /// The Decl is determined contextually. + node_offset_switch_range: i32, /// Upgrade to a `SrcLoc` based on the `Decl` or file in the provided scope. pub fn toSrcLoc(lazy: LazySrcLoc, scope: *Scope) SrcLoc { @@ -1836,6 +1851,8 @@ pub const LazySrcLoc = union(enum) { .node_offset_bin_lhs, .node_offset_bin_rhs, .node_offset_switch_operand, + .node_offset_switch_special_prong, + .node_offset_switch_range, => .{ .container = .{ .decl = scope.srcDecl().? }, .lazy = lazy, @@ -1876,6 +1893,8 @@ pub const LazySrcLoc = union(enum) { .node_offset_bin_lhs, .node_offset_bin_rhs, .node_offset_switch_operand, + .node_offset_switch_special_prong, + .node_offset_switch_range, => .{ .container = .{ .decl = decl }, .lazy = lazy, diff --git a/src/RangeSet.zig b/src/RangeSet.zig index 5daacbbf08..f8116a4375 100644 --- a/src/RangeSet.zig +++ b/src/RangeSet.zig @@ -44,6 +44,9 @@ fn lessThan(_: void, a: Range, b: Range) bool { } pub fn spans(self: *RangeSet, start: Value, end: Value) !bool { + if (self.ranges.items.len == 0) + return false; + std.sort.sort(Range, self.ranges.items, {}, lessThan); if (!self.ranges.items[0].start.eql(start) or diff --git a/src/Sema.zig b/src/Sema.zig index 20f7c9d9ca..4cac97f731 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -59,6 +59,9 @@ const Scope = Module.Scope; const InnerError = Module.InnerError; const Decl = Module.Decl; const LazySrcLoc = Module.LazySrcLoc; +const RangeSet = @import("RangeSet.zig"); + +const ValueSrcMap = std.HashMap(Value, LazySrcLoc, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage); pub fn root(sema: *Sema, root_block: *Scope.Block) !zir.Inst.Index { const inst_data = sema.code.instructions.items(.data)[0].pl_node; @@ -242,18 +245,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 => 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_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_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_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), + .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 @@ -2205,14 +2208,14 @@ 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 ElseProng = enum { full, @"else", under }; +const SpecialProng = enum { none, @"else", under }; fn zirSwitchBr( sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index, is_ref: bool, - else_prong: ElseProng, + special_prong: SpecialProng, ) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -2228,15 +2231,23 @@ fn zirSwitchBr( else operand_ptr; - return sema.analyzeSwitch(block, operand, extra.end, else_prong, extra.data.cases_len, 0, 0); + return sema.analyzeSwitch( + block, + operand, + extra.end, + special_prong, + extra.data.cases_len, + 0, + inst_data.src_node, + ); } -fn zirSwitchBrRange( +fn zirSwitchBrMulti( sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index, is_ref: bool, - else_prong: ElseProng, + special_prong: SpecialProng, ) InnerError!zir.Inst.Index { const tracy = trace(@src()); defer tracy.end(); @@ -2244,7 +2255,7 @@ fn zirSwitchBrRange( 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 extra = sema.code.extraData(zir.Inst.SwitchBrMulti, inst_data.payload_index); const operand_ptr = try sema.resolveInst(extra.data.operand); const operand = if (is_ref) @@ -2256,196 +2267,285 @@ fn zirSwitchBrRange( block, operand, extra.end, - else_prong, + special_prong, extra.data.scalar_cases_len, extra.data.multi_cases_len, - extra.data.range_cases_len, + inst_data.src_node, ); } fn analyzeSwitch( sema: *Sema, - parent_block: *Scope.Block, + block: *Scope.Block, operand: *Inst, extra_end: usize, - else_prong: ElseProng, + special_prong: SpecialProng, scalar_cases_len: usize, multi_cases_len: usize, - range_cases_len: usize, + src_node_offset: i32, ) 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, operand.ty, resolved, resolved_src); - const item = try sema.resolveConstValue(parent_block, case_src, casted); - - if (target_val.eql(item)) { - _ = try sema.analyzeBody(parent_block, case.body); - return always_noreturn; - } - } - _ = try sema.analyzeBody(parent_block, inst.positionals.else_body); - return always_noreturn; - } - - if (inst.positionals.cases.len == 0) { - // no cases just analyze else_branch - _ = try sema.analyzeBody(parent_block, inst.positionals.else_body); - return always_noreturn; - } - - try sema.requireRuntimeBlock(parent_block, inst.base.src); - const cases = try sema.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len); - - var case_block: Scope.Block = .{ - .parent = parent_block, - .sema = sema, - .src_decl = parent_block.src_decl, - .instructions = .{}, - .inlining = parent_block.inlining, - .is_comptime = parent_block.is_comptime, + const special: struct { body: []const zir.Inst.Index, end: usize } = switch (special_prong) { + .none => .{ .body = &.{}, .end = extra_end }, + .under, .@"else" => blk: { + const body_len = sema.code.extra[extra_end]; + const extra_body_start = extra_end + 1; + break :blk .{ + .body = sema.code.extra[extra_body_start..][0..body_len], + .end = extra_body_start + body_len, + }; + }, }; - defer case_block.instructions.deinit(sema.gpa); - for (inst.positionals.cases) |case, i| { - // Reset without freeing. - case_block.instructions.items.len = 0; + const src: LazySrcLoc = .{ .node_offset = src_node_offset }; + const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset }; + const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset }; - const resolved = try sema.resolveInst(case.item); - 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); - - cases[i] = .{ - .item = item, - .body = .{ .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items) }, + // Validate usage of '_' prongs. + if (special_prong == .under and !operand.ty.isExhaustiveEnum()) { + const msg = msg: { + const msg = try sema.mod.errMsg( + &block.base, + src, + "'_' prong only allowed when switching on non-exhaustive enums", + .{}, + ); + errdefer msg.destroy(sema.gpa); + try sema.mod.errNote( + &block.base, + special_prong_src, + msg, + "'_' prong here", + .{}, + ); + break :msg msg; }; + return sema.mod.failWithOwnedErrorMsg(&block.base, msg); } - case_block.instructions.items.len = 0; - _ = try sema.analyzeBody(&case_block, inst.positionals.else_body); - - const else_body: ir.Body = .{ - .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items), - }; - - return mod.addSwitchBr(parent_block, inst.base.src, operand, cases, else_body); -} - -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 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 operand type supports ranges - if (inst.positionals.range) |range_inst| { - switch (operand.ty.zigTypeTag()) { - .Int, .ComptimeInt => {}, - else => { - 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 + // Validate for duplicate items, missing else prong, and invalid range. 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", .{}), + .Enum => return sema.mod.fail(&block.base, src, "TODO validate switch .Enum", .{}), + .ErrorSet => return sema.mod.fail(&block.base, src, "TODO validate switch .ErrorSet", .{}), + .Union => return sema.mod.fail(&block.base, src, "TODO validate switch .Union", .{}), .Int, .ComptimeInt => { - var range_set = @import("RangeSet.zig").init(sema.gpa); + var range_set = RangeSet.init(sema.gpa); defer range_set.deinit(); - 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, operand.ty, start_resolved); - const end_resolved = try sema.resolveInst(range.positionals.rhs); - const end_casted = try sema.coerce(block, operand.ty, end_resolved); + var extra_index: usize = special.end; + { + var scalar_i: usize = 0; + while (scalar_i < scalar_cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body_len; - break :blk try range_set.add( - try sema.resolveConstValue(block, range_start_src, start_casted), - try sema.resolveConstValue(block, range_end_src, end_casted), - item.src, + try sema.validateSwitchItem( + block, + &range_set, + item_ref, + src_node_offset, ); - } else blk: { - const resolved = try sema.resolveInst(item); - 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); - }; - - if (maybe_src) |previous_src| { - return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{}); - // TODO notes "previous value is here" previous_src } } + { + var multi_i: usize = 0; + while (multi_i < multi_cases_len) : (multi_i += 1) { + const items_len = sema.code.extra[extra_index]; + extra_index += 1; + const ranges_len = sema.code.extra[extra_index]; + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const items = sema.code.refSlice(extra_index, items_len); + extra_index += items_len; - if (operand.ty.zigTypeTag() == .Int) { - var arena = std.heap.ArenaAllocator.init(sema.gpa); - defer arena.deinit(); - - 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", .{}); + for (items) |item_ref| { + try sema.validateSwitchItem( + block, + &range_set, + item_ref, + src_node_offset, + ); } - return; + + var range_i: usize = 0; + while (range_i < ranges_len) : (range_i += 1) { + const item_first = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const item_last = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + + try sema.validateSwitchRange( + block, + &range_set, + item_first, + item_last, + src_node_offset, + ); + } + + extra_index += body_len; } } - if (inst.positionals.special_prong != .@"else") { - return sema.mod.fail(&block.base, inst.base.src, "switch must handle all possibilities", .{}); + check_range: { + if (operand.ty.zigTypeTag() == .Int) { + var arena = std.heap.ArenaAllocator.init(sema.gpa); + defer arena.deinit(); + + const min_int = try operand.ty.minInt(&arena, sema.mod.getTarget()); + const max_int = try operand.ty.maxInt(&arena, sema.mod.getTarget()); + if (try range_set.spans(min_int, max_int)) { + if (special_prong == .@"else") { + return sema.mod.fail( + &block.base, + special_prong_src, + "unreachable else prong; all cases already handled", + .{}, + ); + } + break :check_range; + } + } + if (special_prong != .@"else") { + return sema.mod.fail( + &block.base, + src, + "switch must handle all possibilities", + .{}, + ); + } } }, .Bool => { var true_count: u8 = 0; var false_count: u8 = 0; - for (inst.positionals.items) |item| { - const resolved = try sema.resolveInst(item); - const casted = try sema.coerce(block, Type.initTag(.bool), resolved); - if ((try sema.resolveConstValue(block, item_src, casted)).toBool()) { - true_count += 1; - } else { - false_count += 1; - } - if (true_count + false_count > 2) { - return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{}); + var extra_index: usize = special.end; + { + var scalar_i: usize = 0; + while (scalar_i < scalar_cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + + try sema.validateSwitchItemBool( + block, + &true_count, + &false_count, + item_ref, + src_node_offset, + ); } } - if ((true_count + false_count < 2) and inst.positionals.special_prong != .@"else") { - return sema.mod.fail(&block.base, inst.base.src, "switch must handle all possibilities", .{}); + { + var multi_i: usize = 0; + while (multi_i < multi_cases_len) : (multi_i += 1) { + const items_len = sema.code.extra[extra_index]; + extra_index += 1; + const ranges_len = sema.code.extra[extra_index]; + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const items = sema.code.refSlice(extra_index, items_len); + extra_index += items_len + body_len; + + for (items) |item_ref| { + try sema.validateSwitchItemBool( + block, + &true_count, + &false_count, + item_ref, + src_node_offset, + ); + } + + try sema.validateSwitchNoRange(block, ranges_len, operand.ty, src_node_offset); + } } - if ((true_count + false_count == 2) and inst.positionals.special_prong == .@"else") { - return sema.mod.fail(&block.base, inst.base.src, "unreachable else prong, all cases already handled", .{}); + switch (special_prong) { + .@"else" => { + if (true_count + false_count == 2) { + return sema.mod.fail( + &block.base, + src, + "unreachable else prong; all cases already handled", + .{}, + ); + } + }, + .under, .none => { + if (true_count + false_count < 2) { + return sema.mod.fail( + &block.base, + src, + "switch must handle all possibilities", + .{}, + ); + } + }, } }, .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 '{}'", .{operand.ty}); + if (special_prong != .@"else") { + return sema.mod.fail( + &block.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); + var seen_values = ValueSrcMap.init(sema.gpa); defer seen_values.deinit(); - for (inst.positionals.items) |item| { - const resolved = try sema.resolveInst(item); - const casted = try sema.coerce(block, operand.ty, resolved); - const val = try sema.resolveConstValue(block, item_src, casted); + var extra_index: usize = special.end; + { + var scalar_i: usize = 0; + while (scalar_i < scalar_cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body_len; - if (try seen_values.fetchPut(val, item.src)) |prev| { - return sema.mod.fail(&block.base, item.src, "duplicate switch value", .{}); - // TODO notes "previous value here" prev.value + try sema.validateSwitchItemSparse( + block, + &seen_values, + item_ref, + src_node_offset, + ); + } + } + { + var multi_i: usize = 0; + while (multi_i < multi_cases_len) : (multi_i += 1) { + const items_len = sema.code.extra[extra_index]; + extra_index += 1; + const ranges_len = sema.code.extra[extra_index]; + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const items = sema.code.refSlice(extra_index, items_len); + extra_index += items_len + body_len; + + for (items) |item_ref| { + try sema.validateSwitchItemSparse( + block, + &seen_values, + item_ref, + src_node_offset, + ); + } + + try sema.validateSwitchNoRange(block, ranges_len, operand.ty, src_node_offset); } } }, @@ -2464,10 +2564,298 @@ fn validateSwitch(sema: *Sema, block: *Scope.Block, operand: *Inst, inst: zir.In .AnyFrame, .ComptimeFloat, .Float, - => { - return sema.mod.fail(&block.base, operand.src, "invalid switch operand type '{}'", .{operand.ty}); - }, + => return sema.mod.fail(&block.base, operand_src, "invalid switch operand type '{}'", .{ + operand.ty, + }), } + + if (try sema.resolveDefinedValue(block, src, operand)) |operand_val| { + var extra_index: usize = special.end; + { + var scalar_i: usize = 0; + while (scalar_i < scalar_cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + + 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); + } + } + } + { + var multi_i: usize = 0; + while (multi_i < multi_cases_len) : (multi_i += 1) { + const items_len = sema.code.extra[extra_index]; + extra_index += 1; + const ranges_len = sema.code.extra[extra_index]; + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const items = sema.code.refSlice(extra_index, items_len); + extra_index += items_len; + const body = sema.code.extra[extra_index + 2 * ranges_len ..][0..body_len]; + + for (items) |item_ref| { + 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); + } + } + + var range_i: usize = 0; + while (range_i < ranges_len) : (range_i += 1) { + const item_first = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const item_last = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + + const first_tv = try sema.resolveInstConst(block, .todo, item_first); + const last_tv = try sema.resolveInstConst(block, .todo, item_last); + if (Value.compare(operand_val, .gte, first_tv.val) and + Value.compare(operand_val, .lte, last_tv.val)) + { + return sema.analyzeBody(block, body); + } + } + + extra_index += body_len; + } + } + return sema.analyzeBody(block, special.body); + } + + if (scalar_cases_len + multi_cases_len == 0) { + return sema.analyzeBody(block, special.body); + } + + try sema.requireRuntimeBlock(block, src); + // 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(); + defer case_block.instructions.deinit(sema.gpa); + + var extra_index: usize = special.end; + + var scalar_i: usize = 0; + while (scalar_i < scalar_cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + + case_block.instructions.shrinkRetainingCapacity(0); + const item = try sema.resolveInst(item_ref); + const item_val = try sema.resolveConstValue(block, item.src, item); + + _ = try sema.analyzeBody(&case_block, body); + + cases[scalar_i] = .{ + .item = item_val, + .body = .{ .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items) }, + }; + } + + var first_condbr: *Inst.CondBr = undefined; + var prev_condbr: ?*Inst.CondBr = null; + + var multi_i: usize = 0; + while (multi_i < multi_cases_len) : (multi_i += 1) { + const items_len = sema.code.extra[extra_index]; + extra_index += 1; + const ranges_len = sema.code.extra[extra_index]; + extra_index += 1; + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const items = sema.code.refSlice(extra_index, items_len); + extra_index += items_len; + + case_block.instructions.shrinkRetainingCapacity(0); + + var any_ok: ?*Inst = null; + const bool_ty = comptime Type.initTag(.bool); + + for (items) |item_ref| { + const item = try sema.resolveInst(item_ref); + _ = try sema.resolveConstValue(block, item.src, item); + + const cmp_ok = try case_block.addBinOp(item.src, bool_ty, .cmp_eq, operand, item); + if (any_ok) |some| { + any_ok = try case_block.addBinOp(item.src, bool_ty, .bool_or, some, cmp_ok); + } else { + any_ok = cmp_ok; + } + } + + var range_i: usize = 0; + while (range_i < ranges_len) : (range_i += 1) { + const first_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const last_ref = @intToEnum(zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + + 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); + + const range_src = item_first.src; + + // operand >= first and operand <= last + const range_first_ok = try case_block.addBinOp( + item_first.src, + bool_ty, + .cmp_gte, + operand, + item_first, + ); + const range_last_ok = try case_block.addBinOp( + item_last.src, + bool_ty, + .cmp_lte, + operand, + item_last, + ); + const range_ok = try case_block.addBinOp( + range_src, + bool_ty, + .bool_and, + range_first_ok, + range_last_ok, + ); + if (any_ok) |some| { + any_ok = try case_block.addBinOp(range_src, bool_ty, .bool_or, some, range_ok); + } else { + any_ok = range_ok; + } + } + + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + _ = try sema.analyzeBody(&case_block, body); + const then_body: Body = .{ + .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items), + }; + const new_condbr = try sema.arena.create(Inst.CondBr); + new_condbr.* = .{ + .base = .{ + .tag = .condbr, + .ty = Type.initTag(.noreturn), + .src = src, + }, + .condition = any_ok.?, + .then_body = then_body, + .else_body = undefined, + }; + if (prev_condbr) |condbr| { + condbr.else_body = .{ + .instructions = try sema.arena.dupe(*Inst, &[1]*Inst{&new_condbr.base}), + }; + } else { + first_condbr = new_condbr; + } + prev_condbr = new_condbr; + } + + case_block.instructions.shrinkRetainingCapacity(0); + _ = try sema.analyzeBody(&case_block, special.body); + const else_body: Body = .{ + .instructions = try sema.arena.dupe(*Inst, case_block.instructions.items), + }; + first_condbr.else_body = else_body; + + const final_else_body: Body = .{ + .instructions = try sema.arena.dupe(*Inst, &[1]*Inst{&first_condbr.base}), + }; + + _ = try block.addSwitchBr(src, operand, cases, final_else_body); + return always_noreturn; +} + +fn validateSwitchItem( + sema: *Sema, + block: *Scope.Block, + range_set: *RangeSet, + item_ref: zir.Inst.Ref, + src_node_offset: i32, +) InnerError!void { + @panic("TODO"); +} + +fn validateSwitchItemBool( + sema: *Sema, + block: *Scope.Block, + true_count: *u8, + false_count: *u8, + item_ref: zir.Inst.Ref, + src_node_offset: i32, +) InnerError!void { + @panic("TODO"); +} + +fn validateSwitchRange( + sema: *Sema, + block: *Scope.Block, + range_set: *RangeSet, + item_first: zir.Inst.Ref, + item_last: zir.Inst.Ref, + src_node_offset: i32, +) InnerError!void { + @panic("TODO"); +} + +fn validateSwitchItemSparse( + sema: *Sema, + block: *Scope.Block, + seen_values: *ValueSrcMap, + item_ref: zir.Inst.Ref, + src_node_offset: i32, +) InnerError!void { + @panic("TODO"); +} + +fn validateSwitchNoRange( + sema: *Sema, + block: *Scope.Block, + ranges_len: u32, + operand_ty: Type, + src_node_offset: i32, +) InnerError!void { + if (ranges_len == 0) + return; + + const operand_src: LazySrcLoc = .{ .node_offset_switch_operand = src_node_offset }; + const range_src: LazySrcLoc = .{ .node_offset_switch_range = src_node_offset }; + + const msg = msg: { + const msg = try sema.mod.errMsg( + &block.base, + operand_src, + "ranges not allowed when switching on type '{}'", + .{operand_ty}, + ); + errdefer msg.destroy(sema.gpa); + try sema.mod.errNote( + &block.base, + range_src, + msg, + "range here", + .{}, + ); + break :msg msg; + }; + return sema.mod.failWithOwnedErrorMsg(&block.base, msg); } fn zirImport(sema: *Sema, block: *Scope.Block, inst: zir.Inst.Index) InnerError!*Inst { @@ -3095,30 +3483,21 @@ fn zirCondbr( return always_noreturn; } - var true_block: Scope.Block = .{ - .parent = parent_block, - .sema = sema, - .src_decl = parent_block.src_decl, - .instructions = .{}, - .inlining = parent_block.inlining, - .is_comptime = parent_block.is_comptime, - }; - defer true_block.instructions.deinit(sema.gpa); - _ = try sema.analyzeBody(&true_block, then_body); + var sub_block = parent_block.makeSubBlock(); + defer sub_block.instructions.deinit(sema.gpa); - var false_block: Scope.Block = .{ - .parent = parent_block, - .sema = sema, - .src_decl = parent_block.src_decl, - .instructions = .{}, - .inlining = parent_block.inlining, - .is_comptime = parent_block.is_comptime, + _ = try sema.analyzeBody(&sub_block, then_body); + const tzir_then_body: ir.Body = .{ + .instructions = try sema.arena.dupe(*Inst, sub_block.instructions.items), + }; + + sub_block.instructions.shrinkRetainingCapacity(0); + + _ = try sema.analyzeBody(&sub_block, else_body); + const tzir_else_body: ir.Body = .{ + .instructions = try sema.arena.dupe(*Inst, sub_block.instructions.items), }; - defer false_block.instructions.deinit(sema.gpa); - _ = try sema.analyzeBody(&false_block, else_body); - const tzir_then_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, true_block.instructions.items) }; - const tzir_else_body: ir.Body = .{ .instructions = try sema.arena.dupe(*Inst, false_block.instructions.items) }; _ = try parent_block.addCondBr(src, cond, tzir_then_body, tzir_else_body); return always_noreturn; } diff --git a/src/type.zig b/src/type.zig index f6ffaefe0b..333854296e 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3303,6 +3303,10 @@ pub const Type = extern union { } } + pub fn isExhaustiveEnum(ty: Type) bool { + return false; // TODO + } + /// This enum does not directly correspond to `std.builtin.TypeId` because /// it has extra enum tags in it, as a way of using less memory. For example, /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types diff --git a/src/zir.zig b/src/zir.zig index 09ba091e81..744dedf4a7 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -589,31 +589,31 @@ pub const Inst = struct { /// 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 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 has a range field. - switch_br_else_range, + /// 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_underscore, - /// Same as switch_br, except has a range field. - switch_br_underscore_range, + 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_range` but the target is a pointer to the value being switched on. - switch_br_ref_range, + /// 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_range` but the target is a pointer to the + /// Same as `switch_br_else_multi` 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 + switch_br_ref_else_multi, + /// Same as `switch_br_under` 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 + switch_br_ref_under, + /// Same as `switch_br_under_multi` but the target is a pointer to /// the value being switched on. - switch_br_ref_underscore_range, + switch_br_ref_under_multi, /// Returns whether the instruction is one of the control flow "noreturn" types. /// Function calls do not count. @@ -757,17 +757,17 @@ pub const Inst = struct { .repeat, .repeat_inline, .switch_br, - .switch_br_range, + .switch_br_multi, .switch_br_else, - .switch_br_else_range, - .switch_br_underscore, - .switch_br_underscore_range, + .switch_br_else_multi, + .switch_br_under, + .switch_br_under_multi, .switch_br_ref, - .switch_br_ref_range, + .switch_br_ref_multi, .switch_br_ref_else, - .switch_br_ref_else_range, - .switch_br_ref_underscore, - .switch_br_ref_underscore_range, + .switch_br_ref_else_multi, + .switch_br_ref_under, + .switch_br_ref_under_multi, => true, }; } @@ -1333,7 +1333,7 @@ pub const Inst = struct { /// This form is supported when there are no ranges, and exactly 1 item per block. /// Depending on zir tag and len fields, extra fields trail /// this one in the extra array. - /// 0. else_body { // If the tag has "_else" or "_underscore" in it. + /// 0. else_body { // If the tag has "_else" or "_under" in it. /// body_len: u32, /// body member Index for every body_len /// } @@ -1351,7 +1351,7 @@ pub const Inst = struct { /// 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. + /// 0. else_body { // If the tag has "_else" or "_under" in it. /// body_len: u32, /// body member Index for every body_len /// } @@ -1362,19 +1362,19 @@ pub const Inst = struct { /// } /// 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` + /// ranges_len: u32, + /// body_len: u32, + /// item: Ref // for every items_len + /// ranges: { // for every ranges_len + /// item_first: Ref, + /// item_last: Ref, + /// } + /// body member Index for every body_len /// } - /// 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 { + pub const SwitchBrMulti = struct { operand: Ref, scalar_cases_len: u32, multi_cases_len: u32, - range_cases_len: u32, }; pub const Field = struct { @@ -1544,19 +1544,19 @@ const Writer = struct { .switch_br, .switch_br_else, - .switch_br_underscore, + .switch_br_under, .switch_br_ref, .switch_br_ref_else, - .switch_br_ref_underscore, + .switch_br_ref_under, => 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), + .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), .compile_log, .typeof_peer, @@ -1766,17 +1766,98 @@ const Writer = struct { 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) "); + var extra_index: usize = extra.end; + { + var scalar_i: usize = 0; + while (scalar_i < extra.data.cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + const body_len = self.code.extra[extra_index]; + extra_index += 1; + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + + try stream.writeAll(", "); + try self.writeInstRef(stream, item_ref); + try stream.writeAll(" => {\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + } + try stream.writeAll(") "); try self.writeSrc(stream, inst_data.src()); } - fn writePlNodeSwitchBrRange(self: *Writer, stream: anytype, inst: Inst.Index) !void { + fn writePlNodeSwitchBrMulti(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); + const extra = self.code.extraData(Inst.SwitchBrMulti, inst_data.payload_index); try self.writeInstRef(stream, extra.data.operand); - try stream.writeAll(", TODO) "); + var extra_index: usize = extra.end; + { + var scalar_i: usize = 0; + while (scalar_i < extra.data.scalar_cases_len) : (scalar_i += 1) { + const item_ref = @intToEnum(Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + const body_len = self.code.extra[extra_index]; + extra_index += 1; + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + + try stream.writeAll(", "); + try self.writeInstRef(stream, item_ref); + try stream.writeAll(" => {\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + } + { + var multi_i: usize = 0; + while (multi_i < extra.data.multi_cases_len) : (multi_i += 1) { + const items_len = self.code.extra[extra_index]; + extra_index += 1; + const ranges_len = self.code.extra[extra_index]; + extra_index += 1; + const body_len = self.code.extra[extra_index]; + extra_index += 1; + const items = self.code.refSlice(extra_index, items_len); + extra_index += items_len; + + for (items) |item_ref| { + try stream.writeAll(", "); + try self.writeInstRef(stream, item_ref); + } + + var range_i: usize = 0; + while (range_i < ranges_len) : (range_i += 1) { + const item_first = @intToEnum(Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + const item_last = @intToEnum(Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + + try stream.writeAll(", "); + try self.writeInstRef(stream, item_first); + try stream.writeAll("..."); + try self.writeInstRef(stream, item_last); + } + + const body = self.code.extra[extra_index..][0..body_len]; + extra_index += body_len; + try stream.writeAll(" => {\n"); + self.indent += 2; + try self.writeBody(stream, body); + self.indent -= 2; + try stream.writeByteNTimes(' ', self.indent); + try stream.writeAll("}"); + } + } + try stream.writeAll(") "); try self.writeSrc(stream, inst_data.src()); }