From b4598bff98e2c13b28d907ffea5a9c82c5a349e0 Mon Sep 17 00:00:00 2001 From: sergeych Date: Sun, 25 Jan 2026 21:37:20 +0300 Subject: [PATCH] Add object comparison opcodes to bytecode --- docs/BytecodeSpec.md | 4 +++ .../sergeych/lyng/bytecode/BytecodeBuilder.kt | 1 + .../lyng/bytecode/BytecodeCompiler.kt | 16 +++++++++ .../lyng/bytecode/BytecodeDisassembler.kt | 1 + .../net/sergeych/lyng/bytecode/BytecodeVm.kt | 36 +++++++++++++++++++ .../net/sergeych/lyng/bytecode/Opcode.kt | 4 +++ .../src/commonTest/kotlin/BytecodeVmTest.kt | 27 ++++++++++++++ 7 files changed, 89 insertions(+) diff --git a/docs/BytecodeSpec.md b/docs/BytecodeSpec.md index f9780d4..e92dd74 100644 --- a/docs/BytecodeSpec.md +++ b/docs/BytecodeSpec.md @@ -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 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt index 46fdcf7..edda073 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt @@ -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 -> diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index 0e1de60..5e168c5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -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 } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt index 5cd94e8..e5c5bce 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt @@ -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 -> diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt index 8033c0e..1b8bc64 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt @@ -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 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt index ee36a14..cff1407 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -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), diff --git a/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt b/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt index 3f21bac..d5735e4 100644 --- a/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt +++ b/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt @@ -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()) + } }