| ... | ... | @@ -50,6 +50,7 @@ src_loc: Module.SrcLoc, |
| 50 | 50 | stack_align: u32, |
| 51 | 51 | |
| 52 | 52 | ret_backpatch: ?Mir.Inst.Index = null, |
| 53 | compare_flags_inst: ?Air.Inst.Index = null, |
| 53 | 54 | |
| 54 | 55 | /// MIR Instructions |
| 55 | 56 | mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, |
| ... | ... | @@ -774,6 +775,9 @@ fn processDeath(self: *Self, inst: Air.Inst.Index) void { |
| 774 | 775 | const canon_reg = reg.to64(); |
| 775 | 776 | self.register_manager.freeReg(canon_reg); |
| 776 | 777 | }, |
| 778 | .compare_flags_signed, .compare_flags_unsigned => { |
| 779 | self.compare_flags_inst = null; |
| 780 | }, |
| 777 | 781 | else => {}, // TODO process stack allocation death |
| 778 | 782 | } |
| 779 | 783 | } |
| ... | ... | @@ -889,6 +893,22 @@ pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void |
| 889 | 893 | try self.genSetStack(self.air.typeOfIndex(inst), stack_mcv.stack_offset, reg_mcv, .{}); |
| 890 | 894 | } |
| 891 | 895 | |
| 896 | pub fn spillCompareFlagsIfOccupied(self: *Self) !void { |
| 897 | if (self.compare_flags_inst) |inst_to_save| { |
| 898 | const mcv = self.getResolvedInstValue(inst_to_save); |
| 899 | assert(mcv == .compare_flags_signed or mcv == .compare_flags_unsigned); |
| 900 | |
| 901 | const new_mcv = try self.allocRegOrMem(inst_to_save, true); |
| 902 | try self.setRegOrMem(self.air.typeOfIndex(inst_to_save), new_mcv, mcv); |
| 903 | log.debug("spilling {d} to mcv {any}", .{ inst_to_save, new_mcv }); |
| 904 | |
| 905 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 906 | try branch.inst_table.put(self.gpa, inst_to_save, new_mcv); |
| 907 | |
| 908 | self.compare_flags_inst = null; |
| 909 | } |
| 910 | } |
| 911 | |
| 892 | 912 | /// Copies a value to a register without tracking the register. The register is not considered |
| 893 | 913 | /// allocated. A second call to `copyToTmpRegister` may return the same register. |
| 894 | 914 | /// This can have a side effect of spilling instructions to the stack to free up a register. |
| ... | ... | @@ -2763,6 +2783,8 @@ fn genBinMathOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MC |
| 2763 | 2783 | .memory, |
| 2764 | 2784 | .got_load, |
| 2765 | 2785 | .direct_load, |
| 2786 | .compare_flags_signed, |
| 2787 | .compare_flags_unsigned, |
| 2766 | 2788 | => { |
| 2767 | 2789 | assert(abi_size <= 8); |
| 2768 | 2790 | self.register_manager.freezeRegs(&.{dst_reg}); |
| ... | ... | @@ -2784,12 +2806,6 @@ fn genBinMathOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MC |
| 2784 | 2806 | .data = .{ .imm = @bitCast(u32, -off) }, |
| 2785 | 2807 | }); |
| 2786 | 2808 | }, |
| 2787 | | .compare_flags_unsigned => { |
| 2788 | | return self.fail("TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{}); |
| 2789 | | }, |
| 2790 | | .compare_flags_signed => { |
| 2791 | | return self.fail("TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{}); |
| 2792 | | }, |
| 2793 | 2809 | } |
| 2794 | 2810 | }, |
| 2795 | 2811 | .stack_offset => |off| { |
| ... | ... | @@ -3061,6 +3077,8 @@ fn airCall(self: *Self, inst: Air.Inst.Index) !void { |
| 3061 | 3077 | var info = try self.resolveCallingConventionValues(fn_ty); |
| 3062 | 3078 | defer info.deinit(self); |
| 3063 | 3079 | |
| 3080 | try self.spillCompareFlagsIfOccupied(); |
| 3081 | |
| 3064 | 3082 | if (info.return_value == .stack_offset) { |
| 3065 | 3083 | const ret_ty = fn_ty.fnReturnType(); |
| 3066 | 3084 | const ret_abi_size = @intCast(u32, ret_ty.abiSize(self.target.*)); |
| ... | ... | @@ -3384,17 +3402,24 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { |
| 3384 | 3402 | break :blk ty.intInfo(self.target.*).signedness; |
| 3385 | 3403 | }; |
| 3386 | 3404 | |
| 3387 | | const lhs = try self.resolveInst(bin_op.lhs); |
| 3388 | | const rhs = try self.resolveInst(bin_op.rhs); |
| 3405 | try self.spillCompareFlagsIfOccupied(); |
| 3406 | self.compare_flags_inst = inst; |
| 3407 | |
| 3389 | 3408 | const result: MCValue = result: { |
| 3390 | 3409 | // There are 2 operands, destination and source. |
| 3391 | 3410 | // Either one, but not both, can be a memory operand. |
| 3392 | 3411 | // Source operand can be an immediate, 8 bits or 32 bits. |
| 3393 | | // TODO this looks wrong. Why do simply reuse lhs without checking if it is dead or alive? |
| 3394 | | const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory())) |
| 3395 | | MCValue{ .register = try self.copyToTmpRegister(ty, lhs) } |
| 3396 | | else |
| 3397 | | lhs; |
| 3412 | // TODO look into reusing the operand |
| 3413 | const lhs = try self.resolveInst(bin_op.lhs); |
| 3414 | lhs.freezeIfRegister(&self.register_manager); |
| 3415 | defer lhs.unfreezeIfRegister(&self.register_manager); |
| 3416 | |
| 3417 | const dst_reg = try self.copyToTmpRegister(ty, lhs); |
| 3418 | self.register_manager.freezeRegs(&.{dst_reg}); |
| 3419 | defer self.register_manager.unfreezeRegs(&.{dst_reg}); |
| 3420 | |
| 3421 | const dst_mcv = MCValue{ .register = dst_reg }; |
| 3422 | |
| 3398 | 3423 | // This instruction supports only signed 32-bit immediates at most. |
| 3399 | 3424 | const src_mcv = try self.limitImmediateType(bin_op.rhs, i32); |
| 3400 | 3425 | |
| ... | ... | @@ -3404,6 +3429,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { |
| 3404 | 3429 | .unsigned => MCValue{ .compare_flags_unsigned = op }, |
| 3405 | 3430 | }; |
| 3406 | 3431 | }; |
| 3432 | |
| 3407 | 3433 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 3408 | 3434 | } |
| 3409 | 3435 | |
| ... | ... | @@ -3451,6 +3477,7 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { |
| 3451 | 3477 | }); |
| 3452 | 3478 | }, |
| 3453 | 3479 | .register => |reg| { |
| 3480 | try self.spillCompareFlagsIfOccupied(); |
| 3454 | 3481 | _ = try self.addInst(.{ |
| 3455 | 3482 | .tag = .@"test", |
| 3456 | 3483 | .ops = (Mir.Ops{ |
| ... | ... | @@ -3467,19 +3494,15 @@ fn genCondBrMir(self: *Self, ty: Type, mcv: MCValue) !u32 { |
| 3467 | 3494 | .data = .{ .inst = undefined }, |
| 3468 | 3495 | }); |
| 3469 | 3496 | }, |
| 3470 | | .immediate => { |
| 3471 | | if (abi_size <= 8) { |
| 3472 | | const reg = try self.copyToTmpRegister(ty, mcv); |
| 3473 | | return self.genCondBrMir(ty, .{ .register = reg }); |
| 3474 | | } |
| 3475 | | return self.fail("TODO implement condbr when condition is immediate larger than 4 bytes", .{}); |
| 3476 | | }, |
| 3477 | | .stack_offset => { |
| 3497 | .immediate, |
| 3498 | .stack_offset, |
| 3499 | => { |
| 3500 | try self.spillCompareFlagsIfOccupied(); |
| 3478 | 3501 | if (abi_size <= 8) { |
| 3479 | 3502 | const reg = try self.copyToTmpRegister(ty, mcv); |
| 3480 | 3503 | return self.genCondBrMir(ty, .{ .register = reg }); |
| 3481 | 3504 | } |
| 3482 | | return self.fail("TODO implement condbr when condition is stack offset with abi larger than 8 bytes", .{}); |
| 3505 | return self.fail("TODO implement condbr when condition is {} with abi larger than 8 bytes", .{mcv}); |
| 3483 | 3506 | }, |
| 3484 | 3507 | else => return self.fail("TODO implement condbr when condition is {s}", .{@tagName(mcv)}), |
| 3485 | 3508 | } |
| ... | ... | @@ -3496,9 +3519,23 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 3496 | 3519 | |
| 3497 | 3520 | const reloc = try self.genCondBrMir(cond_ty, cond); |
| 3498 | 3521 | |
| 3522 | // If the condition dies here in this condbr instruction, process |
| 3523 | // that death now instead of later as this has an effect on |
| 3524 | // whether it needs to be spilled in the branches |
| 3525 | // TODO I need investigate how to make this work without removing |
| 3526 | // an assertion from getResolvedInstValue() |
| 3527 | if (self.liveness.operandDies(inst, 0)) { |
| 3528 | const op_int = @enumToInt(pl_op.operand); |
| 3529 | if (op_int >= Air.Inst.Ref.typed_value_map.len) { |
| 3530 | const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len); |
| 3531 | self.processDeath(op_index); |
| 3532 | } |
| 3533 | } |
| 3534 | |
| 3499 | 3535 | // Capture the state of register and stack allocation state so that we can revert to it. |
| 3500 | 3536 | const parent_next_stack_offset = self.next_stack_offset; |
| 3501 | 3537 | const parent_free_registers = self.register_manager.free_registers; |
| 3538 | const parent_compare_flags_inst = self.compare_flags_inst; |
| 3502 | 3539 | var parent_stack = try self.stack.clone(self.gpa); |
| 3503 | 3540 | defer parent_stack.deinit(self.gpa); |
| 3504 | 3541 | const parent_registers = self.register_manager.registers; |
| ... | ... | @@ -3520,6 +3557,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 3520 | 3557 | defer saved_then_branch.deinit(self.gpa); |
| 3521 | 3558 | |
| 3522 | 3559 | self.register_manager.registers = parent_registers; |
| 3560 | self.compare_flags_inst = parent_compare_flags_inst; |
| 3523 | 3561 | |
| 3524 | 3562 | self.stack.deinit(self.gpa); |
| 3525 | 3563 | self.stack = parent_stack; |
| ... | ... | @@ -3590,6 +3628,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 3590 | 3628 | // We already deleted the items from this table that matched the else_branch. |
| 3591 | 3629 | // So these are all instructions that are only overridden in the then branch. |
| 3592 | 3630 | parent_branch.inst_table.putAssumeCapacity(then_key, then_value); |
| 3631 | log.debug("then_value = {}", .{then_value}); |
| 3593 | 3632 | if (then_value == .dead) |
| 3594 | 3633 | continue; |
| 3595 | 3634 | const parent_mcv = blk: { |
| ... | ... | @@ -3614,23 +3653,31 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { |
| 3614 | 3653 | return self.finishAir(inst, .unreach, .{ pl_op.operand, .none, .none }); |
| 3615 | 3654 | } |
| 3616 | 3655 | |
| 3617 | | fn isNull(self: *Self, ty: Type, operand: MCValue) !MCValue { |
| 3656 | fn isNull(self: *Self, inst: Air.Inst.Index, ty: Type, operand: MCValue) !MCValue { |
| 3657 | try self.spillCompareFlagsIfOccupied(); |
| 3658 | self.compare_flags_inst = inst; |
| 3659 | |
| 3618 | 3660 | try self.genBinMathOpMir(.cmp, ty, operand, MCValue{ .immediate = 0 }); |
| 3619 | 3661 | return MCValue{ .compare_flags_unsigned = .eq }; |
| 3620 | 3662 | } |
| 3621 | 3663 | |
| 3622 | | fn isNonNull(self: *Self, ty: Type, operand: MCValue) !MCValue { |
| 3623 | | const is_null_res = try self.isNull(ty, operand); |
| 3664 | fn isNonNull(self: *Self, inst: Air.Inst.Index, ty: Type, operand: MCValue) !MCValue { |
| 3665 | const is_null_res = try self.isNull(inst, ty, operand); |
| 3624 | 3666 | assert(is_null_res.compare_flags_unsigned == .eq); |
| 3625 | 3667 | return MCValue{ .compare_flags_unsigned = .neq }; |
| 3626 | 3668 | } |
| 3627 | 3669 | |
| 3628 | | fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue { |
| 3670 | fn isErr(self: *Self, inst: Air.Inst.Index, ty: Type, operand: MCValue) !MCValue { |
| 3629 | 3671 | const err_type = ty.errorUnionSet(); |
| 3630 | 3672 | const payload_type = ty.errorUnionPayload(); |
| 3631 | 3673 | if (!err_type.hasRuntimeBits()) { |
| 3632 | 3674 | return MCValue{ .immediate = 0 }; // always false |
| 3633 | | } else if (!payload_type.hasRuntimeBits()) { |
| 3675 | } |
| 3676 | |
| 3677 | try self.spillCompareFlagsIfOccupied(); |
| 3678 | self.compare_flags_inst = inst; |
| 3679 | |
| 3680 | if (!payload_type.hasRuntimeBits()) { |
| 3634 | 3681 | if (err_type.abiSize(self.target.*) <= 8) { |
| 3635 | 3682 | try self.genBinMathOpMir(.cmp, err_type, operand, MCValue{ .immediate = 0 }); |
| 3636 | 3683 | return MCValue{ .compare_flags_unsigned = .gt }; |
| ... | ... | @@ -3643,8 +3690,8 @@ fn isErr(self: *Self, ty: Type, operand: MCValue) !MCValue { |
| 3643 | 3690 | } |
| 3644 | 3691 | } |
| 3645 | 3692 | |
| 3646 | | fn isNonErr(self: *Self, ty: Type, operand: MCValue) !MCValue { |
| 3647 | | const is_err_res = try self.isErr(ty, operand); |
| 3693 | fn isNonErr(self: *Self, inst: Air.Inst.Index, ty: Type, operand: MCValue) !MCValue { |
| 3694 | const is_err_res = try self.isErr(inst, ty, operand); |
| 3648 | 3695 | switch (is_err_res) { |
| 3649 | 3696 | .compare_flags_unsigned => |op| { |
| 3650 | 3697 | assert(op == .gt); |
| ... | ... | @@ -3663,7 +3710,7 @@ fn airIsNull(self: *Self, inst: Air.Inst.Index) !void { |
| 3663 | 3710 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { |
| 3664 | 3711 | const operand = try self.resolveInst(un_op); |
| 3665 | 3712 | const ty = self.air.typeOf(un_op); |
| 3666 | | break :result try self.isNull(ty, operand); |
| 3713 | break :result try self.isNull(inst, ty, operand); |
| 3667 | 3714 | }; |
| 3668 | 3715 | return self.finishAir(inst, result, .{ un_op, .none, .none }); |
| 3669 | 3716 | } |
| ... | ... | @@ -3684,7 +3731,7 @@ fn airIsNullPtr(self: *Self, inst: Air.Inst.Index) !void { |
| 3684 | 3731 | }; |
| 3685 | 3732 | const ptr_ty = self.air.typeOf(un_op); |
| 3686 | 3733 | try self.load(operand, operand_ptr, ptr_ty); |
| 3687 | | break :result try self.isNull(ptr_ty.elemType(), operand); |
| 3734 | break :result try self.isNull(inst, ptr_ty.elemType(), operand); |
| 3688 | 3735 | }; |
| 3689 | 3736 | return self.finishAir(inst, result, .{ un_op, .none, .none }); |
| 3690 | 3737 | } |
| ... | ... | @@ -3694,7 +3741,7 @@ fn airIsNonNull(self: *Self, inst: Air.Inst.Index) !void { |
| 3694 | 3741 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { |
| 3695 | 3742 | const operand = try self.resolveInst(un_op); |
| 3696 | 3743 | const ty = self.air.typeOf(un_op); |
| 3697 | | break :result try self.isNonNull(ty, operand); |
| 3744 | break :result try self.isNonNull(inst, ty, operand); |
| 3698 | 3745 | }; |
| 3699 | 3746 | return self.finishAir(inst, result, .{ un_op, .none, .none }); |
| 3700 | 3747 | } |
| ... | ... | @@ -3715,7 +3762,7 @@ fn airIsNonNullPtr(self: *Self, inst: Air.Inst.Index) !void { |
| 3715 | 3762 | }; |
| 3716 | 3763 | const ptr_ty = self.air.typeOf(un_op); |
| 3717 | 3764 | try self.load(operand, operand_ptr, ptr_ty); |
| 3718 | | break :result try self.isNonNull(ptr_ty.elemType(), operand); |
| 3765 | break :result try self.isNonNull(inst, ptr_ty.elemType(), operand); |
| 3719 | 3766 | }; |
| 3720 | 3767 | return self.finishAir(inst, result, .{ un_op, .none, .none }); |
| 3721 | 3768 | } |
| ... | ... | @@ -3725,7 +3772,7 @@ fn airIsErr(self: *Self, inst: Air.Inst.Index) !void { |
| 3725 | 3772 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { |
| 3726 | 3773 | const operand = try self.resolveInst(un_op); |
| 3727 | 3774 | const ty = self.air.typeOf(un_op); |
| 3728 | | break :result try self.isErr(ty, operand); |
| 3775 | break :result try self.isErr(inst, ty, operand); |
| 3729 | 3776 | }; |
| 3730 | 3777 | return self.finishAir(inst, result, .{ un_op, .none, .none }); |
| 3731 | 3778 | } |
| ... | ... | @@ -3746,7 +3793,7 @@ fn airIsErrPtr(self: *Self, inst: Air.Inst.Index) !void { |
| 3746 | 3793 | }; |
| 3747 | 3794 | const ptr_ty = self.air.typeOf(un_op); |
| 3748 | 3795 | try self.load(operand, operand_ptr, ptr_ty); |
| 3749 | | break :result try self.isErr(ptr_ty.elemType(), operand); |
| 3796 | break :result try self.isErr(inst, ptr_ty.elemType(), operand); |
| 3750 | 3797 | }; |
| 3751 | 3798 | return self.finishAir(inst, result, .{ un_op, .none, .none }); |
| 3752 | 3799 | } |
| ... | ... | @@ -3756,7 +3803,7 @@ fn airIsNonErr(self: *Self, inst: Air.Inst.Index) !void { |
| 3756 | 3803 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { |
| 3757 | 3804 | const operand = try self.resolveInst(un_op); |
| 3758 | 3805 | const ty = self.air.typeOf(un_op); |
| 3759 | | break :result try self.isNonErr(ty, operand); |
| 3806 | break :result try self.isNonErr(inst, ty, operand); |
| 3760 | 3807 | }; |
| 3761 | 3808 | return self.finishAir(inst, result, .{ un_op, .none, .none }); |
| 3762 | 3809 | } |
| ... | ... | @@ -3777,7 +3824,7 @@ fn airIsNonErrPtr(self: *Self, inst: Air.Inst.Index) !void { |
| 3777 | 3824 | }; |
| 3778 | 3825 | const ptr_ty = self.air.typeOf(un_op); |
| 3779 | 3826 | try self.load(operand, operand_ptr, ptr_ty); |
| 3780 | | break :result try self.isNonErr(ptr_ty.elemType(), operand); |
| 3827 | break :result try self.isNonErr(inst, ptr_ty.elemType(), operand); |
| 3781 | 3828 | }; |
| 3782 | 3829 | return self.finishAir(inst, result, .{ un_op, .none, .none }); |
| 3783 | 3830 | } |
| ... | ... | @@ -3832,6 +3879,7 @@ fn genCondSwitchMir(self: *Self, ty: Type, condition: MCValue, case: MCValue) !u |
| 3832 | 3879 | .compare_flags_signed => unreachable, |
| 3833 | 3880 | .compare_flags_unsigned => unreachable, |
| 3834 | 3881 | .register => |cond_reg| { |
| 3882 | try self.spillCompareFlagsIfOccupied(); |
| 3835 | 3883 | switch (case) { |
| 3836 | 3884 | .none => unreachable, |
| 3837 | 3885 | .undef => unreachable, |
| ... | ... | @@ -3916,9 +3964,23 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { |
| 3916 | 3964 | relocs[item_i] = try self.genCondSwitchMir(condition_ty, condition, item_mcv); |
| 3917 | 3965 | } |
| 3918 | 3966 | |
| 3967 | // If the condition dies here in this condbr instruction, process |
| 3968 | // that death now instead of later as this has an effect on |
| 3969 | // whether it needs to be spilled in the branches |
| 3970 | // TODO I need investigate how to make this work without removing |
| 3971 | // an assertion from getResolvedInstValue() |
| 3972 | if (self.liveness.operandDies(inst, 0)) { |
| 3973 | const op_int = @enumToInt(pl_op.operand); |
| 3974 | if (op_int >= Air.Inst.Ref.typed_value_map.len) { |
| 3975 | const op_index = @intCast(Air.Inst.Index, op_int - Air.Inst.Ref.typed_value_map.len); |
| 3976 | self.processDeath(op_index); |
| 3977 | } |
| 3978 | } |
| 3979 | |
| 3919 | 3980 | // Capture the state of register and stack allocation state so that we can revert to it. |
| 3920 | 3981 | const parent_next_stack_offset = self.next_stack_offset; |
| 3921 | 3982 | const parent_free_registers = self.register_manager.free_registers; |
| 3983 | const parent_compare_flags_inst = self.compare_flags_inst; |
| 3922 | 3984 | var parent_stack = try self.stack.clone(self.gpa); |
| 3923 | 3985 | defer parent_stack.deinit(self.gpa); |
| 3924 | 3986 | const parent_registers = self.register_manager.registers; |
| ... | ... | @@ -3940,6 +4002,7 @@ fn airSwitch(self: *Self, inst: Air.Inst.Index) !void { |
| 3940 | 4002 | defer saved_case_branch.deinit(self.gpa); |
| 3941 | 4003 | |
| 3942 | 4004 | self.register_manager.registers = parent_registers; |
| 4005 | self.compare_flags_inst = parent_compare_flags_inst; |
| 3943 | 4006 | self.stack.deinit(self.gpa); |
| 3944 | 4007 | self.stack = parent_stack; |
| 3945 | 4008 | parent_stack = .{}; |
| ... | ... | @@ -5208,7 +5271,8 @@ fn getResolvedInstValue(self: *Self, inst: Air.Inst.Index) MCValue { |
| 5208 | 5271 | while (true) { |
| 5209 | 5272 | i -= 1; |
| 5210 | 5273 | if (self.branch_stack.items[i].inst_table.get(inst)) |mcv| { |
| 5211 | | assert(mcv != .dead); |
| 5274 | // TODO see comment in `airCondBr` and `airSwitch` |
| 5275 | // assert(mcv != .dead); |
| 5212 | 5276 | return mcv; |
| 5213 | 5277 | } |
| 5214 | 5278 | } |