Add short-circuit ops in bytecode compiler and VM
This commit is contained in:
parent
3d9170d677
commit
8ae6eb8d69
@ -125,11 +125,14 @@ class BytecodeCompiler {
|
||||
}
|
||||
|
||||
private fun compileBinary(ref: BinaryOpRef): CompiledValue? {
|
||||
val op = binaryOp(ref)
|
||||
if (op == BinOp.AND || op == BinOp.OR) {
|
||||
return compileLogical(op, binaryLeft(ref), binaryRight(ref), refPos(ref))
|
||||
}
|
||||
val a = compileRef(binaryLeft(ref)) ?: return null
|
||||
val b = compileRef(binaryRight(ref)) ?: return null
|
||||
if (a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN) return null
|
||||
val out = allocSlot()
|
||||
val op = binaryOp(ref)
|
||||
return when (op) {
|
||||
BinOp.PLUS -> when (a.type) {
|
||||
SlotType.INT -> {
|
||||
@ -305,6 +308,33 @@ class BytecodeCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileLogical(op: BinOp, left: ObjRef, right: ObjRef, pos: Pos): CompiledValue? {
|
||||
val leftValue = compileRefWithFallback(left, SlotType.BOOL, pos) ?: return null
|
||||
if (leftValue.type != SlotType.BOOL) return null
|
||||
val resultSlot = allocSlot()
|
||||
val shortLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
if (op == BinOp.AND) {
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_FALSE,
|
||||
listOf(BytecodeBuilder.Operand.IntVal(leftValue.slot), BytecodeBuilder.Operand.LabelRef(shortLabel))
|
||||
)
|
||||
} else {
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(BytecodeBuilder.Operand.IntVal(leftValue.slot), BytecodeBuilder.Operand.LabelRef(shortLabel))
|
||||
)
|
||||
}
|
||||
val rightValue = compileRefWithFallback(right, SlotType.BOOL, pos) ?: return null
|
||||
emitMove(rightValue, resultSlot)
|
||||
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
||||
builder.mark(shortLabel)
|
||||
val constId = builder.addConst(BytecodeConst.Bool(op == BinOp.OR))
|
||||
builder.emit(Opcode.CONST_BOOL, constId, resultSlot)
|
||||
builder.mark(endLabel)
|
||||
return CompiledValue(resultSlot, SlotType.BOOL)
|
||||
}
|
||||
|
||||
private fun compileAssign(ref: AssignRef): CompiledValue? {
|
||||
val target = assignTarget(ref) ?: return null
|
||||
if (refDepth(target) != 0) return null
|
||||
@ -394,4 +424,5 @@ class BytecodeCompiler {
|
||||
private fun unaryOp(ref: UnaryOpRef): UnaryOp = ref.op
|
||||
private fun assignTarget(ref: AssignRef): LocalSlotRef? = ref.target as? LocalSlotRef
|
||||
private fun assignValue(ref: AssignRef): ObjRef = ref.value
|
||||
private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn
|
||||
}
|
||||
|
||||
@ -200,6 +200,15 @@ class BytecodeVm {
|
||||
ip = target
|
||||
}
|
||||
}
|
||||
Opcode.JMP_IF_TRUE -> {
|
||||
val cond = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val target = decoder.readIp(code, ip, fn.ipWidth)
|
||||
ip += fn.ipWidth
|
||||
if (frame.getBool(cond)) {
|
||||
ip = target
|
||||
}
|
||||
}
|
||||
Opcode.EVAL_FALLBACK -> {
|
||||
val id = decoder.readConstId(code, ip, 2)
|
||||
ip += 2
|
||||
|
||||
@ -25,8 +25,12 @@ import net.sergeych.lyng.bytecode.Opcode
|
||||
import net.sergeych.lyng.obj.BinaryOpRef
|
||||
import net.sergeych.lyng.obj.BinOp
|
||||
import net.sergeych.lyng.obj.ConstRef
|
||||
import net.sergeych.lyng.obj.ObjFalse
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import net.sergeych.lyng.obj.ObjTrue
|
||||
import net.sergeych.lyng.obj.ValueFnRef
|
||||
import net.sergeych.lyng.obj.ObjVoid
|
||||
import net.sergeych.lyng.obj.toBool
|
||||
import net.sergeych.lyng.obj.toInt
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
@ -93,4 +97,36 @@ class BytecodeVmTest {
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(ObjVoid, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun andIsShortCircuit() = kotlinx.coroutines.test.runTest {
|
||||
val throwingRef = ValueFnRef { error("should not execute") }
|
||||
val expr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.AND,
|
||||
ConstRef(ObjFalse.asReadonly),
|
||||
throwingRef
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val fn = BytecodeCompiler().compileExpression("andShort", expr) ?: error("bytecode compile failed")
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(false, result.toBool())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun orIsShortCircuit() = kotlinx.coroutines.test.runTest {
|
||||
val throwingRef = ValueFnRef { error("should not execute") }
|
||||
val expr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.OR,
|
||||
ConstRef(ObjTrue.asReadonly),
|
||||
throwingRef
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val fn = BytecodeCompiler().compileExpression("orShort", expr) ?: error("bytecode compile failed")
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(true, result.toBool())
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user