Expand bytecode expression support for mixed ops

This commit is contained in:
Sergey Chernov 2026-01-25 21:15:29 +03:00
parent 8ae6eb8d69
commit 9c56cf751b
7 changed files with 737 additions and 94 deletions

View File

@ -149,6 +149,8 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
- CMP_GT_REAL_INT S, S -> S - CMP_GT_REAL_INT S, S -> S
- CMP_GTE_INT_REAL S, S -> S - CMP_GTE_INT_REAL S, S -> S
- CMP_GTE_REAL_INT S, S -> S - CMP_GTE_REAL_INT S, S -> S
- CMP_NEQ_INT_REAL S, S -> S
- CMP_NEQ_REAL_INT S, S -> S
### Boolean ops ### Boolean ops
- NOT_BOOL S -> S - NOT_BOOL S -> S

View File

@ -132,7 +132,7 @@ class BytecodeBuilder {
Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL, Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL,
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_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT,
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 ->

View File

@ -26,6 +26,7 @@ import net.sergeych.lyng.obj.*
class BytecodeCompiler { class BytecodeCompiler {
private val builder = BytecodeBuilder() private val builder = BytecodeBuilder()
private var nextSlot = 0 private var nextSlot = 0
private val slotTypes = mutableMapOf<Int, SlotType>()
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): BytecodeFunction? { fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): BytecodeFunction? {
return when (stmt) { return when (stmt) {
@ -52,7 +53,7 @@ class BytecodeCompiler {
is LocalSlotRef -> { is LocalSlotRef -> {
if (ref.name.isEmpty()) return null if (ref.name.isEmpty()) return null
if (refDepth(ref) != 0) return null if (refDepth(ref) != 0) return null
CompiledValue(refSlot(ref), SlotType.UNKNOWN) CompiledValue(refSlot(ref), slotTypes[refSlot(ref)] ?: SlotType.UNKNOWN)
} }
is BinaryOpRef -> compileBinary(ref) is BinaryOpRef -> compileBinary(ref)
is UnaryOpRef -> compileUnary(ref) is UnaryOpRef -> compileUnary(ref)
@ -131,50 +132,101 @@ class BytecodeCompiler {
} }
val a = compileRef(binaryLeft(ref)) ?: return null val a = compileRef(binaryLeft(ref)) ?: return null
val b = compileRef(binaryRight(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 typesMismatch = a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN
if (typesMismatch && op !in setOf(BinOp.EQ, BinOp.NEQ, BinOp.LT, BinOp.LTE, BinOp.GT, BinOp.GTE)) {
return null
}
val out = allocSlot() val out = allocSlot()
return when (op) { return when (op) {
BinOp.PLUS -> when (a.type) { BinOp.PLUS -> when (a.type) {
SlotType.INT -> { SlotType.INT -> {
builder.emit(Opcode.ADD_INT, a.slot, b.slot, out) when (b.type) {
CompiledValue(out, SlotType.INT) SlotType.INT -> {
builder.emit(Opcode.ADD_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.INT)
}
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out)
else -> null
}
} }
SlotType.REAL -> { SlotType.REAL -> {
builder.emit(Opcode.ADD_REAL, a.slot, b.slot, out) when (b.type) {
CompiledValue(out, SlotType.REAL) SlotType.REAL -> {
builder.emit(Opcode.ADD_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.REAL)
}
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out)
else -> null
}
} }
else -> null else -> null
} }
BinOp.MINUS -> when (a.type) { BinOp.MINUS -> when (a.type) {
SlotType.INT -> { SlotType.INT -> {
builder.emit(Opcode.SUB_INT, a.slot, b.slot, out) when (b.type) {
CompiledValue(out, SlotType.INT) SlotType.INT -> {
builder.emit(Opcode.SUB_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.INT)
}
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out)
else -> null
}
} }
SlotType.REAL -> { SlotType.REAL -> {
builder.emit(Opcode.SUB_REAL, a.slot, b.slot, out) when (b.type) {
CompiledValue(out, SlotType.REAL) SlotType.REAL -> {
builder.emit(Opcode.SUB_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.REAL)
}
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out)
else -> null
}
} }
else -> null else -> null
} }
BinOp.STAR -> when (a.type) { BinOp.STAR -> when (a.type) {
SlotType.INT -> { SlotType.INT -> {
builder.emit(Opcode.MUL_INT, a.slot, b.slot, out) when (b.type) {
CompiledValue(out, SlotType.INT) SlotType.INT -> {
builder.emit(Opcode.MUL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.INT)
}
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out)
else -> null
}
} }
SlotType.REAL -> { SlotType.REAL -> {
builder.emit(Opcode.MUL_REAL, a.slot, b.slot, out) when (b.type) {
CompiledValue(out, SlotType.REAL) SlotType.REAL -> {
builder.emit(Opcode.MUL_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.REAL)
}
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out)
else -> null
}
} }
else -> null else -> null
} }
BinOp.SLASH -> when (a.type) { BinOp.SLASH -> when (a.type) {
SlotType.INT -> { SlotType.INT -> {
builder.emit(Opcode.DIV_INT, a.slot, b.slot, out) when (b.type) {
CompiledValue(out, SlotType.INT) SlotType.INT -> {
builder.emit(Opcode.DIV_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.INT)
}
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out)
else -> null
}
} }
SlotType.REAL -> { SlotType.REAL -> {
builder.emit(Opcode.DIV_REAL, a.slot, b.slot, out) when (b.type) {
CompiledValue(out, SlotType.REAL) SlotType.REAL -> {
builder.emit(Opcode.DIV_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.REAL)
}
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out)
else -> null
}
} }
else -> null else -> null
} }
@ -184,90 +236,22 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.INT) CompiledValue(out, SlotType.INT)
} }
BinOp.EQ -> { BinOp.EQ -> {
when (a.type) { compileCompareEq(a, b, out)
SlotType.INT -> {
builder.emit(Opcode.CMP_EQ_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.REAL -> {
builder.emit(Opcode.CMP_EQ_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.BOOL -> {
builder.emit(Opcode.CMP_EQ_BOOL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
} }
BinOp.NEQ -> { BinOp.NEQ -> {
when (a.type) { compileCompareNeq(a, b, out)
SlotType.INT -> {
builder.emit(Opcode.CMP_NEQ_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.REAL -> {
builder.emit(Opcode.CMP_NEQ_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.BOOL -> {
builder.emit(Opcode.CMP_NEQ_BOOL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
} }
BinOp.LT -> { BinOp.LT -> {
when (a.type) { compileCompareLt(a, b, out)
SlotType.INT -> {
builder.emit(Opcode.CMP_LT_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.REAL -> {
builder.emit(Opcode.CMP_LT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
} }
BinOp.LTE -> { BinOp.LTE -> {
when (a.type) { compileCompareLte(a, b, out)
SlotType.INT -> {
builder.emit(Opcode.CMP_LTE_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.REAL -> {
builder.emit(Opcode.CMP_LTE_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
} }
BinOp.GT -> { BinOp.GT -> {
when (a.type) { compileCompareGt(a, b, out)
SlotType.INT -> {
builder.emit(Opcode.CMP_GT_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.REAL -> {
builder.emit(Opcode.CMP_GT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
} }
BinOp.GTE -> { BinOp.GTE -> {
when (a.type) { compileCompareGte(a, b, out)
SlotType.INT -> {
builder.emit(Opcode.CMP_GTE_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.REAL -> {
builder.emit(Opcode.CMP_GTE_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
} }
BinOp.AND -> { BinOp.AND -> {
if (a.type != SlotType.BOOL) return null if (a.type != SlotType.BOOL) return null
@ -308,6 +292,173 @@ class BytecodeCompiler {
} }
} }
private fun compileRealArithmeticWithCoercion(
op: Opcode,
a: CompiledValue,
b: CompiledValue,
out: Int
): CompiledValue? {
if (a.type == SlotType.INT && b.type == SlotType.REAL) {
val left = allocSlot()
builder.emit(Opcode.INT_TO_REAL, a.slot, left)
builder.emit(op, left, b.slot, out)
return CompiledValue(out, SlotType.REAL)
}
if (a.type == SlotType.REAL && b.type == SlotType.INT) {
val right = allocSlot()
builder.emit(Opcode.INT_TO_REAL, b.slot, right)
builder.emit(op, a.slot, right, out)
return CompiledValue(out, SlotType.REAL)
}
return null
}
private fun compileCompareEq(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
return when {
a.type == SlotType.INT && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_EQ_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_EQ_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.BOOL && b.type == SlotType.BOOL -> {
builder.emit(Opcode.CMP_EQ_BOOL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.INT && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_EQ_INT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_EQ_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
private fun compileCompareNeq(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
return when {
a.type == SlotType.INT && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_NEQ_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_NEQ_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.BOOL && b.type == SlotType.BOOL -> {
builder.emit(Opcode.CMP_NEQ_BOOL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.INT && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_NEQ_INT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_NEQ_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
private fun compileCompareLt(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
return when {
a.type == SlotType.INT && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_LT_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_LT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.INT && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_LT_INT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_LT_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
private fun compileCompareLte(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
return when {
a.type == SlotType.INT && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_LTE_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_LTE_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.INT && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_LTE_INT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_LTE_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
private fun compileCompareGt(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
return when {
a.type == SlotType.INT && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_GT_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_GT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.INT && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_GT_INT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_GT_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
private fun compileCompareGte(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
return when {
a.type == SlotType.INT && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_GTE_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_GTE_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.INT && b.type == SlotType.REAL -> {
builder.emit(Opcode.CMP_GTE_INT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
a.type == SlotType.REAL && b.type == SlotType.INT -> {
builder.emit(Opcode.CMP_GTE_REAL_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
private fun compileLogical(op: BinOp, left: ObjRef, right: ObjRef, pos: Pos): CompiledValue? { private fun compileLogical(op: BinOp, left: ObjRef, right: ObjRef, pos: Pos): CompiledValue? {
val leftValue = compileRefWithFallback(left, SlotType.BOOL, pos) ?: return null val leftValue = compileRefWithFallback(left, SlotType.BOOL, pos) ?: return null
if (leftValue.type != SlotType.BOOL) return null if (leftValue.type != SlotType.BOOL) return null
@ -346,6 +497,7 @@ class BytecodeCompiler {
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot) SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot)
else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot) else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot)
} }
updateSlotType(slot, value.type)
return CompiledValue(slot, value.type) return CompiledValue(slot, value.type)
} }
@ -412,6 +564,7 @@ class BytecodeCompiler {
} }
val id = builder.addFallback(stmt) val id = builder.addFallback(stmt)
builder.emit(Opcode.EVAL_FALLBACK, id, slot) builder.emit(Opcode.EVAL_FALLBACK, id, slot)
updateSlotType(slot, forceType ?: SlotType.OBJ)
return CompiledValue(slot, forceType ?: SlotType.OBJ) return CompiledValue(slot, forceType ?: SlotType.OBJ)
} }
@ -425,4 +578,12 @@ class BytecodeCompiler {
private fun assignTarget(ref: AssignRef): LocalSlotRef? = ref.target as? LocalSlotRef private fun assignTarget(ref: AssignRef): LocalSlotRef? = ref.target as? LocalSlotRef
private fun assignValue(ref: AssignRef): ObjRef = ref.value private fun assignValue(ref: AssignRef): ObjRef = ref.value
private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn
private fun updateSlotType(slot: Int, type: SlotType) {
if (type == SlotType.UNKNOWN) {
slotTypes.remove(slot)
} else {
slotTypes[slot] = type
}
}
} }

View File

@ -100,7 +100,7 @@ object BytecodeDisassembler {
Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL, Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL,
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_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT,
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 ->

View File

@ -115,6 +115,34 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
frame.setObj(dst, frame.getObj(src)) frame.setObj(dst, frame.getObj(src))
} }
Opcode.INT_TO_REAL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setReal(dst, frame.getInt(src).toDouble())
}
Opcode.REAL_TO_INT -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setInt(dst, frame.getReal(src).toLong())
}
Opcode.BOOL_TO_INT -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setInt(dst, if (frame.getBool(src)) 1L else 0L)
}
Opcode.INT_TO_BOOL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setBool(dst, frame.getInt(src) != 0L)
}
Opcode.ADD_INT -> { Opcode.ADD_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
@ -124,6 +152,163 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) + frame.getInt(b)) frame.setInt(dst, frame.getInt(a) + frame.getInt(b))
} }
Opcode.SUB_INT -> {
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.setInt(dst, frame.getInt(a) - frame.getInt(b))
}
Opcode.MUL_INT -> {
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.setInt(dst, frame.getInt(a) * frame.getInt(b))
}
Opcode.DIV_INT -> {
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.setInt(dst, frame.getInt(a) / frame.getInt(b))
}
Opcode.MOD_INT -> {
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.setInt(dst, frame.getInt(a) % frame.getInt(b))
}
Opcode.NEG_INT -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setInt(dst, -frame.getInt(src))
}
Opcode.INC_INT -> {
val slot = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setInt(slot, frame.getInt(slot) + 1L)
}
Opcode.DEC_INT -> {
val slot = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setInt(slot, frame.getInt(slot) - 1L)
}
Opcode.ADD_REAL -> {
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.setReal(dst, frame.getReal(a) + frame.getReal(b))
}
Opcode.SUB_REAL -> {
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.setReal(dst, frame.getReal(a) - frame.getReal(b))
}
Opcode.MUL_REAL -> {
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.setReal(dst, frame.getReal(a) * frame.getReal(b))
}
Opcode.DIV_REAL -> {
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.setReal(dst, frame.getReal(a) / frame.getReal(b))
}
Opcode.NEG_REAL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setReal(dst, -frame.getReal(src))
}
Opcode.AND_INT -> {
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.setInt(dst, frame.getInt(a) and frame.getInt(b))
}
Opcode.OR_INT -> {
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.setInt(dst, frame.getInt(a) or frame.getInt(b))
}
Opcode.XOR_INT -> {
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.setInt(dst, frame.getInt(a) xor frame.getInt(b))
}
Opcode.SHL_INT -> {
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.setInt(dst, frame.getInt(a) shl frame.getInt(b).toInt())
}
Opcode.SHR_INT -> {
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.setInt(dst, frame.getInt(a) shr frame.getInt(b).toInt())
}
Opcode.USHR_INT -> {
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.setInt(dst, frame.getInt(a) ushr frame.getInt(b).toInt())
}
Opcode.INV_INT -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setInt(dst, frame.getInt(src).inv())
}
Opcode.CMP_LT_INT -> { Opcode.CMP_LT_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
@ -133,6 +318,33 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a) < frame.getInt(b)) frame.setBool(dst, frame.getInt(a) < frame.getInt(b))
} }
Opcode.CMP_LTE_INT -> {
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.getInt(a) <= frame.getInt(b))
}
Opcode.CMP_GT_INT -> {
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.getInt(a) > frame.getInt(b))
}
Opcode.CMP_GTE_INT -> {
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.getInt(a) >= frame.getInt(b))
}
Opcode.CMP_EQ_INT -> { Opcode.CMP_EQ_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
@ -151,6 +363,60 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a) != frame.getInt(b)) frame.setBool(dst, frame.getInt(a) != frame.getInt(b))
} }
Opcode.CMP_EQ_REAL -> {
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.getReal(a) == frame.getReal(b))
}
Opcode.CMP_NEQ_REAL -> {
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.getReal(a) != frame.getReal(b))
}
Opcode.CMP_LT_REAL -> {
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.getReal(a) < frame.getReal(b))
}
Opcode.CMP_LTE_REAL -> {
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.getReal(a) <= frame.getReal(b))
}
Opcode.CMP_GT_REAL -> {
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.getReal(a) > frame.getReal(b))
}
Opcode.CMP_GTE_REAL -> {
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.getReal(a) >= frame.getReal(b))
}
Opcode.CMP_EQ_BOOL -> { Opcode.CMP_EQ_BOOL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
@ -169,6 +435,121 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getBool(a) != frame.getBool(b)) frame.setBool(dst, frame.getBool(a) != frame.getBool(b))
} }
Opcode.CMP_EQ_INT_REAL -> {
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.getInt(a).toDouble() == frame.getReal(b))
}
Opcode.CMP_EQ_REAL_INT -> {
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.getReal(a) == frame.getInt(b).toDouble())
}
Opcode.CMP_LT_INT_REAL -> {
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.getInt(a).toDouble() < frame.getReal(b))
}
Opcode.CMP_LT_REAL_INT -> {
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.getReal(a) < frame.getInt(b).toDouble())
}
Opcode.CMP_LTE_INT_REAL -> {
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.getInt(a).toDouble() <= frame.getReal(b))
}
Opcode.CMP_LTE_REAL_INT -> {
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.getReal(a) <= frame.getInt(b).toDouble())
}
Opcode.CMP_GT_INT_REAL -> {
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.getInt(a).toDouble() > frame.getReal(b))
}
Opcode.CMP_GT_REAL_INT -> {
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.getReal(a) > frame.getInt(b).toDouble())
}
Opcode.CMP_GTE_INT_REAL -> {
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.getInt(a).toDouble() >= frame.getReal(b))
}
Opcode.CMP_GTE_REAL_INT -> {
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.getReal(a) >= frame.getInt(b).toDouble())
}
Opcode.CMP_NEQ_INT_REAL -> {
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.getInt(a).toDouble() != frame.getReal(b))
}
Opcode.CMP_NEQ_REAL_INT -> {
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.getReal(a) != frame.getInt(b).toDouble())
}
Opcode.NOT_BOOL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
frame.setBool(dst, !frame.getBool(src))
}
Opcode.AND_BOOL -> { Opcode.AND_BOOL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth

View File

@ -81,6 +81,8 @@ enum class Opcode(val code: Int) {
CMP_GT_REAL_INT(0x67), CMP_GT_REAL_INT(0x67),
CMP_GTE_INT_REAL(0x68), CMP_GTE_INT_REAL(0x68),
CMP_GTE_REAL_INT(0x69), CMP_GTE_REAL_INT(0x69),
CMP_NEQ_INT_REAL(0x6A),
CMP_NEQ_REAL_INT(0x6B),
NOT_BOOL(0x70), NOT_BOOL(0x70),
AND_BOOL(0x71), AND_BOOL(0x71),

View File

@ -25,12 +25,16 @@ import net.sergeych.lyng.bytecode.Opcode
import net.sergeych.lyng.obj.BinaryOpRef import net.sergeych.lyng.obj.BinaryOpRef
import net.sergeych.lyng.obj.BinOp import net.sergeych.lyng.obj.BinOp
import net.sergeych.lyng.obj.ConstRef import net.sergeych.lyng.obj.ConstRef
import net.sergeych.lyng.obj.LocalSlotRef
import net.sergeych.lyng.obj.ObjFalse 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.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
import net.sergeych.lyng.obj.toBool import net.sergeych.lyng.obj.toBool
import net.sergeych.lyng.obj.toDouble
import net.sergeych.lyng.obj.toInt import net.sergeych.lyng.obj.toInt
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -129,4 +133,97 @@ class BytecodeVmTest {
val result = BytecodeVm().execute(fn, Scope(), emptyList()) val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(true, result.toBool()) assertEquals(true, result.toBool())
} }
@Test
fun realArithmeticUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
val expr = ExpressionStatement(
BinaryOpRef(
BinOp.PLUS,
ConstRef(ObjReal.of(2.5).asReadonly),
ConstRef(ObjReal.of(3.25).asReadonly),
),
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("realPlus", expr) ?: error("bytecode compile failed")
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(5.75, result.toDouble())
}
@Test
fun mixedIntRealComparisonUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
val ltExpr = ExpressionStatement(
BinaryOpRef(
BinOp.LT,
ConstRef(ObjInt.of(2).asReadonly),
ConstRef(ObjReal.of(2.5).asReadonly),
),
net.sergeych.lyng.Pos.builtIn
)
val ltFn = BytecodeCompiler().compileExpression("mixedLt", ltExpr) ?: error("bytecode compile failed")
val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList())
assertEquals(true, ltResult.toBool())
val eqExpr = ExpressionStatement(
BinaryOpRef(
BinOp.EQ,
ConstRef(ObjReal.of(4.0).asReadonly),
ConstRef(ObjInt.of(4).asReadonly),
),
net.sergeych.lyng.Pos.builtIn
)
val eqFn = BytecodeCompiler().compileExpression("mixedEq", eqExpr) ?: error("bytecode compile failed")
val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList())
assertEquals(true, eqResult.toBool())
}
@Test
fun mixedIntRealArithmeticUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
val expr = ExpressionStatement(
BinaryOpRef(
BinOp.PLUS,
ConstRef(ObjInt.of(2).asReadonly),
ConstRef(ObjReal.of(3.5).asReadonly),
),
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("mixedPlus", expr) ?: error("bytecode compile failed")
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(5.5, result.toDouble())
}
@Test
fun mixedIntRealNotEqualUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
val expr = ExpressionStatement(
BinaryOpRef(
BinOp.NEQ,
ConstRef(ObjInt.of(3).asReadonly),
ConstRef(ObjReal.of(2.5).asReadonly),
),
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("mixedNeq", expr) ?: error("bytecode compile failed")
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(true, result.toBool())
}
@Test
fun localSlotTypeTrackingEnablesArithmetic() = kotlinx.coroutines.test.runTest {
val slotRef = LocalSlotRef("a", 0, 0, net.sergeych.lyng.Pos.builtIn)
val assign = AssignRef(
slotRef,
ConstRef(ObjInt.of(2).asReadonly),
net.sergeych.lyng.Pos.builtIn
)
val expr = ExpressionStatement(
BinaryOpRef(
BinOp.PLUS,
assign,
slotRef
),
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed")
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(4, result.toInt())
}
} }