diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt index 2dd25e3..a5d87af 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt @@ -25,7 +25,7 @@ interface BytecodeDecoder { object Decoder8 : BytecodeDecoder { override fun readOpcode(code: ByteArray, ip: Int): Opcode = - Opcode.fromCode(code[ip]) ?: error("Unknown opcode: ${code[ip]}") + Opcode.fromCode(code[ip].toInt() and 0xFF) ?: error("Unknown opcode: ${code[ip]}") override fun readSlot(code: ByteArray, ip: Int): Int = code[ip].toInt() and 0xFF @@ -38,7 +38,7 @@ object Decoder8 : BytecodeDecoder { object Decoder16 : BytecodeDecoder { override fun readOpcode(code: ByteArray, ip: Int): Opcode = - Opcode.fromCode(code[ip]) ?: error("Unknown opcode: ${code[ip]}") + Opcode.fromCode(code[ip].toInt() and 0xFF) ?: error("Unknown opcode: ${code[ip]}") override fun readSlot(code: ByteArray, ip: Int): Int = (code[ip].toInt() and 0xFF) or ((code[ip + 1].toInt() and 0xFF) shl 8) @@ -52,7 +52,7 @@ object Decoder16 : BytecodeDecoder { object Decoder32 : BytecodeDecoder { override fun readOpcode(code: ByteArray, ip: Int): Opcode = - Opcode.fromCode(code[ip]) ?: error("Unknown opcode: ${code[ip]}") + Opcode.fromCode(code[ip].toInt() and 0xFF) ?: error("Unknown opcode: ${code[ip]}") override fun readSlot(code: ByteArray, ip: Int): Int = readUInt(code, ip, 4) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt index 102f392..dd6d674 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt @@ -33,6 +33,7 @@ class BytecodeFrame( private val boolSlots: BooleanArray = BooleanArray(slotCount) fun getSlotType(slot: Int): SlotType = SlotType.values().first { it.code == slotTypes[slot] } + fun getSlotTypeCode(slot: Int): Byte = slotTypes[slot] fun setSlotType(slot: Int, type: SlotType) { slotTypes[slot] = type.code } 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 22d5d91..4478b74 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt @@ -17,8 +17,7 @@ package net.sergeych.lyng.bytecode import net.sergeych.lyng.Scope -import net.sergeych.lyng.obj.Obj -import net.sergeych.lyng.obj.ObjVoid +import net.sergeych.lyng.obj.* class BytecodeVm { suspend fun execute(fn: BytecodeFunction, scope: Scope, args: List): Obj { @@ -41,10 +40,93 @@ class BytecodeVm { Opcode.NOP -> { // no-op } + Opcode.CONST_INT -> { + val constId = decoder.readConstId(code, ip, fn.constIdWidth) + ip += fn.constIdWidth + val dst = decoder.readSlot(code, ip) + ip += fn.slotWidth + val c = fn.constants[constId] as? BytecodeConst.IntVal + ?: error("CONST_INT expects IntVal at $constId") + frame.setInt(dst, c.value) + } + Opcode.CONST_REAL -> { + val constId = decoder.readConstId(code, ip, fn.constIdWidth) + ip += fn.constIdWidth + val dst = decoder.readSlot(code, ip) + ip += fn.slotWidth + val c = fn.constants[constId] as? BytecodeConst.RealVal + ?: error("CONST_REAL expects RealVal at $constId") + frame.setReal(dst, c.value) + } + Opcode.CONST_BOOL -> { + val constId = decoder.readConstId(code, ip, fn.constIdWidth) + ip += fn.constIdWidth + val dst = decoder.readSlot(code, ip) + ip += fn.slotWidth + val c = fn.constants[constId] as? BytecodeConst.Bool + ?: error("CONST_BOOL expects Bool at $constId") + frame.setBool(dst, c.value) + } + Opcode.CONST_NULL -> { + val dst = decoder.readSlot(code, ip) + ip += fn.slotWidth + frame.setObj(dst, ObjNull) + } + Opcode.MOVE_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.MOVE_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.MOVE_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.MOVE_OBJ -> { + val src = decoder.readSlot(code, ip) + ip += fn.slotWidth + val dst = decoder.readSlot(code, ip) + ip += fn.slotWidth + frame.setObj(dst, frame.getObj(src)) + } + Opcode.ADD_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.RET -> { + val slot = decoder.readSlot(code, ip) + return slotToObj(frame, slot) + } Opcode.RET_VOID -> return ObjVoid else -> error("Opcode not implemented: $op") } } return ObjVoid } + + private fun slotToObj(frame: BytecodeFrame, slot: Int): Obj { + return when (frame.getSlotTypeCode(slot)) { + SlotType.INT.code -> ObjInt.of(frame.getInt(slot)) + SlotType.REAL.code -> ObjReal.of(frame.getReal(slot)) + SlotType.BOOL.code -> if (frame.getBool(slot)) ObjTrue else ObjFalse + SlotType.OBJ.code -> frame.getObj(slot) + else -> ObjVoid + } + } } 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 76ad779..daceabf 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -16,7 +16,7 @@ package net.sergeych.lyng.bytecode -enum class Opcode(val code: Byte) { +enum class Opcode(val code: Int) { NOP(0x00), MOVE_OBJ(0x01), MOVE_INT(0x02), @@ -105,7 +105,7 @@ enum class Opcode(val code: Byte) { ; companion object { - private val byCode: Map = values().associateBy { it.code } - fun fromCode(code: Byte): Opcode? = byCode[code] + private val byCode: Map = values().associateBy { it.code } + fun fromCode(code: Int): Opcode? = byCode[code] } } diff --git a/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt b/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt new file mode 100644 index 0000000..511ee5a --- /dev/null +++ b/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt @@ -0,0 +1,40 @@ +/* + * 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. + */ + +import net.sergeych.lyng.Scope +import net.sergeych.lyng.bytecode.BytecodeBuilder +import net.sergeych.lyng.bytecode.BytecodeConst +import net.sergeych.lyng.bytecode.BytecodeVm +import net.sergeych.lyng.bytecode.Opcode +import net.sergeych.lyng.obj.toInt +import kotlin.test.Test +import kotlin.test.assertEquals + +class BytecodeVmTest { + @Test + fun addsIntConstants() = kotlinx.coroutines.test.runTest { + val builder = BytecodeBuilder() + val k0 = builder.addConst(BytecodeConst.IntVal(2)) + val k1 = builder.addConst(BytecodeConst.IntVal(3)) + builder.emit(Opcode.CONST_INT, k0, 0) + builder.emit(Opcode.CONST_INT, k1, 1) + builder.emit(Opcode.ADD_INT, 0, 1, 2) + builder.emit(Opcode.RET, 2) + val fn = builder.build("addInts", localCount = 3) + val result = BytecodeVm().execute(fn, Scope(), emptyList()) + assertEquals(5, result.toInt()) + } +}