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 f61f8a5..264440a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -181,33 +181,99 @@ class BytecodeCompiler { CompiledValue(out, SlotType.INT) } BinOp.EQ -> { - if (a.type != SlotType.INT) return null - builder.emit(Opcode.CMP_EQ_INT, a.slot, b.slot, out) - CompiledValue(out, SlotType.BOOL) + 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 + } } BinOp.NEQ -> { - if (a.type != SlotType.INT) return null - builder.emit(Opcode.CMP_NEQ_INT, a.slot, b.slot, out) - CompiledValue(out, SlotType.BOOL) + 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 + } } BinOp.LT -> { - if (a.type != SlotType.INT) return null - builder.emit(Opcode.CMP_LT_INT, a.slot, b.slot, out) - CompiledValue(out, SlotType.BOOL) + 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 + } } BinOp.LTE -> { - if (a.type != SlotType.INT) return null - builder.emit(Opcode.CMP_LTE_INT, a.slot, b.slot, out) - CompiledValue(out, SlotType.BOOL) + 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 + } } BinOp.GT -> { - if (a.type != SlotType.INT) return null - builder.emit(Opcode.CMP_GT_INT, a.slot, b.slot, out) - CompiledValue(out, SlotType.BOOL) + 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 + } } BinOp.GTE -> { - if (a.type != SlotType.INT) return null - builder.emit(Opcode.CMP_GTE_INT, a.slot, b.slot, out) + 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 + } + } + BinOp.AND -> { + if (a.type != SlotType.BOOL) return null + builder.emit(Opcode.AND_BOOL, a.slot, b.slot, out) + CompiledValue(out, SlotType.BOOL) + } + BinOp.OR -> { + if (a.type != SlotType.BOOL) return null + builder.emit(Opcode.OR_BOOL, a.slot, b.slot, out) CompiledValue(out, SlotType.BOOL) } BinOp.BAND -> { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt new file mode 100644 index 0000000..25f3a53 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2026 Sergey S. Chernov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sergeych.lyng.bytecode + +object BytecodeDisassembler { + fun disassemble(fn: BytecodeFunction): String { + val decoder = when (fn.slotWidth) { + 1 -> Decoder8 + 2 -> Decoder16 + 4 -> Decoder32 + else -> error("Unsupported slot width: ${fn.slotWidth}") + } + val out = StringBuilder() + val code = fn.code + var ip = 0 + while (ip < code.size) { + val op = decoder.readOpcode(code, ip) + val startIp = ip + ip += 1 + val kinds = operandKinds(op) + val operands = ArrayList(kinds.size) + for (kind in kinds) { + when (kind) { + OperandKind.SLOT -> { + val v = decoder.readSlot(code, ip) + ip += fn.slotWidth + operands += "s$v" + } + OperandKind.CONST -> { + val v = decoder.readConstId(code, ip, fn.constIdWidth) + ip += fn.constIdWidth + operands += "k$v" + } + OperandKind.IP -> { + val v = decoder.readIp(code, ip, fn.ipWidth) + ip += fn.ipWidth + operands += "ip$v" + } + OperandKind.COUNT -> { + val v = decoder.readConstId(code, ip, 2) + ip += 2 + operands += "n$v" + } + OperandKind.ID -> { + val v = decoder.readConstId(code, ip, 2) + ip += 2 + operands += "#$v" + } + } + } + out.append(startIp).append(": ").append(op.name) + if (operands.isNotEmpty()) { + out.append(' ').append(operands.joinToString(", ")) + } + out.append('\n') + } + return out.toString() + } + + private enum class OperandKind { + SLOT, + CONST, + IP, + COUNT, + ID, + } + + private fun operandKinds(op: Opcode): List { + return when (op) { + Opcode.NOP, Opcode.RET_VOID -> emptyList() + Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, + Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL, + Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT -> + listOf(OperandKind.SLOT, OperandKind.SLOT) + Opcode.CONST_NULL -> + listOf(OperandKind.SLOT) + Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL -> + listOf(OperandKind.CONST, OperandKind.SLOT) + Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, + Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL, + Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT, + Opcode.CMP_EQ_INT, Opcode.CMP_NEQ_INT, Opcode.CMP_LT_INT, Opcode.CMP_LTE_INT, + Opcode.CMP_GT_INT, Opcode.CMP_GTE_INT, + Opcode.CMP_EQ_REAL, Opcode.CMP_NEQ_REAL, Opcode.CMP_LT_REAL, Opcode.CMP_LTE_REAL, + Opcode.CMP_GT_REAL, Opcode.CMP_GTE_REAL, + 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.AND_BOOL, Opcode.OR_BOOL -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) + Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> + listOf(OperandKind.SLOT) + Opcode.JMP -> + listOf(OperandKind.IP) + Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE -> + listOf(OperandKind.SLOT, OperandKind.IP) + Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK -> + listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) + Opcode.CALL_VIRTUAL -> + listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) + Opcode.GET_FIELD -> + listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) + Opcode.SET_FIELD -> + listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) + Opcode.GET_INDEX -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) + Opcode.SET_INDEX -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) + Opcode.EVAL_FALLBACK -> + listOf(OperandKind.ID, OperandKind.SLOT) + } + } +} 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 0473729..5d71998 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt @@ -142,6 +142,51 @@ class BytecodeVm { ip += fn.slotWidth frame.setBool(dst, frame.getInt(a) == frame.getInt(b)) } + Opcode.CMP_NEQ_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_BOOL -> { + 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.getBool(a) == frame.getBool(b)) + } + Opcode.CMP_NEQ_BOOL -> { + 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.getBool(a) != frame.getBool(b)) + } + Opcode.AND_BOOL -> { + 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.getBool(a) && frame.getBool(b)) + } + Opcode.OR_BOOL -> { + 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.getBool(a) || frame.getBool(b)) + } Opcode.JMP -> { val target = decoder.readIp(code, ip, fn.ipWidth) ip = target