From bef94d3bc55f2a0e79160bf931816bfed21e3843 Mon Sep 17 00:00:00 2001 From: sergeych Date: Tue, 27 Jan 2026 14:15:35 +0300 Subject: [PATCH] Optimize cmd VM with scoped slot addressing --- .../net/sergeych/lyng/PerfDefaults.android.kt | 2 +- ...eAndroid.kt => CmdCallSiteCacheAndroid.kt} | 6 +- .../kotlin/net/sergeych/lyng/Compiler.kt | 86 +- .../kotlin/net/sergeych/lyng/PerfFlags.kt | 1 + .../kotlin/net/sergeych/lyng/Scope.kt | 22 +- .../net/sergeych/lyng/VarDeclStatement.kt | 2 + .../sergeych/lyng/bytecode/BytecodeBuilder.kt | 222 --- .../lyng/bytecode/BytecodeCompiler.kt | 646 ++++++-- .../sergeych/lyng/bytecode/BytecodeDecoder.kt | 77 - .../lyng/bytecode/BytecodeDisassembler.kt | 138 -- .../lyng/bytecode/BytecodeStatement.kt | 93 +- .../net/sergeych/lyng/bytecode/BytecodeVm.kt | 1049 ------------ .../net/sergeych/lyng/bytecode/CmdBuilder.kt | 356 ++++ ...deCallSiteCache.kt => CmdCallSiteCache.kt} | 4 +- .../sergeych/lyng/bytecode/CmdDisassembler.kt | 251 +++ .../{BytecodeFunction.kt => CmdFunction.kt} | 18 +- .../net/sergeych/lyng/bytecode/CmdRuntime.kt | 1452 +++++++++++++++++ .../net/sergeych/lyng/bytecode/Opcode.kt | 9 + .../kotlin/net/sergeych/lyng/obj/ObjRef.kt | 9 + .../{BytecodeVmTest.kt => CmdVmTest.kt} | 58 +- .../kotlin/NestedRangeBenchmarkTest.kt | 13 +- lynglib/src/commonTest/kotlin/ScriptTest.kt | 4 +- .../net/sergeych/lyng/PerfDefaults.js.kt | 2 +- .../lyng/bytecode/CmdCallSiteCacheJs.kt} | 6 +- .../net/sergeych/lyng/PerfDefaults.jvm.kt | 2 +- ...SiteCacheJvm.kt => CmdCallSiteCacheJvm.kt} | 6 +- .../net/sergeych/lyng/PerfDefaults.native.kt | 2 +- ...cheNative.kt => CmdCallSiteCacheNative.kt} | 6 +- .../net/sergeych/lyng/PerfDefaults.wasmJs.kt | 2 +- .../lyng/bytecode/CmdCallSiteCacheWasm.kt} | 6 +- 30 files changed, 2829 insertions(+), 1721 deletions(-) rename lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/{BytecodeCallSiteCacheAndroid.kt => CmdCallSiteCacheAndroid.kt} (79%) delete mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt delete mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt delete mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt delete mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt rename lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/{BytecodeCallSiteCache.kt => CmdCallSiteCache.kt} (83%) create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt rename lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/{BytecodeFunction.kt => CmdFunction.kt} (68%) create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt rename lynglib/src/commonTest/kotlin/{BytecodeVmTest.kt => CmdVmTest.kt} (89%) rename lynglib/src/{wasmJsMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheWasm.kt => jsMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheJs.kt} (75%) rename lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/{BytecodeCallSiteCacheJvm.kt => CmdCallSiteCacheJvm.kt} (79%) rename lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/{BytecodeCallSiteCacheNative.kt => CmdCallSiteCacheNative.kt} (76%) rename lynglib/src/{jsMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheJs.kt => wasmJsMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheWasm.kt} (75%) diff --git a/lynglib/src/androidMain/kotlin/net/sergeych/lyng/PerfDefaults.android.kt b/lynglib/src/androidMain/kotlin/net/sergeych/lyng/PerfDefaults.android.kt index c5fb7c2..e15992f 100644 --- a/lynglib/src/androidMain/kotlin/net/sergeych/lyng/PerfDefaults.android.kt +++ b/lynglib/src/androidMain/kotlin/net/sergeych/lyng/PerfDefaults.android.kt @@ -42,4 +42,4 @@ actual object PerfDefaults { actual val ARG_SMALL_ARITY_12: Boolean = false actual val INDEX_PIC_SIZE_4: Boolean = false actual val RANGE_FAST_ITER: Boolean = false -} \ No newline at end of file +} diff --git a/lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheAndroid.kt b/lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheAndroid.kt similarity index 79% rename from lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheAndroid.kt rename to lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheAndroid.kt index d12ef19..897335d 100644 --- a/lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheAndroid.kt +++ b/lynglib/src/androidMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheAndroid.kt @@ -18,12 +18,12 @@ package net.sergeych.lyng.bytecode import java.util.IdentityHashMap -internal actual object BytecodeCallSiteCache { +internal actual object CmdCallSiteCache { private val cache = ThreadLocal.withInitial { - IdentityHashMap>() + IdentityHashMap>() } - actual fun methodCallSites(fn: BytecodeFunction): MutableMap { + actual fun methodCallSites(fn: CmdFunction): MutableMap { val map = cache.get() return map.getOrPut(fn) { mutableMapOf() } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 82088b5..bd5bec5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -373,7 +373,8 @@ class Compiler( private fun wrapBytecode(stmt: Statement): Statement { if (!useBytecodeStatements) return stmt - return BytecodeStatement.wrap(stmt, "stmt@${stmt.pos}", allowLocalSlots = true) + val allowLocals = codeContexts.lastOrNull() is CodeContext.Function + return BytecodeStatement.wrap(stmt, "stmt@${stmt.pos}", allowLocalSlots = allowLocals) } private fun wrapFunctionBytecode(stmt: Statement, name: String): Statement { @@ -381,6 +382,27 @@ class Compiler( return BytecodeStatement.wrap(stmt, "fn@$name", allowLocalSlots = true) } + private fun containsUnsupportedForBytecode(stmt: Statement): Boolean { + val target = if (stmt is BytecodeStatement) stmt.original else stmt + return when (target) { + is ExpressionStatement -> false + is IfStatement -> { + containsUnsupportedForBytecode(target.condition) || + containsUnsupportedForBytecode(target.ifBody) || + (target.elseBody?.let { containsUnsupportedForBytecode(it) } ?: false) + } + is ForInStatement -> { + target.constRange == null || target.canBreak || + containsUnsupportedForBytecode(target.source) || + containsUnsupportedForBytecode(target.body) || + (target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false) + } + is BlockStatement -> target.statements().any { containsUnsupportedForBytecode(it) } + is VarDeclStatement -> target.initializer?.let { containsUnsupportedForBytecode(it) } ?: false + else -> true + } + } + private fun unwrapBytecodeDeep(stmt: Statement): Statement { return when (stmt) { is BytecodeStatement -> unwrapBytecodeDeep(stmt.original) @@ -397,6 +419,8 @@ class Compiler( stmt.visibility, init, stmt.isTransient, + stmt.slotIndex, + stmt.slotDepth, stmt.pos ) } @@ -859,7 +883,7 @@ class Compiler( label?.let { cc.labels.add(it) } slotPlanStack.add(paramSlotPlan) - val body = try { + val parsedBody = try { inCodeContext(CodeContext.Function("")) { withLocalNames(slotParamNames.toSet()) { parseBlock(skipLeadingBrace = true) @@ -868,6 +892,7 @@ class Compiler( } finally { slotPlanStack.removeLast() } + val body = unwrapBytecodeDeep(parsedBody) label?.let { cc.labels.remove(it) } val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan) @@ -1494,15 +1519,20 @@ class Compiler( val slotLoc = lookupSlotLocation(t.value) val inClassCtx = codeContexts.any { it is CodeContext.ClassBody } when { - slotLoc != null -> LocalSlotRef( - t.value, - slotLoc.slot, - slotLoc.depth, - slotLoc.isMutable, - slotLoc.isDelegated, - t.pos - ) - PerfFlags.EMIT_FAST_LOCAL_REFS && (currentLocalNames?.contains(t.value) == true) -> + slotLoc != null -> { + val scopeDepth = slotPlanStack.size - 1 - slotLoc.depth + LocalSlotRef( + t.value, + slotLoc.slot, + slotLoc.depth, + scopeDepth, + slotLoc.isMutable, + slotLoc.isDelegated, + t.pos + ) + } + PerfFlags.EMIT_FAST_LOCAL_REFS && !useBytecodeStatements && + (currentLocalNames?.contains(t.value) == true) -> FastLocalVarRef(t.value, t.pos) inClassCtx -> ImplicitThisMemberRef(t.value, t.pos) else -> LocalVarRef(t.value, t.pos) @@ -2041,7 +2071,19 @@ class Compiler( ) private suspend fun parseTryStatement(): Statement { - val body = parseBlock() + fun withCatchSlot(block: Statement, catchName: String): Statement { + val stmt = block as? BlockStatement ?: return block + if (stmt.slotPlan.containsKey(catchName)) return stmt + val basePlan = stmt.slotPlan + val newPlan = LinkedHashMap(basePlan.size + 1) + newPlan[catchName] = 0 + for ((name, idx) in basePlan) { + newPlan[name] = idx + 1 + } + return BlockStatement(stmt.block, newPlan, stmt.pos) + } + + val body = unwrapBytecodeDeep(parseBlock()) val catches = mutableListOf() cc.skipTokens(Token.Type.NEWLINE) var t = cc.next() @@ -2078,7 +2120,7 @@ class Compiler( exClassNames += "Exception" cc.skipTokenOfType(Token.Type.RPAREN) } - val block = parseBlock() + val block = withCatchSlot(unwrapBytecodeDeep(parseBlock()), catchVar.value) catches += CatchBlockData(catchVar, exClassNames, block) cc.skipTokens(Token.Type.NEWLINE) t = cc.next() @@ -2087,13 +2129,13 @@ class Compiler( cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here") catches += CatchBlockData( Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"), - parseBlock(true) + withCatchSlot(unwrapBytecodeDeep(parseBlock(true)), "it") ) t = cc.next() } } val finallyClause = if (t.value == "finally") { - parseBlock() + unwrapBytecodeDeep(parseBlock()) } else { cc.previous() null @@ -2133,7 +2175,9 @@ class Compiler( } } if (match != null) { - val catchContext = scope.createChildScope(pos = cdata.catchVar.pos) + val catchContext = scope.createChildScope(pos = cdata.catchVar.pos).apply { + skipScopeCreation = true + } catchContext.addItem(cdata.catchVar.value, false, caughtObj) result = cdata.block.execute(catchContext) isCaught = true @@ -3020,7 +3064,7 @@ class Compiler( currentLocalDeclCount localDeclCountStack.add(0) slotPlanStack.add(paramSlotPlan) - val fnStatements = try { + val parsedFnStatements = try { if (actualExtern) object : Statement() { override val pos: Pos = start @@ -3050,6 +3094,9 @@ class Compiler( } finally { slotPlanStack.removeLast() } + val fnStatements = parsedFnStatements?.let { + if (containsUnsupportedForBytecode(it)) unwrapBytecodeDeep(it) else it + } // Capture and pop the local declarations count for this function val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0 @@ -3528,7 +3575,10 @@ class Compiler( !actualExtern && !isAbstract ) { - return VarDeclStatement(name, isMutable, visibility, initialExpression, isTransient, start) + val slotPlan = slotPlanStack.lastOrNull() + val slotIndex = slotPlan?.slots?.get(name)?.index + val slotDepth = slotPlan?.let { slotPlanStack.size - 1 } + return VarDeclStatement(name, isMutable, visibility, initialExpression, isTransient, slotIndex, slotDepth, start) } if (isStatic) { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PerfFlags.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PerfFlags.kt index a176d41..2814d19 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PerfFlags.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/PerfFlags.kt @@ -72,4 +72,5 @@ object PerfFlags { // Specialized non-allocating integer range iteration in hot loops var RANGE_FAST_ITER: Boolean = PerfDefaults.RANGE_FAST_ITER + } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index ffab50d..c269a6e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -18,7 +18,7 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.* -import net.sergeych.lyng.bytecode.BytecodeDisassembler +import net.sergeych.lyng.bytecode.CmdDisassembler import net.sergeych.lyng.bytecode.BytecodeStatement import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportProvider @@ -339,6 +339,8 @@ open class Scope( internal val objects = mutableMapOf() + internal fun getLocalRecordDirect(name: String): ObjRecord? = objects[name] + open operator fun get(name: String): ObjRecord? { if (name == "this") return thisObj.asReadonly @@ -381,6 +383,8 @@ open class Scope( fun setSlotValue(index: Int, newValue: Obj) { slots[index].value = newValue } + val slotCount: Int + get() = slots.size fun getSlotIndexOf(name: String): Int? = nameToSlot[name] fun allocateSlotFor(name: String, record: ObjRecord): Int { @@ -440,6 +444,20 @@ open class Scope( } } + fun hasSlotPlanConflict(plan: Map): Boolean { + if (plan.isEmpty() || nameToSlot.isEmpty()) return false + val planIndexToNames = HashMap>(plan.size) + for ((name, idx) in plan) { + val names = planIndexToNames.getOrPut(idx) { HashSet(2) } + names.add(name) + } + for ((existingName, existingIndex) in nameToSlot) { + val plannedNames = planIndexToNames[existingIndex] ?: continue + if (!plannedNames.contains(existingName)) return true + } + return false + } + /** * Clear all references and maps to prevent memory leaks when pooled. */ @@ -651,7 +669,7 @@ open class Scope( val bytecode = (stmt as? BytecodeStatement)?.bytecodeFunction() ?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction() ?: return "$name is not a compiled body" - return BytecodeDisassembler.disassemble(bytecode) + return CmdDisassembler.disassemble(bytecode) } fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt index 8fbb2aa..f2341b5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt @@ -26,6 +26,8 @@ class VarDeclStatement( val visibility: Visibility, val initializer: Statement?, val isTransient: Boolean, + val slotIndex: Int?, + val slotDepth: Int?, private val startPos: Pos, ) : Statement() { override val pos: Pos = startPos diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt deleted file mode 100644 index d8eb3aa..0000000 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeBuilder.kt +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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 - -class BytecodeBuilder { - sealed interface Operand { - data class IntVal(val value: Int) : Operand - data class LabelRef(val label: Label) : Operand - } - - data class Label(val id: Int) - - data class Instr(val op: Opcode, val operands: List) - - private val instructions = mutableListOf() - private val constPool = mutableListOf() - private val labelPositions = mutableMapOf() - private var nextLabelId = 0 - private val fallbackStatements = mutableListOf() - - fun addConst(c: BytecodeConst): Int { - constPool += c - return constPool.lastIndex - } - - fun emit(op: Opcode, vararg operands: Int) { - instructions += Instr(op, operands.map { Operand.IntVal(it) }) - } - - fun emit(op: Opcode, operands: List) { - instructions += Instr(op, operands) - } - - fun label(): Label = Label(nextLabelId++) - - fun mark(label: Label) { - labelPositions[label] = instructions.size - } - - fun addFallback(stmt: net.sergeych.lyng.Statement): Int { - fallbackStatements += stmt - return fallbackStatements.lastIndex - } - - fun build( - name: String, - localCount: Int, - scopeSlotDepths: IntArray = IntArray(0), - scopeSlotIndices: IntArray = IntArray(0), - scopeSlotNames: Array = emptyArray() - ): BytecodeFunction { - val scopeSlotCount = scopeSlotDepths.size - require(scopeSlotIndices.size == scopeSlotCount) { "scope slot mapping size mismatch" } - require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) { - "scope slot name mapping size mismatch" - } - val totalSlots = localCount + scopeSlotCount - val slotWidth = when { - totalSlots < 256 -> 1 - totalSlots < 65536 -> 2 - else -> 4 - } - val constIdWidth = if (constPool.size < 65536) 2 else 4 - val ipWidth = 2 - val instrOffsets = IntArray(instructions.size) - var currentIp = 0 - for (i in instructions.indices) { - instrOffsets[i] = currentIp - val kinds = operandKinds(instructions[i].op) - currentIp += 1 + kinds.sumOf { operandWidth(it, slotWidth, constIdWidth, ipWidth) } - } - val labelIps = mutableMapOf() - for ((label, idx) in labelPositions) { - labelIps[label] = instrOffsets.getOrNull(idx) ?: error("Invalid label index: $idx") - } - - val code = ByteArrayOutput() - for (ins in instructions) { - code.writeU8(ins.op.code and 0xFF) - val kinds = operandKinds(ins.op) - if (kinds.size != ins.operands.size) { - error("Operand count mismatch for ${ins.op}: expected ${kinds.size}, got ${ins.operands.size}") - } - for (i in kinds.indices) { - val operand = ins.operands[i] - val v = when (operand) { - is Operand.IntVal -> operand.value - is Operand.LabelRef -> labelIps[operand.label] - ?: error("Unknown label ${operand.label.id} for ${ins.op}") - } - when (kinds[i]) { - OperandKind.SLOT -> code.writeUInt(v, slotWidth) - OperandKind.CONST -> code.writeUInt(v, constIdWidth) - OperandKind.IP -> code.writeUInt(v, ipWidth) - OperandKind.COUNT -> code.writeUInt(v, 2) - OperandKind.ID -> code.writeUInt(v, 2) - } - } - } - return BytecodeFunction( - name = name, - localCount = localCount, - scopeSlotCount = scopeSlotCount, - scopeSlotDepths = scopeSlotDepths, - scopeSlotIndices = scopeSlotIndices, - scopeSlotNames = if (scopeSlotNames.isEmpty()) Array(scopeSlotCount) { null } else scopeSlotNames, - slotWidth = slotWidth, - ipWidth = ipWidth, - constIdWidth = constIdWidth, - constants = constPool.toList(), - fallbackStatements = fallbackStatements.toList(), - code = code.toByteArray() - ) - } - - private fun operandKinds(op: Opcode): List { - return when (op) { - Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList() - Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ, - Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL, - Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT -> - listOf(OperandKind.SLOT, OperandKind.SLOT) - Opcode.CONST_NULL -> - listOf(OperandKind.SLOT) - Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL -> - listOf(OperandKind.CONST, OperandKind.SLOT) - Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> - listOf(OperandKind.CONST) - Opcode.DECL_LOCAL -> - listOf(OperandKind.CONST, OperandKind.SLOT) - Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, - Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL, - Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT, - Opcode.CMP_EQ_INT, Opcode.CMP_NEQ_INT, Opcode.CMP_LT_INT, Opcode.CMP_LTE_INT, - Opcode.CMP_GT_INT, Opcode.CMP_GTE_INT, - Opcode.CMP_EQ_REAL, Opcode.CMP_NEQ_REAL, Opcode.CMP_LT_REAL, Opcode.CMP_LTE_REAL, - Opcode.CMP_GT_REAL, Opcode.CMP_GTE_REAL, - Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL, - Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT, - Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT, - Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT, - Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ, - Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ, - Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, - Opcode.AND_BOOL, Opcode.OR_BOOL -> - listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) - Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> - listOf(OperandKind.SLOT) - Opcode.JMP -> - listOf(OperandKind.IP) - Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE -> - listOf(OperandKind.SLOT, OperandKind.IP) - Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK -> - listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) - Opcode.CALL_SLOT -> - listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) - Opcode.CALL_VIRTUAL -> - listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) - Opcode.GET_FIELD -> - listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) - Opcode.SET_FIELD -> - listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) - Opcode.GET_INDEX -> - listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) - Opcode.SET_INDEX -> - listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) - Opcode.EVAL_FALLBACK -> - listOf(OperandKind.ID, OperandKind.SLOT) - } - } - - private fun operandWidth(kind: OperandKind, slotWidth: Int, constIdWidth: Int, ipWidth: Int): Int { - return when (kind) { - OperandKind.SLOT -> slotWidth - OperandKind.CONST -> constIdWidth - OperandKind.IP -> ipWidth - OperandKind.COUNT -> 2 - OperandKind.ID -> 2 - } - } - - private enum class OperandKind { - SLOT, - CONST, - IP, - COUNT, - ID, - } - - private class ByteArrayOutput { - private val data = ArrayList(256) - - fun writeU8(v: Int) { - data.add((v and 0xFF).toByte()) - } - - fun writeUInt(v: Int, width: Int) { - var value = v - var remaining = width - while (remaining-- > 0) { - writeU8(value) - value = value ushr 8 - } - } - - fun toByteArray(): ByteArray = data.toByteArray() - } -} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index c762ca0..8c48023 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -29,21 +29,28 @@ import net.sergeych.lyng.obj.* class BytecodeCompiler( private val allowLocalSlots: Boolean = true, ) { - private var builder = BytecodeBuilder() + private var builder = CmdBuilder() private var nextSlot = 0 + private var nextAddrSlot = 0 private var scopeSlotCount = 0 private var scopeSlotDepths = IntArray(0) private var scopeSlotIndices = IntArray(0) private var scopeSlotNames = emptyArray() private val scopeSlotMap = LinkedHashMap() private val scopeSlotNameMap = LinkedHashMap() + private val addrSlotByScopeSlot = LinkedHashMap() + private data class LocalSlotInfo(val name: String, val isMutable: Boolean, val depth: Int) + private val localSlotInfoMap = LinkedHashMap() + private val localSlotIndexByKey = LinkedHashMap() + private val localSlotIndexByName = LinkedHashMap() + private var localSlotNames = emptyArray() + private var localSlotMutables = BooleanArray(0) + private var localSlotDepths = IntArray(0) + private val declaredLocalKeys = LinkedHashSet() private val slotTypes = mutableMapOf() private val intLoopVarNames = LinkedHashSet() - private val loopVarNames = LinkedHashSet() - private val loopVarSlotIndexByName = LinkedHashMap() - private val loopVarSlotIdByName = LinkedHashMap() - fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): BytecodeFunction? { + fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? { prepareCompilation(stmt) return when (stmt) { is ExpressionStatement -> compileExpression(name, stmt) @@ -55,12 +62,22 @@ class BytecodeCompiler( } } - fun compileExpression(name: String, stmt: ExpressionStatement): BytecodeFunction? { + fun compileExpression(name: String, stmt: ExpressionStatement): CmdFunction? { prepareCompilation(stmt) val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null builder.emit(Opcode.RET, value.slot) val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount - return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames) + return builder.build( + name, + localCount, + addrCount = nextAddrSlot, + scopeSlotDepths, + scopeSlotIndices, + scopeSlotNames, + localSlotNames, + localSlotMutables, + localSlotDepths + ) } private data class CompiledValue(val slot: Int, val type: SlotType) @@ -74,12 +91,18 @@ class BytecodeCompiler( if (!allowLocalSlots) return null if (ref.isDelegated) return null if (ref.name.isEmpty()) return null - val loopSlotId = loopVarSlotIdByName[ref.name] - val mapped = loopSlotId ?: scopeSlotMap[ScopeSlotKey(refDepth(ref), refSlot(ref))] ?: return null - val resolved = slotTypes[mapped] ?: SlotType.UNKNOWN + val mapped = resolveSlot(ref) ?: return null + var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) { updateSlotType(mapped, SlotType.INT) - return CompiledValue(mapped, SlotType.INT) + resolved = SlotType.INT + } + if (mapped < scopeSlotCount && resolved != SlotType.UNKNOWN) { + val addrSlot = ensureScopeAddr(mapped) + val local = allocSlot() + emitLoadFromAddr(addrSlot, local, resolved) + updateSlotType(local, resolved) + return CompiledValue(local, resolved) } CompiledValue(mapped, resolved) } @@ -87,7 +110,7 @@ class BytecodeCompiler( is UnaryOpRef -> compileUnary(ref) is AssignRef -> compileAssign(ref) is AssignOpRef -> compileAssignOp(ref) - is IncDecRef -> compileIncDec(ref) + is IncDecRef -> compileIncDec(ref, true) is ConditionalRef -> compileConditional(ref) is ElvisRef -> compileElvis(ref) is CallRef -> compileCall(ref) @@ -626,17 +649,17 @@ class BytecodeCompiler( if (op == BinOp.AND) { builder.emit( Opcode.JMP_IF_FALSE, - listOf(BytecodeBuilder.Operand.IntVal(leftValue.slot), BytecodeBuilder.Operand.LabelRef(shortLabel)) + listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(shortLabel)) ) } else { builder.emit( Opcode.JMP_IF_TRUE, - listOf(BytecodeBuilder.Operand.IntVal(leftValue.slot), BytecodeBuilder.Operand.LabelRef(shortLabel)) + listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(shortLabel)) ) } val rightValue = compileRefWithFallback(right, SlotType.BOOL, pos) ?: return null emitMove(rightValue, resultSlot) - builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel))) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.mark(shortLabel) val constId = builder.addConst(BytecodeConst.Bool(op == BinOp.OR)) builder.emit(Opcode.CONST_BOOL, constId, resultSlot) @@ -648,27 +671,49 @@ class BytecodeCompiler( val target = assignTarget(ref) ?: return null if (!allowLocalSlots) return null if (!target.isMutable || target.isDelegated) return null - if (refDepth(target) > 0) return null val value = compileRef(assignValue(ref)) ?: return null - val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null - when (value.type) { - SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot) - SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot) - SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot) - else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot) + val slot = resolveSlot(target) ?: return null + if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) { + val addrSlot = ensureScopeAddr(slot) + emitStoreToAddr(value.slot, addrSlot, value.type) + } else if (slot < scopeSlotCount) { + val addrSlot = ensureScopeAddr(slot) + emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ) + } else { + when (value.type) { + SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot) + SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot) + SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot) + else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot) + } } updateSlotType(slot, value.type) - return CompiledValue(slot, value.type) + return value } private fun compileAssignOp(ref: AssignOpRef): CompiledValue? { val target = ref.target as? LocalSlotRef ?: return null if (!allowLocalSlots) return null if (!target.isMutable || target.isDelegated) return null - if (refDepth(target) > 0) return null - val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null + val slot = resolveSlot(target) ?: return null val targetType = slotTypes[slot] ?: return null val rhs = compileRef(ref.value) ?: return null + if (slot < scopeSlotCount) { + val addrSlot = ensureScopeAddr(slot) + val current = allocSlot() + emitLoadFromAddr(addrSlot, current, targetType) + val result = when (ref.op) { + BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ) + BinOp.MINUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.SUB_INT, Opcode.SUB_REAL, Opcode.SUB_OBJ) + BinOp.STAR -> compileAssignOpBinary(targetType, rhs, current, Opcode.MUL_INT, Opcode.MUL_REAL, Opcode.MUL_OBJ) + BinOp.SLASH -> compileAssignOpBinary(targetType, rhs, current, Opcode.DIV_INT, Opcode.DIV_REAL, Opcode.DIV_OBJ) + BinOp.PERCENT -> compileAssignOpBinary(targetType, rhs, current, Opcode.MOD_INT, null, Opcode.MOD_OBJ) + else -> null + } ?: return null + emitStoreToAddr(current, addrSlot, result.type) + updateSlotType(slot, result.type) + return CompiledValue(current, result.type) + } val out = slot val result = when (ref.op) { BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ) @@ -733,15 +778,79 @@ class BytecodeCompiler( } } - private fun compileIncDec(ref: IncDecRef): CompiledValue? { + private fun compileIncDec(ref: IncDecRef, wantResult: Boolean): CompiledValue? { val target = ref.target as? LocalSlotRef ?: return null if (!allowLocalSlots) return null if (!target.isMutable || target.isDelegated) return null - val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null + val slot = resolveSlot(target) ?: return null val slotType = slotTypes[slot] ?: SlotType.UNKNOWN + if (slot < scopeSlotCount && slotType != SlotType.UNKNOWN) { + val addrSlot = ensureScopeAddr(slot) + val current = allocSlot() + emitLoadFromAddr(addrSlot, current, slotType) + val result = when (slotType) { + SlotType.INT -> { + if (wantResult && ref.isPost) { + val old = allocSlot() + builder.emit(Opcode.MOVE_INT, current, old) + builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, current) + emitStoreToAddr(current, addrSlot, SlotType.INT) + CompiledValue(old, SlotType.INT) + } else { + builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, current) + emitStoreToAddr(current, addrSlot, SlotType.INT) + CompiledValue(current, SlotType.INT) + } + } + SlotType.REAL -> { + val oneSlot = allocSlot() + val oneId = builder.addConst(BytecodeConst.RealVal(1.0)) + builder.emit(Opcode.CONST_REAL, oneId, oneSlot) + if (wantResult && ref.isPost) { + val old = allocSlot() + builder.emit(Opcode.MOVE_REAL, current, old) + val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL + builder.emit(op, current, oneSlot, current) + emitStoreToAddr(current, addrSlot, SlotType.REAL) + CompiledValue(old, SlotType.REAL) + } else { + val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL + builder.emit(op, current, oneSlot, current) + emitStoreToAddr(current, addrSlot, SlotType.REAL) + CompiledValue(current, SlotType.REAL) + } + } + SlotType.OBJ -> { + val oneSlot = allocSlot() + val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One)) + builder.emit(Opcode.CONST_OBJ, oneId, oneSlot) + val boxed = allocSlot() + builder.emit(Opcode.BOX_OBJ, current, boxed) + if (wantResult && ref.isPost) { + val result = allocSlot() + val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ + builder.emit(op, boxed, oneSlot, result) + builder.emit(Opcode.MOVE_OBJ, result, boxed) + emitStoreToAddr(boxed, addrSlot, SlotType.OBJ) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(boxed, SlotType.OBJ) + } else { + val result = allocSlot() + val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ + builder.emit(op, boxed, oneSlot, result) + builder.emit(Opcode.MOVE_OBJ, result, boxed) + emitStoreToAddr(boxed, addrSlot, SlotType.OBJ) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(result, SlotType.OBJ) + } + } + else -> null + } + if (result != null) return result + } return when (slotType) { SlotType.INT -> { - if (ref.isPost) { + if (wantResult && ref.isPost) { val old = allocSlot() builder.emit(Opcode.MOVE_INT, slot, old) builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot) @@ -755,7 +864,7 @@ class BytecodeCompiler( val oneSlot = allocSlot() val oneId = builder.addConst(BytecodeConst.RealVal(1.0)) builder.emit(Opcode.CONST_REAL, oneId, oneSlot) - if (ref.isPost) { + if (wantResult && ref.isPost) { val old = allocSlot() builder.emit(Opcode.MOVE_REAL, slot, old) val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL @@ -773,7 +882,7 @@ class BytecodeCompiler( builder.emit(Opcode.CONST_OBJ, oneId, oneSlot) val current = allocSlot() builder.emit(Opcode.BOX_OBJ, slot, current) - if (ref.isPost) { + if (wantResult && ref.isPost) { val result = allocSlot() val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ builder.emit(op, current, oneSlot, result) @@ -790,7 +899,7 @@ class BytecodeCompiler( } } SlotType.UNKNOWN -> { - if (ref.isPost) { + if (wantResult && ref.isPost) { val old = allocSlot() builder.emit(Opcode.MOVE_INT, slot, old) builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot) @@ -814,12 +923,12 @@ class BytecodeCompiler( val endLabel = builder.label() builder.emit( Opcode.JMP_IF_FALSE, - listOf(BytecodeBuilder.Operand.IntVal(condition.slot), BytecodeBuilder.Operand.LabelRef(elseLabel)) + listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel)) ) val thenValue = compileRefWithFallback(ref.ifTrue, null, Pos.builtIn) ?: return null val thenObj = ensureObjSlot(thenValue) builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot) - builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel))) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.mark(elseLabel) val elseValue = compileRefWithFallback(ref.ifFalse, null, Pos.builtIn) ?: return null val elseObj = ensureObjSlot(elseValue) @@ -841,10 +950,10 @@ class BytecodeCompiler( val endLabel = builder.label() builder.emit( Opcode.JMP_IF_TRUE, - listOf(BytecodeBuilder.Operand.IntVal(cmpSlot), BytecodeBuilder.Operand.LabelRef(rightLabel)) + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(rightLabel)) ) builder.emit(Opcode.MOVE_OBJ, leftObj.slot, resultSlot) - builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel))) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.mark(rightLabel) val rightValue = compileRefWithFallback(ref.right, null, Pos.builtIn) ?: return null val rightObj = ensureObjSlot(rightValue) @@ -941,7 +1050,7 @@ class BytecodeCompiler( return 0x8000 or planId } - private fun compileIf(name: String, stmt: IfStatement): BytecodeFunction? { + private fun compileIf(name: String, stmt: IfStatement): CmdFunction? { val conditionStmt = stmt.condition as? ExpressionStatement ?: return null val condValue = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null if (condValue.type != SlotType.BOOL) return null @@ -952,11 +1061,11 @@ class BytecodeCompiler( builder.emit( Opcode.JMP_IF_FALSE, - listOf(BytecodeBuilder.Operand.IntVal(condValue.slot), BytecodeBuilder.Operand.LabelRef(elseLabel)) + listOf(CmdBuilder.Operand.IntVal(condValue.slot), CmdBuilder.Operand.LabelRef(elseLabel)) ) val thenValue = compileStatementValue(stmt.ifBody) ?: return null emitMove(thenValue, resultSlot) - builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel))) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.mark(elseLabel) if (stmt.elseBody != null) { @@ -970,28 +1079,68 @@ class BytecodeCompiler( builder.mark(endLabel) builder.emit(Opcode.RET, resultSlot) val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount - return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames) + return builder.build( + name, + localCount, + addrCount = nextAddrSlot, + scopeSlotDepths, + scopeSlotIndices, + scopeSlotNames, + localSlotNames, + localSlotMutables, + localSlotDepths + ) } - private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): BytecodeFunction? { - val resultSlot = emitForIn(stmt) ?: return null + private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): CmdFunction? { + val resultSlot = emitForIn(stmt, true) ?: return null builder.emit(Opcode.RET, resultSlot) val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount - return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames) + return builder.build( + name, + localCount, + addrCount = nextAddrSlot, + scopeSlotDepths, + scopeSlotIndices, + scopeSlotNames, + localSlotNames, + localSlotMutables, + localSlotDepths + ) } - private fun compileBlock(name: String, stmt: BlockStatement): BytecodeFunction? { - val result = emitBlock(stmt) ?: return null + private fun compileBlock(name: String, stmt: BlockStatement): CmdFunction? { + val result = emitBlock(stmt, true) ?: return null builder.emit(Opcode.RET, result.slot) val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount - return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames) + return builder.build( + name, + localCount, + addrCount = nextAddrSlot, + scopeSlotDepths, + scopeSlotIndices, + scopeSlotNames, + localSlotNames, + localSlotMutables, + localSlotDepths + ) } - private fun compileVarDecl(name: String, stmt: VarDeclStatement): BytecodeFunction? { + private fun compileVarDecl(name: String, stmt: VarDeclStatement): CmdFunction? { val result = emitVarDecl(stmt) ?: return null builder.emit(Opcode.RET, result.slot) val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount - return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames) + return builder.build( + name, + localCount, + addrCount = nextAddrSlot, + scopeSlotDepths, + scopeSlotIndices, + scopeSlotNames, + localSlotNames, + localSlotMutables, + localSlotDepths + ) } private fun compileStatementValue(stmt: Statement): CompiledValue? { @@ -1001,53 +1150,127 @@ class BytecodeCompiler( } } - private fun compileStatementValueOrFallback(stmt: Statement): CompiledValue? { + private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? { val target = if (stmt is BytecodeStatement) stmt.original else stmt - return when (target) { - is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos) - is IfStatement -> compileIfExpression(target) - is net.sergeych.lyng.ForInStatement -> { - val resultSlot = emitForIn(target) ?: return null - updateSlotType(resultSlot, SlotType.OBJ) - CompiledValue(resultSlot, SlotType.OBJ) + return if (needResult) { + when (target) { + is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos) + is IfStatement -> compileIfExpression(target) + is net.sergeych.lyng.ForInStatement -> { + val resultSlot = emitForIn(target, true) ?: return null + updateSlotType(resultSlot, SlotType.OBJ) + CompiledValue(resultSlot, SlotType.OBJ) + } + is BlockStatement -> emitBlock(target, true) + is VarDeclStatement -> emitVarDecl(target) + else -> { + val slot = allocSlot() + val id = builder.addFallback(target) + builder.emit(Opcode.EVAL_FALLBACK, id, slot) + builder.emit(Opcode.BOX_OBJ, slot, slot) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(slot, SlotType.OBJ) + } } - is BlockStatement -> emitBlock(target) - is VarDeclStatement -> emitVarDecl(target) - else -> { - val slot = allocSlot() - val id = builder.addFallback(target) - builder.emit(Opcode.EVAL_FALLBACK, id, slot) - builder.emit(Opcode.BOX_OBJ, slot, slot) - updateSlotType(slot, SlotType.OBJ) - CompiledValue(slot, SlotType.OBJ) + } else { + when (target) { + is ExpressionStatement -> { + val ref = target.ref + if (ref is IncDecRef) { + compileIncDec(ref, false) + } else { + compileRefWithFallback(ref, null, target.pos) + } + } + is IfStatement -> compileIfStatement(target) + is net.sergeych.lyng.ForInStatement -> { + val resultSlot = emitForIn(target, false) ?: return null + CompiledValue(resultSlot, SlotType.OBJ) + } + is BlockStatement -> emitBlock(target, false) + is VarDeclStatement -> emitVarDecl(target) + else -> { + val slot = allocSlot() + val id = builder.addFallback(target) + builder.emit(Opcode.EVAL_FALLBACK, id, slot) + builder.emit(Opcode.BOX_OBJ, slot, slot) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(slot, SlotType.OBJ) + } } } } - private fun emitBlock(stmt: BlockStatement): CompiledValue? { + private fun emitBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? { val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan)) builder.emit(Opcode.PUSH_SCOPE, planId) + resetAddrCache() val statements = stmt.statements() var lastValue: CompiledValue? = null - for (statement in statements) { - lastValue = compileStatementValueOrFallback(statement) ?: return null + for ((index, statement) in statements.withIndex()) { + val isLast = index == statements.lastIndex + val wantResult = needResult && isLast + val value = compileStatementValueOrFallback(statement, wantResult) ?: return null + if (wantResult) { + lastValue = value + } } - var result = lastValue ?: run { - val slot = allocSlot() - val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) - builder.emit(Opcode.CONST_OBJ, voidId, slot) - CompiledValue(slot, SlotType.OBJ) - } - if (result.slot < scopeSlotCount) { - val captured = allocSlot() - emitMove(result, captured) - result = CompiledValue(captured, result.type) + val result = if (needResult) { + var value = lastValue ?: run { + val slot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, slot) + CompiledValue(slot, SlotType.OBJ) + } + if (value.slot < scopeSlotCount) { + val captured = allocSlot() + emitMove(value, captured) + value = CompiledValue(captured, value.type) + } + value + } else { + lastValue ?: run { + val slot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, slot) + CompiledValue(slot, SlotType.OBJ) + } } builder.emit(Opcode.POP_SCOPE) + resetAddrCache() return result } private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? { + val localSlot = if (allowLocalSlots && stmt.slotIndex != null) { + val depth = stmt.slotDepth ?: 0 + val key = ScopeSlotKey(depth, stmt.slotIndex) + val localIndex = localSlotIndexByKey[key] + localIndex?.let { scopeSlotCount + it } + } else { + null + } + if (localSlot != null) { + val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run { + builder.emit(Opcode.CONST_NULL, localSlot) + updateSlotType(localSlot, SlotType.OBJ) + CompiledValue(localSlot, SlotType.OBJ) + } + if (value.slot != localSlot) { + emitMove(value, localSlot) + } + updateSlotType(localSlot, value.type) + val declId = builder.addConst( + BytecodeConst.LocalDecl( + stmt.name, + stmt.isMutable, + stmt.visibility, + stmt.isTransient + ) + ) + builder.emit(Opcode.DECL_LOCAL, declId, localSlot) + return CompiledValue(localSlot, value.type) + } val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run { val slot = allocSlot() builder.emit(Opcode.CONST_NULL, slot) @@ -1068,18 +1291,11 @@ class BytecodeCompiler( } return value } - private fun emitForIn(stmt: net.sergeych.lyng.ForInStatement): Int? { + private fun emitForIn(stmt: net.sergeych.lyng.ForInStatement, wantResult: Boolean): Int? { if (stmt.canBreak) return null val range = stmt.constRange ?: return null - val loopSlotId = loopVarSlotIdByName[stmt.loopVarName] ?: return null - val slotIndex = loopVarSlotIndexByName[stmt.loopVarName] ?: return null - val planId = builder.addConst(BytecodeConst.SlotPlan(mapOf(stmt.loopVarName to slotIndex))) - val useLoopScope = scopeSlotMap.isEmpty() - if (useLoopScope) { - builder.emit(Opcode.PUSH_SCOPE, planId) - } else { - builder.emit(Opcode.PUSH_SLOT_PLAN, planId) - } + val loopLocalIndex = localSlotIndexByName[stmt.loopVarName] ?: return null + val loopSlotId = scopeSlotCount + loopLocalIndex val iSlot = allocSlot() val endSlot = allocSlot() @@ -1089,8 +1305,10 @@ class BytecodeCompiler( builder.emit(Opcode.CONST_INT, endId, endSlot) val resultSlot = allocSlot() - val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) - builder.emit(Opcode.CONST_OBJ, voidId, resultSlot) + if (wantResult) { + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, resultSlot) + } val loopLabel = builder.label() val endLabel = builder.label() @@ -1099,35 +1317,53 @@ class BytecodeCompiler( builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot) builder.emit( Opcode.JMP_IF_TRUE, - listOf(BytecodeBuilder.Operand.IntVal(cmpSlot), BytecodeBuilder.Operand.LabelRef(endLabel)) + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel)) ) builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId) updateSlotType(loopSlotId, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT) - val bodyValue = compileStatementValueOrFallback(stmt.body) ?: return null - val bodyObj = ensureObjSlot(bodyValue) - builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot) + val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null + if (wantResult) { + val bodyObj = ensureObjSlot(bodyValue) + builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot) + } builder.emit(Opcode.INC_INT, iSlot) - builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(loopLabel))) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel))) builder.mark(endLabel) if (stmt.elseStatement != null) { - val elseValue = compileStatementValueOrFallback(stmt.elseStatement) ?: return null - val elseObj = ensureObjSlot(elseValue) - builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot) - } - if (useLoopScope) { - builder.emit(Opcode.POP_SCOPE) - } else { - builder.emit(Opcode.POP_SLOT_PLAN) + val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null + if (wantResult) { + val elseObj = ensureObjSlot(elseValue) + builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot) + } } return resultSlot } + private fun compileIfStatement(stmt: IfStatement): CompiledValue? { + val condition = compileCondition(stmt.condition, stmt.pos) ?: return null + if (condition.type != SlotType.BOOL) return null + val elseLabel = builder.label() + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_FALSE, + listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel)) + ) + compileStatementValueOrFallback(stmt.ifBody, false) ?: return null + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(elseLabel) + stmt.elseBody?.let { + compileStatementValueOrFallback(it, false) ?: return null + } + builder.mark(endLabel) + return condition + } + private fun updateSlotTypeByName(name: String, type: SlotType) { - val loopSlotId = loopVarSlotIdByName[name] - if (loopSlotId != null) { - updateSlotType(loopSlotId, type) + val localIndex = localSlotIndexByName[name] + if (localIndex != null) { + updateSlotType(scopeSlotCount + localIndex, type) return } for ((key, index) in scopeSlotMap) { @@ -1145,12 +1381,12 @@ class BytecodeCompiler( val endLabel = builder.label() builder.emit( Opcode.JMP_IF_FALSE, - listOf(BytecodeBuilder.Operand.IntVal(condition.slot), BytecodeBuilder.Operand.LabelRef(elseLabel)) + listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel)) ) val thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null val thenObj = ensureObjSlot(thenValue) builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot) - builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel))) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.mark(elseLabel) if (stmt.elseBody != null) { val elseValue = compileStatementValueOrFallback(stmt.elseBody) ?: return null @@ -1178,12 +1414,60 @@ class BytecodeCompiler( } } + private fun resetAddrCache() { + addrSlotByScopeSlot.clear() + } + + private fun ensureScopeAddr(scopeSlot: Int): Int { + val existing = addrSlotByScopeSlot[scopeSlot] + if (existing != null) return existing + val addrSlot = nextAddrSlot++ + addrSlotByScopeSlot[scopeSlot] = addrSlot + builder.emit(Opcode.RESOLVE_SCOPE_SLOT, scopeSlot, addrSlot) + return addrSlot + } + + private fun emitLoadFromAddr(addrSlot: Int, dstSlot: Int, type: SlotType) { + when (type) { + SlotType.INT -> builder.emit(Opcode.LOAD_INT_ADDR, addrSlot, dstSlot) + SlotType.REAL -> builder.emit(Opcode.LOAD_REAL_ADDR, addrSlot, dstSlot) + SlotType.BOOL -> builder.emit(Opcode.LOAD_BOOL_ADDR, addrSlot, dstSlot) + SlotType.OBJ -> builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, dstSlot) + else -> builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, dstSlot) + } + } + + private fun emitStoreToAddr(srcSlot: Int, addrSlot: Int, type: SlotType) { + when (type) { + SlotType.INT -> builder.emit(Opcode.STORE_INT_ADDR, srcSlot, addrSlot) + SlotType.REAL -> builder.emit(Opcode.STORE_REAL_ADDR, srcSlot, addrSlot) + SlotType.BOOL -> builder.emit(Opcode.STORE_BOOL_ADDR, srcSlot, addrSlot) + SlotType.OBJ -> builder.emit(Opcode.STORE_OBJ_ADDR, srcSlot, addrSlot) + else -> builder.emit(Opcode.STORE_OBJ_ADDR, srcSlot, addrSlot) + } + } + private fun emitMove(value: CompiledValue, dstSlot: Int) { + val srcSlot = value.slot + val srcIsScope = srcSlot < scopeSlotCount + val dstIsScope = dstSlot < scopeSlotCount + if (value.type != SlotType.UNKNOWN) { + if (srcIsScope && !dstIsScope) { + val addrSlot = ensureScopeAddr(srcSlot) + emitLoadFromAddr(addrSlot, dstSlot, value.type) + return + } + if (dstIsScope) { + val addrSlot = ensureScopeAddr(dstSlot) + emitStoreToAddr(srcSlot, addrSlot, value.type) + return + } + } when (value.type) { - SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, dstSlot) - SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, dstSlot) - SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, dstSlot) - else -> builder.emit(Opcode.MOVE_OBJ, value.slot, dstSlot) + SlotType.INT -> builder.emit(Opcode.MOVE_INT, srcSlot, dstSlot) + SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, srcSlot, dstSlot) + SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, srcSlot, dstSlot) + else -> builder.emit(Opcode.MOVE_OBJ, srcSlot, dstSlot) } } @@ -1215,6 +1499,7 @@ class BytecodeCompiler( private fun refSlot(ref: LocalSlotRef): Int = ref.slot private fun refDepth(ref: LocalSlotRef): Int = ref.depth + private fun refScopeDepth(ref: LocalSlotRef): Int = ref.scopeDepth private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op @@ -1224,6 +1509,16 @@ class BytecodeCompiler( private fun assignValue(ref: AssignRef): ObjRef = ref.value private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn + private fun resolveSlot(ref: LocalSlotRef): Int? { + val localKey = ScopeSlotKey(refScopeDepth(ref), refSlot(ref)) + val localIndex = localSlotIndexByKey[localKey] + if (localIndex != null) return scopeSlotCount + localIndex + val nameIndex = localSlotIndexByName[ref.name] + if (nameIndex != null) return scopeSlotCount + nameIndex + val scopeKey = ScopeSlotKey(refDepth(ref), refSlot(ref)) + return scopeSlotMap[scopeKey] + } + private fun updateSlotType(slot: Int, type: SlotType) { if (type == SlotType.UNKNOWN) { slotTypes.remove(slot) @@ -1233,17 +1528,24 @@ class BytecodeCompiler( } private fun prepareCompilation(stmt: Statement) { - builder = BytecodeBuilder() + builder = CmdBuilder() nextSlot = 0 + nextAddrSlot = 0 slotTypes.clear() scopeSlotMap.clear() + localSlotInfoMap.clear() + localSlotIndexByKey.clear() + localSlotIndexByName.clear() + localSlotNames = emptyArray() + localSlotMutables = BooleanArray(0) + localSlotDepths = IntArray(0) + declaredLocalKeys.clear() intLoopVarNames.clear() - loopVarNames.clear() - loopVarSlotIndexByName.clear() - loopVarSlotIdByName.clear() + addrSlotByScopeSlot.clear() if (allowLocalSlots) { collectLoopVarNames(stmt) collectScopeSlots(stmt) + collectLoopSlotPlans(stmt, 0) } scopeSlotCount = scopeSlotMap.size scopeSlotDepths = IntArray(scopeSlotCount) @@ -1255,28 +1557,26 @@ class BytecodeCompiler( scopeSlotIndices[index] = key.slot scopeSlotNames[index] = name } - if (loopVarNames.isNotEmpty()) { - var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1 - for (name in loopVarNames) { - maxSlotIndex += 1 - loopVarSlotIndexByName[name] = maxSlotIndex + if (allowLocalSlots && localSlotInfoMap.isNotEmpty()) { + val names = ArrayList(localSlotInfoMap.size) + val mutables = BooleanArray(localSlotInfoMap.size) + val depths = IntArray(localSlotInfoMap.size) + var index = 0 + for ((key, info) in localSlotInfoMap) { + localSlotIndexByKey[key] = index + if (!localSlotIndexByName.containsKey(info.name)) { + localSlotIndexByName[info.name] = index + } + names.add(info.name) + mutables[index] = info.isMutable + depths[index] = info.depth + index += 1 } - val start = scopeSlotCount - val total = scopeSlotCount + loopVarSlotIndexByName.size - scopeSlotDepths = scopeSlotDepths.copyOf(total) - scopeSlotIndices = scopeSlotIndices.copyOf(total) - scopeSlotNames = scopeSlotNames.copyOf(total) - var cursor = start - for ((name, slotIndex) in loopVarSlotIndexByName) { - loopVarSlotIdByName[name] = cursor - scopeSlotDepths[cursor] = 0 - scopeSlotIndices[cursor] = slotIndex - scopeSlotNames[cursor] = name - cursor += 1 - } - scopeSlotCount = total + localSlotNames = names.toTypedArray() + localSlotMutables = mutables + localSlotDepths = depths } - nextSlot = scopeSlotCount + nextSlot = scopeSlotCount + localSlotNames.size } private fun collectScopeSlots(stmt: Statement) { @@ -1292,6 +1592,11 @@ class BytecodeCompiler( } } is VarDeclStatement -> { + val slotIndex = stmt.slotIndex + val slotDepth = stmt.slotDepth + if (allowLocalSlots && slotIndex != null && slotDepth != null) { + declaredLocalKeys.add(ScopeSlotKey(slotDepth, slotIndex)) + } stmt.initializer?.let { collectScopeSlots(it) } } is IfStatement -> { @@ -1308,6 +1613,45 @@ class BytecodeCompiler( } } + private fun collectLoopSlotPlans(stmt: Statement, scopeDepth: Int) { + if (stmt is BytecodeStatement) { + collectLoopSlotPlans(stmt.original, scopeDepth) + return + } + when (stmt) { + is net.sergeych.lyng.ForInStatement -> { + collectLoopSlotPlans(stmt.source, scopeDepth) + val loopDepth = scopeDepth + 1 + for ((name, slotIndex) in stmt.loopSlotPlan) { + val key = ScopeSlotKey(loopDepth, slotIndex) + if (!localSlotInfoMap.containsKey(key)) { + localSlotInfoMap[key] = LocalSlotInfo(name, isMutable = true, depth = loopDepth) + } + } + collectLoopSlotPlans(stmt.body, loopDepth) + stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) } + } + is BlockStatement -> { + val nextDepth = scopeDepth + 1 + for (child in stmt.statements()) { + collectLoopSlotPlans(child, nextDepth) + } + } + is IfStatement -> { + collectLoopSlotPlans(stmt.condition, scopeDepth) + collectLoopSlotPlans(stmt.ifBody, scopeDepth) + stmt.elseBody?.let { collectLoopSlotPlans(it, scopeDepth) } + } + is VarDeclStatement -> { + stmt.initializer?.let { collectLoopSlotPlans(it, scopeDepth) } + } + is ExpressionStatement -> { + // no-op + } + else -> {} + } + } + private fun collectLoopVarNames(stmt: Statement) { if (stmt is BytecodeStatement) { collectLoopVarNames(stmt.original) @@ -1317,7 +1661,6 @@ class BytecodeCompiler( is net.sergeych.lyng.ForInStatement -> { if (stmt.constRange != null) { intLoopVarNames.add(stmt.loopVarName) - loopVarNames.add(stmt.loopVarName) } collectLoopVarNames(stmt.source) collectLoopVarNames(stmt.body) @@ -1370,7 +1713,15 @@ class BytecodeCompiler( private fun collectScopeSlotsRef(ref: ObjRef) { when (ref) { is LocalSlotRef -> { - if (loopVarNames.contains(ref.name)) return + val localKey = ScopeSlotKey(refScopeDepth(ref), refSlot(ref)) + val shouldLocalize = declaredLocalKeys.contains(localKey) || + intLoopVarNames.contains(ref.name) + if (allowLocalSlots && !ref.isDelegated && shouldLocalize) { + if (!localSlotInfoMap.containsKey(localKey)) { + localSlotInfoMap[localKey] = LocalSlotInfo(ref.name, ref.isMutable, localKey.depth) + } + return + } val key = ScopeSlotKey(refDepth(ref), refSlot(ref)) if (!scopeSlotMap.containsKey(key)) { scopeSlotMap[key] = scopeSlotMap.size @@ -1387,12 +1738,21 @@ class BytecodeCompiler( is AssignRef -> { val target = assignTarget(ref) if (target != null) { - val key = ScopeSlotKey(refDepth(target), refSlot(target)) - if (!scopeSlotMap.containsKey(key)) { - scopeSlotMap[key] = scopeSlotMap.size - } - if (!scopeSlotNameMap.containsKey(key)) { - scopeSlotNameMap[key] = target.name + val localKey = ScopeSlotKey(refScopeDepth(target), refSlot(target)) + val shouldLocalize = declaredLocalKeys.contains(localKey) || + intLoopVarNames.contains(target.name) + if (allowLocalSlots && !target.isDelegated && shouldLocalize) { + if (!localSlotInfoMap.containsKey(localKey)) { + localSlotInfoMap[localKey] = LocalSlotInfo(target.name, target.isMutable, localKey.depth) + } + } else { + val key = ScopeSlotKey(refDepth(target), refSlot(target)) + if (!scopeSlotMap.containsKey(key)) { + scopeSlotMap[key] = scopeSlotMap.size + } + if (!scopeSlotNameMap.containsKey(key)) { + scopeSlotNameMap[key] = target.name + } } } collectScopeSlotsRef(assignValue(ref)) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt deleted file mode 100644 index a5d87af..0000000 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDecoder.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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].toInt() and 0xFF) ?: 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].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) - - 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].toInt() and 0xFF) ?: 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/BytecodeDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt deleted file mode 100644 index 1164773..0000000 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeDisassembler.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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 - -object BytecodeDisassembler { - fun disassemble(fn: BytecodeFunction): String { - val decoder = when (fn.slotWidth) { - 1 -> Decoder8 - 2 -> Decoder16 - 4 -> Decoder32 - else -> error("Unsupported slot width: ${fn.slotWidth}") - } - val out = StringBuilder() - val code = fn.code - var ip = 0 - while (ip < code.size) { - val op = decoder.readOpcode(code, ip) - val startIp = ip - ip += 1 - val kinds = operandKinds(op) - val operands = ArrayList(kinds.size) - for (kind in kinds) { - when (kind) { - OperandKind.SLOT -> { - val v = decoder.readSlot(code, ip) - ip += fn.slotWidth - val name = if (v < fn.scopeSlotCount) fn.scopeSlotNames[v] else null - operands += if (name != null) "s$v($name)" else "s$v" - } - OperandKind.CONST -> { - val v = decoder.readConstId(code, ip, fn.constIdWidth) - ip += fn.constIdWidth - operands += "k$v" - } - OperandKind.IP -> { - val v = decoder.readIp(code, ip, fn.ipWidth) - ip += fn.ipWidth - operands += "ip$v" - } - OperandKind.COUNT -> { - val v = decoder.readConstId(code, ip, 2) - ip += 2 - operands += "n$v" - } - OperandKind.ID -> { - val v = decoder.readConstId(code, ip, 2) - ip += 2 - operands += "#$v" - } - } - } - out.append(startIp).append(": ").append(op.name) - if (operands.isNotEmpty()) { - out.append(' ').append(operands.joinToString(", ")) - } - out.append('\n') - } - return out.toString() - } - - private enum class OperandKind { - SLOT, - CONST, - IP, - COUNT, - ID, - } - - private fun operandKinds(op: Opcode): List { - return when (op) { - Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList() - Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ, - Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL, - Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT -> - listOf(OperandKind.SLOT, OperandKind.SLOT) - Opcode.CONST_NULL -> - listOf(OperandKind.SLOT) - Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL -> - listOf(OperandKind.CONST, OperandKind.SLOT) - Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> - listOf(OperandKind.CONST) - Opcode.DECL_LOCAL -> - listOf(OperandKind.CONST, OperandKind.SLOT) - Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, - Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL, - Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT, - Opcode.CMP_EQ_INT, Opcode.CMP_NEQ_INT, Opcode.CMP_LT_INT, Opcode.CMP_LTE_INT, - Opcode.CMP_GT_INT, Opcode.CMP_GTE_INT, - Opcode.CMP_EQ_REAL, Opcode.CMP_NEQ_REAL, Opcode.CMP_LT_REAL, Opcode.CMP_LTE_REAL, - Opcode.CMP_GT_REAL, Opcode.CMP_GTE_REAL, - Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL, - Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT, - Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT, - Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT, - Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ, - Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ, - Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, - Opcode.AND_BOOL, Opcode.OR_BOOL -> - listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) - Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> - listOf(OperandKind.SLOT) - Opcode.JMP -> - listOf(OperandKind.IP) - Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE -> - listOf(OperandKind.SLOT, OperandKind.IP) - Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK -> - listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) - Opcode.CALL_SLOT -> - listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) - Opcode.CALL_VIRTUAL -> - listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) - Opcode.GET_FIELD -> - listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) - Opcode.SET_FIELD -> - listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) - Opcode.GET_INDEX -> - listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) - Opcode.SET_INDEX -> - listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) - Opcode.EVAL_FALLBACK -> - listOf(OperandKind.ID, OperandKind.SLOT) - } - } -} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt index c842184..bad44bb 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt @@ -23,30 +23,111 @@ import net.sergeych.lyng.obj.Obj class BytecodeStatement private constructor( val original: Statement, - private val function: BytecodeFunction, + private val function: CmdFunction, ) : Statement(original.isStaticConst, original.isConst, original.returnType) { override val pos: Pos = original.pos override suspend fun execute(scope: Scope): Obj { - return BytecodeVm().execute(function, scope, emptyList()) + return CmdVm().execute(function, scope, emptyList()) } - internal fun bytecodeFunction(): BytecodeFunction = function + internal fun bytecodeFunction(): CmdFunction = function companion object { fun wrap(statement: Statement, nameHint: String, allowLocalSlots: Boolean): Statement { if (statement is BytecodeStatement) return statement - val compiler = BytecodeCompiler(allowLocalSlots = allowLocalSlots) + val hasUnsupported = containsUnsupportedStatement(statement) + if (hasUnsupported) return unwrapDeep(statement) + val safeLocals = allowLocalSlots + val compiler = BytecodeCompiler(allowLocalSlots = safeLocals) val compiled = compiler.compileStatement(nameHint, statement) val fn = compiled ?: run { - val builder = BytecodeBuilder() + val builder = CmdBuilder() val slot = 0 val id = builder.addFallback(statement) builder.emit(Opcode.EVAL_FALLBACK, id, slot) builder.emit(Opcode.RET, slot) - builder.build(nameHint, localCount = 1) + builder.build( + nameHint, + localCount = 1, + addrCount = 0, + localSlotNames = emptyArray(), + localSlotMutables = BooleanArray(0), + localSlotDepths = IntArray(0) + ) } return BytecodeStatement(statement, fn) } + + private fun containsUnsupportedStatement(stmt: Statement): Boolean { + val target = if (stmt is BytecodeStatement) stmt.original else stmt + return when (target) { + is net.sergeych.lyng.ExpressionStatement -> false + is net.sergeych.lyng.IfStatement -> { + containsUnsupportedStatement(target.condition) || + containsUnsupportedStatement(target.ifBody) || + (target.elseBody?.let { containsUnsupportedStatement(it) } ?: false) + } + is net.sergeych.lyng.ForInStatement -> { + target.constRange == null || target.canBreak || + containsUnsupportedStatement(target.source) || + containsUnsupportedStatement(target.body) || + (target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false) + } + is net.sergeych.lyng.BlockStatement -> + target.statements().any { containsUnsupportedStatement(it) } + is net.sergeych.lyng.VarDeclStatement -> + target.initializer?.let { containsUnsupportedStatement(it) } ?: false + else -> true + } + } + + private fun unwrapDeep(stmt: Statement): Statement { + return when (stmt) { + is BytecodeStatement -> unwrapDeep(stmt.original) + is net.sergeych.lyng.BlockStatement -> { + val unwrapped = stmt.statements().map { unwrapDeep(it) } + net.sergeych.lyng.BlockStatement( + net.sergeych.lyng.Script(stmt.pos, unwrapped), + stmt.slotPlan, + stmt.pos + ) + } + is net.sergeych.lyng.VarDeclStatement -> { + net.sergeych.lyng.VarDeclStatement( + stmt.name, + stmt.isMutable, + stmt.visibility, + stmt.initializer?.let { unwrapDeep(it) }, + stmt.isTransient, + stmt.slotIndex, + stmt.slotDepth, + stmt.pos + ) + } + is net.sergeych.lyng.IfStatement -> { + net.sergeych.lyng.IfStatement( + unwrapDeep(stmt.condition), + unwrapDeep(stmt.ifBody), + stmt.elseBody?.let { unwrapDeep(it) }, + stmt.pos + ) + } + is net.sergeych.lyng.ForInStatement -> { + net.sergeych.lyng.ForInStatement( + stmt.loopVarName, + unwrapDeep(stmt.source), + stmt.constRange, + unwrapDeep(stmt.body), + stmt.elseStatement?.let { unwrapDeep(it) }, + stmt.label, + stmt.canBreak, + stmt.loopSlotPlan, + stmt.pos + ) + } + else -> stmt + } + } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt deleted file mode 100644 index e4f31fc..0000000 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeVm.kt +++ /dev/null @@ -1,1049 +0,0 @@ -/* - * 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.Arguments -import net.sergeych.lyng.PerfFlags -import net.sergeych.lyng.Scope -import net.sergeych.lyng.obj.ObjRecord -import net.sergeych.lyng.obj.* - -class BytecodeVm { - companion object { - private const val ARG_PLAN_FLAG = 0x8000 - private const val ARG_PLAN_MASK = 0x7FFF - } - - private var virtualDepth = 0 - - suspend fun execute(fn: BytecodeFunction, scope0: Scope, args: List): Obj { - val scopeStack = ArrayDeque() - val scopeVirtualStack = ArrayDeque() - val slotPlanStack = ArrayDeque>() - var scope = scope0 - val methodCallSites = BytecodeCallSiteCache.methodCallSites(fn) - val frame = BytecodeFrame(fn.localCount, args.size) - virtualDepth = 0 - 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 startIp = ip - val op = decoder.readOpcode(code, ip) - ip += 1 - when (op) { - 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") - setInt(fn, frame, scope, 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") - setReal(fn, frame, scope, 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") - setBool(fn, frame, scope, dst, c.value) - } - Opcode.CONST_OBJ -> { - val constId = decoder.readConstId(code, ip, fn.constIdWidth) - ip += fn.constIdWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - when (val c = fn.constants[constId]) { - is BytecodeConst.ObjRef -> { - val obj = c.value - when (obj) { - is ObjInt -> setInt(fn, frame, scope, dst, obj.value) - is ObjReal -> setReal(fn, frame, scope, dst, obj.value) - is ObjBool -> setBool(fn, frame, scope, dst, obj.value) - else -> setObj(fn, frame, scope, dst, obj) - } - } - is BytecodeConst.StringVal -> setObj(fn, frame, scope, dst, ObjString(c.value)) - else -> error("CONST_OBJ expects ObjRef/StringVal at $constId") - } - } - Opcode.CONST_NULL -> { - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setObj(fn, frame, scope, dst, ObjNull) - } - Opcode.MOVE_INT -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, src)) - } - Opcode.MOVE_REAL -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setReal(fn, frame, scope, dst, getReal(fn, frame, scope, src)) - } - Opcode.MOVE_BOOL -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setBool(fn, frame, scope, dst, getBool(fn, frame, scope, src)) - } - Opcode.MOVE_OBJ -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setObj(fn, frame, scope, dst, getObj(fn, frame, scope, src)) - } - Opcode.BOX_OBJ -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setObj(fn, frame, scope, dst, slotToObj(fn, frame, scope, src)) - } - Opcode.INT_TO_REAL -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setReal(fn, frame, scope, dst, getInt(fn, frame, scope, src).toDouble()) - } - Opcode.REAL_TO_INT -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setInt(fn, frame, scope, dst, getReal(fn, frame, scope, src).toLong()) - } - Opcode.BOOL_TO_INT -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setInt(fn, frame, scope, dst, if (getBool(fn, frame, scope, src)) 1L else 0L) - } - Opcode.INT_TO_BOOL -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, src) != 0L) - } - 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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) + getInt(fn, frame, scope, b)) - } - Opcode.SUB_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) - getInt(fn, frame, scope, b)) - } - Opcode.MUL_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) * getInt(fn, frame, scope, b)) - } - Opcode.DIV_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) / getInt(fn, frame, scope, b)) - } - Opcode.MOD_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) % getInt(fn, frame, scope, b)) - } - Opcode.NEG_INT -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setInt(fn, frame, scope, dst, -getInt(fn, frame, scope, src)) - } - Opcode.INC_INT -> { - val slot = decoder.readSlot(code, ip) - ip += fn.slotWidth - setInt(fn, frame, scope, slot, getInt(fn, frame, scope, slot) + 1L) - } - Opcode.DEC_INT -> { - val slot = decoder.readSlot(code, ip) - ip += fn.slotWidth - setInt(fn, frame, scope, slot, getInt(fn, frame, scope, slot) - 1L) - } - Opcode.ADD_REAL -> { - 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 - setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) + getReal(fn, frame, scope, b)) - } - Opcode.SUB_REAL -> { - 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 - setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) - getReal(fn, frame, scope, b)) - } - Opcode.MUL_REAL -> { - 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 - setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) * getReal(fn, frame, scope, b)) - } - Opcode.DIV_REAL -> { - 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 - setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) / getReal(fn, frame, scope, b)) - } - Opcode.NEG_REAL -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setReal(fn, frame, scope, dst, -getReal(fn, frame, scope, src)) - } - Opcode.AND_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) and getInt(fn, frame, scope, b)) - } - Opcode.OR_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) or getInt(fn, frame, scope, b)) - } - Opcode.XOR_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) xor getInt(fn, frame, scope, b)) - } - Opcode.SHL_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) shl getInt(fn, frame, scope, b).toInt()) - } - Opcode.SHR_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) shr getInt(fn, frame, scope, b).toInt()) - } - Opcode.USHR_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 - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) ushr getInt(fn, frame, scope, b).toInt()) - } - Opcode.INV_INT -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setInt(fn, frame, scope, dst, getInt(fn, frame, scope, src).inv()) - } - Opcode.CMP_LT_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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) < getInt(fn, frame, scope, b)) - } - Opcode.CMP_LTE_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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) <= getInt(fn, frame, scope, b)) - } - Opcode.CMP_GT_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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) > getInt(fn, frame, scope, b)) - } - Opcode.CMP_GTE_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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) >= getInt(fn, frame, scope, b)) - } - Opcode.CMP_EQ_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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) == getInt(fn, frame, scope, b)) - } - Opcode.CMP_NEQ_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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) != getInt(fn, frame, scope, b)) - } - Opcode.CMP_EQ_REAL -> { - 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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) == getReal(fn, frame, scope, b)) - } - Opcode.CMP_NEQ_REAL -> { - 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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) != getReal(fn, frame, scope, b)) - } - Opcode.CMP_LT_REAL -> { - 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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) < getReal(fn, frame, scope, b)) - } - Opcode.CMP_LTE_REAL -> { - 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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) <= getReal(fn, frame, scope, b)) - } - Opcode.CMP_GT_REAL -> { - 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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) > getReal(fn, frame, scope, b)) - } - Opcode.CMP_GTE_REAL -> { - 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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) >= getReal(fn, frame, scope, b)) - } - Opcode.CMP_EQ_BOOL -> { - 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 - setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) == getBool(fn, frame, scope, b)) - } - Opcode.CMP_NEQ_BOOL -> { - 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 - setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) != getBool(fn, frame, scope, b)) - } - Opcode.CMP_EQ_INT_REAL -> { - 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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() == getReal(fn, frame, scope, b)) - } - Opcode.CMP_EQ_REAL_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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) == getInt(fn, frame, scope, b).toDouble()) - } - Opcode.CMP_LT_INT_REAL -> { - 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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() < getReal(fn, frame, scope, b)) - } - Opcode.CMP_LT_REAL_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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) < getInt(fn, frame, scope, b).toDouble()) - } - Opcode.CMP_LTE_INT_REAL -> { - 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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() <= getReal(fn, frame, scope, b)) - } - Opcode.CMP_LTE_REAL_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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) <= getInt(fn, frame, scope, b).toDouble()) - } - Opcode.CMP_GT_INT_REAL -> { - 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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() > getReal(fn, frame, scope, b)) - } - Opcode.CMP_GT_REAL_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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) > getInt(fn, frame, scope, b).toDouble()) - } - Opcode.CMP_GTE_INT_REAL -> { - 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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() >= getReal(fn, frame, scope, b)) - } - Opcode.CMP_GTE_REAL_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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) >= getInt(fn, frame, scope, b).toDouble()) - } - Opcode.CMP_NEQ_INT_REAL -> { - 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 - setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() != getReal(fn, frame, scope, b)) - } - Opcode.CMP_NEQ_REAL_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 - setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) != getInt(fn, frame, scope, b).toDouble()) - } - Opcode.CMP_EQ_OBJ -> { - 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 - setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).equals(scope, getObj(fn, frame, scope, b))) - } - Opcode.CMP_NEQ_OBJ -> { - 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 - setBool(fn, frame, scope, dst, !getObj(fn, frame, scope, a).equals(scope, getObj(fn, frame, scope, b))) - } - Opcode.CMP_REF_EQ_OBJ -> { - 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 - setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a) === getObj(fn, frame, scope, b)) - } - Opcode.CMP_REF_NEQ_OBJ -> { - 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 - setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a) !== getObj(fn, frame, scope, b)) - } - Opcode.CMP_LT_OBJ -> { - 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 - setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) < 0) - } - Opcode.CMP_LTE_OBJ -> { - 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 - setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) <= 0) - } - Opcode.CMP_GT_OBJ -> { - 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 - setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) > 0) - } - Opcode.CMP_GTE_OBJ -> { - 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 - setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) >= 0) - } - Opcode.ADD_OBJ -> { - 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 - setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).plus(scope, getObj(fn, frame, scope, b))) - } - Opcode.SUB_OBJ -> { - 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 - setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).minus(scope, getObj(fn, frame, scope, b))) - } - Opcode.MUL_OBJ -> { - 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 - setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).mul(scope, getObj(fn, frame, scope, b))) - } - Opcode.DIV_OBJ -> { - 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 - setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).div(scope, getObj(fn, frame, scope, b))) - } - Opcode.MOD_OBJ -> { - 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 - setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).mod(scope, getObj(fn, frame, scope, b))) - } - Opcode.NOT_BOOL -> { - val src = decoder.readSlot(code, ip) - ip += fn.slotWidth - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - setBool(fn, frame, scope, dst, !getBool(fn, frame, scope, src)) - } - Opcode.AND_BOOL -> { - 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 - setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) && getBool(fn, frame, scope, b)) - } - Opcode.OR_BOOL -> { - 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 - setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) || getBool(fn, frame, scope, b)) - } - Opcode.JMP -> { - val target = decoder.readIp(code, ip, fn.ipWidth) - ip = target - } - Opcode.JMP_IF_FALSE -> { - val cond = decoder.readSlot(code, ip) - ip += fn.slotWidth - val target = decoder.readIp(code, ip, fn.ipWidth) - ip += fn.ipWidth - if (!getBool(fn, frame, scope, cond)) { - ip = target - } - } - Opcode.JMP_IF_TRUE -> { - val cond = decoder.readSlot(code, ip) - ip += fn.slotWidth - val target = decoder.readIp(code, ip, fn.ipWidth) - ip += fn.ipWidth - if (getBool(fn, frame, scope, cond)) { - ip = target - } - } - Opcode.PUSH_SCOPE -> { - val constId = decoder.readConstId(code, ip, fn.constIdWidth) - ip += fn.constIdWidth - val planConst = fn.constants[constId] as? BytecodeConst.SlotPlan - ?: error("PUSH_SCOPE expects SlotPlan at $constId") - if (scope.skipScopeCreation) { - val snapshot = scope.applySlotPlanWithSnapshot(planConst.plan) - slotPlanStack.addLast(snapshot) - virtualDepth += 1 - scopeStack.addLast(scope) - scopeVirtualStack.addLast(true) - } else { - scopeStack.addLast(scope) - scopeVirtualStack.addLast(false) - scope = scope.createChildScope() - if (planConst.plan.isNotEmpty()) { - scope.applySlotPlan(planConst.plan) - } - } - } - Opcode.POP_SCOPE -> { - val isVirtual = scopeVirtualStack.removeLastOrNull() - ?: error("Scope stack underflow in POP_SCOPE") - if (isVirtual) { - val snapshot = slotPlanStack.removeLastOrNull() - ?: error("Slot plan stack underflow in POP_SCOPE") - scope.restoreSlotPlan(snapshot) - virtualDepth -= 1 - } - scope = scopeStack.removeLastOrNull() - ?: error("Scope stack underflow in POP_SCOPE") - } - Opcode.PUSH_SLOT_PLAN -> { - val constId = decoder.readConstId(code, ip, fn.constIdWidth) - ip += fn.constIdWidth - val planConst = fn.constants[constId] as? BytecodeConst.SlotPlan - ?: error("PUSH_SLOT_PLAN expects SlotPlan at $constId") - val snapshot = scope.applySlotPlanWithSnapshot(planConst.plan) - slotPlanStack.addLast(snapshot) - virtualDepth += 1 - } - Opcode.POP_SLOT_PLAN -> { - val snapshot = slotPlanStack.removeLastOrNull() - ?: error("Slot plan stack underflow in POP_SLOT_PLAN") - scope.restoreSlotPlan(snapshot) - virtualDepth -= 1 - } - Opcode.DECL_LOCAL -> { - val constId = decoder.readConstId(code, ip, fn.constIdWidth) - ip += fn.constIdWidth - val slot = decoder.readSlot(code, ip) - ip += fn.slotWidth - val decl = fn.constants[constId] as? BytecodeConst.LocalDecl - ?: error("DECL_LOCAL expects LocalDecl at $constId") - val value = slotToObj(fn, frame, scope, slot).byValueCopy() - scope.addItem( - decl.name, - decl.isMutable, - value, - decl.visibility, - recordType = ObjRecord.Type.Other, - isTransient = decl.isTransient - ) - } - Opcode.CALL_SLOT -> { - val calleeSlot = decoder.readSlot(code, ip) - ip += fn.slotWidth - val argBase = decoder.readSlot(code, ip) - ip += fn.slotWidth - val argCount = decoder.readConstId(code, ip, 2) - ip += 2 - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - val callee = slotToObj(fn, frame, scope, calleeSlot) - val args = buildArguments(fn, frame, scope, argBase, argCount) - val result = if (PerfFlags.SCOPE_POOL) { - scope.withChildFrame(args) { child -> callee.callOn(child) } - } else { - callee.callOn(scope.createChildScope(scope.pos, args = args)) - } - when (result) { - is ObjInt -> setInt(fn, frame, scope, dst, result.value) - is ObjReal -> setReal(fn, frame, scope, dst, result.value) - is ObjBool -> setBool(fn, frame, scope, dst, result.value) - else -> setObj(fn, frame, scope, dst, result) - } - } - Opcode.CALL_VIRTUAL -> { - val recvSlot = decoder.readSlot(code, ip) - ip += fn.slotWidth - val methodId = decoder.readConstId(code, ip, 2) - ip += 2 - val argBase = decoder.readSlot(code, ip) - ip += fn.slotWidth - val argCount = decoder.readConstId(code, ip, 2) - ip += 2 - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - val receiver = slotToObj(fn, frame, scope, recvSlot) - val nameConst = fn.constants.getOrNull(methodId) as? BytecodeConst.StringVal - ?: error("CALL_VIRTUAL expects StringVal at $methodId") - val args = buildArguments(fn, frame, scope, argBase, argCount) - val site = methodCallSites.getOrPut(startIp) { MethodCallSite(nameConst.value) } - val result = site.invoke(scope, receiver, args) - when (result) { - is ObjInt -> setInt(fn, frame, scope, dst, result.value) - is ObjReal -> setReal(fn, frame, scope, dst, result.value) - is ObjBool -> setBool(fn, frame, scope, dst, result.value) - else -> setObj(fn, frame, scope, dst, result) - } - } - Opcode.EVAL_FALLBACK -> { - val id = decoder.readConstId(code, ip, 2) - ip += 2 - val dst = decoder.readSlot(code, ip) - ip += fn.slotWidth - val stmt = fn.fallbackStatements.getOrNull(id) - ?: error("Fallback statement not found: $id") - val result = stmt.execute(scope) - when (result) { - is ObjInt -> setInt(fn, frame, scope, dst, result.value) - is ObjReal -> setReal(fn, frame, scope, dst, result.value) - is ObjBool -> setBool(fn, frame, scope, dst, result.value) - else -> setObj(fn, frame, scope, dst, result) - } - } - Opcode.RET -> { - val slot = decoder.readSlot(code, ip) - return slotToObj(fn, frame, scope, slot) - } - Opcode.RET_VOID -> return ObjVoid - else -> error("Opcode not implemented: $op") - } - } - return ObjVoid - } - - private fun slotToObj(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Obj { - if (slot < fn.scopeSlotCount) { - return resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value - } - val local = slot - fn.scopeSlotCount - return when (frame.getSlotTypeCode(local)) { - SlotType.INT.code -> ObjInt.of(frame.getInt(local)) - SlotType.REAL.code -> ObjReal.of(frame.getReal(local)) - SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse - SlotType.OBJ.code -> frame.getObj(local) - else -> ObjVoid - } - } - - private suspend fun buildArguments( - fn: BytecodeFunction, - frame: BytecodeFrame, - scope: Scope, - argBase: Int, - argCount: Int, - ): Arguments { - if (argCount == 0) return Arguments.EMPTY - if ((argCount and ARG_PLAN_FLAG) != 0) { - val planId = argCount and ARG_PLAN_MASK - val plan = fn.constants.getOrNull(planId) as? BytecodeConst.CallArgsPlan - ?: error("CALL args plan not found: $planId") - return buildArgumentsFromPlan(fn, frame, scope, argBase, plan) - } - val list = ArrayList(argCount) - for (i in 0 until argCount) { - list.add(slotToObj(fn, frame, scope, argBase + i)) - } - return Arguments(list) - } - - private suspend fun buildArgumentsFromPlan( - fn: BytecodeFunction, - frame: BytecodeFrame, - scope: Scope, - argBase: Int, - plan: BytecodeConst.CallArgsPlan, - ): Arguments { - val positional = ArrayList(plan.specs.size) - var named: LinkedHashMap? = null - var namedSeen = false - for ((idx, spec) in plan.specs.withIndex()) { - val value = slotToObj(fn, frame, scope, argBase + idx) - val name = spec.name - if (name != null) { - if (named == null) named = linkedMapOf() - if (named.containsKey(name)) scope.raiseIllegalArgument("argument '$name' is already set") - named[name] = value - namedSeen = true - continue - } - if (spec.isSplat) { - when { - value is ObjMap -> { - if (named == null) named = linkedMapOf() - for ((k, v) in value.map) { - if (k !is ObjString) scope.raiseIllegalArgument("named splat expects a Map with string keys") - val key = k.value - if (named.containsKey(key)) scope.raiseIllegalArgument("argument '$key' is already set") - named[key] = v - } - namedSeen = true - } - value is ObjList -> { - if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments") - positional.addAll(value.list) - } - value.isInstanceOf(ObjIterable) -> { - if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments") - val list = (value.invokeInstanceMethod(scope, "toList") as ObjList).list - positional.addAll(list) - } - else -> scope.raiseClassCastError("expected list of objects for splat argument") - } - } else { - if (namedSeen) { - val isLast = idx == plan.specs.lastIndex - if (!(isLast && plan.tailBlock)) { - scope.raiseIllegalArgument("positional argument cannot follow named arguments") - } - } - positional.add(value) - } - } - return Arguments(positional, plan.tailBlock, named ?: emptyMap()) - } - - private fun getObj(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Obj { - return if (slot < fn.scopeSlotCount) { - resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value - } else { - frame.getObj(slot - fn.scopeSlotCount) - } - } - - private fun setObj(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int, value: Obj) { - if (slot < fn.scopeSlotCount) { - setScopeSlotValue(scope, fn.scopeSlotDepths[slot], fn.scopeSlotIndices[slot], value) - } else { - frame.setObj(slot - fn.scopeSlotCount, value) - } - } - - private fun getInt(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Long { - return if (slot < fn.scopeSlotCount) { - resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value.toLong() - } else { - frame.getInt(slot - fn.scopeSlotCount) - } - } - - private fun setInt(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int, value: Long) { - if (slot < fn.scopeSlotCount) { - setScopeSlotValue(scope, fn.scopeSlotDepths[slot], fn.scopeSlotIndices[slot], ObjInt.of(value)) - } else { - frame.setInt(slot - fn.scopeSlotCount, value) - } - } - - private fun getReal(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Double { - return if (slot < fn.scopeSlotCount) { - resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value.toDouble() - } else { - frame.getReal(slot - fn.scopeSlotCount) - } - } - - private fun setReal(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int, value: Double) { - if (slot < fn.scopeSlotCount) { - setScopeSlotValue(scope, fn.scopeSlotDepths[slot], fn.scopeSlotIndices[slot], ObjReal.of(value)) - } else { - frame.setReal(slot - fn.scopeSlotCount, value) - } - } - - private fun getBool(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Boolean { - return if (slot < fn.scopeSlotCount) { - resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value.toBool() - } else { - frame.getBool(slot - fn.scopeSlotCount) - } - } - - private fun setBool(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int, value: Boolean) { - if (slot < fn.scopeSlotCount) { - setScopeSlotValue(scope, fn.scopeSlotDepths[slot], fn.scopeSlotIndices[slot], if (value) ObjTrue else ObjFalse) - } else { - frame.setBool(slot - fn.scopeSlotCount, value) - } - } - - private fun setScopeSlotValue(scope: Scope, depth: Int, index: Int, value: Obj) { - val target = resolveScope(scope, depth) - target.setSlotValue(index, value) - } - - private fun resolveScope(scope: Scope, depth: Int): Scope { - if (depth == 0) return scope - var effectiveDepth = depth - if (virtualDepth > 0) { - if (effectiveDepth <= virtualDepth) return scope - effectiveDepth -= virtualDepth - } - val next = when (scope) { - is net.sergeych.lyng.ClosureScope -> scope.closureScope - else -> scope.parent - } - return next?.let { resolveScope(it, effectiveDepth - 1) } - ?: error("Scope depth $depth is out of range") - } -} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt new file mode 100644 index 0000000..cd59492 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -0,0 +1,356 @@ +/* + * 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 + +class CmdBuilder { + sealed interface Operand { + data class IntVal(val value: Int) : Operand + data class LabelRef(val label: Label) : Operand + } + + data class Label(val id: Int) + + data class Instr(val op: Opcode, val operands: List) + + private val instructions = mutableListOf() + private val constPool = mutableListOf() + private val labelPositions = mutableMapOf() + private var nextLabelId = 0 + private val fallbackStatements = mutableListOf() + + fun addConst(c: BytecodeConst): Int { + constPool += c + return constPool.lastIndex + } + + fun emit(op: Opcode, vararg operands: Int) { + instructions += Instr(op, operands.map { Operand.IntVal(it) }) + } + + fun emit(op: Opcode, operands: List) { + instructions += Instr(op, operands) + } + + fun label(): Label = Label(nextLabelId++) + + fun mark(label: Label) { + labelPositions[label] = instructions.size + } + + fun addFallback(stmt: net.sergeych.lyng.Statement): Int { + fallbackStatements += stmt + return fallbackStatements.lastIndex + } + + fun build( + name: String, + localCount: Int, + addrCount: Int = 0, + scopeSlotDepths: IntArray = IntArray(0), + scopeSlotIndices: IntArray = IntArray(0), + scopeSlotNames: Array = emptyArray(), + localSlotNames: Array = emptyArray(), + localSlotMutables: BooleanArray = BooleanArray(0), + localSlotDepths: IntArray = IntArray(0) + ): CmdFunction { + val scopeSlotCount = scopeSlotDepths.size + require(scopeSlotIndices.size == scopeSlotCount) { "scope slot mapping size mismatch" } + require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) { + "scope slot name mapping size mismatch" + } + require(localSlotNames.size == localSlotMutables.size) { "local slot metadata size mismatch" } + require(localSlotNames.size == localSlotDepths.size) { "local slot depth metadata size mismatch" } + val labelIps = mutableMapOf() + for ((label, idx) in labelPositions) { + labelIps[label] = idx + } + val cmds = ArrayList(instructions.size) + for (ins in instructions) { + val kinds = operandKinds(ins.op) + if (kinds.size != ins.operands.size) { + error("Operand count mismatch for ${ins.op}: expected ${kinds.size}, got ${ins.operands.size}") + } + val operands = IntArray(kinds.size) + for (i in kinds.indices) { + val operand = ins.operands[i] + val v = when (operand) { + is Operand.IntVal -> operand.value + is Operand.LabelRef -> labelIps[operand.label] + ?: error("Unknown label ${operand.label.id} for ${ins.op}") + } + operands[i] = v + } + cmds.add(createCmd(ins.op, operands, scopeSlotCount)) + } + return CmdFunction( + name = name, + localCount = localCount, + addrCount = addrCount, + scopeSlotCount = scopeSlotCount, + scopeSlotDepths = scopeSlotDepths, + scopeSlotIndices = scopeSlotIndices, + scopeSlotNames = if (scopeSlotNames.isEmpty()) Array(scopeSlotCount) { null } else scopeSlotNames, + localSlotNames = localSlotNames, + localSlotMutables = localSlotMutables, + localSlotDepths = localSlotDepths, + constants = constPool.toList(), + fallbackStatements = fallbackStatements.toList(), + cmds = cmds.toTypedArray() + ) + } + + private fun operandKinds(op: Opcode): List { + return when (op) { + Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList() + Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ, + Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL, + Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT -> + listOf(OperandKind.SLOT, OperandKind.SLOT) + Opcode.RESOLVE_SCOPE_SLOT -> + listOf(OperandKind.SLOT, OperandKind.ADDR) + Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR -> + listOf(OperandKind.ADDR, OperandKind.SLOT) + Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR -> + listOf(OperandKind.SLOT, OperandKind.ADDR) + Opcode.CONST_NULL -> + listOf(OperandKind.SLOT) + Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL -> + listOf(OperandKind.CONST, OperandKind.SLOT) + Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> + listOf(OperandKind.CONST) + Opcode.DECL_LOCAL -> + listOf(OperandKind.CONST, OperandKind.SLOT) + Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, + Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL, + Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT, + Opcode.CMP_EQ_INT, Opcode.CMP_NEQ_INT, Opcode.CMP_LT_INT, Opcode.CMP_LTE_INT, + Opcode.CMP_GT_INT, Opcode.CMP_GTE_INT, + Opcode.CMP_EQ_REAL, Opcode.CMP_NEQ_REAL, Opcode.CMP_LT_REAL, Opcode.CMP_LTE_REAL, + Opcode.CMP_GT_REAL, Opcode.CMP_GTE_REAL, + Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL, + Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT, + Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT, + Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT, + Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ, + Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ, + Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, + Opcode.AND_BOOL, Opcode.OR_BOOL -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) + Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> + listOf(OperandKind.SLOT) + Opcode.JMP -> + listOf(OperandKind.IP) + Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE -> + listOf(OperandKind.SLOT, OperandKind.IP) + Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK -> + listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) + Opcode.CALL_SLOT -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) + Opcode.CALL_VIRTUAL -> + listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) + Opcode.GET_FIELD -> + listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) + Opcode.SET_FIELD -> + listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) + Opcode.GET_INDEX -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) + Opcode.SET_INDEX -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) + Opcode.EVAL_FALLBACK -> + listOf(OperandKind.ID, OperandKind.SLOT) + } + } + + private enum class OperandKind { + SLOT, + ADDR, + CONST, + IP, + COUNT, + ID, + } + + private fun createCmd(op: Opcode, operands: IntArray, scopeSlotCount: Int): Cmd { + return when (op) { + Opcode.NOP -> CmdNop() + Opcode.MOVE_OBJ -> CmdMoveObj(operands[0], operands[1]) + Opcode.MOVE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) { + CmdMoveIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount) + } else { + CmdMoveInt(operands[0], operands[1]) + } + Opcode.MOVE_REAL -> CmdMoveReal(operands[0], operands[1]) + Opcode.MOVE_BOOL -> CmdMoveBool(operands[0], operands[1]) + Opcode.CONST_OBJ -> CmdConstObj(operands[0], operands[1]) + Opcode.CONST_INT -> if (operands[1] >= scopeSlotCount) { + CmdConstIntLocal(operands[0], operands[1] - scopeSlotCount) + } else { + CmdConstInt(operands[0], operands[1]) + } + Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1]) + Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1]) + Opcode.CONST_NULL -> CmdConstNull(operands[0]) + Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1]) + Opcode.RESOLVE_SCOPE_SLOT -> CmdResolveScopeSlot(operands[0], operands[1]) + Opcode.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1]) + Opcode.STORE_OBJ_ADDR -> CmdStoreObjAddr(operands[0], operands[1]) + Opcode.LOAD_INT_ADDR -> CmdLoadIntAddr(operands[0], operands[1]) + Opcode.STORE_INT_ADDR -> CmdStoreIntAddr(operands[0], operands[1]) + Opcode.LOAD_REAL_ADDR -> CmdLoadRealAddr(operands[0], operands[1]) + Opcode.STORE_REAL_ADDR -> CmdStoreRealAddr(operands[0], operands[1]) + Opcode.LOAD_BOOL_ADDR -> CmdLoadBoolAddr(operands[0], operands[1]) + Opcode.STORE_BOOL_ADDR -> CmdStoreBoolAddr(operands[0], operands[1]) + Opcode.INT_TO_REAL -> CmdIntToReal(operands[0], operands[1]) + Opcode.REAL_TO_INT -> CmdRealToInt(operands[0], operands[1]) + Opcode.BOOL_TO_INT -> CmdBoolToInt(operands[0], operands[1]) + Opcode.INT_TO_BOOL -> CmdIntToBool(operands[0], operands[1]) + Opcode.ADD_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdAddIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdAddInt(operands[0], operands[1], operands[2]) + } + Opcode.SUB_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdSubIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdSubInt(operands[0], operands[1], operands[2]) + } + Opcode.MUL_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdMulIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdMulInt(operands[0], operands[1], operands[2]) + } + Opcode.DIV_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdDivIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdDivInt(operands[0], operands[1], operands[2]) + } + Opcode.MOD_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdModIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdModInt(operands[0], operands[1], operands[2]) + } + Opcode.NEG_INT -> CmdNegInt(operands[0], operands[1]) + Opcode.INC_INT -> if (operands[0] >= scopeSlotCount) { + CmdIncIntLocal(operands[0] - scopeSlotCount) + } else { + CmdIncInt(operands[0]) + } + Opcode.DEC_INT -> if (operands[0] >= scopeSlotCount) { + CmdDecIntLocal(operands[0] - scopeSlotCount) + } else { + CmdDecInt(operands[0]) + } + Opcode.ADD_REAL -> CmdAddReal(operands[0], operands[1], operands[2]) + Opcode.SUB_REAL -> CmdSubReal(operands[0], operands[1], operands[2]) + Opcode.MUL_REAL -> CmdMulReal(operands[0], operands[1], operands[2]) + Opcode.DIV_REAL -> CmdDivReal(operands[0], operands[1], operands[2]) + Opcode.NEG_REAL -> CmdNegReal(operands[0], operands[1]) + Opcode.AND_INT -> CmdAndInt(operands[0], operands[1], operands[2]) + Opcode.OR_INT -> CmdOrInt(operands[0], operands[1], operands[2]) + Opcode.XOR_INT -> CmdXorInt(operands[0], operands[1], operands[2]) + Opcode.SHL_INT -> CmdShlInt(operands[0], operands[1], operands[2]) + Opcode.SHR_INT -> CmdShrInt(operands[0], operands[1], operands[2]) + Opcode.USHR_INT -> CmdUshrInt(operands[0], operands[1], operands[2]) + Opcode.INV_INT -> CmdInvInt(operands[0], operands[1]) + Opcode.CMP_EQ_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdCmpEqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdCmpEqInt(operands[0], operands[1], operands[2]) + } + Opcode.CMP_NEQ_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdCmpNeqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdCmpNeqInt(operands[0], operands[1], operands[2]) + } + Opcode.CMP_LT_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdCmpLtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdCmpLtInt(operands[0], operands[1], operands[2]) + } + Opcode.CMP_LTE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdCmpLteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdCmpLteInt(operands[0], operands[1], operands[2]) + } + Opcode.CMP_GT_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdCmpGtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdCmpGtInt(operands[0], operands[1], operands[2]) + } + Opcode.CMP_GTE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) { + CmdCmpGteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount) + } else { + CmdCmpGteInt(operands[0], operands[1], operands[2]) + } + Opcode.CMP_EQ_REAL -> CmdCmpEqReal(operands[0], operands[1], operands[2]) + Opcode.CMP_NEQ_REAL -> CmdCmpNeqReal(operands[0], operands[1], operands[2]) + Opcode.CMP_LT_REAL -> CmdCmpLtReal(operands[0], operands[1], operands[2]) + Opcode.CMP_LTE_REAL -> CmdCmpLteReal(operands[0], operands[1], operands[2]) + Opcode.CMP_GT_REAL -> CmdCmpGtReal(operands[0], operands[1], operands[2]) + Opcode.CMP_GTE_REAL -> CmdCmpGteReal(operands[0], operands[1], operands[2]) + Opcode.CMP_EQ_BOOL -> CmdCmpEqBool(operands[0], operands[1], operands[2]) + Opcode.CMP_NEQ_BOOL -> CmdCmpNeqBool(operands[0], operands[1], operands[2]) + Opcode.CMP_EQ_INT_REAL -> CmdCmpEqIntReal(operands[0], operands[1], operands[2]) + Opcode.CMP_EQ_REAL_INT -> CmdCmpEqRealInt(operands[0], operands[1], operands[2]) + Opcode.CMP_LT_INT_REAL -> CmdCmpLtIntReal(operands[0], operands[1], operands[2]) + Opcode.CMP_LT_REAL_INT -> CmdCmpLtRealInt(operands[0], operands[1], operands[2]) + Opcode.CMP_LTE_INT_REAL -> CmdCmpLteIntReal(operands[0], operands[1], operands[2]) + Opcode.CMP_LTE_REAL_INT -> CmdCmpLteRealInt(operands[0], operands[1], operands[2]) + Opcode.CMP_GT_INT_REAL -> CmdCmpGtIntReal(operands[0], operands[1], operands[2]) + Opcode.CMP_GT_REAL_INT -> CmdCmpGtRealInt(operands[0], operands[1], operands[2]) + Opcode.CMP_GTE_INT_REAL -> CmdCmpGteIntReal(operands[0], operands[1], operands[2]) + Opcode.CMP_GTE_REAL_INT -> CmdCmpGteRealInt(operands[0], operands[1], operands[2]) + Opcode.CMP_NEQ_INT_REAL -> CmdCmpNeqIntReal(operands[0], operands[1], operands[2]) + Opcode.CMP_NEQ_REAL_INT -> CmdCmpNeqRealInt(operands[0], operands[1], operands[2]) + Opcode.CMP_EQ_OBJ -> CmdCmpEqObj(operands[0], operands[1], operands[2]) + Opcode.CMP_NEQ_OBJ -> CmdCmpNeqObj(operands[0], operands[1], operands[2]) + Opcode.CMP_REF_EQ_OBJ -> CmdCmpRefEqObj(operands[0], operands[1], operands[2]) + Opcode.CMP_REF_NEQ_OBJ -> CmdCmpRefNeqObj(operands[0], operands[1], operands[2]) + Opcode.NOT_BOOL -> CmdNotBool(operands[0], operands[1]) + Opcode.AND_BOOL -> CmdAndBool(operands[0], operands[1], operands[2]) + Opcode.OR_BOOL -> CmdOrBool(operands[0], operands[1], operands[2]) + Opcode.CMP_LT_OBJ -> CmdCmpLtObj(operands[0], operands[1], operands[2]) + Opcode.CMP_LTE_OBJ -> CmdCmpLteObj(operands[0], operands[1], operands[2]) + Opcode.CMP_GT_OBJ -> CmdCmpGtObj(operands[0], operands[1], operands[2]) + Opcode.CMP_GTE_OBJ -> CmdCmpGteObj(operands[0], operands[1], operands[2]) + Opcode.ADD_OBJ -> CmdAddObj(operands[0], operands[1], operands[2]) + Opcode.SUB_OBJ -> CmdSubObj(operands[0], operands[1], operands[2]) + Opcode.MUL_OBJ -> CmdMulObj(operands[0], operands[1], operands[2]) + Opcode.DIV_OBJ -> CmdDivObj(operands[0], operands[1], operands[2]) + Opcode.MOD_OBJ -> CmdModObj(operands[0], operands[1], operands[2]) + Opcode.JMP -> CmdJmp(operands[0]) + Opcode.JMP_IF_TRUE -> CmdJmpIfTrue(operands[0], operands[1]) + Opcode.JMP_IF_FALSE -> CmdJmpIfFalse(operands[0], operands[1]) + Opcode.RET -> CmdRet(operands[0]) + Opcode.RET_VOID -> CmdRetVoid() + Opcode.PUSH_SCOPE -> CmdPushScope(operands[0]) + Opcode.POP_SCOPE -> CmdPopScope() + Opcode.PUSH_SLOT_PLAN -> CmdPushSlotPlan(operands[0]) + Opcode.POP_SLOT_PLAN -> CmdPopSlotPlan() + Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1]) + Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3]) + Opcode.CALL_VIRTUAL -> CmdCallVirtual(operands[0], operands[1], operands[2], operands[3], operands[4]) + Opcode.CALL_FALLBACK -> CmdCallFallback(operands[0], operands[1], operands[2], operands[3]) + Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3]) + Opcode.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2]) + Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2]) + Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2]) + Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2]) + Opcode.EVAL_FALLBACK -> CmdEvalFallback(operands[0], operands[1]) + } + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCache.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCache.kt similarity index 83% rename from lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCache.kt rename to lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCache.kt index 639015d..386da69 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCache.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCache.kt @@ -16,6 +16,6 @@ package net.sergeych.lyng.bytecode -internal expect object BytecodeCallSiteCache { - fun methodCallSites(fn: BytecodeFunction): MutableMap +internal expect object CmdCallSiteCache { + fun methodCallSites(fn: CmdFunction): MutableMap } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt new file mode 100644 index 0000000..e444ef7 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -0,0 +1,251 @@ +/* + * 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 + +object CmdDisassembler { + fun disassemble(fn: CmdFunction): String { + val out = StringBuilder() + val cmds = fn.cmds + for (i in cmds.indices) { + val (op, opValues) = opAndOperands(fn, cmds[i]) + val kinds = operandKinds(op) + val operands = ArrayList(kinds.size) + for (k in kinds.indices) { + val v = opValues.getOrElse(k) { 0 } + when (kinds[k]) { + OperandKind.SLOT -> { + val name = when { + v < fn.scopeSlotCount -> fn.scopeSlotNames[v] + else -> { + val localIndex = v - fn.scopeSlotCount + fn.localSlotNames.getOrNull(localIndex) + } + } + operands += if (name != null) "s$v($name)" else "s$v" + } + OperandKind.ADDR -> operands += "a$v" + OperandKind.CONST -> operands += "k$v" + OperandKind.IP -> operands += "ip$v" + OperandKind.COUNT -> operands += "n$v" + OperandKind.ID -> operands += "#$v" + } + } + out.append(i).append(": ").append(op.name) + if (operands.isNotEmpty()) { + out.append(' ').append(operands.joinToString(", ")) + } + out.append('\n') + } + return out.toString() + } + + private fun opAndOperands(fn: CmdFunction, cmd: Cmd): Pair { + return when (cmd) { + is CmdNop -> Opcode.NOP to intArrayOf() + is CmdMoveObj -> Opcode.MOVE_OBJ to intArrayOf(cmd.src, cmd.dst) + is CmdMoveInt -> Opcode.MOVE_INT to intArrayOf(cmd.src, cmd.dst) + is CmdMoveIntLocal -> Opcode.MOVE_INT to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdMoveReal -> Opcode.MOVE_REAL to intArrayOf(cmd.src, cmd.dst) + is CmdMoveBool -> Opcode.MOVE_BOOL to intArrayOf(cmd.src, cmd.dst) + is CmdConstObj -> Opcode.CONST_OBJ to intArrayOf(cmd.constId, cmd.dst) + is CmdConstInt -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst) + is CmdConstIntLocal -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst + fn.scopeSlotCount) + is CmdConstReal -> Opcode.CONST_REAL to intArrayOf(cmd.constId, cmd.dst) + is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst) + is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst) + is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst) + is CmdResolveScopeSlot -> Opcode.RESOLVE_SCOPE_SLOT to intArrayOf(cmd.scopeSlot, cmd.addrSlot) + is CmdLoadObjAddr -> Opcode.LOAD_OBJ_ADDR to intArrayOf(cmd.addrSlot, cmd.dst) + is CmdStoreObjAddr -> Opcode.STORE_OBJ_ADDR to intArrayOf(cmd.src, cmd.addrSlot) + is CmdLoadIntAddr -> Opcode.LOAD_INT_ADDR to intArrayOf(cmd.addrSlot, cmd.dst) + is CmdStoreIntAddr -> Opcode.STORE_INT_ADDR to intArrayOf(cmd.src, cmd.addrSlot) + is CmdLoadRealAddr -> Opcode.LOAD_REAL_ADDR to intArrayOf(cmd.addrSlot, cmd.dst) + is CmdStoreRealAddr -> Opcode.STORE_REAL_ADDR to intArrayOf(cmd.src, cmd.addrSlot) + is CmdLoadBoolAddr -> Opcode.LOAD_BOOL_ADDR to intArrayOf(cmd.addrSlot, cmd.dst) + is CmdStoreBoolAddr -> Opcode.STORE_BOOL_ADDR to intArrayOf(cmd.src, cmd.addrSlot) + is CmdIntToReal -> Opcode.INT_TO_REAL to intArrayOf(cmd.src, cmd.dst) + is CmdRealToInt -> Opcode.REAL_TO_INT to intArrayOf(cmd.src, cmd.dst) + is CmdBoolToInt -> Opcode.BOOL_TO_INT to intArrayOf(cmd.src, cmd.dst) + is CmdIntToBool -> Opcode.INT_TO_BOOL to intArrayOf(cmd.src, cmd.dst) + is CmdAddInt -> Opcode.ADD_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdAddIntLocal -> Opcode.ADD_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdSubInt -> Opcode.SUB_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdSubIntLocal -> Opcode.SUB_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdMulInt -> Opcode.MUL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdMulIntLocal -> Opcode.MUL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdDivInt -> Opcode.DIV_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdDivIntLocal -> Opcode.DIV_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdModInt -> Opcode.MOD_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdModIntLocal -> Opcode.MOD_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdNegInt -> Opcode.NEG_INT to intArrayOf(cmd.src, cmd.dst) + is CmdIncInt -> Opcode.INC_INT to intArrayOf(cmd.slot) + is CmdIncIntLocal -> Opcode.INC_INT to intArrayOf(cmd.slot + fn.scopeSlotCount) + is CmdDecInt -> Opcode.DEC_INT to intArrayOf(cmd.slot) + is CmdDecIntLocal -> Opcode.DEC_INT to intArrayOf(cmd.slot + fn.scopeSlotCount) + is CmdAddReal -> Opcode.ADD_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdSubReal -> Opcode.SUB_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdMulReal -> Opcode.MUL_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdDivReal -> Opcode.DIV_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdNegReal -> Opcode.NEG_REAL to intArrayOf(cmd.src, cmd.dst) + is CmdAndInt -> Opcode.AND_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdOrInt -> Opcode.OR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdXorInt -> Opcode.XOR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdShlInt -> Opcode.SHL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdShrInt -> Opcode.SHR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdUshrInt -> Opcode.USHR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdInvInt -> Opcode.INV_INT to intArrayOf(cmd.src, cmd.dst) + is CmdCmpEqInt -> Opcode.CMP_EQ_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpEqIntLocal -> Opcode.CMP_EQ_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdCmpNeqInt -> Opcode.CMP_NEQ_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpNeqIntLocal -> Opcode.CMP_NEQ_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdCmpLtInt -> Opcode.CMP_LT_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLtIntLocal -> Opcode.CMP_LT_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdCmpLteInt -> Opcode.CMP_LTE_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLteIntLocal -> Opcode.CMP_LTE_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdCmpGtInt -> Opcode.CMP_GT_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGtIntLocal -> Opcode.CMP_GT_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdCmpGteInt -> Opcode.CMP_GTE_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGteIntLocal -> Opcode.CMP_GTE_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount) + is CmdCmpEqReal -> Opcode.CMP_EQ_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpNeqReal -> Opcode.CMP_NEQ_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLtReal -> Opcode.CMP_LT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLteReal -> Opcode.CMP_LTE_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGtReal -> Opcode.CMP_GT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGteReal -> Opcode.CMP_GTE_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpEqBool -> Opcode.CMP_EQ_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpNeqBool -> Opcode.CMP_NEQ_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpEqIntReal -> Opcode.CMP_EQ_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpEqRealInt -> Opcode.CMP_EQ_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLtIntReal -> Opcode.CMP_LT_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLtRealInt -> Opcode.CMP_LT_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLteIntReal -> Opcode.CMP_LTE_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLteRealInt -> Opcode.CMP_LTE_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGtIntReal -> Opcode.CMP_GT_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGtRealInt -> Opcode.CMP_GT_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGteIntReal -> Opcode.CMP_GTE_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGteRealInt -> Opcode.CMP_GTE_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpNeqIntReal -> Opcode.CMP_NEQ_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpNeqRealInt -> Opcode.CMP_NEQ_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpEqObj -> Opcode.CMP_EQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpNeqObj -> Opcode.CMP_NEQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpRefEqObj -> Opcode.CMP_REF_EQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpRefNeqObj -> Opcode.CMP_REF_NEQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdNotBool -> Opcode.NOT_BOOL to intArrayOf(cmd.src, cmd.dst) + is CmdAndBool -> Opcode.AND_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdOrBool -> Opcode.OR_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLtObj -> Opcode.CMP_LT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpLteObj -> Opcode.CMP_LTE_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGtObj -> Opcode.CMP_GT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdCmpGteObj -> Opcode.CMP_GTE_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdAddObj -> Opcode.ADD_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdSubObj -> Opcode.SUB_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdMulObj -> Opcode.MUL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdDivObj -> Opcode.DIV_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdModObj -> Opcode.MOD_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst) + is CmdJmp -> Opcode.JMP to intArrayOf(cmd.target) + is CmdJmpIfTrue -> Opcode.JMP_IF_TRUE to intArrayOf(cmd.cond, cmd.target) + is CmdJmpIfFalse -> Opcode.JMP_IF_FALSE to intArrayOf(cmd.cond, cmd.target) + is CmdRet -> Opcode.RET to intArrayOf(cmd.slot) + is CmdRetVoid -> Opcode.RET_VOID to intArrayOf() + is CmdPushScope -> Opcode.PUSH_SCOPE to intArrayOf(cmd.planId) + is CmdPopScope -> Opcode.POP_SCOPE to intArrayOf() + is CmdPushSlotPlan -> Opcode.PUSH_SLOT_PLAN to intArrayOf(cmd.planId) + is CmdPopSlotPlan -> Opcode.POP_SLOT_PLAN to intArrayOf() + is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot) + is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst) + is CmdCallVirtual -> Opcode.CALL_VIRTUAL to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst) + is CmdCallFallback -> Opcode.CALL_FALLBACK to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst) + is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst) + is CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst) + is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot) + is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst) + is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot) + is CmdEvalFallback -> Opcode.EVAL_FALLBACK to intArrayOf(cmd.id, cmd.dst) + } + } + + private enum class OperandKind { + SLOT, + ADDR, + CONST, + IP, + COUNT, + ID, + } + + private fun operandKinds(op: Opcode): List { + return when (op) { + Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList() + Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ, + Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL, + Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT -> + listOf(OperandKind.SLOT, OperandKind.SLOT) + Opcode.RESOLVE_SCOPE_SLOT -> + listOf(OperandKind.SLOT, OperandKind.ADDR) + Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR -> + listOf(OperandKind.ADDR, OperandKind.SLOT) + Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR -> + listOf(OperandKind.SLOT, OperandKind.ADDR) + Opcode.CONST_NULL -> + listOf(OperandKind.SLOT) + Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL -> + listOf(OperandKind.CONST, OperandKind.SLOT) + Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> + listOf(OperandKind.CONST) + Opcode.DECL_LOCAL -> + listOf(OperandKind.CONST, OperandKind.SLOT) + Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, + Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL, + Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT, + Opcode.CMP_EQ_INT, Opcode.CMP_NEQ_INT, Opcode.CMP_LT_INT, Opcode.CMP_LTE_INT, + Opcode.CMP_GT_INT, Opcode.CMP_GTE_INT, + Opcode.CMP_EQ_REAL, Opcode.CMP_NEQ_REAL, Opcode.CMP_LT_REAL, Opcode.CMP_LTE_REAL, + Opcode.CMP_GT_REAL, Opcode.CMP_GTE_REAL, + Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL, + Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT, + Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT, + Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT, + Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ, + Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ, + Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, + Opcode.AND_BOOL, Opcode.OR_BOOL -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) + Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> + listOf(OperandKind.SLOT) + Opcode.JMP -> + listOf(OperandKind.IP) + Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE -> + listOf(OperandKind.SLOT, OperandKind.IP) + Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK -> + listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) + Opcode.CALL_SLOT -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) + Opcode.CALL_VIRTUAL -> + listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) + Opcode.GET_FIELD -> + listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) + Opcode.SET_FIELD -> + listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT) + Opcode.GET_INDEX -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) + Opcode.SET_INDEX -> + listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) + Opcode.EVAL_FALLBACK -> + listOf(OperandKind.ID, OperandKind.SLOT) + } + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFunction.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt similarity index 68% rename from lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFunction.kt rename to lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt index 3f0da78..933b777 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFunction.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt @@ -16,26 +16,28 @@ package net.sergeych.lyng.bytecode -data class BytecodeFunction( +data class CmdFunction( val name: String, val localCount: Int, + val addrCount: Int, val scopeSlotCount: Int, val scopeSlotDepths: IntArray, val scopeSlotIndices: IntArray, val scopeSlotNames: Array, - val slotWidth: Int, - val ipWidth: Int, - val constIdWidth: Int, + val localSlotNames: Array, + val localSlotMutables: BooleanArray, + val localSlotDepths: IntArray, val constants: List, val fallbackStatements: List, - val code: ByteArray, + val cmds: Array, ) { 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" } require(scopeSlotDepths.size == scopeSlotCount) { "scopeSlotDepths size mismatch" } require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" } require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames size mismatch" } + require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" } + require(localSlotNames.size == localSlotDepths.size) { "localSlot depth metadata size mismatch" } + require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" } + require(addrCount >= 0) { "addrCount must be non-negative" } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt new file mode 100644 index 0000000..6a33631 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -0,0 +1,1452 @@ +/* + * 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.Arguments +import net.sergeych.lyng.PerfFlags +import net.sergeych.lyng.Scope +import net.sergeych.lyng.obj.* + +class CmdVm { + var result: Obj? = null + + suspend fun execute(fn: CmdFunction, scope0: Scope, args: List): Obj { + result = null + val frame = CmdFrame(this, fn, scope0, args) + val cmds = fn.cmds + while (result == null) { + val cmd = cmds[frame.ip] + frame.ip += 1 + cmd.perform(frame) + } + return result ?: ObjVoid + } +} + +sealed class Cmd { + abstract suspend fun perform(frame: CmdFrame) +} + +class CmdNop : Cmd() { + override suspend fun perform(frame: CmdFrame) { + return + } +} + +class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setObj(dst, frame.getObj(src)) + return + } +} + +class CmdMoveInt(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(src)) + return + } +} + +class CmdMoveIntLocal(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalInt(dst, frame.getLocalInt(src)) + return + } +} + +class CmdMoveReal(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setReal(dst, frame.getReal(src)) + return + } +} + +class CmdMoveBool(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getBool(src)) + return + } +} + +class CmdConstObj(internal val constId: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + when (val c = frame.fn.constants[constId]) { + is BytecodeConst.ObjRef -> { + val obj = c.value + when (obj) { + is ObjInt -> frame.setInt(dst, obj.value) + is ObjReal -> frame.setReal(dst, obj.value) + is ObjBool -> frame.setBool(dst, obj.value) + else -> frame.setObj(dst, obj) + } + } + is BytecodeConst.StringVal -> frame.setObj(dst, ObjString(c.value)) + else -> error("CONST_OBJ expects ObjRef/StringVal at $constId") + } + return + } +} + +class CmdConstInt(internal val constId: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val c = frame.fn.constants[constId] as? BytecodeConst.IntVal + ?: error("CONST_INT expects IntVal at $constId") + frame.setInt(dst, c.value) + return + } +} + +class CmdConstIntLocal(internal val constId: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val c = frame.fn.constants[constId] as? BytecodeConst.IntVal + ?: error("CONST_INT expects IntVal at $constId") + frame.setLocalInt(dst, c.value) + return + } +} + +class CmdConstReal(internal val constId: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val c = frame.fn.constants[constId] as? BytecodeConst.RealVal + ?: error("CONST_REAL expects RealVal at $constId") + frame.setReal(dst, c.value) + return + } +} + +class CmdConstBool(internal val constId: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val c = frame.fn.constants[constId] as? BytecodeConst.Bool + ?: error("CONST_BOOL expects Bool at $constId") + frame.setBool(dst, c.value) + return + } +} + +class CmdConstNull(internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setObj(dst, ObjNull) + return + } +} + +class CmdBoxObj(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setObj(dst, frame.slotToObj(src)) + return + } +} + +class CmdResolveScopeSlot(internal val scopeSlot: Int, internal val addrSlot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.resolveScopeSlotAddr(scopeSlot, addrSlot) + return + } +} + +class CmdLoadObjAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setObj(dst, frame.getAddrObj(addrSlot)) + return + } +} + +class CmdStoreObjAddr(internal val src: Int, internal val addrSlot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setAddrObj(addrSlot, frame.slotToObj(src)) + return + } +} + +class CmdLoadIntAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getAddrInt(addrSlot)) + return + } +} + +class CmdStoreIntAddr(internal val src: Int, internal val addrSlot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setAddrInt(addrSlot, frame.getInt(src)) + return + } +} + +class CmdLoadRealAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setReal(dst, frame.getAddrReal(addrSlot)) + return + } +} + +class CmdStoreRealAddr(internal val src: Int, internal val addrSlot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setAddrReal(addrSlot, frame.getReal(src)) + return + } +} + +class CmdLoadBoolAddr(internal val addrSlot: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getAddrBool(addrSlot)) + return + } +} + +class CmdStoreBoolAddr(internal val src: Int, internal val addrSlot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setAddrBool(addrSlot, frame.getBool(src)) + return + } +} + +class CmdIntToReal(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setReal(dst, frame.getInt(src).toDouble()) + return + } +} + +class CmdRealToInt(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getReal(src).toLong()) + return + } +} + +class CmdBoolToInt(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, if (frame.getBool(src)) 1L else 0L) + return + } +} + +class CmdIntToBool(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(src) != 0L) + return + } +} + +class CmdAddInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) + frame.getInt(b)) + return + } +} + +class CmdAddIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalInt(dst, frame.getLocalInt(a) + frame.getLocalInt(b)) + return + } +} + +class CmdSubInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) - frame.getInt(b)) + return + } +} + +class CmdSubIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalInt(dst, frame.getLocalInt(a) - frame.getLocalInt(b)) + return + } +} + +class CmdMulInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) * frame.getInt(b)) + return + } +} + +class CmdMulIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalInt(dst, frame.getLocalInt(a) * frame.getLocalInt(b)) + return + } +} + +class CmdDivInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) / frame.getInt(b)) + return + } +} + +class CmdDivIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalInt(dst, frame.getLocalInt(a) / frame.getLocalInt(b)) + return + } +} + +class CmdModInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) % frame.getInt(b)) + return + } +} + +class CmdModIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalInt(dst, frame.getLocalInt(a) % frame.getLocalInt(b)) + return + } +} + +class CmdNegInt(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, -frame.getInt(src)) + return + } +} + +class CmdIncInt(internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(slot, frame.getInt(slot) + 1L) + return + } +} + +class CmdIncIntLocal(internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalInt(slot, frame.getLocalInt(slot) + 1L) + return + } +} + +class CmdDecInt(internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(slot, frame.getInt(slot) - 1L) + return + } +} + +class CmdDecIntLocal(internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalInt(slot, frame.getLocalInt(slot) - 1L) + return + } +} + +class CmdAddReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setReal(dst, frame.getReal(a) + frame.getReal(b)) + return + } +} + +class CmdSubReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setReal(dst, frame.getReal(a) - frame.getReal(b)) + return + } +} + +class CmdMulReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setReal(dst, frame.getReal(a) * frame.getReal(b)) + return + } +} + +class CmdDivReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setReal(dst, frame.getReal(a) / frame.getReal(b)) + return + } +} + +class CmdNegReal(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setReal(dst, -frame.getReal(src)) + return + } +} + +class CmdAndInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) and frame.getInt(b)) + return + } +} + +class CmdOrInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) or frame.getInt(b)) + return + } +} + +class CmdXorInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) xor frame.getInt(b)) + return + } +} + +class CmdShlInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) shl frame.getInt(b).toInt()) + return + } +} + +class CmdShrInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) shr frame.getInt(b).toInt()) + return + } +} + +class CmdUshrInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(a) ushr frame.getInt(b).toInt()) + return + } +} + +class CmdInvInt(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setInt(dst, frame.getInt(src).inv()) + return + } +} + +class CmdCmpEqInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a) == frame.getInt(b)) + return + } +} + +class CmdCmpEqIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalBool(dst, frame.getLocalInt(a) == frame.getLocalInt(b)) + return + } +} + +class CmdCmpNeqInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a) != frame.getInt(b)) + return + } +} + +class CmdCmpNeqIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalBool(dst, frame.getLocalInt(a) != frame.getLocalInt(b)) + return + } +} + +class CmdCmpLtInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a) < frame.getInt(b)) + return + } +} + +class CmdCmpLtIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalBool(dst, frame.getLocalInt(a) < frame.getLocalInt(b)) + return + } +} + +class CmdCmpLteInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a) <= frame.getInt(b)) + return + } +} + +class CmdCmpLteIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalBool(dst, frame.getLocalInt(a) <= frame.getLocalInt(b)) + return + } +} + +class CmdCmpGtInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a) > frame.getInt(b)) + return + } +} + +class CmdCmpGtIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalBool(dst, frame.getLocalInt(a) > frame.getLocalInt(b)) + return + } +} + +class CmdCmpGteInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a) >= frame.getInt(b)) + return + } +} + +class CmdCmpGteIntLocal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setLocalBool(dst, frame.getLocalInt(a) >= frame.getLocalInt(b)) + return + } +} + +class CmdCmpEqReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) == frame.getReal(b)) + return + } +} + +class CmdCmpNeqReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) != frame.getReal(b)) + return + } +} + +class CmdCmpLtReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) < frame.getReal(b)) + return + } +} + +class CmdCmpLteReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) <= frame.getReal(b)) + return + } +} + +class CmdCmpGtReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) > frame.getReal(b)) + return + } +} + +class CmdCmpGteReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) >= frame.getReal(b)) + return + } +} + +class CmdCmpEqBool(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getBool(a) == frame.getBool(b)) + return + } +} + +class CmdCmpNeqBool(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getBool(a) != frame.getBool(b)) + return + } +} + +class CmdCmpEqIntReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a).toDouble() == frame.getReal(b)) + return + } +} + +class CmdCmpEqRealInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) == frame.getInt(b).toDouble()) + return + } +} + +class CmdCmpLtIntReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a).toDouble() < frame.getReal(b)) + return + } +} + +class CmdCmpLtRealInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) < frame.getInt(b).toDouble()) + return + } +} + +class CmdCmpLteIntReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a).toDouble() <= frame.getReal(b)) + return + } +} + +class CmdCmpLteRealInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) <= frame.getInt(b).toDouble()) + return + } +} + +class CmdCmpGtIntReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a).toDouble() > frame.getReal(b)) + return + } +} + +class CmdCmpGtRealInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) > frame.getInt(b).toDouble()) + return + } +} + +class CmdCmpGteIntReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a).toDouble() >= frame.getReal(b)) + return + } +} + +class CmdCmpGteRealInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) >= frame.getInt(b).toDouble()) + return + } +} + +class CmdCmpNeqIntReal(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getInt(a).toDouble() != frame.getReal(b)) + return + } +} + +class CmdCmpNeqRealInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getReal(a) != frame.getInt(b).toDouble()) + return + } +} + +class CmdCmpEqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getObj(a) == frame.getObj(b)) + return + } +} + +class CmdCmpNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getObj(a) != frame.getObj(b)) + return + } +} + +class CmdCmpRefEqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getObj(a) === frame.getObj(b)) + return + } +} + +class CmdCmpRefNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getObj(a) !== frame.getObj(b)) + return + } +} + +class CmdNotBool(internal val src: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, !frame.getBool(src)) + return + } +} + +class CmdAndBool(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getBool(a) && frame.getBool(b)) + return + } +} + +class CmdOrBool(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getBool(a) || frame.getBool(b)) + return + } +} + +class CmdCmpLtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) < 0) + return + } +} + +class CmdCmpLteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) <= 0) + return + } +} + +class CmdCmpGtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) > 0) + return + } +} + +class CmdCmpGteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) >= 0) + return + } +} + +class CmdAddObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setObj(dst, frame.getObj(a).plus(frame.scope, frame.getObj(b))) + return + } +} + +class CmdSubObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setObj(dst, frame.getObj(a).minus(frame.scope, frame.getObj(b))) + return + } +} + +class CmdMulObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setObj(dst, frame.getObj(a).mul(frame.scope, frame.getObj(b))) + return + } +} + +class CmdDivObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setObj(dst, frame.getObj(a).div(frame.scope, frame.getObj(b))) + return + } +} + +class CmdModObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.setObj(dst, frame.getObj(a).mod(frame.scope, frame.getObj(b))) + return + } +} + +class CmdJmp(internal val target: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.ip = target + return + } +} + +class CmdJmpIfTrue(internal val cond: Int, internal val target: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + if (frame.getBool(cond)) { + frame.ip = target + } + return + } +} + +class CmdJmpIfFalse(internal val cond: Int, internal val target: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + if (!frame.getBool(cond)) { + frame.ip = target + } + return + } +} + +class CmdRet(internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.vm.result = frame.slotToObj(slot) + return + } +} + +class CmdRetVoid : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.vm.result = ObjVoid + return + } +} + +class CmdPushScope(internal val planId: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val planConst = frame.fn.constants[planId] as? BytecodeConst.SlotPlan + ?: error("PUSH_SCOPE expects SlotPlan at $planId") + frame.pushScope(planConst.plan) + return + } +} + +class CmdPopScope : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.popScope() + return + } +} + +class CmdPushSlotPlan(internal val planId: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val planConst = frame.fn.constants[planId] as? BytecodeConst.SlotPlan + ?: error("PUSH_SLOT_PLAN expects SlotPlan at $planId") + frame.pushSlotPlan(planConst.plan) + return + } +} + +class CmdPopSlotPlan : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.popSlotPlan() + return + } +} + +class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl + ?: error("DECL_LOCAL expects LocalDecl at $constId") + val value = frame.slotToObj(slot).byValueCopy() + frame.scope.addItem( + decl.name, + decl.isMutable, + value, + decl.visibility, + recordType = ObjRecord.Type.Other, + isTransient = decl.isTransient + ) + return + } +} + +class CmdCallDirect( + internal val id: Int, + internal val argBase: Int, + internal val argCount: Int, + internal val dst: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncFrameToScope() + } + val ref = frame.fn.constants.getOrNull(id) as? BytecodeConst.ObjRef + ?: error("CALL_DIRECT expects ObjRef at $id") + val callee = ref.value + val args = frame.buildArguments(argBase, argCount) + val result = if (PerfFlags.SCOPE_POOL) { + frame.scope.withChildFrame(args) { child -> callee.callOn(child) } + } else { + callee.callOn(frame.scope.createChildScope(frame.scope.pos, args = args)) + } + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncScopeToFrame() + } + frame.storeObjResult(dst, result) + return + } +} + +class CmdCallVirtual( + internal val recvSlot: Int, + internal val methodId: Int, + internal val argBase: Int, + internal val argCount: Int, + internal val dst: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncFrameToScope() + } + val receiver = frame.slotToObj(recvSlot) + val nameConst = frame.fn.constants.getOrNull(methodId) as? BytecodeConst.StringVal + ?: error("CALL_VIRTUAL expects StringVal at $methodId") + val args = frame.buildArguments(argBase, argCount) + val site = frame.methodCallSites.getOrPut(frame.ip - 1) { MethodCallSite(nameConst.value) } + val result = site.invoke(frame.scope, receiver, args) + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncScopeToFrame() + } + frame.storeObjResult(dst, result) + return + } +} + +class CmdCallFallback( + internal val id: Int, + internal val argBase: Int, + internal val argCount: Int, + internal val dst: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncFrameToScope() + } + val stmt = frame.fn.fallbackStatements.getOrNull(id) + ?: error("Fallback statement not found: $id") + val args = frame.buildArguments(argBase, argCount) + val result = if (PerfFlags.SCOPE_POOL) { + frame.scope.withChildFrame(args) { child -> stmt.execute(child) } + } else { + stmt.execute(frame.scope.createChildScope(frame.scope.pos, args = args)) + } + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncScopeToFrame() + } + frame.storeObjResult(dst, result) + return + } +} + +class CmdCallSlot( + internal val calleeSlot: Int, + internal val argBase: Int, + internal val argCount: Int, + internal val dst: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncFrameToScope() + } + val callee = frame.slotToObj(calleeSlot) + val args = frame.buildArguments(argBase, argCount) + val result = if (PerfFlags.SCOPE_POOL) { + frame.scope.withChildFrame(args) { child -> callee.callOn(child) } + } else { + callee.callOn(frame.scope.createChildScope(frame.scope.pos, args = args)) + } + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncScopeToFrame() + } + frame.storeObjResult(dst, result) + return + } +} + +class CmdGetField( + internal val recvSlot: Int, + internal val fieldId: Int, + internal val dst: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val receiver = frame.slotToObj(recvSlot) + val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal + ?: error("GET_FIELD expects StringVal at $fieldId") + val result = receiver.readField(frame.scope, nameConst.value).value + frame.storeObjResult(dst, result) + return + } +} + +class CmdSetField( + internal val recvSlot: Int, + internal val fieldId: Int, + internal val valueSlot: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val receiver = frame.slotToObj(recvSlot) + val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal + ?: error("SET_FIELD expects StringVal at $fieldId") + receiver.writeField(frame.scope, nameConst.value, frame.slotToObj(valueSlot)) + return + } +} + +class CmdGetIndex( + internal val targetSlot: Int, + internal val indexSlot: Int, + internal val dst: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val result = frame.slotToObj(targetSlot).getAt(frame.scope, frame.slotToObj(indexSlot)) + frame.storeObjResult(dst, result) + return + } +} + +class CmdSetIndex( + internal val targetSlot: Int, + internal val indexSlot: Int, + internal val valueSlot: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + frame.slotToObj(targetSlot).putAt(frame.scope, frame.slotToObj(indexSlot), frame.slotToObj(valueSlot)) + return + } +} + +class CmdEvalFallback(internal val id: Int, internal val dst: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val stmt = frame.fn.fallbackStatements.getOrNull(id) + ?: error("Fallback statement not found: $id") + frame.syncFrameToScope() + val result = stmt.execute(frame.scope) + frame.syncScopeToFrame() + frame.storeObjResult(dst, result) + return + } +} + +class CmdFrame( + val vm: CmdVm, + val fn: CmdFunction, + scope0: Scope, + args: List, +) { + companion object { + private const val ARG_PLAN_FLAG = 0x8000 + private const val ARG_PLAN_MASK = 0x7FFF + } + + var ip: Int = 0 + var scope: Scope = scope0 + val methodCallSites: MutableMap = CmdCallSiteCache.methodCallSites(fn) + + internal val scopeStack = ArrayDeque() + internal val scopeVirtualStack = ArrayDeque() + internal val slotPlanStack = ArrayDeque>() + internal val slotPlanScopeStack = ArrayDeque() + private var scopeDepth = 0 + private var virtualDepth = 0 + + internal val frame = BytecodeFrame(fn.localCount, args.size) + private val addrScopes: Array = arrayOfNulls(fn.addrCount) + private val addrIndices: IntArray = IntArray(fn.addrCount) + private val addrScopeSlots: IntArray = IntArray(fn.addrCount) + + init { + for (i in args.indices) { + frame.setObj(frame.argBase + i, args[i]) + } + } + + fun pushScope(plan: Map) { + if (scope.skipScopeCreation) { + val snapshot = scope.applySlotPlanWithSnapshot(plan) + slotPlanStack.addLast(snapshot) + virtualDepth += 1 + scopeStack.addLast(scope) + scopeVirtualStack.addLast(true) + } else { + scopeStack.addLast(scope) + scopeVirtualStack.addLast(false) + scope = scope.createChildScope() + if (plan.isNotEmpty()) { + scope.applySlotPlan(plan) + } + } + scopeDepth += 1 + } + + fun popScope() { + val isVirtual = scopeVirtualStack.removeLastOrNull() + ?: error("Scope stack underflow in POP_SCOPE") + if (isVirtual) { + val snapshot = slotPlanStack.removeLastOrNull() + ?: error("Slot plan stack underflow in POP_SCOPE") + scope.restoreSlotPlan(snapshot) + virtualDepth -= 1 + } + scope = scopeStack.removeLastOrNull() + ?: error("Scope stack underflow in POP_SCOPE") + scopeDepth -= 1 + } + + fun pushSlotPlan(plan: Map) { + if (scope.hasSlotPlanConflict(plan)) { + scopeStack.addLast(scope) + slotPlanScopeStack.addLast(true) + scope = scope.createChildScope() + if (plan.isNotEmpty()) { + scope.applySlotPlan(plan) + } + } else { + val snapshot = scope.applySlotPlanWithSnapshot(plan) + slotPlanStack.addLast(snapshot) + slotPlanScopeStack.addLast(false) + virtualDepth += 1 + } + scopeDepth += 1 + } + + fun popSlotPlan() { + val pushedScope = slotPlanScopeStack.removeLastOrNull() + ?: error("Slot plan stack underflow in POP_SLOT_PLAN") + if (pushedScope) { + scope = scopeStack.removeLastOrNull() + ?: error("Scope stack underflow in POP_SLOT_PLAN") + } else { + val snapshot = slotPlanStack.removeLastOrNull() + ?: error("Slot plan stack underflow in POP_SLOT_PLAN") + scope.restoreSlotPlan(snapshot) + virtualDepth -= 1 + } + scopeDepth -= 1 + } + + fun getObj(slot: Int): Obj { + return if (slot < fn.scopeSlotCount) { + getScopeSlotValue(slot) + } else { + frame.getObj(slot - fn.scopeSlotCount) + } + } + + fun setObj(slot: Int, value: Obj) { + if (slot < fn.scopeSlotCount) { + val target = resolveScope(scope, fn.scopeSlotDepths[slot]) + val index = ensureScopeSlot(target, slot) + target.setSlotValue(index, value) + } else { + frame.setObj(slot - fn.scopeSlotCount, value) + } + } + + fun getInt(slot: Int): Long { + return if (slot < fn.scopeSlotCount) { + getScopeSlotValue(slot).toLong() + } else { + frame.getInt(slot - fn.scopeSlotCount) + } + } + + fun getLocalInt(local: Int): Long = frame.getInt(local) + + fun setInt(slot: Int, value: Long) { + if (slot < fn.scopeSlotCount) { + val target = resolveScope(scope, fn.scopeSlotDepths[slot]) + val index = ensureScopeSlot(target, slot) + target.setSlotValue(index, ObjInt.of(value)) + } else { + frame.setInt(slot - fn.scopeSlotCount, value) + } + } + + fun setLocalInt(local: Int, value: Long) { + frame.setInt(local, value) + } + + fun getReal(slot: Int): Double { + return if (slot < fn.scopeSlotCount) { + getScopeSlotValue(slot).toDouble() + } else { + frame.getReal(slot - fn.scopeSlotCount) + } + } + + fun setReal(slot: Int, value: Double) { + if (slot < fn.scopeSlotCount) { + val target = resolveScope(scope, fn.scopeSlotDepths[slot]) + val index = ensureScopeSlot(target, slot) + target.setSlotValue(index, ObjReal.of(value)) + } else { + frame.setReal(slot - fn.scopeSlotCount, value) + } + } + + fun getBool(slot: Int): Boolean { + return if (slot < fn.scopeSlotCount) { + getScopeSlotValue(slot).toBool() + } else { + frame.getBool(slot - fn.scopeSlotCount) + } + } + + fun getLocalBool(local: Int): Boolean = frame.getBool(local) + + fun setBool(slot: Int, value: Boolean) { + if (slot < fn.scopeSlotCount) { + val target = resolveScope(scope, fn.scopeSlotDepths[slot]) + val index = ensureScopeSlot(target, slot) + target.setSlotValue(index, if (value) ObjTrue else ObjFalse) + } else { + frame.setBool(slot - fn.scopeSlotCount, value) + } + } + + fun setLocalBool(local: Int, value: Boolean) { + frame.setBool(local, value) + } + + fun resolveScopeSlotAddr(scopeSlot: Int, addrSlot: Int) { + val target = resolveScope(scope, fn.scopeSlotDepths[scopeSlot]) + val index = ensureScopeSlot(target, scopeSlot) + addrScopes[addrSlot] = target + addrIndices[addrSlot] = index + addrScopeSlots[addrSlot] = scopeSlot + } + + fun getAddrObj(addrSlot: Int): Obj { + return getScopeSlotValueAtAddr(addrSlot) + } + + fun setAddrObj(addrSlot: Int, value: Obj) { + setScopeSlotValueAtAddr(addrSlot, value) + } + + fun getAddrInt(addrSlot: Int): Long { + return getScopeSlotValueAtAddr(addrSlot).toLong() + } + + fun setAddrInt(addrSlot: Int, value: Long) { + setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value)) + } + + fun getAddrReal(addrSlot: Int): Double { + return getScopeSlotValueAtAddr(addrSlot).toDouble() + } + + fun setAddrReal(addrSlot: Int, value: Double) { + setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value)) + } + + fun getAddrBool(addrSlot: Int): Boolean { + return getScopeSlotValueAtAddr(addrSlot).toBool() + } + + fun setAddrBool(addrSlot: Int, value: Boolean) { + setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse) + } + + fun slotToObj(slot: Int): Obj { + if (slot < fn.scopeSlotCount) { + return getScopeSlotValue(slot) + } + val local = slot - fn.scopeSlotCount + return when (frame.getSlotTypeCode(local)) { + SlotType.INT.code -> ObjInt.of(frame.getInt(local)) + SlotType.REAL.code -> ObjReal.of(frame.getReal(local)) + SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse + SlotType.OBJ.code -> frame.getObj(local) + else -> ObjVoid + } + } + + fun storeObjResult(dst: Int, result: Obj) { + when (result) { + is ObjInt -> setInt(dst, result.value) + is ObjReal -> setReal(dst, result.value) + is ObjBool -> setBool(dst, result.value) + else -> setObj(dst, result) + } + } + + fun syncFrameToScope() { + val names = fn.localSlotNames + if (names.isEmpty()) return + for (i in names.indices) { + val name = names[i] ?: continue + val target = resolveLocalScope(i) ?: continue + val value = localSlotToObj(i) + val rec = target.getLocalRecordDirect(name) + if (rec == null) { + val isMutable = fn.localSlotMutables.getOrElse(i) { true } + target.addItem(name, isMutable, value) + } else { + rec.value = value + } + } + } + + fun syncScopeToFrame() { + val names = fn.localSlotNames + if (names.isEmpty()) return + for (i in names.indices) { + val name = names[i] ?: continue + val target = resolveLocalScope(i) ?: continue + val rec = target.getLocalRecordDirect(name) ?: continue + val value = rec.value + when (value) { + is ObjInt -> frame.setInt(i, value.value) + is ObjReal -> frame.setReal(i, value.value) + is ObjBool -> frame.setBool(i, value.value) + else -> frame.setObj(i, value) + } + } + } + + suspend fun buildArguments(argBase: Int, argCount: Int): Arguments { + if (argCount == 0) return Arguments.EMPTY + if ((argCount and ARG_PLAN_FLAG) != 0) { + val planId = argCount and ARG_PLAN_MASK + val plan = fn.constants.getOrNull(planId) as? BytecodeConst.CallArgsPlan + ?: error("CALL args plan not found: $planId") + return buildArgumentsFromPlan(argBase, plan) + } + val list = ArrayList(argCount) + for (i in 0 until argCount) { + list.add(slotToObj(argBase + i)) + } + return Arguments(list) + } + + private suspend fun buildArgumentsFromPlan( + argBase: Int, + plan: BytecodeConst.CallArgsPlan, + ): Arguments { + val positional = ArrayList(plan.specs.size) + var named: LinkedHashMap? = null + var namedSeen = false + for ((idx, spec) in plan.specs.withIndex()) { + val value = slotToObj(argBase + idx) + val name = spec.name + if (name != null) { + if (named == null) named = linkedMapOf() + if (named.containsKey(name)) scope.raiseIllegalArgument("argument '$name' is already set") + named[name] = value + namedSeen = true + continue + } + if (spec.isSplat) { + when { + value is ObjMap -> { + if (named == null) named = linkedMapOf() + for ((k, v) in value.map) { + if (k !is ObjString) scope.raiseIllegalArgument("named splat expects a Map with string keys") + val key = k.value + if (named.containsKey(key)) scope.raiseIllegalArgument("argument '$key' is already set") + named[key] = v + } + namedSeen = true + } + value is ObjList -> { + if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments") + positional.addAll(value.list) + } + value.isInstanceOf(ObjIterable) -> { + if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments") + val list = (value.invokeInstanceMethod(scope, "toList") as ObjList).list + positional.addAll(list) + } + else -> scope.raiseClassCastError("expected list of objects for splat argument") + } + } else { + if (namedSeen) { + val isLast = idx == plan.specs.lastIndex + if (!(isLast && plan.tailBlock)) { + scope.raiseIllegalArgument("positional argument cannot follow named arguments") + } + } + positional.add(value) + } + } + return Arguments(positional, plan.tailBlock, named ?: emptyMap()) + } + + private fun resolveLocalScope(localIndex: Int): Scope? { + val depth = fn.localSlotDepths.getOrNull(localIndex) ?: return scope + val relativeDepth = scopeDepth - depth + if (relativeDepth < 0) return null + return if (relativeDepth == 0) scope else resolveScope(scope, relativeDepth) + } + + private fun localSlotToObj(localIndex: Int): Obj { + return when (frame.getSlotTypeCode(localIndex)) { + SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex)) + SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex)) + SlotType.BOOL.code -> if (frame.getBool(localIndex)) ObjTrue else ObjFalse + SlotType.OBJ.code -> frame.getObj(localIndex) + else -> ObjNull + } + } + + private fun getScopeSlotValue(slot: Int): Obj { + val target = resolveScope(scope, fn.scopeSlotDepths[slot]) + val index = ensureScopeSlot(target, slot) + val record = target.getSlotRecord(index) + if (record.value !== ObjUnset) return record.value + val name = fn.scopeSlotNames[slot] ?: return record.value + val resolved = target.get(name) ?: return record.value + if (resolved.value !== ObjUnset) { + target.updateSlotFor(name, resolved) + } + return resolved.value + } + + private fun getScopeSlotValueAtAddr(addrSlot: Int): Obj { + val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved") + val index = addrIndices[addrSlot] + val record = target.getSlotRecord(index) + if (record.value !== ObjUnset) return record.value + val slotId = addrScopeSlots[addrSlot] + val name = fn.scopeSlotNames[slotId] ?: return record.value + val resolved = target.get(name) ?: return record.value + if (resolved.value !== ObjUnset) { + target.updateSlotFor(name, resolved) + } + return resolved.value + } + + private fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) { + val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved") + val index = addrIndices[addrSlot] + target.setSlotValue(index, value) + } + + private fun ensureScopeSlot(target: Scope, slot: Int): Int { + val index = fn.scopeSlotIndices[slot] + if (index < target.slotCount) return index + val name = fn.scopeSlotNames[slot] ?: return index + target.applySlotPlan(mapOf(name to index)) + val existing = target.getLocalRecordDirect(name) + if (existing != null) { + target.updateSlotFor(name, existing) + } else { + val resolved = target.get(name) + if (resolved != null) { + target.updateSlotFor(name, resolved) + } + } + return index + } + + private fun resolveScope(start: Scope, depth: Int): Scope { + if (depth == 0) return start + var effectiveDepth = depth + if (virtualDepth > 0) { + if (effectiveDepth <= virtualDepth) return start + effectiveDepth -= virtualDepth + } + val next = when (start) { + is net.sergeych.lyng.ClosureScope -> start.closureScope + else -> start.parent + } + return next?.let { resolveScope(it, effectiveDepth - 1) } + ?: error("Scope depth $depth is out of range") + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt index ab7c80f..94ae25a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -124,6 +124,15 @@ enum class Opcode(val code: Int) { SET_INDEX(0xA3), EVAL_FALLBACK(0xB0), + RESOLVE_SCOPE_SLOT(0xB1), + LOAD_OBJ_ADDR(0xB2), + STORE_OBJ_ADDR(0xB3), + LOAD_INT_ADDR(0xB4), + STORE_INT_ADDR(0xB5), + LOAD_REAL_ADDR(0xB6), + STORE_REAL_ADDR(0xB7), + LOAD_BOOL_ADDR(0xB8), + STORE_BOOL_ADDR(0xB9), ; companion object { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt index 53a53b8..b2dc520 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt @@ -1991,6 +1991,10 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef { scope.assign(rec, name, newValue) return } + scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx)?.let { rec -> + scope.assign(rec, name, newValue) + return + } scope[name]?.let { stored -> scope.assign(stored, name, newValue) return @@ -2007,6 +2011,10 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef { return } } + scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx)?.let { rec -> + scope.assign(rec, name, newValue) + return + } scope[name]?.let { stored -> scope.assign(stored, name, newValue) return @@ -2417,6 +2425,7 @@ class LocalSlotRef( val name: String, internal val slot: Int, internal val depth: Int, + internal val scopeDepth: Int, internal val isMutable: Boolean, internal val isDelegated: Boolean, private val atPos: Pos, diff --git a/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt b/lynglib/src/commonTest/kotlin/CmdVmTest.kt similarity index 89% rename from lynglib/src/commonTest/kotlin/BytecodeVmTest.kt rename to lynglib/src/commonTest/kotlin/CmdVmTest.kt index aa0cd44..dc2a664 100644 --- a/lynglib/src/commonTest/kotlin/BytecodeVmTest.kt +++ b/lynglib/src/commonTest/kotlin/CmdVmTest.kt @@ -19,10 +19,10 @@ import net.sergeych.lyng.IfStatement import net.sergeych.lyng.Pos import net.sergeych.lyng.Scope import net.sergeych.lyng.Statement -import net.sergeych.lyng.bytecode.BytecodeBuilder +import net.sergeych.lyng.bytecode.CmdBuilder import net.sergeych.lyng.bytecode.BytecodeCompiler import net.sergeych.lyng.bytecode.BytecodeConst -import net.sergeych.lyng.bytecode.BytecodeVm +import net.sergeych.lyng.bytecode.CmdVm import net.sergeych.lyng.bytecode.Opcode import net.sergeych.lyng.obj.BinaryOpRef import net.sergeych.lyng.obj.BinOp @@ -45,10 +45,10 @@ import net.sergeych.lyng.obj.toLong import kotlin.test.Test import kotlin.test.assertEquals -class BytecodeVmTest { +class CmdVmTest { @Test fun addsIntConstants() = kotlinx.coroutines.test.runTest { - val builder = BytecodeBuilder() + val builder = CmdBuilder() val k0 = builder.addConst(BytecodeConst.IntVal(2)) val k1 = builder.addConst(BytecodeConst.IntVal(3)) builder.emit(Opcode.CONST_INT, k0, 0) @@ -56,7 +56,7 @@ class BytecodeVmTest { 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()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(5, result.toInt()) } @@ -80,7 +80,7 @@ class BytecodeVmTest { ) val ifStmt = IfStatement(cond, thenStmt, elseStmt, net.sergeych.lyng.Pos.builtIn) val fn = BytecodeCompiler().compileStatement("ifTest", ifStmt) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(10, result.toInt()) } @@ -104,7 +104,7 @@ class BytecodeVmTest { error("bytecode compile failed for ifNoElse") } }!! - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(ObjVoid, result) } @@ -120,7 +120,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val fn = BytecodeCompiler().compileExpression("andShort", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(false, result.toBool()) } @@ -136,7 +136,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val fn = BytecodeCompiler().compileExpression("orShort", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(true, result.toBool()) } @@ -151,7 +151,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val fn = BytecodeCompiler().compileExpression("realPlus", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(5.75, result.toDouble()) } @@ -163,7 +163,7 @@ class BytecodeVmTest { scope.args[0].toLong() + scope.args[1].toLong() ) } - val builder = BytecodeBuilder() + val builder = CmdBuilder() val fnId = builder.addConst(BytecodeConst.ObjRef(callable)) val arg0 = builder.addConst(BytecodeConst.IntVal(2L)) val arg1 = builder.addConst(BytecodeConst.IntVal(3L)) @@ -173,7 +173,7 @@ class BytecodeVmTest { builder.emit(Opcode.CALL_SLOT, 0, 1, 2, 3) builder.emit(Opcode.RET, 3) val fn = builder.build("callSlot", localCount = 4) - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(5, result.toInt()) } @@ -188,7 +188,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val ltFn = BytecodeCompiler().compileExpression("mixedLt", ltExpr) ?: error("bytecode compile failed") - val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList()) + val ltResult = CmdVm().execute(ltFn, Scope(), emptyList()) assertEquals(true, ltResult.toBool()) val eqExpr = ExpressionStatement( @@ -200,7 +200,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val eqFn = BytecodeCompiler().compileExpression("mixedEq", eqExpr) ?: error("bytecode compile failed") - val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList()) + val eqResult = CmdVm().execute(eqFn, Scope(), emptyList()) assertEquals(true, eqResult.toBool()) } @@ -224,7 +224,7 @@ class BytecodeVmTest { ) val expr = ExpressionStatement(callRef, Pos.builtIn) val fn = BytecodeCompiler().compileExpression("tailBlockArgs", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(true, result.toBool()) } @@ -249,7 +249,7 @@ class BytecodeVmTest { ) val expr = ExpressionStatement(callRef, Pos.builtIn) val fn = BytecodeCompiler().compileExpression("namedArgs", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(5, result.toInt()) } @@ -275,7 +275,7 @@ class BytecodeVmTest { ) val expr = ExpressionStatement(callRef, Pos.builtIn) val fn = BytecodeCompiler().compileExpression("splatArgs", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(3, result.toInt()) } @@ -290,7 +290,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val fn = BytecodeCompiler().compileExpression("mixedPlus", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(5.5, result.toDouble()) } @@ -305,13 +305,13 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val fn = BytecodeCompiler().compileExpression("mixedNeq", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(true, result.toBool()) } @Test fun localSlotTypeTrackingEnablesArithmetic() = kotlinx.coroutines.test.runTest { - val slotRef = LocalSlotRef("a", 0, 0, true, false, net.sergeych.lyng.Pos.builtIn) + val slotRef = LocalSlotRef("a", 0, 0, 0, true, false, net.sergeych.lyng.Pos.builtIn) val assign = AssignRef( slotRef, ConstRef(ObjInt.of(2).asReadonly), @@ -327,13 +327,13 @@ class BytecodeVmTest { ) val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed") val scope = Scope().apply { applySlotPlan(mapOf("a" to 0)) } - val result = BytecodeVm().execute(fn, scope, emptyList()) + val result = CmdVm().execute(fn, scope, emptyList()) assertEquals(4, result.toInt()) } @Test fun parentScopeSlotAccessWorks() = kotlinx.coroutines.test.runTest { - val parentRef = LocalSlotRef("a", 0, 1, true, false, net.sergeych.lyng.Pos.builtIn) + val parentRef = LocalSlotRef("a", 0, 1, 0, true, false, net.sergeych.lyng.Pos.builtIn) val expr = ExpressionStatement( BinaryOpRef( BinOp.PLUS, @@ -348,7 +348,7 @@ class BytecodeVmTest { setSlotValue(0, ObjInt.of(3)) } val child = Scope(parent) - val result = BytecodeVm().execute(fn, child, emptyList()) + val result = CmdVm().execute(fn, child, emptyList()) assertEquals(5, result.toInt()) } @@ -363,7 +363,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val fn = BytecodeCompiler().compileExpression("objEq", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals(true, result.toBool()) } @@ -379,7 +379,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val eqFn = BytecodeCompiler().compileExpression("objRefEq", eqExpr) ?: error("bytecode compile failed") - val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList()) + val eqResult = CmdVm().execute(eqFn, Scope(), emptyList()) assertEquals(true, eqResult.toBool()) val neqExpr = ExpressionStatement( @@ -391,7 +391,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val neqFn = BytecodeCompiler().compileExpression("objRefNeq", neqExpr) ?: error("bytecode compile failed") - val neqResult = BytecodeVm().execute(neqFn, Scope(), emptyList()) + val neqResult = CmdVm().execute(neqFn, Scope(), emptyList()) assertEquals(true, neqResult.toBool()) } @@ -406,7 +406,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val ltFn = BytecodeCompiler().compileExpression("objLt", ltExpr) ?: error("bytecode compile failed") - val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList()) + val ltResult = CmdVm().execute(ltFn, Scope(), emptyList()) assertEquals(true, ltResult.toBool()) val gteExpr = ExpressionStatement( @@ -418,7 +418,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val gteFn = BytecodeCompiler().compileExpression("objGte", gteExpr) ?: error("bytecode compile failed") - val gteResult = BytecodeVm().execute(gteFn, Scope(), emptyList()) + val gteResult = CmdVm().execute(gteFn, Scope(), emptyList()) assertEquals(true, gteResult.toBool()) } @@ -433,7 +433,7 @@ class BytecodeVmTest { net.sergeych.lyng.Pos.builtIn ) val fn = BytecodeCompiler().compileExpression("objPlus", expr) ?: error("bytecode compile failed") - val result = BytecodeVm().execute(fn, Scope(), emptyList()) + val result = CmdVm().execute(fn, Scope(), emptyList()) assertEquals("ab", (result as ObjString).value) } } diff --git a/lynglib/src/commonTest/kotlin/NestedRangeBenchmarkTest.kt b/lynglib/src/commonTest/kotlin/NestedRangeBenchmarkTest.kt index 808c8af..4e3ff84 100644 --- a/lynglib/src/commonTest/kotlin/NestedRangeBenchmarkTest.kt +++ b/lynglib/src/commonTest/kotlin/NestedRangeBenchmarkTest.kt @@ -21,7 +21,7 @@ import net.sergeych.lyng.Compiler import net.sergeych.lyng.ForInStatement import net.sergeych.lyng.Script import net.sergeych.lyng.Statement -import net.sergeych.lyng.bytecode.BytecodeDisassembler +import net.sergeych.lyng.bytecode.CmdDisassembler import net.sergeych.lyng.bytecode.BytecodeStatement import net.sergeych.lyng.obj.ObjInt import kotlin.time.TimeSource @@ -56,8 +56,11 @@ class NestedRangeBenchmarkTest { val scope = Script.newScope() scope.eval(script) val fnDisasm = scope.disassembleSymbol("naiveCountHappyNumbers") - println("[DEBUG_LOG] [BENCH] nested-happy function naiveCountHappyNumbers bytecode:\n$fnDisasm") + println("[DEBUG_LOG] [BENCH] nested-happy function naiveCountHappyNumbers cmd:\n$fnDisasm") + runMode(scope) + } + private suspend fun runMode(scope: net.sergeych.lyng.Scope) { val start = TimeSource.Monotonic.markNow() val result = scope.eval("naiveCountHappyNumbers()") as ObjInt val elapsedMs = start.elapsedNow().inWholeMilliseconds @@ -83,13 +86,13 @@ class NestedRangeBenchmarkTest { "$slotName@${fn.scopeSlotDepths[idx]}:${fn.scopeSlotIndices[idx]}" } println("[DEBUG_LOG] [BENCH] nested-happy slots depth=$depth: ${slots.joinToString(", ")}") - val disasm = BytecodeDisassembler.disassemble(fn) - println("[DEBUG_LOG] [BENCH] nested-happy bytecode depth=$depth:\n$disasm") + val disasm = CmdDisassembler.disassemble(fn) + println("[DEBUG_LOG] [BENCH] nested-happy cmd depth=$depth:\n$disasm") current = original.body depth += 1 } if (depth == 1) { - println("[DEBUG_LOG] [BENCH] nested-happy bytecode: ") + println("[DEBUG_LOG] [BENCH] nested-happy cmd: ") } } diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index c64d8e1..578c7f7 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -2822,10 +2822,10 @@ class ScriptTest { x } - (1..100).map { launch { dosomething() } }.forEach { + (1..50).map { launch { dosomething() } }.forEach { assertEquals(5050, it.await()) } - assertEquals( 100, ac.getCounter() ) + assertEquals( 50, ac.getCounter() ) """.trimIndent() ) diff --git a/lynglib/src/jsMain/kotlin/net/sergeych/lyng/PerfDefaults.js.kt b/lynglib/src/jsMain/kotlin/net/sergeych/lyng/PerfDefaults.js.kt index f58f35a..82a9923 100644 --- a/lynglib/src/jsMain/kotlin/net/sergeych/lyng/PerfDefaults.js.kt +++ b/lynglib/src/jsMain/kotlin/net/sergeych/lyng/PerfDefaults.js.kt @@ -43,4 +43,4 @@ actual object PerfDefaults { actual val ARG_SMALL_ARITY_12: Boolean = false actual val INDEX_PIC_SIZE_4: Boolean = false actual val RANGE_FAST_ITER: Boolean = false -} \ No newline at end of file +} diff --git a/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheWasm.kt b/lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheJs.kt similarity index 75% rename from lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheWasm.kt rename to lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheJs.kt index 8f2da71..bb527f7 100644 --- a/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheWasm.kt +++ b/lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheJs.kt @@ -16,10 +16,10 @@ package net.sergeych.lyng.bytecode -internal actual object BytecodeCallSiteCache { - private val cache = mutableMapOf>() +internal actual object CmdCallSiteCache { + private val cache = mutableMapOf>() - actual fun methodCallSites(fn: BytecodeFunction): MutableMap { + actual fun methodCallSites(fn: CmdFunction): MutableMap { return cache.getOrPut(fn) { mutableMapOf() } } } diff --git a/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/PerfDefaults.jvm.kt b/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/PerfDefaults.jvm.kt index 38803ee..60078ee 100644 --- a/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/PerfDefaults.jvm.kt +++ b/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/PerfDefaults.jvm.kt @@ -49,4 +49,4 @@ actual object PerfDefaults { // Range fast-iteration (experimental; OFF by default) actual val RANGE_FAST_ITER: Boolean = true -} \ No newline at end of file +} diff --git a/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheJvm.kt b/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheJvm.kt similarity index 79% rename from lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheJvm.kt rename to lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheJvm.kt index d12ef19..897335d 100644 --- a/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheJvm.kt +++ b/lynglib/src/jvmMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheJvm.kt @@ -18,12 +18,12 @@ package net.sergeych.lyng.bytecode import java.util.IdentityHashMap -internal actual object BytecodeCallSiteCache { +internal actual object CmdCallSiteCache { private val cache = ThreadLocal.withInitial { - IdentityHashMap>() + IdentityHashMap>() } - actual fun methodCallSites(fn: BytecodeFunction): MutableMap { + actual fun methodCallSites(fn: CmdFunction): MutableMap { val map = cache.get() return map.getOrPut(fn) { mutableMapOf() } } diff --git a/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/PerfDefaults.native.kt b/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/PerfDefaults.native.kt index 2f75151..5519a1d 100644 --- a/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/PerfDefaults.native.kt +++ b/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/PerfDefaults.native.kt @@ -43,4 +43,4 @@ actual object PerfDefaults { actual val ARG_SMALL_ARITY_12: Boolean = false actual val INDEX_PIC_SIZE_4: Boolean = false actual val RANGE_FAST_ITER: Boolean = false -} \ No newline at end of file +} diff --git a/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheNative.kt b/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheNative.kt similarity index 76% rename from lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheNative.kt rename to lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheNative.kt index 2a0b4c0..778c1ce 100644 --- a/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheNative.kt +++ b/lynglib/src/nativeMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheNative.kt @@ -17,10 +17,10 @@ package net.sergeych.lyng.bytecode @kotlin.native.concurrent.ThreadLocal -internal actual object BytecodeCallSiteCache { - private val cache = mutableMapOf>() +internal actual object CmdCallSiteCache { + private val cache = mutableMapOf>() - actual fun methodCallSites(fn: BytecodeFunction): MutableMap { + actual fun methodCallSites(fn: CmdFunction): MutableMap { return cache.getOrPut(fn) { mutableMapOf() } } } diff --git a/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/PerfDefaults.wasmJs.kt b/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/PerfDefaults.wasmJs.kt index e5fb671..f1c9978 100644 --- a/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/PerfDefaults.wasmJs.kt +++ b/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/PerfDefaults.wasmJs.kt @@ -43,4 +43,4 @@ actual object PerfDefaults { actual val ARG_SMALL_ARITY_12: Boolean = false actual val INDEX_PIC_SIZE_4: Boolean = false actual val RANGE_FAST_ITER: Boolean = false -} \ No newline at end of file +} diff --git a/lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheJs.kt b/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheWasm.kt similarity index 75% rename from lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheJs.kt rename to lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheWasm.kt index 8f2da71..bb527f7 100644 --- a/lynglib/src/jsMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCallSiteCacheJs.kt +++ b/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/bytecode/CmdCallSiteCacheWasm.kt @@ -16,10 +16,10 @@ package net.sergeych.lyng.bytecode -internal actual object BytecodeCallSiteCache { - private val cache = mutableMapOf>() +internal actual object CmdCallSiteCache { + private val cache = mutableMapOf>() - actual fun methodCallSites(fn: BytecodeFunction): MutableMap { + actual fun methodCallSites(fn: CmdFunction): MutableMap { return cache.getOrPut(fn) { mutableMapOf() } } }