diff --git a/src/Module.zig b/src/Module.zig index a9b427229..81dc72e60 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2122,16 +2122,18 @@ pub fn addSwitchBr( src: usize, target_ptr: *Inst, cases: []Inst.SwitchBr.Case, + else_body: ir.Body, ) !*Inst { const inst = try block.arena.create(Inst.SwitchBr); inst.* = .{ .base = .{ .tag = .switchbr, - .ty = Type.initTag(.void), + .ty = Type.initTag(.noreturn), .src = src, }, .target_ptr = target_ptr, .cases = cases, + .else_body = else_body, }; try block.instructions.append(self.gpa, &inst.base); return &inst.base; diff --git a/src/astgen.zig b/src/astgen.zig index e2d30f990..f098e2b8c 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -1581,14 +1581,6 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }; defer block_scope.instructions.deinit(mod.gpa); - var item_scope: Scope.GenZIR = .{ - .parent = scope, - .decl = scope.decl().?, - .arena = scope.arena(), - .instructions = .{}, - }; - defer item_scope.instructions.deinit(mod.gpa); - const tree = scope.tree(); const switch_src = tree.token_locs[switch_node.switch_token].start; const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr); @@ -1598,6 +1590,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node .target_ptr = target_ptr, .cases = undefined, // populated below .items = &[_]*zir.Inst{}, // populated below + .else_body = undefined, // populated below }, .{})).castTag(.switchbr).?; var items = std.ArrayList(*zir.Inst).init(mod.gpa); @@ -1611,7 +1604,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }); // then add block containing the switch. const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ - .instructions = undefined, // populated below + .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), }); // Most result location types can be forwarded directly; however @@ -1622,6 +1615,14 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, }; + var item_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = scope.decl().?, + .arena = scope.arena(), + .instructions = .{}, + }; + defer item_scope.instructions.deinit(mod.gpa); + var case_scope: Scope.GenZIR = .{ .parent = scope, .decl = block_scope.decl, @@ -1630,6 +1631,14 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }; defer case_scope.instructions.deinit(mod.gpa); + var else_scope: Scope.GenZIR = .{ + .parent = scope, + .decl = block_scope.decl, + .arena = block_scope.arena, + .instructions = .{}, + }; + defer else_scope.instructions.deinit(mod.gpa); + // first we gather all the switch items and check else/'_' prongs var else_src: ?usize = null; var underscore_src: ?usize = null; @@ -1701,12 +1710,12 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node if (first_range == null) first_range = range_inst; // target >= start and target <= end - const range_start_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_gte, target, start); - const range_end_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_lte, target, end); - const range_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .booland, range_start_ok, range_end_ok); + const range_start_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_gte, target, start); + const range_end_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .cmp_lte, target, end); + const range_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .booland, range_start_ok, range_end_ok); if (any_ok) |some| { - any_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .boolor, some, range_ok); + any_ok = try addZIRBinOp(mod, &else_scope.base, range_src, .boolor, some, range_ok); } else { any_ok = range_ok; } @@ -1715,16 +1724,16 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node const item_inst = try expr(mod, &item_scope.base, .none, item); try items.append(item_inst); - const cpm_ok = try addZIRBinOp(mod, &block_scope.base, item_inst.src, .cmp_eq, target, item_inst); + 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, &block_scope.base, item_inst.src, .boolor, some, cpm_ok); + any_ok = try addZIRBinOp(mod, &else_scope.base, item_inst.src, .boolor, some, cpm_ok); } else { any_ok = cpm_ok; } } - const condbr = try addZIRInstSpecial(mod, &block_scope.base, case_src, zir.Inst.CondBr, .{ + const condbr = try addZIRInstSpecial(mod, &else_scope.base, case_src, zir.Inst.CondBr, .{ .condition = any_ok.?, .then_body = undefined, // populated below .else_body = undefined, // populated below @@ -1754,6 +1763,14 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node }; } + // Generate else block or a break last to finish the block. + if (special_case) |case| { + try switchCaseExpr(mod, &else_scope.base, case_rl, block, case); + } else { + // Not handling all possible cases is a compile error. + _ = try addZIRNoOp(mod, &else_scope.base, switch_src, .unreach_nocheck); + } + // All items have been generated, add the instructions to the comptime block. item_block.positionals.body = .{ .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items), @@ -1765,18 +1782,8 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items); switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items); switch_inst.kw_args.range = first_range; - - // Generate else block or a break last to finish the block. - if (special_case) |case| { - try switchCaseExpr(mod, &block_scope.base, case_rl, block, case); - } else { - // Not handling all possible cases is a compile error. - _ = try addZIRNoOp(mod, &block_scope.base, switch_src, .unreach_nocheck); - } - - // Set block instructions now that it is finished. - block.positionals.body = .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), + switch_inst.positionals.else_body = .{ + .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), }; return &block.base; } diff --git a/src/ir.zig b/src/ir.zig index e81fe38cf..1a31044ab 100644 --- a/src/ir.zig +++ b/src/ir.zig @@ -472,8 +472,11 @@ pub const Inst = struct { target_ptr: *Inst, cases: []Case, /// Set of instructions whose lifetimes end at the start of one of the cases. - /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... , case_n_count ... else_count]. + /// In same order as cases, deaths[0..case_0_count, case_0_count .. case_1_count, ... ]. deaths: [*]*Inst = undefined, + else_index: u32 = 0, + else_deaths: u32 = 0, + else_body: Body, pub const Case = struct { item: Value, @@ -498,6 +501,9 @@ pub const Inst = struct { const case = self.cases[case_index]; return (self.deaths + case.index)[0..case.deaths]; } + pub fn elseDeaths(self: *const SwitchBr) []*Inst { + return (self.deaths + self.else_index)[0..self.else_deaths]; + } }; }; diff --git a/src/zir.zig b/src/zir.zig index b86e5778c..da93a7250 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -509,7 +509,6 @@ pub const Inst = struct { .slice, .slice_start, .import, - .switchbr, .switch_range, => false, @@ -522,6 +521,7 @@ pub const Inst = struct { .unreach_nocheck, .@"unreachable", .loop, + .switchbr, => true, }; } @@ -1012,9 +1012,10 @@ pub const Inst = struct { positionals: struct { target_ptr: *Inst, - cases: []Case, /// List of all individual items and ranges items: []*Inst, + cases: []Case, + else_body: Module.Body, }, kw_args: struct { /// Pointer to first range if such exists. @@ -2569,6 +2570,7 @@ const EmitZIR = struct { .target_ptr = try self.resolveInst(new_body, old_inst.target_ptr), .cases = cases, .items = &[_]*Inst{}, // TODO this should actually be populated + .else_body = undefined, // populated below }, .kw_args = .{}, }; @@ -2590,6 +2592,12 @@ const EmitZIR = struct { .body = .{ .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items) }, }; } + + body_tmp.items.len = 0; + try self.emitBody(old_inst.else_body, inst_table, &body_tmp); + new_inst.positionals.else_body = .{ + .instructions = try self.arena.allocator.dupe(*Inst, body_tmp.items), + }; break :blk &new_inst.base; }, .varptr => @panic("TODO"), diff --git a/src/zir_sema.zig b/src/zir_sema.zig index e438b53c9..fc7291aa8 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -1238,7 +1238,20 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src); try validateSwitch(mod, scope, target, inst); - // TODO comptime execution + if (try mod.resolveDefinedValue(scope, target)) |target_val| { + for (inst.positionals.cases) |case| { + const resolved = try resolveInst(mod, scope, case.item); + const casted = try mod.coerce(scope, target.ty, resolved); + const item = try mod.resolveConstValue(scope, casted); + + if (target_val.eql(item)) { + try analyzeBody(mod, scope, case.body); + return mod.constNoReturn(scope, inst.base.src); + } + } + try analyzeBody(mod, scope, inst.positionals.else_body); + return mod.constNoReturn(scope, inst.base.src); + } const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); const cases = try parent_block.arena.alloc(Inst.SwitchBr.Case, inst.positionals.cases.len); @@ -1253,7 +1266,7 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In }; defer case_block.instructions.deinit(mod.gpa); - for (inst.positionals.cases[0..inst.positionals.cases.len]) |case, i| { + for (inst.positionals.cases) |case, i| { // Reset without freeing. case_block.instructions.items.len = 0; @@ -1269,7 +1282,14 @@ fn analyzeInstSwitchBr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) In }; } - return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases); + case_block.instructions.items.len = 0; + try analyzeBody(mod, &case_block.base, inst.positionals.else_body); + + const else_body: ir.Body = .{ + .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items), + }; + + return mod.addSwitchBr(parent_block, inst.base.src, target_ptr, cases, else_body); } fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.SwitchBr) InnerError!void { @@ -1354,14 +1374,14 @@ fn validateSwitch(mod: *Module, scope: *Scope, target: *Inst, inst: *zir.Inst.Sw false_count += 1; } - if (true_count > 1 or false_count > 1) { + if (true_count + false_count > 2) { return mod.fail(scope, item.src, "duplicate switch value", .{}); } } - if ((true_count == 0 or false_count == 0) and inst.kw_args.special_prong != .@"else") { + if ((true_count + false_count < 2) and inst.kw_args.special_prong != .@"else") { return mod.fail(scope, inst.base.src, "switch must handle all possibilities", .{}); } - if ((true_count == 1 and false_count == 1) and inst.kw_args.special_prong == .@"else") { + if ((true_count + false_count == 2) and inst.kw_args.special_prong == .@"else") { return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); } }, @@ -1696,7 +1716,7 @@ fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerE if (try mod.resolveDefinedValue(scope, cond)) |cond_val| { const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body; try analyzeBody(mod, scope, body.*); - return mod.constVoid(scope, inst.base.src); + return mod.constNoReturn(scope, inst.base.src); } const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src);