diff --git a/src/Module.zig b/src/Module.zig index 0bafc72e6b..b495afb336 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -375,6 +375,10 @@ pub const Scope = struct { } } + pub fn isComptime(self: *Scope) bool { + return self.getGenZIR().force_comptime; + } + pub fn ownerDecl(self: *Scope) ?*Decl { return switch (self.tag) { .block => self.cast(Block).?.owner_decl, @@ -712,6 +716,7 @@ pub const Scope = struct { parent: *Scope, decl: *Decl, arena: *Allocator, + force_comptime: bool, /// The first N instructions in a function body ZIR are arg instructions. instructions: std.ArrayListUnmanaged(*zir.Inst) = .{}, label: ?Label = null, @@ -1008,6 +1013,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .decl = decl, .arena = &fn_type_scope_arena.allocator, .parent = &decl.container.base, + .force_comptime = true, }; defer fn_type_scope.instructions.deinit(self.gpa); @@ -1171,6 +1177,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .decl = decl, .arena = &decl_arena.allocator, .parent = &decl.container.base, + .force_comptime = false, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1369,6 +1376,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .decl = decl, .arena = &gen_scope_arena.allocator, .parent = &decl.container.base, + .force_comptime = false, }; defer gen_scope.instructions.deinit(self.gpa); @@ -1428,6 +1436,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { .decl = decl, .arena = &type_scope_arena.allocator, .parent = &decl.container.base, + .force_comptime = true, }; defer type_scope.instructions.deinit(self.gpa); @@ -1497,13 +1506,15 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { decl.analysis = .in_progress; - // A comptime decl does not store any value so we can just deinit this arena after analysis is done. + // A comptime decl does not store any value so we can just deinit + // this arena after analysis is done. var analysis_arena = std.heap.ArenaAllocator.init(self.gpa); defer analysis_arena.deinit(); var gen_scope: Scope.GenZIR = .{ .decl = decl, .arena = &analysis_arena.allocator, .parent = &decl.container.base, + .force_comptime = true, }; defer gen_scope.instructions.deinit(self.gpa); diff --git a/src/astgen.zig b/src/astgen.zig index 446ee98eb4..dfc5f06ddc 100644 --- a/src/astgen.zig +++ b/src/astgen.zig @@ -309,7 +309,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr .Catch => return catchExpr(mod, scope, rl, node.castTag(.Catch).?), .Comptime => return comptimeKeyword(mod, scope, rl, node.castTag(.Comptime).?), .OrElse => return orelseExpr(mod, scope, rl, node.castTag(.OrElse).?), - .Switch => return switchExpr(mod, scope, rl, node.castTag(.Switch).?), + .Switch => return mod.failNode(scope, node, "TODO implement astgen.expr for .Switch", .{}), .ContainerDecl => return containerDecl(mod, scope, rl, node.castTag(.ContainerDecl).?), .Defer => return mod.failNode(scope, node, "TODO implement astgen.expr for .Defer", .{}), @@ -334,11 +334,19 @@ fn comptimeKeyword(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.C return comptimeExpr(mod, scope, rl, node.expr); } -pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerError!*zir.Inst { - const tree = parent_scope.tree(); - const src = tree.token_locs[node.firstToken()].start; +pub fn comptimeExpr( + mod: *Module, + parent_scope: *Scope, + rl: ResultLoc, + node: *ast.Node, +) InnerError!*zir.Inst { + // If we are already in a comptime scope, no need to make another one. + if (parent_scope.isComptime()) { + return expr(mod, parent_scope, rl, node); + } - // Optimization for labeled blocks: don't need to have 2 layers of blocks, we can reuse the existing one. + // Optimization for labeled blocks: don't need to have 2 layers of blocks, + // we can reuse the existing one. if (node.castTag(.LabeledBlock)) |block_node| { return labeledBlockExpr(mod, parent_scope, rl, block_node, .block_comptime); } @@ -348,6 +356,7 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as .parent = parent_scope, .decl = parent_scope.ownerDecl().?, .arena = parent_scope.arena(), + .force_comptime = true, .instructions = .{}, }; defer block_scope.instructions.deinit(mod.gpa); @@ -356,6 +365,9 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as // instruction is the block's result value. _ = try expr(mod, &block_scope.base, rl, node); + const tree = parent_scope.tree(); + const src = tree.token_locs[node.firstToken()].start; + const block = try addZIRInstBlock(mod, parent_scope, src, .block_comptime_flat, .{ .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), }); @@ -410,7 +422,7 @@ fn breakExpr( try gen_zir.labeled_breaks.append(mod.gpa, br.castTag(.@"break").?); if (have_store_to_block) { - const inst_list = parent_scope.cast(Scope.GenZIR).?.instructions.items; + const inst_list = parent_scope.getGenZIR().instructions.items; const last_inst = inst_list[inst_list.len - 2]; const store_inst = last_inst.castTag(.store_to_block_ptr).?; assert(store_inst.positionals.lhs == gen_zir.rl_ptr.?); @@ -559,6 +571,7 @@ fn labeledBlockExpr( .parent = parent_scope, .decl = parent_scope.ownerDecl().?, .arena = gen_zir.arena, + .force_comptime = parent_scope.isComptime(), .instructions = .{}, // TODO @as here is working around a stage1 miscompilation bug :( .label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{ @@ -746,6 +759,7 @@ fn varDecl( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; defer init_scope.instructions.deinit(mod.gpa); @@ -1107,6 +1121,7 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; defer gen_scope.instructions.deinit(mod.gpa); @@ -1343,6 +1358,7 @@ fn orelseCatchExpr( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; setBlockResultLoc(&block_scope, rl); @@ -1367,6 +1383,7 @@ fn orelseCatchExpr( .parent = &block_scope.base, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer then_scope.instructions.deinit(mod.gpa); @@ -1395,6 +1412,7 @@ fn orelseCatchExpr( .parent = &block_scope.base, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer else_scope.instructions.deinit(mod.gpa); @@ -1416,6 +1434,7 @@ fn orelseCatchExpr( then_result, unwrapped_payload, block, + block, ); } @@ -1432,7 +1451,8 @@ fn finishThenElseBlock( else_src: usize, then_result: *zir.Inst, else_result: ?*zir.Inst, - block: *zir.Inst.Block, + main_block: *zir.Inst.Block, + then_break_block: *zir.Inst.Block, ) InnerError!*zir.Inst { // We now have enough information to decide whether the result instruction should // be communicated via result location pointer or break instructions. @@ -1441,42 +1461,42 @@ fn finishThenElseBlock( .break_void => { if (!then_result.tag.isNoReturn()) { _ = try addZirInstTag(mod, &then_scope.base, then_src, .break_void, .{ - .block = block, + .block = then_break_block, }); } if (else_result) |inst| { if (!inst.tag.isNoReturn()) { _ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{ - .block = block, + .block = main_block, }); } } else { _ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{ - .block = block, + .block = main_block, }); } assert(!strat.elide_store_to_block_ptr_instructions); try copyBodyNoEliding(then_body, then_scope.*); try copyBodyNoEliding(else_body, else_scope.*); - return &block.base; + return &main_block.base; }, .break_operand => { if (!then_result.tag.isNoReturn()) { _ = try addZirInstTag(mod, &then_scope.base, then_src, .@"break", .{ - .block = block, + .block = then_break_block, .operand = then_result, }); } if (else_result) |inst| { if (!inst.tag.isNoReturn()) { _ = try addZirInstTag(mod, &else_scope.base, else_src, .@"break", .{ - .block = block, + .block = main_block, .operand = inst, }); } } else { _ = try addZirInstTag(mod, &else_scope.base, else_src, .break_void, .{ - .block = block, + .block = main_block, }); } if (strat.elide_store_to_block_ptr_instructions) { @@ -1487,8 +1507,8 @@ fn finishThenElseBlock( try copyBodyNoEliding(else_body, else_scope.*); } switch (rl) { - .ref => return &block.base, - else => return rvalue(mod, parent_scope, rl, &block.base), + .ref => return &main_block.base, + else => return rvalue(mod, parent_scope, rl, &main_block.base), } }, } @@ -1643,6 +1663,7 @@ fn boolBinOp( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; defer block_scope.instructions.deinit(mod.gpa); @@ -1662,6 +1683,7 @@ fn boolBinOp( .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer rhs_scope.instructions.deinit(mod.gpa); @@ -1676,6 +1698,7 @@ fn boolBinOp( .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer const_scope.instructions.deinit(mod.gpa); @@ -1789,6 +1812,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; setBlockResultLoc(&block_scope, rl); @@ -1813,6 +1837,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer then_scope.instructions.deinit(mod.gpa); @@ -1830,6 +1855,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn .parent = scope, .decl = block_scope.decl, .arena = block_scope.arena, + .force_comptime = block_scope.force_comptime, .instructions = .{}, }; defer else_scope.instructions.deinit(mod.gpa); @@ -1863,6 +1889,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn then_result, else_result, block, + block, ); } @@ -1912,6 +1939,7 @@ fn whileExpr( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; setBlockResultLoc(&loop_scope, rl); @@ -1921,6 +1949,7 @@ fn whileExpr( .parent = &loop_scope.base, .decl = loop_scope.decl, .arena = loop_scope.arena, + .force_comptime = loop_scope.force_comptime, .instructions = .{}, }; defer continue_scope.instructions.deinit(mod.gpa); @@ -1978,6 +2007,7 @@ fn whileExpr( .parent = &continue_scope.base, .decl = continue_scope.decl, .arena = continue_scope.arena, + .force_comptime = continue_scope.force_comptime, .instructions = .{}, }; defer then_scope.instructions.deinit(mod.gpa); @@ -1992,6 +2022,7 @@ fn whileExpr( .parent = &continue_scope.base, .decl = continue_scope.decl, .arena = continue_scope.arena, + .force_comptime = continue_scope.force_comptime, .instructions = .{}, }; defer else_scope.instructions.deinit(mod.gpa); @@ -2027,6 +2058,7 @@ fn whileExpr( then_result, else_result, while_block, + cond_block, ); } @@ -2068,6 +2100,7 @@ fn forExpr( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; setBlockResultLoc(&loop_scope, rl); @@ -2077,6 +2110,7 @@ fn forExpr( .parent = &loop_scope.base, .decl = loop_scope.decl, .arena = loop_scope.arena, + .force_comptime = loop_scope.force_comptime, .instructions = .{}, }; defer cond_scope.instructions.deinit(mod.gpa); @@ -2134,6 +2168,7 @@ fn forExpr( .parent = &cond_scope.base, .decl = cond_scope.decl, .arena = cond_scope.arena, + .force_comptime = cond_scope.force_comptime, .instructions = .{}, }; defer then_scope.instructions.deinit(mod.gpa); @@ -2174,6 +2209,7 @@ fn forExpr( .parent = &cond_scope.base, .decl = cond_scope.decl, .arena = cond_scope.arena, + .force_comptime = cond_scope.force_comptime, .instructions = .{}, }; defer else_scope.instructions.deinit(mod.gpa); @@ -2206,281 +2242,10 @@ fn forExpr( then_result, else_result, for_block, + cond_block, ); } -fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp { - var cur = node; - while (true) { - switch (cur.tag) { - .Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur), - .GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr, - else => return null, - } - } -} - -fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst { - if (true) { - @panic("TODO reimplement this"); - } - var block_scope: Scope.GenZIR = .{ - .parent = scope, - .decl = scope.ownerDecl().?, - .arena = scope.arena(), - .instructions = .{}, - }; - defer block_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); - const target = try addZIRUnOp(mod, &block_scope.base, target_ptr.src, .deref, target_ptr); - // Add the switch instruction here so that it comes before any range checks. - const switch_inst = (try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{ - .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); - defer items.deinit(); - var cases = std.ArrayList(zir.Inst.SwitchBr.Case).init(mod.gpa); - defer cases.deinit(); - - // Add comptime block containing all prong items first, - const item_block = try addZIRInstBlock(mod, scope, switch_src, .block_comptime_flat, .{ - .instructions = undefined, // populated below - }); - // then add block containing the switch. - const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), - }); - - // Most result location types can be forwarded directly; however - // if we need to write to a pointer which has an inferred type, - // proper type inference requires peer type resolution on the switch case. - const case_rl: ResultLoc = switch (rl) { - .discard, .none, .ty, .ptr, .ref => rl, - .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, - }; - - var item_scope: Scope.GenZIR = .{ - .parent = scope, - .decl = scope.ownerDecl().?, - .arena = scope.arena(), - .instructions = .{}, - }; - defer item_scope.instructions.deinit(mod.gpa); - - var case_scope: Scope.GenZIR = .{ - .parent = scope, - .decl = block_scope.decl, - .arena = block_scope.arena, - .instructions = .{}, - }; - 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; - var first_range: ?*zir.Inst = null; - var special_case: ?*ast.Node.SwitchCase = null; - for (switch_node.cases()) |uncasted_case| { - const case = uncasted_case.castTag(.SwitchCase).?; - const case_src = tree.token_locs[case.firstToken()].start; - // reset without freeing to reduce allocations. - case_scope.instructions.items.len = 0; - assert(case.items_len != 0); - - // Check for else/_ prong, those are handled last. - if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { - 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; - special_case = case; - continue; - } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and - mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) - { - 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; - special_case = case; - continue; - } - - if (else_src) |some_else| { - if (underscore_src) |some_underscore| { - const msg = msg: { - const msg = try mod.errMsg( - scope, - switch_src, - "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 this is a simple one item prong then it is handled by the switchbr. - if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) { - const item = try expr(mod, &item_scope.base, .none, case.items()[0]); - try items.append(item); - try switchCaseExpr(mod, &case_scope.base, case_rl, block, case); - - try cases.append(.{ - .item = item, - .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) }, - }); - continue; - } - - // TODO if the case has few items and no ranges it might be better - // to just handle them as switch prongs. - - // 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) - var any_ok: ?*zir.Inst = null; - for (case.items()) |item| { - if (getRangeNode(item)) |range| { - const start = try expr(mod, &item_scope.base, .none, range.lhs); - const end = try expr(mod, &item_scope.base, .none, range.rhs); - const range_src = tree.token_locs[range.op_token].start; - const range_inst = try addZIRBinOp(mod, &item_scope.base, range_src, .switch_range, start, end); - try items.append(range_inst); - if (first_range == null) first_range = range_inst; - - // target >= start and target <= end - 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, .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 = try expr(mod, &item_scope.base, .none, item); - try items.append(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, &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 scope.arena().dupe(*zir.Inst, case_scope.instructions.items), - }); - - // reset cond_scope for then_body - case_scope.instructions.items.len = 0; - try switchCaseExpr(mod, &case_scope.base, case_rl, block, case); - condbr.positionals.then_body = .{ - .instructions = try scope.arena().dupe(*zir.Inst, 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 scope.arena().dupe(*zir.Inst, case_scope.instructions.items), - }; - } - - // 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, .unreachable_unsafe); - } - - // 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), - }; - - // Actually populate switch instruction values. - if (else_src != null) switch_inst.kw_args.special_prong = .@"else"; - if (underscore_src != null) switch_inst.kw_args.special_prong = .underscore; - 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; - switch_inst.positionals.else_body = .{ - .instructions = try block_scope.arena.dupe(*zir.Inst, else_scope.instructions.items), - }; - return &block.base; -} - -fn switchCaseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, block: *zir.Inst.Block, case: *ast.Node.SwitchCase) !void { - const tree = scope.tree(); - const case_src = tree.token_locs[case.firstToken()].start; - if (case.payload != null) { - return mod.fail(scope, case_src, "TODO switch case payload capture", .{}); - } - - const case_body = try expr(mod, scope, rl, case.expr); - if (!case_body.tag.isNoReturn()) { - _ = try addZIRInst(mod, scope, case_src, zir.Inst.Break, .{ - .block = block, - .operand = case_body, - }, .{}); - } -} - fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[cfe.ltoken].start; @@ -2859,6 +2624,7 @@ fn asRlPtr( .parent = scope, .decl = scope.ownerDecl().?, .arena = scope.arena(), + .force_comptime = scope.isComptime(), .instructions = .{}, }; defer as_scope.instructions.deinit(mod.gpa); diff --git a/src/zir.zig b/src/zir.zig index 83eee71f87..2559fcdc8e 100644 --- a/src/zir.zig +++ b/src/zir.zig @@ -336,12 +336,6 @@ pub const Inst = struct { enum_literal, /// Create an enum type. enum_type, - /// A switch expression. - switchbr, - /// A range in a switch case, `lhs...rhs`. - /// Only checks that `lhs >= rhs` if they are ints, everything else is - /// validated by the .switch instruction. - switch_range, /// Does nothing; returns a void value. void_value, @@ -441,7 +435,6 @@ pub const Inst = struct { .error_union_type, .merge_error_sets, .slice_start, - .switch_range, => BinOp, .block, @@ -478,7 +471,6 @@ pub const Inst = struct { .enum_literal => EnumLiteral, .error_set => ErrorSet, .slice => Slice, - .switchbr => SwitchBr, .typeof_peer => TypeOfPeer, .container_field_named => ContainerFieldNamed, .container_field_typed => ContainerFieldTyped, @@ -605,7 +597,6 @@ pub const Inst = struct { .slice, .slice_start, .import, - .switch_range, .typeof_peer, .resolve_inferred_alloc, .set_eval_branch_quota, @@ -625,7 +616,6 @@ pub const Inst = struct { .unreachable_unsafe, .unreachable_safe, .loop, - .switchbr, .container_field_named, .container_field_typed, .container_field, @@ -1091,32 +1081,6 @@ pub const Inst = struct { }, }; - pub const SwitchBr = struct { - pub const base_tag = Tag.switchbr; - base: Inst, - - positionals: struct { - target_ptr: *Inst, - /// List of all individual items and ranges - items: []*Inst, - cases: []Case, - else_body: Body, - }, - kw_args: struct { - /// Pointer to first range if such exists. - range: ?*Inst = null, - special_prong: enum { - none, - @"else", - underscore, - } = .none, - }, - - pub const Case = struct { - item: *Inst, - body: Body, - }; - }; pub const TypeOfPeer = struct { pub const base_tag = .typeof_peer; base: Inst, @@ -1467,26 +1431,6 @@ const Writer = struct { } try stream.writeByte(']'); }, - []Inst.SwitchBr.Case => { - if (param.len == 0) { - return stream.writeAll("{}"); - } - try stream.writeAll("{\n"); - for (param) |*case, i| { - if (i != 0) { - try stream.writeAll(",\n"); - } - try stream.writeByteNTimes(' ', self.indent); - self.indent += 2; - try self.writeParamToStream(stream, &case.item); - try stream.writeAll(" => "); - try self.writeParamToStream(stream, &case.body); - self.indent -= 2; - } - try stream.writeByte('\n'); - try stream.writeByteNTimes(' ', self.indent - 2); - try stream.writeByte('}'); - }, else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), } } diff --git a/src/zir_sema.zig b/src/zir_sema.zig index 773c782746..301b95ad97 100644 --- a/src/zir_sema.zig +++ b/src/zir_sema.zig @@ -151,8 +151,6 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError! .slice => return zirSlice(mod, scope, old_inst.castTag(.slice).?), .slice_start => return zirSliceStart(mod, scope, old_inst.castTag(.slice_start).?), .import => return zirImport(mod, scope, old_inst.castTag(.import).?), - .switchbr => return zirSwitchbr(mod, scope, old_inst.castTag(.switchbr).?), - .switch_range => return zirSwitchRange(mod, scope, old_inst.castTag(.switch_range).?), .bool_and => return zirBoolOp(mod, scope, old_inst.castTag(.bool_and).?), .bool_or => return zirBoolOp(mod, scope, old_inst.castTag(.bool_or).?), .void_value => return mod.constVoid(scope, old_inst.src), @@ -795,6 +793,12 @@ fn analyzeBlockBody( var coerce_block = parent_block.makeSubBlock(); defer coerce_block.instructions.deinit(mod.gpa); const coerced_operand = try mod.coerce(&coerce_block.base, resolved_ty, br.operand); + // If no instructions were produced, such as in the case of a coercion of a + // constant value to a new type, we can simply point the br operand to it. + if (coerce_block.instructions.items.len == 0) { + br.operand = coerced_operand; + continue; + } 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. @@ -1531,234 +1535,6 @@ fn zirSliceStart(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError! return mod.analyzeSlice(scope, inst.base.src, array_ptr, start, null, null); } -fn zirSwitchRange(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - const start = try resolveInst(mod, scope, inst.positionals.lhs); - const end = try resolveInst(mod, scope, inst.positionals.rhs); - - switch (start.ty.zigTypeTag()) { - .Int, .ComptimeInt => {}, - else => return mod.constVoid(scope, inst.base.src), - } - switch (end.ty.zigTypeTag()) { - .Int, .ComptimeInt => {}, - else => return mod.constVoid(scope, inst.base.src), - } - if (start.value()) |start_val| { - if (end.value()) |end_val| { - if (start_val.compare(.gte, end_val)) { - return mod.fail(scope, inst.base.src, "range start value must be smaller than the end value", .{}); - } - } - } - return mod.constVoid(scope, inst.base.src); -} - -fn zirSwitchbr(mod: *Module, scope: *Scope, inst: *zir.Inst.SwitchBr) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - const target_ptr = try resolveInst(mod, scope, inst.positionals.target_ptr); - const target = try mod.analyzeDeref(scope, inst.base.src, target_ptr, inst.positionals.target_ptr.src); - try validateSwitch(mod, scope, target, inst); - - 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.cast(Scope.Block).?, case.body); - return mod.constNoReturn(scope, inst.base.src); - } - } - try analyzeBody(mod, scope.cast(Scope.Block).?, inst.positionals.else_body); - return mod.constNoReturn(scope, inst.base.src); - } - - if (inst.positionals.cases.len == 0) { - // no cases just analyze else_branch - try analyzeBody(mod, scope.cast(Scope.Block).?, 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); - - var case_block: Scope.Block = .{ - .parent = parent_block, - .inst_table = parent_block.inst_table, - .func = parent_block.func, - .owner_decl = parent_block.owner_decl, - .src_decl = parent_block.src_decl, - .instructions = .{}, - .arena = parent_block.arena, - .inlining = parent_block.inlining, - .is_comptime = parent_block.is_comptime, - .branch_quota = parent_block.branch_quota, - }; - defer case_block.instructions.deinit(mod.gpa); - - for (inst.positionals.cases) |case, i| { - // Reset without freeing. - case_block.instructions.items.len = 0; - - const resolved = try resolveInst(mod, scope, case.item); - const casted = try mod.coerce(scope, target.ty, resolved); - const item = try mod.resolveConstValue(scope, casted); - - try analyzeBody(mod, &case_block, case.body); - - cases[i] = .{ - .item = item, - .body = .{ .instructions = try parent_block.arena.dupe(*Inst, case_block.instructions.items) }, - }; - } - - case_block.instructions.items.len = 0; - try analyzeBody(mod, &case_block, 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 { - // validate usage of '_' prongs - if (inst.kw_args.special_prong == .underscore and target.ty.zigTypeTag() != .Enum) { - return mod.fail(scope, 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 - if (inst.kw_args.range) |range_inst| { - switch (target.ty.zigTypeTag()) { - .Int, .ComptimeInt => {}, - else => { - return mod.fail(scope, target.src, "ranges not allowed when switching on type {}", .{target.ty}); - // TODO notes "range used here" range_inst.src - }, - } - } - - // validate for duplicate items/missing else prong - switch (target.ty.zigTypeTag()) { - .Enum => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Enum", .{}), - .ErrorSet => return mod.fail(scope, inst.base.src, "TODO validateSwitch .ErrorSet", .{}), - .Union => return mod.fail(scope, inst.base.src, "TODO validateSwitch .Union", .{}), - .Int, .ComptimeInt => { - var range_set = @import("RangeSet.zig").init(mod.gpa); - defer range_set.deinit(); - - for (inst.positionals.items) |item| { - const maybe_src = if (item.castTag(.switch_range)) |range| blk: { - const start_resolved = try resolveInst(mod, scope, range.positionals.lhs); - const start_casted = try mod.coerce(scope, target.ty, start_resolved); - const end_resolved = try resolveInst(mod, scope, range.positionals.rhs); - const end_casted = try mod.coerce(scope, target.ty, end_resolved); - - break :blk try range_set.add( - try mod.resolveConstValue(scope, start_casted), - try mod.resolveConstValue(scope, end_casted), - item.src, - ); - } else blk: { - const resolved = try resolveInst(mod, scope, item); - const casted = try mod.coerce(scope, target.ty, resolved); - const value = try mod.resolveConstValue(scope, casted); - break :blk try range_set.add(value, value, item.src); - }; - - if (maybe_src) |previous_src| { - return mod.fail(scope, item.src, "duplicate switch value", .{}); - // TODO notes "previous value is here" previous_src - } - } - - if (target.ty.zigTypeTag() == .Int) { - var arena = std.heap.ArenaAllocator.init(mod.gpa); - defer arena.deinit(); - - const start = try target.ty.minInt(&arena, mod.getTarget()); - const end = try target.ty.maxInt(&arena, mod.getTarget()); - if (try range_set.spans(start, end)) { - if (inst.kw_args.special_prong == .@"else") { - return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); - } - return; - } - } - - if (inst.kw_args.special_prong != .@"else") { - return mod.fail(scope, inst.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 resolveInst(mod, scope, item); - const casted = try mod.coerce(scope, Type.initTag(.bool), resolved); - if ((try mod.resolveConstValue(scope, casted)).toBool()) { - true_count += 1; - } else { - false_count += 1; - } - - if (true_count + false_count > 2) { - return mod.fail(scope, item.src, "duplicate switch value", .{}); - } - } - 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 + false_count == 2) and inst.kw_args.special_prong == .@"else") { - return mod.fail(scope, inst.base.src, "unreachable else prong, all cases already handled", .{}); - } - }, - .EnumLiteral, .Void, .Fn, .Pointer, .Type => { - if (inst.kw_args.special_prong != .@"else") { - return mod.fail(scope, inst.base.src, "else prong required when switching on type '{}'", .{target.ty}); - } - - var seen_values = std.HashMap(Value, usize, Value.hash, Value.eql, std.hash_map.DefaultMaxLoadPercentage).init(mod.gpa); - defer seen_values.deinit(); - - for (inst.positionals.items) |item| { - const resolved = try resolveInst(mod, scope, item); - const casted = try mod.coerce(scope, target.ty, resolved); - const val = try mod.resolveConstValue(scope, casted); - - if (try seen_values.fetchPut(val, item.src)) |prev| { - return mod.fail(scope, item.src, "duplicate switch value", .{}); - // TODO notes "previous value here" prev.value - } - } - }, - - .ErrorUnion, - .NoReturn, - .Array, - .Struct, - .Undefined, - .Null, - .Optional, - .BoundFn, - .Opaque, - .Vector, - .Frame, - .AnyFrame, - .ComptimeFloat, - .Float, - => { - return mod.fail(scope, target.src, "invalid switch target type '{}'", .{target.ty}); - }, - } -} - fn zirImport(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 0d5a52980b..78d7eba262 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -962,43 +962,6 @@ pub fn addCases(ctx: *TestContext) !void { , "hello\nhello\nhello\nhello\nhello\n", ); - - // comptime switch - - // Basic for loop - case.addCompareOutput( - \\pub export fn _start() noreturn { - \\ assert(foo() == 1); - \\ exit(); - \\} - \\ - \\fn foo() u32 { - \\ const a: comptime_int = 1; - \\ var b: u32 = 0; - \\ switch (a) { - \\ 1 => b = 1, - \\ 2 => b = 2, - \\ else => unreachable, - \\ } - \\ return b; - \\} - \\ - \\pub fn assert(ok: bool) void { - \\ if (!ok) unreachable; // assertion failure - \\} - \\ - \\fn exit() noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (0) - \\ : "rcx", "r11", "memory" - \\ ); - \\ unreachable; - \\} - , - "", - ); } {