Add minimal bytecode VM execution test
This commit is contained in:
parent
d8b00a805c
commit
ea877748e5
@ -25,7 +25,7 @@ interface BytecodeDecoder {
|
|||||||
|
|
||||||
object Decoder8 : BytecodeDecoder {
|
object Decoder8 : BytecodeDecoder {
|
||||||
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
|
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
|
override fun readSlot(code: ByteArray, ip: Int): Int = code[ip].toInt() and 0xFF
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ object Decoder8 : BytecodeDecoder {
|
|||||||
|
|
||||||
object Decoder16 : BytecodeDecoder {
|
object Decoder16 : BytecodeDecoder {
|
||||||
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
|
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 =
|
override fun readSlot(code: ByteArray, ip: Int): Int =
|
||||||
(code[ip].toInt() and 0xFF) or ((code[ip + 1].toInt() and 0xFF) shl 8)
|
(code[ip].toInt() and 0xFF) or ((code[ip + 1].toInt() and 0xFF) shl 8)
|
||||||
@ -52,7 +52,7 @@ object Decoder16 : BytecodeDecoder {
|
|||||||
|
|
||||||
object Decoder32 : BytecodeDecoder {
|
object Decoder32 : BytecodeDecoder {
|
||||||
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
|
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)
|
override fun readSlot(code: ByteArray, ip: Int): Int = readUInt(code, ip, 4)
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ class BytecodeFrame(
|
|||||||
private val boolSlots: BooleanArray = BooleanArray(slotCount)
|
private val boolSlots: BooleanArray = BooleanArray(slotCount)
|
||||||
|
|
||||||
fun getSlotType(slot: Int): SlotType = SlotType.values().first { it.code == slotTypes[slot] }
|
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) {
|
fun setSlotType(slot: Int, type: SlotType) {
|
||||||
slotTypes[slot] = type.code
|
slotTypes[slot] = type.code
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,7 @@
|
|||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
|
||||||
|
|
||||||
class BytecodeVm {
|
class BytecodeVm {
|
||||||
suspend fun execute(fn: BytecodeFunction, scope: Scope, args: List<Obj>): Obj {
|
suspend fun execute(fn: BytecodeFunction, scope: Scope, args: List<Obj>): Obj {
|
||||||
@ -41,10 +40,93 @@ class BytecodeVm {
|
|||||||
Opcode.NOP -> {
|
Opcode.NOP -> {
|
||||||
// no-op
|
// 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
|
Opcode.RET_VOID -> return ObjVoid
|
||||||
else -> error("Opcode not implemented: $op")
|
else -> error("Opcode not implemented: $op")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ObjVoid
|
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
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
enum class Opcode(val code: Byte) {
|
enum class Opcode(val code: Int) {
|
||||||
NOP(0x00),
|
NOP(0x00),
|
||||||
MOVE_OBJ(0x01),
|
MOVE_OBJ(0x01),
|
||||||
MOVE_INT(0x02),
|
MOVE_INT(0x02),
|
||||||
@ -105,7 +105,7 @@ enum class Opcode(val code: Byte) {
|
|||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val byCode: Map<Byte, Opcode> = values().associateBy { it.code }
|
private val byCode: Map<Int, Opcode> = values().associateBy { it.code }
|
||||||
fun fromCode(code: Byte): Opcode? = byCode[code]
|
fun fromCode(code: Int): Opcode? = byCode[code]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
lynglib/src/commonTest/kotlin/BytecodeVmTest.kt
Normal file
40
lynglib/src/commonTest/kotlin/BytecodeVmTest.kt
Normal file
@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user