Add minimal bytecode VM execution test

This commit is contained in:
sergeych committed 2026-01-25 17:17:14 +03:00
1 parent d8b00a805c
commit ea877748e5
5 files changed
+131 -8

No files matched your search

@@ -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)
@@ -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
}
@@ -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>): 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
}
}
}
@@ -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<Byte, Opcode> = values().associateBy { it.code }
fun fromCode(code: Byte): Opcode? = byCode[code]
private val byCode: Map<Int, Opcode> = values().associateBy { it.code }
fun fromCode(code: Int): Opcode? = byCode[code]
}
}