Elide redundant bool conversions in logical ops

This commit is contained in:
Sergey Chernov 2026-02-16 17:42:04 +03:00
parent 3e654ddd60
commit 7c28296f92
2 changed files with 11 additions and 34 deletions

View File

@ -2339,60 +2339,36 @@ class BytecodeCompiler(
private fun compileLogicalAnd(ref: LogicalAndRef): CompiledValue? {
val leftValue = compileRefWithFallback(ref.left(), SlotType.BOOL, Pos.builtIn) ?: return null
val leftBool = if (leftValue.type == SlotType.BOOL) {
leftValue
} else {
val slot = allocSlot()
builder.emit(Opcode.OBJ_TO_BOOL, leftValue.slot, slot)
CompiledValue(slot, SlotType.BOOL)
}
if (leftValue.type != SlotType.BOOL) return null
val resultSlot = allocSlot()
val falseId = builder.addConst(BytecodeConst.Bool(false))
builder.emit(Opcode.CONST_BOOL, falseId, resultSlot)
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_FALSE,
listOf(CmdBuilder.Operand.IntVal(leftBool.slot), CmdBuilder.Operand.LabelRef(endLabel))
listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(endLabel))
)
val rightValue = compileRefWithFallback(ref.right(), SlotType.BOOL, Pos.builtIn) ?: return null
val rightBool = if (rightValue.type == SlotType.BOOL) {
rightValue
} else {
val slot = allocSlot()
builder.emit(Opcode.OBJ_TO_BOOL, rightValue.slot, slot)
CompiledValue(slot, SlotType.BOOL)
}
builder.emit(Opcode.MOVE_BOOL, rightBool.slot, resultSlot)
if (rightValue.type != SlotType.BOOL) return null
builder.emit(Opcode.MOVE_BOOL, rightValue.slot, resultSlot)
builder.mark(endLabel)
return CompiledValue(resultSlot, SlotType.BOOL)
}
private fun compileLogicalOr(ref: LogicalOrRef): CompiledValue? {
val leftValue = compileRefWithFallback(ref.left(), SlotType.BOOL, Pos.builtIn) ?: return null
val leftBool = if (leftValue.type == SlotType.BOOL) {
leftValue
} else {
val slot = allocSlot()
builder.emit(Opcode.OBJ_TO_BOOL, leftValue.slot, slot)
CompiledValue(slot, SlotType.BOOL)
}
if (leftValue.type != SlotType.BOOL) return null
val resultSlot = allocSlot()
val trueId = builder.addConst(BytecodeConst.Bool(true))
builder.emit(Opcode.CONST_BOOL, trueId, resultSlot)
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(leftBool.slot), CmdBuilder.Operand.LabelRef(endLabel))
listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(endLabel))
)
val rightValue = compileRefWithFallback(ref.right(), SlotType.BOOL, Pos.builtIn) ?: return null
val rightBool = if (rightValue.type == SlotType.BOOL) {
rightValue
} else {
val slot = allocSlot()
builder.emit(Opcode.OBJ_TO_BOOL, rightValue.slot, slot)
CompiledValue(slot, SlotType.BOOL)
}
builder.emit(Opcode.MOVE_BOOL, rightBool.slot, resultSlot)
if (rightValue.type != SlotType.BOOL) return null
builder.emit(Opcode.MOVE_BOOL, rightValue.slot, resultSlot)
builder.mark(endLabel)
return CompiledValue(resultSlot, SlotType.BOOL)
}

View File

@ -10,8 +10,9 @@ Candidates (not started)
2) Mixed numeric ops (done)
- Allow INT+REAL arithmetic to use primitive REAL ops (no obj fallback).
- MixedCompareBenchmarkTest: 347 ms -> 275 ms.
3) Boolean conversion
- Skip OBJ_TO_BOOL when compiler already has a BOOL slot.
3) Boolean conversion (done; do not revert without review)
- Skip redundant OBJ_TO_BOOL in logical AND/OR when compiler already emits BOOL.
- MixedCompareBenchmarkTest: 275 ms -> 249 ms.
4) Range/loop hot path
- Keep range iteration in INT ops, avoid accidental boxing.
- Confirm loop-var slots avoid re-boxing in loop bodies.