compiler: Construct logical targets in checked code
84ff2d4f061e768403b6586cf3c576c43bd3f53a723d66241311eee15443c21e
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 | + | } |