compiler: Construct logical targets in checked code

84ff2d4f061e768403b6586cf3c576c43bd3f53a723d66241311eee15443c21e
Alexis Sellier committed ago 1 parent 71575880
lib/std/lang/lower.rad +26 -22
583 583
}
584 584
585 585
/// Logical operator.
586 586
union LogicalOp: Copy { And, Or }
587 587
588 +
/// Control-flow destinations and result for short-circuit evaluation.
589 +
record LogicalTargets: Copy {
590 +
    /// Block that skips evaluating `b`.
591 +
    shortCircuitBlock: BlockId,
592 +
    /// Block that evaluates `b`.
593 +
    evalBlock: BlockId,
594 +
    /// Result when short-circuiting (`0` or `1`).
595 +
    shortCircuitVal: i64,
596 +
}
597 +
598 +
/// Select both logical edges and their constant result from the operator.
599 +
fn logicalTargets(op: LogicalOp, thenBlock: BlockId, elseBlock: BlockId) -> LogicalTargets {
600 +
    match op {
601 +
        case LogicalOp::And => return LogicalTargets {
602 +
            shortCircuitBlock: elseBlock, evalBlock: thenBlock, shortCircuitVal: 0,
603 +
        },
604 +
        case LogicalOp::Or => return LogicalTargets {
605 +
            shortCircuitBlock: thenBlock, evalBlock: elseBlock, shortCircuitVal: 1,
606 +
        },
607 +
    }
608 +
}
609 +
588 610
/// Iterator state for for-loop lowering.
589 611
union ForIter: Copy {
590 612
    /// Range iterator: `for i in 0..n`.
591 613
    Range {
592 614
        valVar: Var,
6048 6070
        self, mergeLabel, il::Param { value: resultReg, type: il::Type::W8 }
6049 6071
    );
6050 6072
    // Evaluate left operand and branch.
6051 6073
    try emitCondBranch(self, binop.left, thenBlock, elseBlock);
6052 6074
6053 -
    // Block that skips evaluating `b`.
6054 -
    let mut shortCircuitBlock: BlockId = undefined;
6055 -
    // Block that evaluates `b`.
6056 -
    let mut evalBlock: BlockId = undefined;
6057 -
    // Result when short-circuiting (`0` or `1`).
6058 -
    let mut shortCircuitVal: i64 = undefined;
6059 -
6060 -
    match op {
6061 -
        case LogicalOp::And => {
6062 -
            set shortCircuitBlock = elseBlock;
6063 -
            set evalBlock = thenBlock;
6064 -
            set shortCircuitVal = 0;
6065 -
        }
6066 -
        case LogicalOp::Or => {
6067 -
            set shortCircuitBlock = thenBlock;
6068 -
            set evalBlock = elseBlock;
6069 -
            set shortCircuitVal = 1;
6070 -
        }
6071 -
    }
6075 +
    let targets = logicalTargets(op, thenBlock, elseBlock);
6072 6076
    // Emit short-circuit branch: jump to merge with constant result.
6073 -
    try switchToAndSeal(self, shortCircuitBlock);
6074 -
    try emitJmpWithArg(self, mergeBlock, il::Val::Imm(shortCircuitVal));
6077 +
    try switchToAndSeal(self, targets.shortCircuitBlock);
6078 +
    try emitJmpWithArg(self, mergeBlock, il::Val::Imm(targets.shortCircuitVal));
6075 6079
6076 6080
    // Emit evaluation branch: evaluate right operand and jump to merge.
6077 -
    try switchToAndSeal(self, evalBlock);
6081 +
    try switchToAndSeal(self, targets.evalBlock);
6078 6082
    try emitJmpWithArg(self, mergeBlock, try lowerExpr(self, binop.right));
6079 6083
6080 6084
    try switchToAndSeal(self, mergeBlock);
6081 6085
    return il::Val::Reg(resultReg);
6082 6086
}
test/tests/bool.short.circuit.order.rad added +29 -0
1 +
//! returns: 0
2 +
//! Nested boolean expressions preserve operand order and skipped effects.
3 +
4 +
/// Append the operand's position to the evaluation trace.
5 +
fn operand(trace: &mut u32, position: u32, value: bool) -> bool {
6 +
    set *trace = *trace * 10 + position;
7 +
    return value;
8 +
}
9 +
10 +
/// Check both nested operator orders for every input combination.
11 +
@default fn main() -> u32 {
12 +
    for bits in 0..8 {
13 +
        let a = bits & 1 <> 0;
14 +
        let b = bits & 2 <> 0;
15 +
        let c = bits & 4 <> 0;
16 +
        let mut trace: u32 = 0;
17 +
        let first = operand(&mut trace, 1, a) and
18 +
            (operand(&mut trace, 2, b) or operand(&mut trace, 3, c));
19 +
        assert first == (a and (b or c));
20 +
        assert trace == (1 if not a else 12 if b else 123);
21 +
22 +
        set trace = 0;
23 +
        let second = operand(&mut trace, 1, a) or
24 +
            (operand(&mut trace, 2, b) and operand(&mut trace, 3, c));
25 +
        assert second == (a or (b and c));
26 +
        assert trace == (1 if a else 12 if not b else 123);
27 +
    }
28 +
    return 0;
29 +
}