Optimize cmd VM with scoped slot addressing

This commit is contained in:
Sergey Chernov 2026-01-27 14:15:35 +03:00
parent 7de856fc62
commit bef94d3bc5
30 changed files with 2829 additions and 1721 deletions

View File

@ -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
}
}

View File

@ -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<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
IdentityHashMap<CmdFunction, MutableMap<Int, MethodCallSite>>()
}
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
val map = cache.get()
return map.getOrPut(fn) { mutableMapOf() }
}

View File

@ -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("<lambda>")) {
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<String, Int>(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<CatchBlockData>()
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) {

View File

@ -72,4 +72,5 @@ object PerfFlags {
// Specialized non-allocating integer range iteration in hot loops
var RANGE_FAST_ITER: Boolean = PerfDefaults.RANGE_FAST_ITER
}

View File

@ -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<String, ObjRecord>()
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<String, Int>): Boolean {
if (plan.isEmpty() || nameToSlot.isEmpty()) return false
val planIndexToNames = HashMap<Int, HashSet<String>>(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) {

View File

@ -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

View File

@ -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<Operand>)
private val instructions = mutableListOf<Instr>()
private val constPool = mutableListOf<BytecodeConst>()
private val labelPositions = mutableMapOf<Label, Int>()
private var nextLabelId = 0
private val fallbackStatements = mutableListOf<net.sergeych.lyng.Statement>()
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<Operand>) {
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<String?> = 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<Label, Int>()
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<OperandKind> {
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<Byte>(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()
}
}

View File

@ -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<String?>()
private val scopeSlotMap = LinkedHashMap<ScopeSlotKey, Int>()
private val scopeSlotNameMap = LinkedHashMap<ScopeSlotKey, String>()
private val addrSlotByScopeSlot = LinkedHashMap<Int, Int>()
private data class LocalSlotInfo(val name: String, val isMutable: Boolean, val depth: Int)
private val localSlotInfoMap = LinkedHashMap<ScopeSlotKey, LocalSlotInfo>()
private val localSlotIndexByKey = LinkedHashMap<ScopeSlotKey, Int>()
private val localSlotIndexByName = LinkedHashMap<String, Int>()
private var localSlotNames = emptyArray<String?>()
private var localSlotMutables = BooleanArray(0)
private var localSlotDepths = IntArray(0)
private val declaredLocalKeys = LinkedHashSet<ScopeSlotKey>()
private val slotTypes = mutableMapOf<Int, SlotType>()
private val intLoopVarNames = LinkedHashSet<String>()
private val loopVarNames = LinkedHashSet<String>()
private val loopVarSlotIndexByName = LinkedHashMap<String, Int>()
private val loopVarSlotIdByName = LinkedHashMap<String, Int>()
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<String?>(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))

View File

@ -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
}

View File

@ -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<String>(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<OperandKind> {
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)
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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<Operand>)
private val instructions = mutableListOf<Instr>()
private val constPool = mutableListOf<BytecodeConst>()
private val labelPositions = mutableMapOf<Label, Int>()
private var nextLabelId = 0
private val fallbackStatements = mutableListOf<net.sergeych.lyng.Statement>()
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<Operand>) {
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<String?> = emptyArray(),
localSlotNames: Array<String?> = 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<Label, Int>()
for ((label, idx) in labelPositions) {
labelIps[label] = idx
}
val cmds = ArrayList<Cmd>(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<OperandKind> {
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])
}
}
}

View File

@ -16,6 +16,6 @@
package net.sergeych.lyng.bytecode
internal expect object BytecodeCallSiteCache {
fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite>
internal expect object CmdCallSiteCache {
fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite>
}

View File

@ -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<String>(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<Opcode, IntArray> {
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<OperandKind> {
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)
}
}
}

View File

@ -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<String?>,
val slotWidth: Int,
val ipWidth: Int,
val constIdWidth: Int,
val localSlotNames: Array<String?>,
val localSlotMutables: BooleanArray,
val localSlotDepths: IntArray,
val constants: List<BytecodeConst>,
val fallbackStatements: List<net.sergeych.lyng.Statement>,
val code: ByteArray,
val cmds: Array<Cmd>,
) {
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" }
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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 {

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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: <not found>")
println("[DEBUG_LOG] [BENCH] nested-happy cmd: <not found>")
}
}

View File

@ -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()
)

View File

@ -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
}
}

View File

@ -16,10 +16,10 @@
package net.sergeych.lyng.bytecode
internal actual object BytecodeCallSiteCache {
private val cache = mutableMapOf<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
internal actual object CmdCallSiteCache {
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
return cache.getOrPut(fn) { mutableMapOf() }
}
}

View File

@ -49,4 +49,4 @@ actual object PerfDefaults {
// Range fast-iteration (experimental; OFF by default)
actual val RANGE_FAST_ITER: Boolean = true
}
}

View File

@ -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<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
IdentityHashMap<CmdFunction, MutableMap<Int, MethodCallSite>>()
}
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
val map = cache.get()
return map.getOrPut(fn) { mutableMapOf() }
}

View File

@ -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
}
}

View File

@ -17,10 +17,10 @@
package net.sergeych.lyng.bytecode
@kotlin.native.concurrent.ThreadLocal
internal actual object BytecodeCallSiteCache {
private val cache = mutableMapOf<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
internal actual object CmdCallSiteCache {
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
return cache.getOrPut(fn) { mutableMapOf() }
}
}

View File

@ -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
}
}

View File

@ -16,10 +16,10 @@
package net.sergeych.lyng.bytecode
internal actual object BytecodeCallSiteCache {
private val cache = mutableMapOf<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
internal actual object CmdCallSiteCache {
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
return cache.getOrPut(fn) { mutableMapOf() }
}
}