Add minimal bytecode VM execution test
This commit is contained in:
parent
d8b00a805c
commit
ea877748e5
@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
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