Add object comparison opcodes to bytecode

This commit is contained in:
Sergey Chernov 2026-01-25 21:37:20 +03:00
parent fd1548c86c
commit b4598bff98
7 changed files with 89 additions and 0 deletions

View File

@ -155,6 +155,10 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
- CMP_NEQ_OBJ S, S -> S
- CMP_REF_EQ_OBJ S, S -> S
- CMP_REF_NEQ_OBJ S, S -> S
- CMP_LT_OBJ S, S -> S
- CMP_LTE_OBJ S, S -> S
- CMP_GT_OBJ S, S -> S
- CMP_GTE_OBJ S, S -> S
### Boolean ops
- NOT_BOOL S -> S

View File

@ -134,6 +134,7 @@ class BytecodeBuilder {
Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT,
Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT,
Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ,
Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ,
Opcode.AND_BOOL, Opcode.OR_BOOL ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->

View File

@ -404,6 +404,10 @@ class BytecodeCompiler {
builder.emit(Opcode.CMP_LT_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
builder.emit(Opcode.CMP_LT_OBJ, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
@ -427,6 +431,10 @@ class BytecodeCompiler {
builder.emit(Opcode.CMP_LTE_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
builder.emit(Opcode.CMP_LTE_OBJ, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
@ -450,6 +458,10 @@ class BytecodeCompiler {
builder.emit(Opcode.CMP_GT_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
builder.emit(Opcode.CMP_GT_OBJ, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
@ -473,6 +485,10 @@ class BytecodeCompiler {
builder.emit(Opcode.CMP_GTE_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
builder.emit(Opcode.CMP_GTE_OBJ, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}

View File

@ -102,6 +102,7 @@ object BytecodeDisassembler {
Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT,
Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT,
Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ,
Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ,
Opcode.AND_BOOL, Opcode.OR_BOOL ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->

View File

@ -583,6 +583,42 @@ class BytecodeVm {
ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a) !== frame.getObj(b))
}
Opcode.CMP_LT_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a).compareTo(scope, frame.getObj(b)) < 0)
}
Opcode.CMP_LTE_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a).compareTo(scope, frame.getObj(b)) <= 0)
}
Opcode.CMP_GT_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a).compareTo(scope, frame.getObj(b)) > 0)
}
Opcode.CMP_GTE_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a).compareTo(scope, frame.getObj(b)) >= 0)
}
Opcode.NOT_BOOL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth

View File

@ -91,6 +91,10 @@ enum class Opcode(val code: Int) {
NOT_BOOL(0x70),
AND_BOOL(0x71),
OR_BOOL(0x72),
CMP_LT_OBJ(0x73),
CMP_LTE_OBJ(0x74),
CMP_GT_OBJ(0x75),
CMP_GTE_OBJ(0x76),
JMP(0x80),
JMP_IF_TRUE(0x81),

View File

@ -271,4 +271,31 @@ class BytecodeVmTest {
val neqResult = BytecodeVm().execute(neqFn, Scope(), emptyList())
assertEquals(true, neqResult.toBool())
}
@Test
fun objectComparisonUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
val ltExpr = ExpressionStatement(
BinaryOpRef(
BinOp.LT,
ConstRef(ObjString("a").asReadonly),
ConstRef(ObjString("b").asReadonly),
),
net.sergeych.lyng.Pos.builtIn
)
val ltFn = BytecodeCompiler().compileExpression("objLt", ltExpr) ?: error("bytecode compile failed")
val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList())
assertEquals(true, ltResult.toBool())
val gteExpr = ExpressionStatement(
BinaryOpRef(
BinOp.GTE,
ConstRef(ObjString("b").asReadonly),
ConstRef(ObjString("a").asReadonly),
),
net.sergeych.lyng.Pos.builtIn
)
val gteFn = BytecodeCompiler().compileExpression("objGte", gteExpr) ?: error("bytecode compile failed")
val gteResult = BytecodeVm().execute(gteFn, Scope(), emptyList())
assertEquals(true, gteResult.toBool())
}
}