Expand bytecode spec and add VM skeleton
This commit is contained in:
parent
bc9e557814
commit
f42ea0a04c
@ -176,7 +176,24 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
|
||||
### Fallback
|
||||
- EVAL_FALLBACK T -> S
|
||||
|
||||
## 6) Function Header (binary container)
|
||||
## 6) Const Pool Encoding (v0)
|
||||
|
||||
Each const entry is encoded as:
|
||||
[tag:U8] [payload...]
|
||||
|
||||
Tags:
|
||||
- 0x00: NULL
|
||||
- 0x01: BOOL (payload: U8 0/1)
|
||||
- 0x02: INT (payload: S64, little-endian)
|
||||
- 0x03: REAL (payload: F64, IEEE-754, little-endian)
|
||||
- 0x04: STRING (payload: U32 length + UTF-8 bytes)
|
||||
- 0x05: OBJ_REF (payload: U32 index into external Obj table)
|
||||
|
||||
Notes:
|
||||
- OBJ_REF is reserved for embedding prebuilt Obj handles if needed.
|
||||
- Strings use UTF-8; length is bytes, not chars.
|
||||
|
||||
## 7) Function Header (binary container)
|
||||
|
||||
Suggested layout for a bytecode function blob:
|
||||
- magic: U32 ("LYBC")
|
||||
@ -190,10 +207,34 @@ Suggested layout for a bytecode function blob:
|
||||
- constPool: [const entries...]
|
||||
- code: [bytecode...]
|
||||
|
||||
Const pool entries are encoded as type-tagged values (Obj/Int/Real/Bool/String)
|
||||
in a simple tagged format. This is intentionally unspecified in v0.
|
||||
Const pool entries use the encoding described in section 6.
|
||||
|
||||
## 7) Notes
|
||||
## 8) Sample Bytecode (illustrative)
|
||||
|
||||
Example Lyng:
|
||||
val x = 2
|
||||
val y = 3
|
||||
val z = x + y
|
||||
|
||||
Assume:
|
||||
- localCount = 3 (x,y,z)
|
||||
- argCount = 0
|
||||
- slot width = 1 byte
|
||||
- const pool: [INT 2, INT 3]
|
||||
|
||||
Bytecode:
|
||||
CONST_INT k0 -> s0
|
||||
CONST_INT k1 -> s1
|
||||
ADD_INT s0, s1 -> s2
|
||||
RET_VOID
|
||||
|
||||
Encoded (opcode values symbolic):
|
||||
[OP_CONST_INT][k0][s0]
|
||||
[OP_CONST_INT][k1][s1]
|
||||
[OP_ADD_INT][s0][s1][s2]
|
||||
[OP_RET_VOID]
|
||||
|
||||
## 9) Notes
|
||||
|
||||
- Mixed-mode is allowed: compiler can emit FALLBACK ops for unsupported nodes.
|
||||
- The VM must be suspendable; on suspension, store ip + minimal operand state.
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
|
||||
sealed class BytecodeConst {
|
||||
object Null : BytecodeConst()
|
||||
data class Bool(val value: Boolean) : BytecodeConst()
|
||||
data class IntVal(val value: Long) : BytecodeConst()
|
||||
data class RealVal(val value: Double) : BytecodeConst()
|
||||
data class StringVal(val value: String) : BytecodeConst()
|
||||
data class ObjRef(val value: Obj) : BytecodeConst()
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
interface BytecodeDecoder {
|
||||
fun readOpcode(code: ByteArray, ip: Int): Opcode
|
||||
fun readSlot(code: ByteArray, ip: Int): Int
|
||||
fun readConstId(code: ByteArray, ip: Int, width: Int): Int
|
||||
fun readIp(code: ByteArray, ip: Int, width: Int): Int
|
||||
}
|
||||
|
||||
object Decoder8 : BytecodeDecoder {
|
||||
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
|
||||
Opcode.fromCode(code[ip]) ?: error("Unknown opcode: ${code[ip]}")
|
||||
|
||||
override fun readSlot(code: ByteArray, ip: Int): Int = code[ip].toInt() and 0xFF
|
||||
|
||||
override fun readConstId(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
|
||||
override fun readIp(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
}
|
||||
|
||||
object Decoder16 : BytecodeDecoder {
|
||||
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
|
||||
Opcode.fromCode(code[ip]) ?: 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)
|
||||
|
||||
override fun readConstId(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
|
||||
override fun readIp(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
}
|
||||
|
||||
object Decoder32 : BytecodeDecoder {
|
||||
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
|
||||
Opcode.fromCode(code[ip]) ?: error("Unknown opcode: ${code[ip]}")
|
||||
|
||||
override fun readSlot(code: ByteArray, ip: Int): Int = readUInt(code, ip, 4)
|
||||
|
||||
override fun readConstId(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
|
||||
override fun readIp(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
}
|
||||
|
||||
private fun readUInt(code: ByteArray, ip: Int, width: Int): Int {
|
||||
var result = 0
|
||||
var shift = 0
|
||||
var idx = ip
|
||||
var remaining = width
|
||||
while (remaining-- > 0) {
|
||||
result = result or ((code[idx].toInt() and 0xFF) shl shift)
|
||||
shift += 8
|
||||
idx++
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjNull
|
||||
|
||||
class BytecodeFrame(
|
||||
val localCount: Int,
|
||||
val argCount: Int,
|
||||
) {
|
||||
val slotCount: Int = localCount + argCount
|
||||
val argBase: Int = localCount
|
||||
|
||||
private val slotTypes: ByteArray = ByteArray(slotCount) { SlotType.UNKNOWN.code }
|
||||
private val objSlots: Array<Obj?> = arrayOfNulls(slotCount)
|
||||
private val intSlots: LongArray = LongArray(slotCount)
|
||||
private val realSlots: DoubleArray = DoubleArray(slotCount)
|
||||
private val boolSlots: BooleanArray = BooleanArray(slotCount)
|
||||
|
||||
fun getSlotType(slot: Int): SlotType = SlotType.values().first { it.code == slotTypes[slot] }
|
||||
fun setSlotType(slot: Int, type: SlotType) {
|
||||
slotTypes[slot] = type.code
|
||||
}
|
||||
|
||||
fun getObj(slot: Int): Obj = objSlots[slot] ?: ObjNull
|
||||
fun setObj(slot: Int, value: Obj) {
|
||||
objSlots[slot] = value
|
||||
slotTypes[slot] = SlotType.OBJ.code
|
||||
}
|
||||
|
||||
fun getInt(slot: Int): Long = intSlots[slot]
|
||||
fun setInt(slot: Int, value: Long) {
|
||||
intSlots[slot] = value
|
||||
slotTypes[slot] = SlotType.INT.code
|
||||
}
|
||||
|
||||
fun getReal(slot: Int): Double = realSlots[slot]
|
||||
fun setReal(slot: Int, value: Double) {
|
||||
realSlots[slot] = value
|
||||
slotTypes[slot] = SlotType.REAL.code
|
||||
}
|
||||
|
||||
fun getBool(slot: Int): Boolean = boolSlots[slot]
|
||||
fun setBool(slot: Int, value: Boolean) {
|
||||
boolSlots[slot] = value
|
||||
slotTypes[slot] = SlotType.BOOL.code
|
||||
}
|
||||
|
||||
fun clearSlot(slot: Int) {
|
||||
slotTypes[slot] = SlotType.UNKNOWN.code
|
||||
objSlots[slot] = null
|
||||
intSlots[slot] = 0L
|
||||
realSlots[slot] = 0.0
|
||||
boolSlots[slot] = false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
data class BytecodeFunction(
|
||||
val name: String,
|
||||
val localCount: Int,
|
||||
val slotWidth: Int,
|
||||
val ipWidth: Int,
|
||||
val constIdWidth: Int,
|
||||
val constants: List<BytecodeConst>,
|
||||
val code: ByteArray,
|
||||
) {
|
||||
init {
|
||||
require(slotWidth == 1 || slotWidth == 2 || slotWidth == 4) { "slotWidth must be 1,2,4" }
|
||||
require(ipWidth == 2 || ipWidth == 4) { "ipWidth must be 2 or 4" }
|
||||
require(constIdWidth == 2 || constIdWidth == 4) { "constIdWidth must be 2 or 4" }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjVoid
|
||||
|
||||
class BytecodeVm {
|
||||
suspend fun execute(fn: BytecodeFunction, scope: Scope, args: List<Obj>): Obj {
|
||||
val frame = BytecodeFrame(fn.localCount, args.size)
|
||||
for (i in args.indices) {
|
||||
frame.setObj(frame.argBase + i, args[i])
|
||||
}
|
||||
val decoder = when (fn.slotWidth) {
|
||||
1 -> Decoder8
|
||||
2 -> Decoder16
|
||||
4 -> Decoder32
|
||||
else -> error("Unsupported slot width: ${fn.slotWidth}")
|
||||
}
|
||||
var ip = 0
|
||||
val code = fn.code
|
||||
while (ip < code.size) {
|
||||
val op = decoder.readOpcode(code, ip)
|
||||
ip += 1
|
||||
when (op) {
|
||||
Opcode.NOP -> {
|
||||
// no-op
|
||||
}
|
||||
Opcode.RET_VOID -> return ObjVoid
|
||||
else -> error("Opcode not implemented: $op")
|
||||
}
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
enum class Opcode(val code: Byte) {
|
||||
NOP(0x00),
|
||||
MOVE_OBJ(0x01),
|
||||
MOVE_INT(0x02),
|
||||
MOVE_REAL(0x03),
|
||||
MOVE_BOOL(0x04),
|
||||
CONST_OBJ(0x05),
|
||||
CONST_INT(0x06),
|
||||
CONST_REAL(0x07),
|
||||
CONST_BOOL(0x08),
|
||||
CONST_NULL(0x09),
|
||||
|
||||
INT_TO_REAL(0x10),
|
||||
REAL_TO_INT(0x11),
|
||||
BOOL_TO_INT(0x12),
|
||||
INT_TO_BOOL(0x13),
|
||||
|
||||
ADD_INT(0x20),
|
||||
SUB_INT(0x21),
|
||||
MUL_INT(0x22),
|
||||
DIV_INT(0x23),
|
||||
MOD_INT(0x24),
|
||||
NEG_INT(0x25),
|
||||
INC_INT(0x26),
|
||||
DEC_INT(0x27),
|
||||
|
||||
ADD_REAL(0x30),
|
||||
SUB_REAL(0x31),
|
||||
MUL_REAL(0x32),
|
||||
DIV_REAL(0x33),
|
||||
NEG_REAL(0x34),
|
||||
|
||||
AND_INT(0x40),
|
||||
OR_INT(0x41),
|
||||
XOR_INT(0x42),
|
||||
SHL_INT(0x43),
|
||||
SHR_INT(0x44),
|
||||
USHR_INT(0x45),
|
||||
INV_INT(0x46),
|
||||
|
||||
CMP_EQ_INT(0x50),
|
||||
CMP_NEQ_INT(0x51),
|
||||
CMP_LT_INT(0x52),
|
||||
CMP_LTE_INT(0x53),
|
||||
CMP_GT_INT(0x54),
|
||||
CMP_GTE_INT(0x55),
|
||||
CMP_EQ_REAL(0x56),
|
||||
CMP_NEQ_REAL(0x57),
|
||||
CMP_LT_REAL(0x58),
|
||||
CMP_LTE_REAL(0x59),
|
||||
CMP_GT_REAL(0x5A),
|
||||
CMP_GTE_REAL(0x5B),
|
||||
CMP_EQ_BOOL(0x5C),
|
||||
CMP_NEQ_BOOL(0x5D),
|
||||
|
||||
CMP_EQ_INT_REAL(0x60),
|
||||
CMP_EQ_REAL_INT(0x61),
|
||||
CMP_LT_INT_REAL(0x62),
|
||||
CMP_LT_REAL_INT(0x63),
|
||||
CMP_LTE_INT_REAL(0x64),
|
||||
CMP_LTE_REAL_INT(0x65),
|
||||
CMP_GT_INT_REAL(0x66),
|
||||
CMP_GT_REAL_INT(0x67),
|
||||
CMP_GTE_INT_REAL(0x68),
|
||||
CMP_GTE_REAL_INT(0x69),
|
||||
|
||||
NOT_BOOL(0x70),
|
||||
AND_BOOL(0x71),
|
||||
OR_BOOL(0x72),
|
||||
|
||||
JMP(0x80),
|
||||
JMP_IF_TRUE(0x81),
|
||||
JMP_IF_FALSE(0x82),
|
||||
RET(0x83),
|
||||
RET_VOID(0x84),
|
||||
|
||||
CALL_DIRECT(0x90),
|
||||
CALL_VIRTUAL(0x91),
|
||||
CALL_FALLBACK(0x92),
|
||||
|
||||
GET_FIELD(0xA0),
|
||||
SET_FIELD(0xA1),
|
||||
GET_INDEX(0xA2),
|
||||
SET_INDEX(0xA3),
|
||||
|
||||
EVAL_FALLBACK(0xB0),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private val byCode: Map<Byte, Opcode> = values().associateBy { it.code }
|
||||
fun fromCode(code: Byte): Opcode? = byCode[code]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
enum class SlotType(val code: Byte) {
|
||||
UNKNOWN(0),
|
||||
OBJ(1),
|
||||
INT(2),
|
||||
REAL(3),
|
||||
BOOL(4),
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user