diff --git a/docs/BytecodeSpec.md b/docs/BytecodeSpec.md index 2ef1e1d..32576af 100644 --- a/docs/BytecodeSpec.md +++ b/docs/BytecodeSpec.md @@ -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_GTE_INT_REAL 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 - 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 d58b153..9c316e4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt @@ -132,7 +132,7 @@ class BytecodeBuilder { 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_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 -> 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 45bfeaa..df255a4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -26,6 +26,7 @@ import net.sergeych.lyng.obj.* class BytecodeCompiler { private val builder = BytecodeBuilder() private var nextSlot = 0 + private val slotTypes = mutableMapOf() fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): BytecodeFunction? { return when (stmt) { @@ -52,7 +53,7 @@ class BytecodeCompiler { is LocalSlotRef -> { if (ref.name.isEmpty()) 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 UnaryOpRef -> compileUnary(ref) @@ -131,50 +132,101 @@ class BytecodeCompiler { } val a = compileRef(binaryLeft(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() return when (op) { BinOp.PLUS -> when (a.type) { SlotType.INT -> { - builder.emit(Opcode.ADD_INT, a.slot, b.slot, out) - CompiledValue(out, SlotType.INT) + when (b.type) { + 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 -> { - builder.emit(Opcode.ADD_REAL, a.slot, b.slot, out) - CompiledValue(out, SlotType.REAL) + when (b.type) { + 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 } BinOp.MINUS -> when (a.type) { SlotType.INT -> { - builder.emit(Opcode.SUB_INT, a.slot, b.slot, out) - CompiledValue(out, SlotType.INT) + when (b.type) { + 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 -> { - builder.emit(Opcode.SUB_REAL, a.slot, b.slot, out) - CompiledValue(out, SlotType.REAL) + when (b.type) { + 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 } BinOp.STAR -> when (a.type) { SlotType.INT -> { - builder.emit(Opcode.MUL_INT, a.slot, b.slot, out) - CompiledValue(out, SlotType.INT) + when (b.type) { + 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 -> { - builder.emit(Opcode.MUL_REAL, a.slot, b.slot, out) - CompiledValue(out, SlotType.REAL) + when (b.type) { + 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 } BinOp.SLASH -> when (a.type) { SlotType.INT -> { - builder.emit(Opcode.DIV_INT, a.slot, b.slot, out) - CompiledValue(out, SlotType.INT) + when (b.type) { + 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 -> { - builder.emit(Opcode.DIV_REAL, a.slot, b.slot, out) - CompiledValue(out, SlotType.REAL) + when (b.type) { + 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 } @@ -184,90 +236,22 @@ class BytecodeCompiler { CompiledValue(out, SlotType.INT) } BinOp.EQ -> { - when (a.type) { - 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 - } + compileCompareEq(a, b, out) } BinOp.NEQ -> { - when (a.type) { - 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 - } + compileCompareNeq(a, b, out) } BinOp.LT -> { - when (a.type) { - 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 - } + compileCompareLt(a, b, out) } BinOp.LTE -> { - when (a.type) { - 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 - } + compileCompareLte(a, b, out) } BinOp.GT -> { - when (a.type) { - 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 - } + compileCompareGt(a, b, out) } BinOp.GTE -> { - when (a.type) { - 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 - } + compileCompareGte(a, b, out) } BinOp.AND -> { 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? { val leftValue = compileRefWithFallback(left, SlotType.BOOL, pos) ?: 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) else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot) } + updateSlotType(slot, value.type) return CompiledValue(slot, value.type) } @@ -412,6 +564,7 @@ class BytecodeCompiler { } val id = builder.addFallback(stmt) builder.emit(Opcode.EVAL_FALLBACK, id, slot) + updateSlotType(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 assignValue(ref: AssignRef): ObjRef = ref.value 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 + } + } } 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 25f3a53..028017c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt @@ -100,7 +100,7 @@ object BytecodeDisassembler { 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_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 -> 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 0208260..4606641 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt @@ -115,6 +115,34 @@ class BytecodeVm { ip += fn.slotWidth 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 -> { val a = decoder.readSlot(code, ip) ip += fn.slotWidth @@ -124,6 +152,163 @@ class BytecodeVm { ip += fn.slotWidth 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 -> { val a = decoder.readSlot(code, ip) ip += fn.slotWidth @@ -133,6 +318,33 @@ class BytecodeVm { ip += fn.slotWidth 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 -> { val a = decoder.readSlot(code, ip) ip += fn.slotWidth @@ -151,6 +363,60 @@ class BytecodeVm { ip += fn.slotWidth 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 -> { val a = decoder.readSlot(code, ip) ip += fn.slotWidth @@ -169,6 +435,121 @@ class BytecodeVm { ip += fn.slotWidth 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 -> { val a = 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 daceabf..1f24710 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -81,6 +81,8 @@ enum class Opcode(val code: Int) { CMP_GT_REAL_INT(0x67), CMP_GTE_INT_REAL(0x68), CMP_GTE_REAL_INT(0x69), + CMP_NEQ_INT_REAL(0x6A), + CMP_NEQ_REAL_INT(0x6B), NOT_BOOL(0x70), AND_BOOL(0x71), diff --git a/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt b/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt index 83a70bf..9c34c10 100644 --- a/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt +++ b/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt @@ -25,12 +25,16 @@ import net.sergeych.lyng.bytecode.Opcode import net.sergeych.lyng.obj.BinaryOpRef import net.sergeych.lyng.obj.BinOp import net.sergeych.lyng.obj.ConstRef +import net.sergeych.lyng.obj.LocalSlotRef 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.AssignRef import net.sergeych.lyng.obj.ValueFnRef import net.sergeych.lyng.obj.ObjVoid import net.sergeych.lyng.obj.toBool +import net.sergeych.lyng.obj.toDouble import net.sergeych.lyng.obj.toInt import kotlin.test.Test import kotlin.test.assertEquals @@ -129,4 +133,97 @@ class BytecodeVmTest { val result = BytecodeVm().execute(fn, Scope(), emptyList()) 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()) + } }