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