From f42ea0a04c784cace89fd795e48dab04694665df Mon Sep 17 00:00:00 2001 From: sergeych Date: Sun, 25 Jan 2026 17:00:08 +0300 Subject: [PATCH] Expand bytecode spec and add VM skeleton --- docs/BytecodeSpec.md | 49 +++++++- .../sergeych/lyng/bytecode/BytecodeConst.kt | 28 +++++ .../sergeych/lyng/bytecode/BytecodeDecoder.kt | 77 ++++++++++++ .../sergeych/lyng/bytecode/BytecodeFrame.kt | 71 +++++++++++ .../lyng/bytecode/BytecodeFunction.kt | 33 ++++++ .../net/sergeych/lyng/bytecode/BytecodeVm.kt | 50 ++++++++ .../net/sergeych/lyng/bytecode/Opcode.kt | 111 ++++++++++++++++++ .../net/sergeych/lyng/bytecode/SlotType.kt | 25 ++++ 8 files changed, 440 insertions(+), 4 deletions(-) create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFunction.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/SlotType.kt diff --git a/docs/BytecodeSpec.md b/docs/BytecodeSpec.md index c57bd43..2ef1e1d 100644 --- a/docs/BytecodeSpec.md +++ b/docs/BytecodeSpec.md @@ -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. diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt new file mode 100644 index 0000000..c227839 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt @@ -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() +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt new file mode 100644 index 0000000..2dd25e3 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt @@ -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 +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt new file mode 100644 index 0000000..102f392 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt @@ -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 = 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 + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFunction.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFunction.kt new file mode 100644 index 0000000..b1e2602 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFunction.kt @@ -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, + 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" } + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt new file mode 100644 index 0000000..22d5d91 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt @@ -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 { + 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 + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt new file mode 100644 index 0000000..76ad779 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -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 = values().associateBy { it.code } + fun fromCode(code: Byte): Opcode? = byCode[code] + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/SlotType.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/SlotType.kt new file mode 100644 index 0000000..86b2812 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/SlotType.kt @@ -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), +}