diff --git a/docs/BytecodeSpec.md b/docs/BytecodeSpec.md index 32576af..f9780d4 100644 --- a/docs/BytecodeSpec.md +++ b/docs/BytecodeSpec.md @@ -151,6 +151,10 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass. - CMP_GTE_REAL_INT S, S -> S - CMP_NEQ_INT_REAL S, S -> S - CMP_NEQ_REAL_INT S, S -> S +- CMP_EQ_OBJ S, S -> S +- CMP_NEQ_OBJ S, S -> S +- CMP_REF_EQ_OBJ S, S -> S +- CMP_REF_NEQ_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 9c316e4..46fdcf7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt @@ -133,6 +133,7 @@ class BytecodeBuilder { Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT, 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.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 df255a4..0e1de60 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -253,6 +253,16 @@ class BytecodeCompiler { BinOp.GTE -> { compileCompareGte(a, b, out) } + BinOp.REF_EQ -> { + if (a.type != SlotType.OBJ || b.type != SlotType.OBJ) return null + builder.emit(Opcode.CMP_REF_EQ_OBJ, a.slot, b.slot, out) + CompiledValue(out, SlotType.BOOL) + } + BinOp.REF_NEQ -> { + if (a.type != SlotType.OBJ || b.type != SlotType.OBJ) return null + builder.emit(Opcode.CMP_REF_NEQ_OBJ, a.slot, b.slot, out) + CompiledValue(out, SlotType.BOOL) + } BinOp.AND -> { if (a.type != SlotType.BOOL) return null builder.emit(Opcode.AND_BOOL, a.slot, b.slot, out) @@ -336,6 +346,10 @@ class BytecodeCompiler { builder.emit(Opcode.CMP_EQ_REAL_INT, a.slot, b.slot, out) CompiledValue(out, SlotType.BOOL) } + a.type == SlotType.OBJ && b.type == SlotType.OBJ -> { + builder.emit(Opcode.CMP_EQ_OBJ, a.slot, b.slot, out) + CompiledValue(out, SlotType.BOOL) + } else -> null } } @@ -363,6 +377,10 @@ class BytecodeCompiler { builder.emit(Opcode.CMP_NEQ_REAL_INT, a.slot, b.slot, out) CompiledValue(out, SlotType.BOOL) } + a.type == SlotType.OBJ && b.type == SlotType.OBJ -> { + builder.emit(Opcode.CMP_NEQ_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 028017c..5cd94e8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt @@ -101,6 +101,7 @@ object BytecodeDisassembler { Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT, 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.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 4606641..8033c0e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt @@ -72,14 +72,18 @@ class BytecodeVm { ip += fn.constIdWidth val dst = decoder.readSlot(code, ip) ip += fn.slotWidth - val c = fn.constants[constId] as? BytecodeConst.ObjRef - ?: error("CONST_OBJ expects ObjRef at $constId") - val obj = c.value - when (obj) { - is ObjInt -> frame.setInt(dst, obj.value) - is ObjReal -> frame.setReal(dst, obj.value) - is ObjBool -> frame.setBool(dst, obj.value) - else -> frame.setObj(dst, obj) + when (val c = fn.constants[constId]) { + is BytecodeConst.ObjRef -> { + val obj = c.value + when (obj) { + is ObjInt -> frame.setInt(dst, obj.value) + is ObjReal -> frame.setReal(dst, obj.value) + is ObjBool -> frame.setBool(dst, obj.value) + else -> frame.setObj(dst, obj) + } + } + is BytecodeConst.StringVal -> frame.setObj(dst, ObjString(c.value)) + else -> error("CONST_OBJ expects ObjRef/StringVal at $constId") } } Opcode.CONST_NULL -> { @@ -543,6 +547,42 @@ class BytecodeVm { ip += fn.slotWidth frame.setBool(dst, frame.getReal(a) != frame.getInt(b).toDouble()) } + Opcode.CMP_EQ_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).equals(scope, frame.getObj(b))) + } + Opcode.CMP_NEQ_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).equals(scope, frame.getObj(b))) + } + Opcode.CMP_REF_EQ_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) === frame.getObj(b)) + } + Opcode.CMP_REF_NEQ_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) !== frame.getObj(b)) + } 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 1f24710..ee36a14 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -83,6 +83,10 @@ enum class Opcode(val code: Int) { CMP_GTE_REAL_INT(0x69), CMP_NEQ_INT_REAL(0x6A), CMP_NEQ_REAL_INT(0x6B), + CMP_EQ_OBJ(0x6C), + CMP_NEQ_OBJ(0x6D), + CMP_REF_EQ_OBJ(0x6E), + CMP_REF_NEQ_OBJ(0x6F), NOT_BOOL(0x70), AND_BOOL(0x71), diff --git a/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt b/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt index 9c34c10..3f21bac 100644 --- a/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt +++ b/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt @@ -30,6 +30,8 @@ import net.sergeych.lyng.obj.ObjFalse import net.sergeych.lyng.obj.ObjInt import net.sergeych.lyng.obj.ObjTrue import net.sergeych.lyng.obj.ObjReal +import net.sergeych.lyng.obj.ObjString +import net.sergeych.lyng.obj.ObjList import net.sergeych.lyng.obj.AssignRef import net.sergeych.lyng.obj.ValueFnRef import net.sergeych.lyng.obj.ObjVoid @@ -226,4 +228,47 @@ class BytecodeVmTest { val result = BytecodeVm().execute(fn, Scope(), emptyList()) assertEquals(4, result.toInt()) } + + @Test + fun objectEqualityUsesBytecodeOps() = kotlinx.coroutines.test.runTest { + val expr = ExpressionStatement( + BinaryOpRef( + BinOp.EQ, + ConstRef(ObjString("abc").asReadonly), + ConstRef(ObjString("abc").asReadonly), + ), + net.sergeych.lyng.Pos.builtIn + ) + val fn = BytecodeCompiler().compileExpression("objEq", expr) ?: error("bytecode compile failed") + val result = BytecodeVm().execute(fn, Scope(), emptyList()) + assertEquals(true, result.toBool()) + } + + @Test + fun objectReferenceEqualityUsesBytecodeOps() = kotlinx.coroutines.test.runTest { + val shared = ObjList() + val eqExpr = ExpressionStatement( + BinaryOpRef( + BinOp.REF_EQ, + ConstRef(shared.asReadonly), + ConstRef(shared.asReadonly), + ), + net.sergeych.lyng.Pos.builtIn + ) + val eqFn = BytecodeCompiler().compileExpression("objRefEq", eqExpr) ?: error("bytecode compile failed") + val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList()) + assertEquals(true, eqResult.toBool()) + + val neqExpr = ExpressionStatement( + BinaryOpRef( + BinOp.REF_NEQ, + ConstRef(ObjList().asReadonly), + ConstRef(ObjList().asReadonly), + ), + net.sergeych.lyng.Pos.builtIn + ) + val neqFn = BytecodeCompiler().compileExpression("objRefNeq", neqExpr) ?: error("bytecode compile failed") + val neqResult = BytecodeVm().execute(neqFn, Scope(), emptyList()) + assertEquals(true, neqResult.toBool()) + } }