| ... | ... | @@ -38,6 +38,21 @@ pub const ResultLoc = union(enum) { |
| 38 | 38 | /// is inferred based on peer type resolution for a `zir.Inst.Block`. |
| 39 | 39 | /// The result instruction from the expression must be ignored. |
| 40 | 40 | block_ptr: *Module.Scope.GenZIR, |
| 41 | |
| 42 | pub const Strategy = struct { |
| 43 | elide_store_to_block_ptr_instructions: bool, |
| 44 | tag: Tag, |
| 45 | |
| 46 | pub const Tag = enum { |
| 47 | /// Both branches will use break_void; result location is used to communicate the |
| 48 | /// result instruction. |
| 49 | break_void, |
| 50 | /// Use break statements to pass the block result value, and call rvalue() at |
| 51 | /// the end depending on rl. Also elide the store_to_block_ptr instructions |
| 52 | /// depending on rl. |
| 53 | break_operand, |
| 54 | }; |
| 55 | }; |
| 41 | 56 | }; |
| 42 | 57 | |
| 43 | 58 | pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*zir.Inst { |
| ... | ... | @@ -348,10 +363,11 @@ pub fn comptimeExpr(mod: *Module, parent_scope: *Scope, rl: ResultLoc, node: *as |
| 348 | 363 | return &block.base; |
| 349 | 364 | } |
| 350 | 365 | |
| 351 | | fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst { |
| 352 | | if (true) { |
| 353 | | @panic("TODO reimplement this"); |
| 354 | | } |
| 366 | fn breakExpr( |
| 367 | mod: *Module, |
| 368 | parent_scope: *Scope, |
| 369 | node: *ast.Node.ControlFlowExpression, |
| 370 | ) InnerError!*zir.Inst { |
| 355 | 371 | const tree = parent_scope.tree(); |
| 356 | 372 | const src = tree.token_locs[node.ltoken].start; |
| 357 | 373 | |
| ... | ... | @@ -377,25 +393,31 @@ fn breakExpr(mod: *Module, parent_scope: *Scope, node: *ast.Node.ControlFlowExpr |
| 377 | 393 | continue; |
| 378 | 394 | }; |
| 379 | 395 | |
| 380 | | if (node.getRHS()) |rhs| { |
| 381 | | // Most result location types can be forwarded directly; however |
| 382 | | // if we need to write to a pointer which has an inferred type, |
| 383 | | // proper type inference requires peer type resolution on the block's |
| 384 | | // break operand expressions. |
| 385 | | const branch_rl: ResultLoc = switch (gen_zir.break_result_loc) { |
| 386 | | .discard, .none, .ty, .ptr, .ref => gen_zir.break_result_loc, |
| 387 | | .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = block_inst }, |
| 388 | | }; |
| 389 | | const operand = try expr(mod, parent_scope, branch_rl, rhs); |
| 390 | | return try addZIRInst(mod, parent_scope, src, zir.Inst.Break, .{ |
| 396 | const rhs = node.getRHS() orelse { |
| 397 | return addZirInstTag(mod, parent_scope, src, .break_void, .{ |
| 391 | 398 | .block = block_inst, |
| 392 | | .operand = operand, |
| 393 | | }, .{}); |
| 394 | | } else { |
| 395 | | return try addZIRInst(mod, parent_scope, src, zir.Inst.BreakVoid, .{ |
| 396 | | .block = block_inst, |
| 397 | | }, .{}); |
| 399 | }); |
| 400 | }; |
| 401 | gen_zir.break_count += 1; |
| 402 | const prev_rvalue_rl_count = gen_zir.rvalue_rl_count; |
| 403 | const operand = try expr(mod, parent_scope, gen_zir.break_result_loc, rhs); |
| 404 | const have_store_to_block = gen_zir.rvalue_rl_count != prev_rvalue_rl_count; |
| 405 | const br = try addZirInstTag(mod, parent_scope, src, .@"break", .{ |
| 406 | .block = block_inst, |
| 407 | .operand = operand, |
| 408 | }); |
| 409 | if (gen_zir.break_result_loc == .block_ptr) { |
| 410 | try gen_zir.labeled_breaks.append(mod.gpa, br.castTag(.@"break").?); |
| 411 | |
| 412 | if (have_store_to_block) { |
| 413 | const inst_list = parent_scope.cast(Scope.GenZIR).?.instructions.items; |
| 414 | const last_inst = inst_list[inst_list.len - 2]; |
| 415 | const store_inst = last_inst.castTag(.store_to_block_ptr).?; |
| 416 | assert(store_inst.positionals.lhs == gen_zir.rl_ptr.?); |
| 417 | try gen_zir.labeled_store_to_block_ptr_list.append(mod.gpa, store_inst); |
| 418 | } |
| 398 | 419 | } |
| 420 | return br; |
| 399 | 421 | }, |
| 400 | 422 | .local_val => scope = scope.cast(Scope.LocalVal).?.parent, |
| 401 | 423 | .local_ptr => scope = scope.cast(Scope.LocalPtr).?.parent, |
| ... | ... | @@ -538,7 +560,6 @@ fn labeledBlockExpr( |
| 538 | 560 | .decl = parent_scope.ownerDecl().?, |
| 539 | 561 | .arena = gen_zir.arena, |
| 540 | 562 | .instructions = .{}, |
| 541 | | .break_result_loc = rl, |
| 542 | 563 | // TODO @as here is working around a stage1 miscompilation bug :( |
| 543 | 564 | .label = @as(?Scope.GenZIR.Label, Scope.GenZIR.Label{ |
| 544 | 565 | .token = block_node.label, |
| ... | ... | @@ -546,19 +567,57 @@ fn labeledBlockExpr( |
| 546 | 567 | }), |
| 547 | 568 | }; |
| 548 | 569 | defer block_scope.instructions.deinit(mod.gpa); |
| 570 | defer block_scope.labeled_breaks.deinit(mod.gpa); |
| 571 | defer block_scope.labeled_store_to_block_ptr_list.deinit(mod.gpa); |
| 572 | |
| 573 | setBlockResultLoc(&block_scope, rl); |
| 549 | 574 | |
| 550 | 575 | try blockExprStmts(mod, &block_scope.base, &block_node.base, block_node.statements()); |
| 576 | |
| 551 | 577 | if (!block_scope.label.?.used) { |
| 552 | 578 | return mod.fail(parent_scope, tree.token_locs[block_node.label].start, "unused block label", .{}); |
| 553 | 579 | } |
| 554 | 580 | |
| 555 | | block_inst.positionals.body.instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items); |
| 556 | 581 | try gen_zir.instructions.append(mod.gpa, &block_inst.base); |
| 557 | 582 | |
| 558 | | return &block_inst.base; |
| 583 | const strat = rlStrategy(rl, &block_scope); |
| 584 | switch (strat.tag) { |
| 585 | .break_void => { |
| 586 | // The code took advantage of the result location as a pointer. |
| 587 | // Turn the break instructions into break_void instructions. |
| 588 | for (block_scope.labeled_breaks.items) |br| { |
| 589 | br.base.tag = .break_void; |
| 590 | } |
| 591 | // TODO technically not needed since we changed the tag to break_void but |
| 592 | // would be better still to elide the ones that are in this list. |
| 593 | try copyBodyNoEliding(&block_inst.positionals.body, block_scope); |
| 594 | |
| 595 | return &block_inst.base; |
| 596 | }, |
| 597 | .break_operand => { |
| 598 | // All break operands are values that did not use the result location pointer. |
| 599 | if (strat.elide_store_to_block_ptr_instructions) { |
| 600 | for (block_scope.labeled_store_to_block_ptr_list.items) |inst| { |
| 601 | inst.base.tag = .void_value; |
| 602 | } |
| 603 | // TODO technically not needed since we changed the tag to void_value but |
| 604 | // would be better still to elide the ones that are in this list. |
| 605 | } |
| 606 | try copyBodyNoEliding(&block_inst.positionals.body, block_scope); |
| 607 | switch (rl) { |
| 608 | .ref => return &block_inst.base, |
| 609 | else => return rvalue(mod, parent_scope, rl, &block_inst.base), |
| 610 | } |
| 611 | }, |
| 612 | } |
| 559 | 613 | } |
| 560 | 614 | |
| 561 | | fn blockExprStmts(mod: *Module, parent_scope: *Scope, node: *ast.Node, statements: []*ast.Node) !void { |
| 615 | fn blockExprStmts( |
| 616 | mod: *Module, |
| 617 | parent_scope: *Scope, |
| 618 | node: *ast.Node, |
| 619 | statements: []*ast.Node, |
| 620 | ) !void { |
| 562 | 621 | const tree = parent_scope.tree(); |
| 563 | 622 | |
| 564 | 623 | var block_arena = std.heap.ArenaAllocator.init(mod.gpa); |
| ... | ... | @@ -1659,7 +1718,6 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 1659 | 1718 | cond_kind = .{ .err_union = null }; |
| 1660 | 1719 | } |
| 1661 | 1720 | } |
| 1662 | | const block_branch_count = 2; // then and else |
| 1663 | 1721 | var block_scope: Scope.GenZIR = .{ |
| 1664 | 1722 | .parent = scope, |
| 1665 | 1723 | .decl = scope.ownerDecl().?, |
| ... | ... | @@ -1668,6 +1726,8 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 1668 | 1726 | }; |
| 1669 | 1727 | defer block_scope.instructions.deinit(mod.gpa); |
| 1670 | 1728 | |
| 1729 | setBlockResultLoc(&block_scope, rl); |
| 1730 | |
| 1671 | 1731 | const tree = scope.tree(); |
| 1672 | 1732 | const if_src = tree.token_locs[if_node.if_token].start; |
| 1673 | 1733 | const cond = try cond_kind.cond(mod, &block_scope, if_src, if_node.condition); |
| ... | ... | @@ -1682,33 +1742,6 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 1682 | 1742 | .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), |
| 1683 | 1743 | }); |
| 1684 | 1744 | |
| 1685 | | // Depending on whether the result location is a pointer or value, different |
| 1686 | | // ZIR needs to be generated. In the former case we rely on storing to the |
| 1687 | | // pointer to communicate the result, and use breakvoid; in the latter case |
| 1688 | | // the block break instructions will have the result values. |
| 1689 | | // One more complication: when the result location is a pointer, we detect |
| 1690 | | // the scenario where the result location is not consumed. In this case |
| 1691 | | // we emit ZIR for the block break instructions to have the result values, |
| 1692 | | // and then rvalue() on that to pass the value to the result location. |
| 1693 | | const branch_rl: ResultLoc = switch (rl) { |
| 1694 | | .discard, .none, .ty, .ptr, .ref => rl, |
| 1695 | | |
| 1696 | | .inferred_ptr => |ptr| blk: { |
| 1697 | | block_scope.rl_ptr = &ptr.base; |
| 1698 | | break :blk .{ .block_ptr = &block_scope }; |
| 1699 | | }, |
| 1700 | | |
| 1701 | | .bitcasted_ptr => |ptr| blk: { |
| 1702 | | block_scope.rl_ptr = &ptr.base; |
| 1703 | | break :blk .{ .block_ptr = &block_scope }; |
| 1704 | | }, |
| 1705 | | |
| 1706 | | .block_ptr => |parent_block_scope| blk: { |
| 1707 | | block_scope.rl_ptr = parent_block_scope.rl_ptr.?; |
| 1708 | | break :blk .{ .block_ptr = &block_scope }; |
| 1709 | | }, |
| 1710 | | }; |
| 1711 | | |
| 1712 | 1745 | const then_src = tree.token_locs[if_node.body.lastToken()].start; |
| 1713 | 1746 | var then_scope: Scope.GenZIR = .{ |
| 1714 | 1747 | .parent = scope, |
| ... | ... | @@ -1721,7 +1754,8 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 1721 | 1754 | // declare payload to the then_scope |
| 1722 | 1755 | const then_sub_scope = try cond_kind.thenSubScope(mod, &then_scope, then_src, if_node.payload); |
| 1723 | 1756 | |
| 1724 | | const then_result = try expr(mod, then_sub_scope, branch_rl, if_node.body); |
| 1757 | block_scope.break_count += 1; |
| 1758 | const then_result = try expr(mod, then_sub_scope, block_scope.break_result_loc, if_node.body); |
| 1725 | 1759 | // We hold off on the break instructions as well as copying the then/else |
| 1726 | 1760 | // instructions into place until we know whether to keep store_to_block_ptr |
| 1727 | 1761 | // instructions or not. |
| ... | ... | @@ -1741,47 +1775,18 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 1741 | 1775 | // declare payload to the then_scope |
| 1742 | 1776 | else_sub_scope = try cond_kind.elseSubScope(mod, &else_scope, else_src, else_node.payload); |
| 1743 | 1777 | |
| 1744 | | break :blk try expr(mod, else_sub_scope, branch_rl, else_node.body); |
| 1778 | block_scope.break_count += 1; |
| 1779 | break :blk try expr(mod, else_sub_scope, block_scope.break_result_loc, else_node.body); |
| 1745 | 1780 | } else blk: { |
| 1746 | 1781 | else_src = tree.token_locs[if_node.lastToken()].start; |
| 1747 | 1782 | else_sub_scope = &else_scope.base; |
| 1748 | | block_scope.rvalue_rl_count += 1; |
| 1749 | 1783 | break :blk null; |
| 1750 | 1784 | }; |
| 1751 | 1785 | |
| 1752 | 1786 | // We now have enough information to decide whether the result instruction should |
| 1753 | 1787 | // be communicated via result location pointer or break instructions. |
| 1754 | | const Strategy = enum { |
| 1755 | | /// Both branches will use break_void; result location is used to communicate the |
| 1756 | | /// result instruction. |
| 1757 | | break_void, |
| 1758 | | /// Use break statements to pass the block result value, and call rvalue() at |
| 1759 | | /// the end depending on rl. Also elide the store_to_block_ptr instructions |
| 1760 | | /// depending on rl. |
| 1761 | | break_operand, |
| 1762 | | }; |
| 1763 | | var elide_store_to_block_ptr_instructions = false; |
| 1764 | | const strategy: Strategy = switch (rl) { |
| 1765 | | // In this branch there will not be any store_to_block_ptr instructions. |
| 1766 | | .discard, .none, .ty, .ref => .break_operand, |
| 1767 | | // The pointer got passed through to the sub-expressions, so we will use |
| 1768 | | // break_void here. |
| 1769 | | // In this branch there will not be any store_to_block_ptr instructions. |
| 1770 | | .ptr => .break_void, |
| 1771 | | .inferred_ptr, .bitcasted_ptr, .block_ptr => blk: { |
| 1772 | | if (block_scope.rvalue_rl_count == 2) { |
| 1773 | | // Neither prong of the if consumed the result location, so we can |
| 1774 | | // use break instructions to create an rvalue. |
| 1775 | | elide_store_to_block_ptr_instructions = true; |
| 1776 | | break :blk Strategy.break_operand; |
| 1777 | | } else { |
| 1778 | | // Allow the store_to_block_ptr instructions to remain so that |
| 1779 | | // semantic analysis can turn them into bitcasts. |
| 1780 | | break :blk Strategy.break_void; |
| 1781 | | } |
| 1782 | | }, |
| 1783 | | }; |
| 1784 | | switch (strategy) { |
| 1788 | const strat = rlStrategy(rl, &block_scope); |
| 1789 | switch (strat.tag) { |
| 1785 | 1790 | .break_void => { |
| 1786 | 1791 | if (!then_result.tag.isNoReturn()) { |
| 1787 | 1792 | _ = try addZirInstTag(mod, then_sub_scope, then_src, .break_void, .{ |
| ... | ... | @@ -1799,7 +1804,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 1799 | 1804 | .block = block, |
| 1800 | 1805 | }); |
| 1801 | 1806 | } |
| 1802 | | assert(!elide_store_to_block_ptr_instructions); |
| 1807 | assert(!strat.elide_store_to_block_ptr_instructions); |
| 1803 | 1808 | try copyBodyNoEliding(&condbr.positionals.then_body, then_scope); |
| 1804 | 1809 | try copyBodyNoEliding(&condbr.positionals.else_body, else_scope); |
| 1805 | 1810 | return &block.base; |
| ... | ... | @@ -1823,7 +1828,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn |
| 1823 | 1828 | .block = block, |
| 1824 | 1829 | }); |
| 1825 | 1830 | } |
| 1826 | | if (elide_store_to_block_ptr_instructions) { |
| 1831 | if (strat.elide_store_to_block_ptr_instructions) { |
| 1827 | 1832 | try copyBodyWithElidedStoreBlockPtr(&condbr.positionals.then_body, then_scope); |
| 1828 | 1833 | try copyBodyWithElidedStoreBlockPtr(&condbr.positionals.else_body, else_scope); |
| 1829 | 1834 | } else { |
| ... | ... | @@ -3376,6 +3381,72 @@ fn rvalueVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, resul |
| 3376 | 3381 | return rvalue(mod, scope, rl, void_inst); |
| 3377 | 3382 | } |
| 3378 | 3383 | |
| 3384 | fn rlStrategy(rl: ResultLoc, block_scope: *Scope.GenZIR) ResultLoc.Strategy { |
| 3385 | var elide_store_to_block_ptr_instructions = false; |
| 3386 | switch (rl) { |
| 3387 | // In this branch there will not be any store_to_block_ptr instructions. |
| 3388 | .discard, .none, .ty, .ref => return .{ |
| 3389 | .tag = .break_operand, |
| 3390 | .elide_store_to_block_ptr_instructions = false, |
| 3391 | }, |
| 3392 | // The pointer got passed through to the sub-expressions, so we will use |
| 3393 | // break_void here. |
| 3394 | // In this branch there will not be any store_to_block_ptr instructions. |
| 3395 | .ptr => return .{ |
| 3396 | .tag = .break_void, |
| 3397 | .elide_store_to_block_ptr_instructions = false, |
| 3398 | }, |
| 3399 | .inferred_ptr, .bitcasted_ptr, .block_ptr => { |
| 3400 | if (block_scope.rvalue_rl_count == block_scope.break_count) { |
| 3401 | // Neither prong of the if consumed the result location, so we can |
| 3402 | // use break instructions to create an rvalue. |
| 3403 | return .{ |
| 3404 | .tag = .break_operand, |
| 3405 | .elide_store_to_block_ptr_instructions = true, |
| 3406 | }; |
| 3407 | } else { |
| 3408 | // Allow the store_to_block_ptr instructions to remain so that |
| 3409 | // semantic analysis can turn them into bitcasts. |
| 3410 | return .{ |
| 3411 | .tag = .break_void, |
| 3412 | .elide_store_to_block_ptr_instructions = false, |
| 3413 | }; |
| 3414 | } |
| 3415 | }, |
| 3416 | } |
| 3417 | } |
| 3418 | |
| 3419 | fn setBlockResultLoc(block_scope: *Scope.GenZIR, parent_rl: ResultLoc) void { |
| 3420 | // Depending on whether the result location is a pointer or value, different |
| 3421 | // ZIR needs to be generated. In the former case we rely on storing to the |
| 3422 | // pointer to communicate the result, and use breakvoid; in the latter case |
| 3423 | // the block break instructions will have the result values. |
| 3424 | // One more complication: when the result location is a pointer, we detect |
| 3425 | // the scenario where the result location is not consumed. In this case |
| 3426 | // we emit ZIR for the block break instructions to have the result values, |
| 3427 | // and then rvalue() on that to pass the value to the result location. |
| 3428 | switch (parent_rl) { |
| 3429 | .discard, .none, .ty, .ptr, .ref => { |
| 3430 | block_scope.break_result_loc = parent_rl; |
| 3431 | }, |
| 3432 | |
| 3433 | .inferred_ptr => |ptr| { |
| 3434 | block_scope.rl_ptr = &ptr.base; |
| 3435 | block_scope.break_result_loc = .{ .block_ptr = block_scope }; |
| 3436 | }, |
| 3437 | |
| 3438 | .bitcasted_ptr => |ptr| { |
| 3439 | block_scope.rl_ptr = &ptr.base; |
| 3440 | block_scope.break_result_loc = .{ .block_ptr = block_scope }; |
| 3441 | }, |
| 3442 | |
| 3443 | .block_ptr => |parent_block_scope| { |
| 3444 | block_scope.rl_ptr = parent_block_scope.rl_ptr.?; |
| 3445 | block_scope.break_result_loc = .{ .block_ptr = block_scope }; |
| 3446 | }, |
| 3447 | } |
| 3448 | } |
| 3449 | |
| 3379 | 3450 | pub fn addZirInstTag( |
| 3380 | 3451 | mod: *Module, |
| 3381 | 3452 | scope: *Scope, |