| ... | ... | @@ -1561,6 +1561,17 @@ fn forExpr(mod: *Module, scope: *Scope, rl: ResultLoc, for_node: *ast.Node.For) |
| 1561 | 1561 | return &for_block.base; |
| 1562 | 1562 | } |
| 1563 | 1563 | |
| 1564 | fn getRangeNode(node: *ast.Node) ?*ast.Node.SimpleInfixOp { |
| 1565 | var cur = node; |
| 1566 | while (true) { |
| 1567 | switch (cur.tag) { |
| 1568 | .Range => return @fieldParentPtr(ast.Node.SimpleInfixOp, "base", cur), |
| 1569 | .GroupedExpression => cur = @fieldParentPtr(ast.Node.GroupedExpression, "base", cur).expr, |
| 1570 | else => return null, |
| 1571 | } |
| 1572 | } |
| 1573 | } |
| 1574 | |
| 1564 | 1575 | fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node.Switch) InnerError!*zir.Inst { |
| 1565 | 1576 | var block_scope: Scope.GenZIR = .{ |
| 1566 | 1577 | .parent = scope, |
| ... | ... | @@ -1581,6 +1592,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node |
| 1581 | 1592 | const tree = scope.tree(); |
| 1582 | 1593 | const switch_src = tree.token_locs[switch_node.switch_token].start; |
| 1583 | 1594 | const target_ptr = try expr(mod, &block_scope.base, .ref, switch_node.expr); |
| 1595 | const target = try addZIRUnOp(mod, &block_scope.base, target_ptr.src, .deref, target_ptr); |
| 1584 | 1596 | // Add the switch instruction here so that it comes before any range checks. |
| 1585 | 1597 | const switch_inst = (try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.SwitchBr, .{ |
| 1586 | 1598 | .target_ptr = target_ptr, |
| ... | ... | @@ -1593,24 +1605,51 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node |
| 1593 | 1605 | var cases = std.ArrayList(zir.Inst.SwitchBr.Case).init(mod.gpa); |
| 1594 | 1606 | defer cases.deinit(); |
| 1595 | 1607 | |
| 1608 | // Add comptime block containing all prong items first, |
| 1609 | const item_block = try addZIRInstBlock(mod, scope, switch_src, .block_comptime_flat, .{ |
| 1610 | .instructions = undefined, // populated below |
| 1611 | }); |
| 1612 | // then add block containing the switch. |
| 1613 | const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ |
| 1614 | .instructions = undefined, // populated below |
| 1615 | }); |
| 1616 | |
| 1617 | // Most result location types can be forwarded directly; however |
| 1618 | // if we need to write to a pointer which has an inferred type, |
| 1619 | // proper type inference requires peer type resolution on the switch case. |
| 1620 | const case_rl: ResultLoc = switch (rl) { |
| 1621 | .discard, .none, .ty, .ptr, .ref => rl, |
| 1622 | .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, |
| 1623 | }; |
| 1624 | |
| 1625 | var case_scope: Scope.GenZIR = .{ |
| 1626 | .parent = scope, |
| 1627 | .decl = block_scope.decl, |
| 1628 | .arena = block_scope.arena, |
| 1629 | .instructions = .{}, |
| 1630 | }; |
| 1631 | defer case_scope.instructions.deinit(mod.gpa); |
| 1632 | |
| 1596 | 1633 | // first we gather all the switch items and check else/'_' prongs |
| 1597 | 1634 | var else_src: ?usize = null; |
| 1598 | 1635 | var underscore_src: ?usize = null; |
| 1599 | | var range_inst: ?*zir.Inst = null; |
| 1636 | var first_range: ?*zir.Inst = null; |
| 1637 | var special_case: ?*ast.Node.SwitchCase = null; |
| 1600 | 1638 | for (switch_node.cases()) |uncasted_case| { |
| 1601 | 1639 | const case = uncasted_case.castTag(.SwitchCase).?; |
| 1602 | 1640 | const case_src = tree.token_locs[case.firstToken()].start; |
| 1641 | // reset without freeing to reduce allocations. |
| 1642 | case_scope.instructions.items.len = 0; |
| 1643 | assert(case.items_len != 0); |
| 1603 | 1644 | |
| 1604 | | if (case.payload != null) { |
| 1605 | | return mod.fail(scope, case_src, "TODO switch case payload capture", .{}); |
| 1606 | | } |
| 1607 | | |
| 1645 | // Check for else/_ prong, those are handled last. |
| 1608 | 1646 | if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { |
| 1609 | 1647 | if (else_src) |src| { |
| 1610 | 1648 | return mod.fail(scope, case_src, "multiple else prongs in switch expression", .{}); |
| 1611 | 1649 | // TODO notes "previous else prong is here" |
| 1612 | 1650 | } |
| 1613 | 1651 | else_src = case_src; |
| 1652 | special_case = case; |
| 1614 | 1653 | continue; |
| 1615 | 1654 | } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and |
| 1616 | 1655 | mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) |
| ... | ... | @@ -1620,6 +1659,7 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node |
| 1620 | 1659 | // TODO notes "previous '_' prong is here" |
| 1621 | 1660 | } |
| 1622 | 1661 | underscore_src = case_src; |
| 1662 | special_case = case; |
| 1623 | 1663 | continue; |
| 1624 | 1664 | } |
| 1625 | 1665 | |
| ... | ... | @@ -1631,103 +1671,107 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node |
| 1631 | 1671 | } |
| 1632 | 1672 | } |
| 1633 | 1673 | |
| 1634 | | // TODO and not range |
| 1635 | | if (case.items_len == 1) { |
| 1674 | // If this is a simple one item prong then it is handled by the switchbr. |
| 1675 | if (case.items_len == 1 and getRangeNode(case.items()[0]) == null) { |
| 1636 | 1676 | const item = try expr(mod, &item_scope.base, .none, case.items()[0]); |
| 1677 | try items.append(item); |
| 1678 | try switchCaseExpr(mod, &case_scope.base, case_rl, block, case); |
| 1679 | |
| 1637 | 1680 | try cases.append(.{ |
| 1638 | 1681 | .item = item, |
| 1639 | | .body = undefined, // populated below |
| 1682 | .body = .{ .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items) }, |
| 1640 | 1683 | }); |
| 1641 | 1684 | continue; |
| 1642 | 1685 | } |
| 1643 | | return mod.fail(scope, case_src, "TODO switch ranges", .{}); |
| 1644 | | } |
| 1645 | 1686 | |
| 1646 | | // Actually populate switch instruction values. |
| 1647 | | if (else_src != null) switch_inst.kw_args.special_prong = .@"else"; |
| 1648 | | if (underscore_src != null) switch_inst.kw_args.special_prong = .underscore; |
| 1649 | | switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items); |
| 1650 | | switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items); |
| 1651 | | switch_inst.kw_args.range = range_inst; |
| 1687 | // TODO if the case has few items and no ranges it might be better |
| 1688 | // to just handle them as switch prongs. |
| 1689 | |
| 1690 | // Check if the target matches any of the items. |
| 1691 | // 1, 2, 3..6 will result in |
| 1692 | // target == 1 or target == 2 or (target >= 3 and target <= 6) |
| 1693 | var any_ok: ?*zir.Inst = null; |
| 1694 | for (case.items()) |item| { |
| 1695 | if (getRangeNode(item)) |range| { |
| 1696 | const start = try expr(mod, &item_scope.base, .none, range.lhs); |
| 1697 | const end = try expr(mod, &item_scope.base, .none, range.rhs); |
| 1698 | const range_src = tree.token_locs[range.op_token].start; |
| 1699 | const range_inst = try addZIRBinOp(mod, &item_scope.base, range_src, .switch_range, start, end); |
| 1700 | try items.append(range_inst); |
| 1701 | if (first_range == null) first_range = range_inst; |
| 1702 | |
| 1703 | // target >= start and target <= end |
| 1704 | const range_start_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_gte, target, start); |
| 1705 | const range_end_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .cmp_lte, target, end); |
| 1706 | const range_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .booland, range_start_ok, range_end_ok); |
| 1707 | |
| 1708 | if (any_ok) |some| { |
| 1709 | any_ok = try addZIRBinOp(mod, &block_scope.base, range_src, .boolor, some, range_ok); |
| 1710 | } else { |
| 1711 | any_ok = range_ok; |
| 1712 | } |
| 1713 | continue; |
| 1714 | } |
| 1652 | 1715 | |
| 1653 | | // Add comptime block containing all prong items first, |
| 1654 | | _ = try addZIRInstBlock(mod, scope, switch_src, .block_comptime_flat, .{ |
| 1655 | | .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items), |
| 1656 | | }); |
| 1657 | | // then add block containing the switch. |
| 1658 | | const block = try addZIRInstBlock(mod, scope, switch_src, .block, .{ |
| 1659 | | .instructions = undefined, // populated below |
| 1660 | | }); |
| 1716 | const item_inst = try expr(mod, &item_scope.base, .none, item); |
| 1717 | try items.append(item_inst); |
| 1718 | const cpm_ok = try addZIRBinOp(mod, &block_scope.base, item_inst.src, .cmp_eq, target, item_inst); |
| 1661 | 1719 | |
| 1662 | | // Most result location types can be forwarded directly; however |
| 1663 | | // if we need to write to a pointer which has an inferred type, |
| 1664 | | // proper type inference requires peer type resolution on the switch case. |
| 1665 | | const case_rl: ResultLoc = switch (rl) { |
| 1666 | | .discard, .none, .ty, .ptr, .ref => rl, |
| 1667 | | .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block }, |
| 1668 | | }; |
| 1720 | if (any_ok) |some| { |
| 1721 | any_ok = try addZIRBinOp(mod, &block_scope.base, item_inst.src, .boolor, some, cpm_ok); |
| 1722 | } else { |
| 1723 | any_ok = cpm_ok; |
| 1724 | } |
| 1725 | } |
| 1669 | 1726 | |
| 1670 | | var case_scope: Scope.GenZIR = .{ |
| 1671 | | .parent = scope, |
| 1672 | | .decl = block_scope.decl, |
| 1673 | | .arena = block_scope.arena, |
| 1674 | | .instructions = .{}, |
| 1675 | | }; |
| 1676 | | defer case_scope.instructions.deinit(mod.gpa); |
| 1727 | const condbr = try addZIRInstSpecial(mod, &block_scope.base, case_src, zir.Inst.CondBr, .{ |
| 1728 | .condition = any_ok.?, |
| 1729 | .then_body = undefined, // populated below |
| 1730 | .else_body = undefined, // populated below |
| 1731 | }, .{}); |
| 1677 | 1732 | |
| 1678 | | // And finally we fill generate the bodies of each case. |
| 1679 | | var case_index: usize = 0; |
| 1680 | | var special_case: ?*ast.Node.SwitchCase = null; |
| 1681 | | for (switch_node.cases()) |uncasted_case| { |
| 1682 | | const case = uncasted_case.castTag(.SwitchCase).?; |
| 1683 | | const case_src = tree.token_locs[case.firstToken()].start; |
| 1684 | | // reset without freeing to reduce allocations. |
| 1685 | | defer case_scope.instructions.items.len = 0; |
| 1733 | try switchCaseExpr(mod, &case_scope.base, case_rl, block, case); |
| 1734 | condbr.positionals.then_body = .{ |
| 1735 | .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), |
| 1736 | }; |
| 1686 | 1737 | |
| 1687 | | if (case.items_len == 1 and case.items()[0].tag == .SwitchElse) { |
| 1688 | | // validated earlier |
| 1689 | | special_case = case; |
| 1690 | | continue; |
| 1691 | | } else if (case.items_len == 1 and case.items()[0].tag == .Identifier and |
| 1692 | | mem.eql(u8, tree.tokenSlice(case.items()[0].firstToken()), "_")) |
| 1693 | | { |
| 1694 | | // validated earlier |
| 1695 | | special_case = case; |
| 1696 | | continue; |
| 1697 | | } |
| 1738 | // reset to add the empty block |
| 1739 | case_scope.instructions.items.len = 0; |
| 1740 | const empty_block = try addZIRInstBlock(mod, &case_scope.base, case_src, .block, .{ |
| 1741 | .instructions = undefined, // populated below |
| 1742 | }); |
| 1743 | condbr.positionals.else_body = .{ |
| 1744 | .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), |
| 1745 | }; |
| 1698 | 1746 | |
| 1699 | | if (case.items_len == 1) { |
| 1700 | | // Generate the body of this case. |
| 1701 | | const case_body = try expr(mod, &case_scope.base, case_rl, case.expr); |
| 1702 | | if (!case_body.tag.isNoReturn()) { |
| 1703 | | _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.Break, .{ |
| 1704 | | .block = block, |
| 1705 | | .operand = case_body, |
| 1706 | | }, .{}); |
| 1707 | | } |
| 1708 | | switch_inst.positionals.cases[case_index].body = .{ |
| 1709 | | .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), |
| 1710 | | }; |
| 1711 | | case_index += 1; |
| 1712 | | continue; |
| 1713 | | } |
| 1714 | | return mod.fail(scope, case_src, "TODO switch ranges", .{}); |
| 1747 | // reset to add a break to the empty block |
| 1748 | case_scope.instructions.items.len = 0; |
| 1749 | _ = try addZIRInst(mod, &case_scope.base, case_src, zir.Inst.BreakVoid, .{ |
| 1750 | .block = empty_block, |
| 1751 | }, .{}); |
| 1752 | empty_block.positionals.body = .{ |
| 1753 | .instructions = try scope.arena().dupe(*zir.Inst, case_scope.instructions.items), |
| 1754 | }; |
| 1715 | 1755 | } |
| 1716 | 1756 | |
| 1757 | // All items have been generated, add the instructions to the comptime block. |
| 1758 | item_block.positionals.body = .{ |
| 1759 | .instructions = try block_scope.arena.dupe(*zir.Inst, item_scope.instructions.items), |
| 1760 | }; |
| 1761 | |
| 1762 | // Actually populate switch instruction values. |
| 1763 | if (else_src != null) switch_inst.kw_args.special_prong = .@"else"; |
| 1764 | if (underscore_src != null) switch_inst.kw_args.special_prong = .underscore; |
| 1765 | switch_inst.positionals.cases = try block_scope.arena.dupe(zir.Inst.SwitchBr.Case, cases.items); |
| 1766 | switch_inst.positionals.items = try block_scope.arena.dupe(*zir.Inst, items.items); |
| 1767 | switch_inst.kw_args.range = first_range; |
| 1768 | |
| 1717 | 1769 | // Generate else block or a break last to finish the block. |
| 1718 | 1770 | if (special_case) |case| { |
| 1719 | | const case_src = tree.token_locs[case.firstToken()].start; |
| 1720 | | const case_body = try expr(mod, &block_scope.base, case_rl, case.expr); |
| 1721 | | if (!case_body.tag.isNoReturn()) { |
| 1722 | | _ = try addZIRInst(mod, &block_scope.base, case_src, zir.Inst.Break, .{ |
| 1723 | | .block = block, |
| 1724 | | .operand = case_body, |
| 1725 | | }, .{}); |
| 1726 | | } |
| 1771 | try switchCaseExpr(mod, &block_scope.base, case_rl, block, case); |
| 1727 | 1772 | } else { |
| 1728 | | _ = try addZIRInst(mod, &block_scope.base, switch_src, zir.Inst.BreakVoid, .{ |
| 1729 | | .block = block, |
| 1730 | | }, .{}); |
| 1773 | // Not handling all possible cases is a compile error. |
| 1774 | _ = try addZIRNoOp(mod, &block_scope.base, switch_src, .unreach_nocheck); |
| 1731 | 1775 | } |
| 1732 | 1776 | |
| 1733 | 1777 | // Set block instructions now that it is finished. |
| ... | ... | @@ -1737,15 +1781,20 @@ fn switchExpr(mod: *Module, scope: *Scope, rl: ResultLoc, switch_node: *ast.Node |
| 1737 | 1781 | return &block.base; |
| 1738 | 1782 | } |
| 1739 | 1783 | |
| 1740 | | /// Only used for `a...b` in switches. |
| 1741 | | fn switchRange(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { |
| 1784 | fn switchCaseExpr(mod: *Module, scope: *Scope, rl: ResultLoc, block: *zir.Inst.Block, case: *ast.Node.SwitchCase) !void { |
| 1742 | 1785 | const tree = scope.tree(); |
| 1743 | | const src = tree.token_locs[node.op_token].start; |
| 1744 | | |
| 1745 | | const start = try expr(mod, scope, .none, node.lhs); |
| 1746 | | const end = try expr(mod, scope, .none, node.rhs); |
| 1786 | const case_src = tree.token_locs[case.firstToken()].start; |
| 1787 | if (case.payload != null) { |
| 1788 | return mod.fail(scope, case_src, "TODO switch case payload capture", .{}); |
| 1789 | } |
| 1747 | 1790 | |
| 1748 | | return try addZIRBinOp(mod, scope, src, .switch_range, start, end); |
| 1791 | const case_body = try expr(mod, scope, rl, case.expr); |
| 1792 | if (!case_body.tag.isNoReturn()) { |
| 1793 | _ = try addZIRInst(mod, scope, case_src, zir.Inst.Break, .{ |
| 1794 | .block = block, |
| 1795 | .operand = case_body, |
| 1796 | }, .{}); |
| 1797 | } |
| 1749 | 1798 | } |
| 1750 | 1799 | |
| 1751 | 1800 | fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { |