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
|
### Fallback
|
||||||
- EVAL_FALLBACK T -> S
|
- 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:
|
Suggested layout for a bytecode function blob:
|
||||||
- magic: U32 ("LYBC")
|
- magic: U32 ("LYBC")
|
||||||
@ -190,10 +207,34 @@ Suggested layout for a bytecode function blob:
|
|||||||
- constPool: [const entries...]
|
- constPool: [const entries...]
|
||||||
- code: [bytecode...]
|
- code: [bytecode...]
|
||||||
|
|
||||||
Const pool entries are encoded as type-tagged values (Obj/Int/Real/Bool/String)
|
Const pool entries use the encoding described in section 6.
|
||||||
in a simple tagged format. This is intentionally unspecified in v0.
|
|
||||||
|
|
||||||
## 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.
|
- Mixed-mode is allowed: compiler can emit FALLBACK ops for unsupported nodes.
|
||||||
- The VM must be suspendable; on suspension, store ip + minimal operand state.
|
- 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