Add object equality and reference ops to bytecode
This commit is contained in:
parent
9c56cf751b
commit
fd1548c86c
@ -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_GTE_REAL_INT S, S -> S
|
||||||
- CMP_NEQ_INT_REAL S, S -> S
|
- CMP_NEQ_INT_REAL S, S -> S
|
||||||
- CMP_NEQ_REAL_INT 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
|
### Boolean ops
|
||||||
- NOT_BOOL S -> S
|
- NOT_BOOL S -> S
|
||||||
|
|||||||
@ -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_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_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_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 ->
|
Opcode.AND_BOOL, Opcode.OR_BOOL ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
|
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
|
||||||
|
|||||||
@ -253,6 +253,16 @@ class BytecodeCompiler {
|
|||||||
BinOp.GTE -> {
|
BinOp.GTE -> {
|
||||||
compileCompareGte(a, b, out)
|
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 -> {
|
BinOp.AND -> {
|
||||||
if (a.type != SlotType.BOOL) return null
|
if (a.type != SlotType.BOOL) return null
|
||||||
builder.emit(Opcode.AND_BOOL, a.slot, b.slot, out)
|
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)
|
builder.emit(Opcode.CMP_EQ_REAL_INT, a.slot, b.slot, out)
|
||||||
CompiledValue(out, SlotType.BOOL)
|
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
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,6 +377,10 @@ class BytecodeCompiler {
|
|||||||
builder.emit(Opcode.CMP_NEQ_REAL_INT, a.slot, b.slot, out)
|
builder.emit(Opcode.CMP_NEQ_REAL_INT, a.slot, b.slot, out)
|
||||||
CompiledValue(out, SlotType.BOOL)
|
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
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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_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_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_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 ->
|
Opcode.AND_BOOL, Opcode.OR_BOOL ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
|
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
|
||||||
|
|||||||
@ -72,8 +72,8 @@ class BytecodeVm {
|
|||||||
ip += fn.constIdWidth
|
ip += fn.constIdWidth
|
||||||
val dst = decoder.readSlot(code, ip)
|
val dst = decoder.readSlot(code, ip)
|
||||||
ip += fn.slotWidth
|
ip += fn.slotWidth
|
||||||
val c = fn.constants[constId] as? BytecodeConst.ObjRef
|
when (val c = fn.constants[constId]) {
|
||||||
?: error("CONST_OBJ expects ObjRef at $constId")
|
is BytecodeConst.ObjRef -> {
|
||||||
val obj = c.value
|
val obj = c.value
|
||||||
when (obj) {
|
when (obj) {
|
||||||
is ObjInt -> frame.setInt(dst, obj.value)
|
is ObjInt -> frame.setInt(dst, obj.value)
|
||||||
@ -82,6 +82,10 @@ class BytecodeVm {
|
|||||||
else -> frame.setObj(dst, obj)
|
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 -> {
|
Opcode.CONST_NULL -> {
|
||||||
val dst = decoder.readSlot(code, ip)
|
val dst = decoder.readSlot(code, ip)
|
||||||
ip += fn.slotWidth
|
ip += fn.slotWidth
|
||||||
@ -543,6 +547,42 @@ class BytecodeVm {
|
|||||||
ip += fn.slotWidth
|
ip += fn.slotWidth
|
||||||
frame.setBool(dst, frame.getReal(a) != frame.getInt(b).toDouble())
|
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 -> {
|
Opcode.NOT_BOOL -> {
|
||||||
val src = decoder.readSlot(code, ip)
|
val src = decoder.readSlot(code, ip)
|
||||||
ip += fn.slotWidth
|
ip += fn.slotWidth
|
||||||
|
|||||||
@ -83,6 +83,10 @@ enum class Opcode(val code: Int) {
|
|||||||
CMP_GTE_REAL_INT(0x69),
|
CMP_GTE_REAL_INT(0x69),
|
||||||
CMP_NEQ_INT_REAL(0x6A),
|
CMP_NEQ_INT_REAL(0x6A),
|
||||||
CMP_NEQ_REAL_INT(0x6B),
|
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),
|
NOT_BOOL(0x70),
|
||||||
AND_BOOL(0x71),
|
AND_BOOL(0x71),
|
||||||
|
|||||||
@ -30,6 +30,8 @@ import net.sergeych.lyng.obj.ObjFalse
|
|||||||
import net.sergeych.lyng.obj.ObjInt
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
import net.sergeych.lyng.obj.ObjTrue
|
import net.sergeych.lyng.obj.ObjTrue
|
||||||
import net.sergeych.lyng.obj.ObjReal
|
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.AssignRef
|
||||||
import net.sergeych.lyng.obj.ValueFnRef
|
import net.sergeych.lyng.obj.ValueFnRef
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
@ -226,4 +228,47 @@ class BytecodeVmTest {
|
|||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(4, result.toInt())
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user