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 ARG_SMALL_ARITY_12: Boolean = false
actual val INDEX_PIC_SIZE_4: Boolean = false actual val INDEX_PIC_SIZE_4: Boolean = false
actual val RANGE_FAST_ITER: Boolean = false actual val RANGE_FAST_ITER: Boolean = false
} }

View File

@ -18,12 +18,12 @@ package net.sergeych.lyng.bytecode
import java.util.IdentityHashMap import java.util.IdentityHashMap
internal actual object BytecodeCallSiteCache { internal actual object CmdCallSiteCache {
private val cache = ThreadLocal.withInitial { 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() val map = cache.get()
return map.getOrPut(fn) { mutableMapOf() } return map.getOrPut(fn) { mutableMapOf() }
} }

View File

@ -373,7 +373,8 @@ class Compiler(
private fun wrapBytecode(stmt: Statement): Statement { private fun wrapBytecode(stmt: Statement): Statement {
if (!useBytecodeStatements) return stmt 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 { private fun wrapFunctionBytecode(stmt: Statement, name: String): Statement {
@ -381,6 +382,27 @@ class Compiler(
return BytecodeStatement.wrap(stmt, "fn@$name", allowLocalSlots = true) 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 { private fun unwrapBytecodeDeep(stmt: Statement): Statement {
return when (stmt) { return when (stmt) {
is BytecodeStatement -> unwrapBytecodeDeep(stmt.original) is BytecodeStatement -> unwrapBytecodeDeep(stmt.original)
@ -397,6 +419,8 @@ class Compiler(
stmt.visibility, stmt.visibility,
init, init,
stmt.isTransient, stmt.isTransient,
stmt.slotIndex,
stmt.slotDepth,
stmt.pos stmt.pos
) )
} }
@ -859,7 +883,7 @@ class Compiler(
label?.let { cc.labels.add(it) } label?.let { cc.labels.add(it) }
slotPlanStack.add(paramSlotPlan) slotPlanStack.add(paramSlotPlan)
val body = try { val parsedBody = try {
inCodeContext(CodeContext.Function("<lambda>")) { inCodeContext(CodeContext.Function("<lambda>")) {
withLocalNames(slotParamNames.toSet()) { withLocalNames(slotParamNames.toSet()) {
parseBlock(skipLeadingBrace = true) parseBlock(skipLeadingBrace = true)
@ -868,6 +892,7 @@ class Compiler(
} finally { } finally {
slotPlanStack.removeLast() slotPlanStack.removeLast()
} }
val body = unwrapBytecodeDeep(parsedBody)
label?.let { cc.labels.remove(it) } label?.let { cc.labels.remove(it) }
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan) val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
@ -1494,15 +1519,20 @@ class Compiler(
val slotLoc = lookupSlotLocation(t.value) val slotLoc = lookupSlotLocation(t.value)
val inClassCtx = codeContexts.any { it is CodeContext.ClassBody } val inClassCtx = codeContexts.any { it is CodeContext.ClassBody }
when { when {
slotLoc != null -> LocalSlotRef( slotLoc != null -> {
t.value, val scopeDepth = slotPlanStack.size - 1 - slotLoc.depth
slotLoc.slot, LocalSlotRef(
slotLoc.depth, t.value,
slotLoc.isMutable, slotLoc.slot,
slotLoc.isDelegated, slotLoc.depth,
t.pos scopeDepth,
) slotLoc.isMutable,
PerfFlags.EMIT_FAST_LOCAL_REFS && (currentLocalNames?.contains(t.value) == true) -> slotLoc.isDelegated,
t.pos
)
}
PerfFlags.EMIT_FAST_LOCAL_REFS && !useBytecodeStatements &&
(currentLocalNames?.contains(t.value) == true) ->
FastLocalVarRef(t.value, t.pos) FastLocalVarRef(t.value, t.pos)
inClassCtx -> ImplicitThisMemberRef(t.value, t.pos) inClassCtx -> ImplicitThisMemberRef(t.value, t.pos)
else -> LocalVarRef(t.value, t.pos) else -> LocalVarRef(t.value, t.pos)
@ -2041,7 +2071,19 @@ class Compiler(
) )
private suspend fun parseTryStatement(): Statement { 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>() val catches = mutableListOf<CatchBlockData>()
cc.skipTokens(Token.Type.NEWLINE) cc.skipTokens(Token.Type.NEWLINE)
var t = cc.next() var t = cc.next()
@ -2078,7 +2120,7 @@ class Compiler(
exClassNames += "Exception" exClassNames += "Exception"
cc.skipTokenOfType(Token.Type.RPAREN) cc.skipTokenOfType(Token.Type.RPAREN)
} }
val block = parseBlock() val block = withCatchSlot(unwrapBytecodeDeep(parseBlock()), catchVar.value)
catches += CatchBlockData(catchVar, exClassNames, block) catches += CatchBlockData(catchVar, exClassNames, block)
cc.skipTokens(Token.Type.NEWLINE) cc.skipTokens(Token.Type.NEWLINE)
t = cc.next() t = cc.next()
@ -2087,13 +2129,13 @@ class Compiler(
cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here") cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here")
catches += CatchBlockData( catches += CatchBlockData(
Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"), Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"),
parseBlock(true) withCatchSlot(unwrapBytecodeDeep(parseBlock(true)), "it")
) )
t = cc.next() t = cc.next()
} }
} }
val finallyClause = if (t.value == "finally") { val finallyClause = if (t.value == "finally") {
parseBlock() unwrapBytecodeDeep(parseBlock())
} else { } else {
cc.previous() cc.previous()
null null
@ -2133,7 +2175,9 @@ class Compiler(
} }
} }
if (match != null) { 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) catchContext.addItem(cdata.catchVar.value, false, caughtObj)
result = cdata.block.execute(catchContext) result = cdata.block.execute(catchContext)
isCaught = true isCaught = true
@ -3020,7 +3064,7 @@ class Compiler(
currentLocalDeclCount currentLocalDeclCount
localDeclCountStack.add(0) localDeclCountStack.add(0)
slotPlanStack.add(paramSlotPlan) slotPlanStack.add(paramSlotPlan)
val fnStatements = try { val parsedFnStatements = try {
if (actualExtern) if (actualExtern)
object : Statement() { object : Statement() {
override val pos: Pos = start override val pos: Pos = start
@ -3050,6 +3094,9 @@ class Compiler(
} finally { } finally {
slotPlanStack.removeLast() slotPlanStack.removeLast()
} }
val fnStatements = parsedFnStatements?.let {
if (containsUnsupportedForBytecode(it)) unwrapBytecodeDeep(it) else it
}
// Capture and pop the local declarations count for this function // Capture and pop the local declarations count for this function
val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0 val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0
@ -3528,7 +3575,10 @@ class Compiler(
!actualExtern && !actualExtern &&
!isAbstract !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) { if (isStatic) {

View File

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

View File

@ -18,7 +18,7 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.* 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.bytecode.BytecodeStatement
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.pacman.ImportProvider import net.sergeych.lyng.pacman.ImportProvider
@ -339,6 +339,8 @@ open class Scope(
internal val objects = mutableMapOf<String, ObjRecord>() internal val objects = mutableMapOf<String, ObjRecord>()
internal fun getLocalRecordDirect(name: String): ObjRecord? = objects[name]
open operator fun get(name: String): ObjRecord? { open operator fun get(name: String): ObjRecord? {
if (name == "this") return thisObj.asReadonly if (name == "this") return thisObj.asReadonly
@ -381,6 +383,8 @@ open class Scope(
fun setSlotValue(index: Int, newValue: Obj) { fun setSlotValue(index: Int, newValue: Obj) {
slots[index].value = newValue slots[index].value = newValue
} }
val slotCount: Int
get() = slots.size
fun getSlotIndexOf(name: String): Int? = nameToSlot[name] fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
fun allocateSlotFor(name: String, record: ObjRecord): Int { 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. * Clear all references and maps to prevent memory leaks when pooled.
*/ */
@ -651,7 +669,7 @@ open class Scope(
val bytecode = (stmt as? BytecodeStatement)?.bytecodeFunction() val bytecode = (stmt as? BytecodeStatement)?.bytecodeFunction()
?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction() ?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
?: return "$name is not a compiled body" ?: return "$name is not a compiled body"
return BytecodeDisassembler.disassemble(bytecode) return CmdDisassembler.disassemble(bytecode)
} }
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) { fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {

View File

@ -26,6 +26,8 @@ class VarDeclStatement(
val visibility: Visibility, val visibility: Visibility,
val initializer: Statement?, val initializer: Statement?,
val isTransient: Boolean, val isTransient: Boolean,
val slotIndex: Int?,
val slotDepth: Int?,
private val startPos: Pos, private val startPos: Pos,
) : Statement() { ) : Statement() {
override val pos: Pos = startPos 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( class BytecodeCompiler(
private val allowLocalSlots: Boolean = true, private val allowLocalSlots: Boolean = true,
) { ) {
private var builder = BytecodeBuilder() private var builder = CmdBuilder()
private var nextSlot = 0 private var nextSlot = 0
private var nextAddrSlot = 0
private var scopeSlotCount = 0 private var scopeSlotCount = 0
private var scopeSlotDepths = IntArray(0) private var scopeSlotDepths = IntArray(0)
private var scopeSlotIndices = IntArray(0) private var scopeSlotIndices = IntArray(0)
private var scopeSlotNames = emptyArray<String?>() private var scopeSlotNames = emptyArray<String?>()
private val scopeSlotMap = LinkedHashMap<ScopeSlotKey, Int>() private val scopeSlotMap = LinkedHashMap<ScopeSlotKey, Int>()
private val scopeSlotNameMap = LinkedHashMap<ScopeSlotKey, String>() 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 slotTypes = mutableMapOf<Int, SlotType>()
private val intLoopVarNames = LinkedHashSet<String>() 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) prepareCompilation(stmt)
return when (stmt) { return when (stmt) {
is ExpressionStatement -> compileExpression(name, 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) prepareCompilation(stmt)
val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null
builder.emit(Opcode.RET, value.slot) builder.emit(Opcode.RET, value.slot)
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount 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) private data class CompiledValue(val slot: Int, val type: SlotType)
@ -74,12 +91,18 @@ class BytecodeCompiler(
if (!allowLocalSlots) return null if (!allowLocalSlots) return null
if (ref.isDelegated) return null if (ref.isDelegated) return null
if (ref.name.isEmpty()) return null if (ref.name.isEmpty()) return null
val loopSlotId = loopVarSlotIdByName[ref.name] val mapped = resolveSlot(ref) ?: return null
val mapped = loopSlotId ?: scopeSlotMap[ScopeSlotKey(refDepth(ref), refSlot(ref))] ?: return null var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
val resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) { if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
updateSlotType(mapped, SlotType.INT) 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) CompiledValue(mapped, resolved)
} }
@ -87,7 +110,7 @@ class BytecodeCompiler(
is UnaryOpRef -> compileUnary(ref) is UnaryOpRef -> compileUnary(ref)
is AssignRef -> compileAssign(ref) is AssignRef -> compileAssign(ref)
is AssignOpRef -> compileAssignOp(ref) is AssignOpRef -> compileAssignOp(ref)
is IncDecRef -> compileIncDec(ref) is IncDecRef -> compileIncDec(ref, true)
is ConditionalRef -> compileConditional(ref) is ConditionalRef -> compileConditional(ref)
is ElvisRef -> compileElvis(ref) is ElvisRef -> compileElvis(ref)
is CallRef -> compileCall(ref) is CallRef -> compileCall(ref)
@ -626,17 +649,17 @@ class BytecodeCompiler(
if (op == BinOp.AND) { if (op == BinOp.AND) {
builder.emit( builder.emit(
Opcode.JMP_IF_FALSE, 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 { } else {
builder.emit( builder.emit(
Opcode.JMP_IF_TRUE, 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 val rightValue = compileRefWithFallback(right, SlotType.BOOL, pos) ?: return null
emitMove(rightValue, resultSlot) emitMove(rightValue, resultSlot)
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(shortLabel) builder.mark(shortLabel)
val constId = builder.addConst(BytecodeConst.Bool(op == BinOp.OR)) val constId = builder.addConst(BytecodeConst.Bool(op == BinOp.OR))
builder.emit(Opcode.CONST_BOOL, constId, resultSlot) builder.emit(Opcode.CONST_BOOL, constId, resultSlot)
@ -648,27 +671,49 @@ class BytecodeCompiler(
val target = assignTarget(ref) ?: return null val target = assignTarget(ref) ?: return null
if (!allowLocalSlots) return null if (!allowLocalSlots) return null
if (!target.isMutable || target.isDelegated) return null if (!target.isMutable || target.isDelegated) return null
if (refDepth(target) > 0) return null
val value = compileRef(assignValue(ref)) ?: return null val value = compileRef(assignValue(ref)) ?: return null
val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null val slot = resolveSlot(target) ?: return null
when (value.type) { if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot) val addrSlot = ensureScopeAddr(slot)
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot) emitStoreToAddr(value.slot, addrSlot, value.type)
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot) } else if (slot < scopeSlotCount) {
else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot) 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) updateSlotType(slot, value.type)
return CompiledValue(slot, value.type) return value
} }
private fun compileAssignOp(ref: AssignOpRef): CompiledValue? { private fun compileAssignOp(ref: AssignOpRef): CompiledValue? {
val target = ref.target as? LocalSlotRef ?: return null val target = ref.target as? LocalSlotRef ?: return null
if (!allowLocalSlots) return null if (!allowLocalSlots) return null
if (!target.isMutable || target.isDelegated) return null if (!target.isMutable || target.isDelegated) return null
if (refDepth(target) > 0) return null val slot = resolveSlot(target) ?: return null
val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null
val targetType = slotTypes[slot] ?: return null val targetType = slotTypes[slot] ?: return null
val rhs = compileRef(ref.value) ?: 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 out = slot
val result = when (ref.op) { val result = when (ref.op) {
BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ) 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 val target = ref.target as? LocalSlotRef ?: return null
if (!allowLocalSlots) return null if (!allowLocalSlots) return null
if (!target.isMutable || target.isDelegated) 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 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) { return when (slotType) {
SlotType.INT -> { SlotType.INT -> {
if (ref.isPost) { if (wantResult && ref.isPost) {
val old = allocSlot() val old = allocSlot()
builder.emit(Opcode.MOVE_INT, slot, old) builder.emit(Opcode.MOVE_INT, slot, old)
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot) builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
@ -755,7 +864,7 @@ class BytecodeCompiler(
val oneSlot = allocSlot() val oneSlot = allocSlot()
val oneId = builder.addConst(BytecodeConst.RealVal(1.0)) val oneId = builder.addConst(BytecodeConst.RealVal(1.0))
builder.emit(Opcode.CONST_REAL, oneId, oneSlot) builder.emit(Opcode.CONST_REAL, oneId, oneSlot)
if (ref.isPost) { if (wantResult && ref.isPost) {
val old = allocSlot() val old = allocSlot()
builder.emit(Opcode.MOVE_REAL, slot, old) builder.emit(Opcode.MOVE_REAL, slot, old)
val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL 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) builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
val current = allocSlot() val current = allocSlot()
builder.emit(Opcode.BOX_OBJ, slot, current) builder.emit(Opcode.BOX_OBJ, slot, current)
if (ref.isPost) { if (wantResult && ref.isPost) {
val result = allocSlot() val result = allocSlot()
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
builder.emit(op, current, oneSlot, result) builder.emit(op, current, oneSlot, result)
@ -790,7 +899,7 @@ class BytecodeCompiler(
} }
} }
SlotType.UNKNOWN -> { SlotType.UNKNOWN -> {
if (ref.isPost) { if (wantResult && ref.isPost) {
val old = allocSlot() val old = allocSlot()
builder.emit(Opcode.MOVE_INT, slot, old) builder.emit(Opcode.MOVE_INT, slot, old)
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot) builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
@ -814,12 +923,12 @@ class BytecodeCompiler(
val endLabel = builder.label() val endLabel = builder.label()
builder.emit( builder.emit(
Opcode.JMP_IF_FALSE, 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 thenValue = compileRefWithFallback(ref.ifTrue, null, Pos.builtIn) ?: return null
val thenObj = ensureObjSlot(thenValue) val thenObj = ensureObjSlot(thenValue)
builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot) 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) builder.mark(elseLabel)
val elseValue = compileRefWithFallback(ref.ifFalse, null, Pos.builtIn) ?: return null val elseValue = compileRefWithFallback(ref.ifFalse, null, Pos.builtIn) ?: return null
val elseObj = ensureObjSlot(elseValue) val elseObj = ensureObjSlot(elseValue)
@ -841,10 +950,10 @@ class BytecodeCompiler(
val endLabel = builder.label() val endLabel = builder.label()
builder.emit( builder.emit(
Opcode.JMP_IF_TRUE, 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.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) builder.mark(rightLabel)
val rightValue = compileRefWithFallback(ref.right, null, Pos.builtIn) ?: return null val rightValue = compileRefWithFallback(ref.right, null, Pos.builtIn) ?: return null
val rightObj = ensureObjSlot(rightValue) val rightObj = ensureObjSlot(rightValue)
@ -941,7 +1050,7 @@ class BytecodeCompiler(
return 0x8000 or planId 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 conditionStmt = stmt.condition as? ExpressionStatement ?: return null
val condValue = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null val condValue = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
if (condValue.type != SlotType.BOOL) return null if (condValue.type != SlotType.BOOL) return null
@ -952,11 +1061,11 @@ class BytecodeCompiler(
builder.emit( builder.emit(
Opcode.JMP_IF_FALSE, 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 val thenValue = compileStatementValue(stmt.ifBody) ?: return null
emitMove(thenValue, resultSlot) emitMove(thenValue, resultSlot)
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(elseLabel) builder.mark(elseLabel)
if (stmt.elseBody != null) { if (stmt.elseBody != null) {
@ -970,28 +1079,68 @@ class BytecodeCompiler(
builder.mark(endLabel) builder.mark(endLabel)
builder.emit(Opcode.RET, resultSlot) builder.emit(Opcode.RET, resultSlot)
val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount 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? { private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): CmdFunction? {
val resultSlot = emitForIn(stmt) ?: return null val resultSlot = emitForIn(stmt, true) ?: return null
builder.emit(Opcode.RET, resultSlot) builder.emit(Opcode.RET, resultSlot)
val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount 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? { private fun compileBlock(name: String, stmt: BlockStatement): CmdFunction? {
val result = emitBlock(stmt) ?: return null val result = emitBlock(stmt, true) ?: return null
builder.emit(Opcode.RET, result.slot) builder.emit(Opcode.RET, result.slot)
val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount 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 val result = emitVarDecl(stmt) ?: return null
builder.emit(Opcode.RET, result.slot) builder.emit(Opcode.RET, result.slot)
val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount 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? { 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 val target = if (stmt is BytecodeStatement) stmt.original else stmt
return when (target) { return if (needResult) {
is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos) when (target) {
is IfStatement -> compileIfExpression(target) is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos)
is net.sergeych.lyng.ForInStatement -> { is IfStatement -> compileIfExpression(target)
val resultSlot = emitForIn(target) ?: return null is net.sergeych.lyng.ForInStatement -> {
updateSlotType(resultSlot, SlotType.OBJ) val resultSlot = emitForIn(target, true) ?: return null
CompiledValue(resultSlot, SlotType.OBJ) 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) } else {
is VarDeclStatement -> emitVarDecl(target) when (target) {
else -> { is ExpressionStatement -> {
val slot = allocSlot() val ref = target.ref
val id = builder.addFallback(target) if (ref is IncDecRef) {
builder.emit(Opcode.EVAL_FALLBACK, id, slot) compileIncDec(ref, false)
builder.emit(Opcode.BOX_OBJ, slot, slot) } else {
updateSlotType(slot, SlotType.OBJ) compileRefWithFallback(ref, null, target.pos)
CompiledValue(slot, SlotType.OBJ) }
}
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)) val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan))
builder.emit(Opcode.PUSH_SCOPE, planId) builder.emit(Opcode.PUSH_SCOPE, planId)
resetAddrCache()
val statements = stmt.statements() val statements = stmt.statements()
var lastValue: CompiledValue? = null var lastValue: CompiledValue? = null
for (statement in statements) { for ((index, statement) in statements.withIndex()) {
lastValue = compileStatementValueOrFallback(statement) ?: return null 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 result = if (needResult) {
val slot = allocSlot() var value = lastValue ?: run {
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) val slot = allocSlot()
builder.emit(Opcode.CONST_OBJ, voidId, slot) val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
CompiledValue(slot, SlotType.OBJ) builder.emit(Opcode.CONST_OBJ, voidId, slot)
} CompiledValue(slot, SlotType.OBJ)
if (result.slot < scopeSlotCount) { }
val captured = allocSlot() if (value.slot < scopeSlotCount) {
emitMove(result, captured) val captured = allocSlot()
result = CompiledValue(captured, result.type) 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) builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
return result return result
} }
private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? { 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 value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
val slot = allocSlot() val slot = allocSlot()
builder.emit(Opcode.CONST_NULL, slot) builder.emit(Opcode.CONST_NULL, slot)
@ -1068,18 +1291,11 @@ class BytecodeCompiler(
} }
return value 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 if (stmt.canBreak) return null
val range = stmt.constRange ?: return null val range = stmt.constRange ?: return null
val loopSlotId = loopVarSlotIdByName[stmt.loopVarName] ?: return null val loopLocalIndex = localSlotIndexByName[stmt.loopVarName] ?: return null
val slotIndex = loopVarSlotIndexByName[stmt.loopVarName] ?: return null val loopSlotId = scopeSlotCount + loopLocalIndex
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 iSlot = allocSlot() val iSlot = allocSlot()
val endSlot = allocSlot() val endSlot = allocSlot()
@ -1089,8 +1305,10 @@ class BytecodeCompiler(
builder.emit(Opcode.CONST_INT, endId, endSlot) builder.emit(Opcode.CONST_INT, endId, endSlot)
val resultSlot = allocSlot() val resultSlot = allocSlot()
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) if (wantResult) {
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot) val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
}
val loopLabel = builder.label() val loopLabel = builder.label()
val endLabel = builder.label() val endLabel = builder.label()
@ -1099,35 +1317,53 @@ class BytecodeCompiler(
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot) builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
builder.emit( builder.emit(
Opcode.JMP_IF_TRUE, 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) builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
updateSlotType(loopSlotId, SlotType.INT) updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
val bodyValue = compileStatementValueOrFallback(stmt.body) ?: return null val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null
val bodyObj = ensureObjSlot(bodyValue) if (wantResult) {
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot) val bodyObj = ensureObjSlot(bodyValue)
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
}
builder.emit(Opcode.INC_INT, iSlot) 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) builder.mark(endLabel)
if (stmt.elseStatement != null) { if (stmt.elseStatement != null) {
val elseValue = compileStatementValueOrFallback(stmt.elseStatement) ?: return null val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
val elseObj = ensureObjSlot(elseValue) if (wantResult) {
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot) 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)
} }
return 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) { private fun updateSlotTypeByName(name: String, type: SlotType) {
val loopSlotId = loopVarSlotIdByName[name] val localIndex = localSlotIndexByName[name]
if (loopSlotId != null) { if (localIndex != null) {
updateSlotType(loopSlotId, type) updateSlotType(scopeSlotCount + localIndex, type)
return return
} }
for ((key, index) in scopeSlotMap) { for ((key, index) in scopeSlotMap) {
@ -1145,12 +1381,12 @@ class BytecodeCompiler(
val endLabel = builder.label() val endLabel = builder.label()
builder.emit( builder.emit(
Opcode.JMP_IF_FALSE, 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 thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null
val thenObj = ensureObjSlot(thenValue) val thenObj = ensureObjSlot(thenValue)
builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot) 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) builder.mark(elseLabel)
if (stmt.elseBody != null) { if (stmt.elseBody != null) {
val elseValue = compileStatementValueOrFallback(stmt.elseBody) ?: return 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) { 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) { when (value.type) {
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, dstSlot) SlotType.INT -> builder.emit(Opcode.MOVE_INT, srcSlot, dstSlot)
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, dstSlot) SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, srcSlot, dstSlot)
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, dstSlot) SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, srcSlot, dstSlot)
else -> builder.emit(Opcode.MOVE_OBJ, value.slot, 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 refSlot(ref: LocalSlotRef): Int = ref.slot
private fun refDepth(ref: LocalSlotRef): Int = ref.depth 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 binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op
@ -1224,6 +1509,16 @@ class BytecodeCompiler(
private fun assignValue(ref: AssignRef): ObjRef = ref.value private fun assignValue(ref: AssignRef): ObjRef = ref.value
private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn 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) { private fun updateSlotType(slot: Int, type: SlotType) {
if (type == SlotType.UNKNOWN) { if (type == SlotType.UNKNOWN) {
slotTypes.remove(slot) slotTypes.remove(slot)
@ -1233,17 +1528,24 @@ class BytecodeCompiler(
} }
private fun prepareCompilation(stmt: Statement) { private fun prepareCompilation(stmt: Statement) {
builder = BytecodeBuilder() builder = CmdBuilder()
nextSlot = 0 nextSlot = 0
nextAddrSlot = 0
slotTypes.clear() slotTypes.clear()
scopeSlotMap.clear() scopeSlotMap.clear()
localSlotInfoMap.clear()
localSlotIndexByKey.clear()
localSlotIndexByName.clear()
localSlotNames = emptyArray()
localSlotMutables = BooleanArray(0)
localSlotDepths = IntArray(0)
declaredLocalKeys.clear()
intLoopVarNames.clear() intLoopVarNames.clear()
loopVarNames.clear() addrSlotByScopeSlot.clear()
loopVarSlotIndexByName.clear()
loopVarSlotIdByName.clear()
if (allowLocalSlots) { if (allowLocalSlots) {
collectLoopVarNames(stmt) collectLoopVarNames(stmt)
collectScopeSlots(stmt) collectScopeSlots(stmt)
collectLoopSlotPlans(stmt, 0)
} }
scopeSlotCount = scopeSlotMap.size scopeSlotCount = scopeSlotMap.size
scopeSlotDepths = IntArray(scopeSlotCount) scopeSlotDepths = IntArray(scopeSlotCount)
@ -1255,28 +1557,26 @@ class BytecodeCompiler(
scopeSlotIndices[index] = key.slot scopeSlotIndices[index] = key.slot
scopeSlotNames[index] = name scopeSlotNames[index] = name
} }
if (loopVarNames.isNotEmpty()) { if (allowLocalSlots && localSlotInfoMap.isNotEmpty()) {
var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1 val names = ArrayList<String?>(localSlotInfoMap.size)
for (name in loopVarNames) { val mutables = BooleanArray(localSlotInfoMap.size)
maxSlotIndex += 1 val depths = IntArray(localSlotInfoMap.size)
loopVarSlotIndexByName[name] = maxSlotIndex 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 localSlotNames = names.toTypedArray()
val total = scopeSlotCount + loopVarSlotIndexByName.size localSlotMutables = mutables
scopeSlotDepths = scopeSlotDepths.copyOf(total) localSlotDepths = depths
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
} }
nextSlot = scopeSlotCount nextSlot = scopeSlotCount + localSlotNames.size
} }
private fun collectScopeSlots(stmt: Statement) { private fun collectScopeSlots(stmt: Statement) {
@ -1292,6 +1592,11 @@ class BytecodeCompiler(
} }
} }
is VarDeclStatement -> { 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) } stmt.initializer?.let { collectScopeSlots(it) }
} }
is IfStatement -> { 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) { private fun collectLoopVarNames(stmt: Statement) {
if (stmt is BytecodeStatement) { if (stmt is BytecodeStatement) {
collectLoopVarNames(stmt.original) collectLoopVarNames(stmt.original)
@ -1317,7 +1661,6 @@ class BytecodeCompiler(
is net.sergeych.lyng.ForInStatement -> { is net.sergeych.lyng.ForInStatement -> {
if (stmt.constRange != null) { if (stmt.constRange != null) {
intLoopVarNames.add(stmt.loopVarName) intLoopVarNames.add(stmt.loopVarName)
loopVarNames.add(stmt.loopVarName)
} }
collectLoopVarNames(stmt.source) collectLoopVarNames(stmt.source)
collectLoopVarNames(stmt.body) collectLoopVarNames(stmt.body)
@ -1370,7 +1713,15 @@ class BytecodeCompiler(
private fun collectScopeSlotsRef(ref: ObjRef) { private fun collectScopeSlotsRef(ref: ObjRef) {
when (ref) { when (ref) {
is LocalSlotRef -> { 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)) val key = ScopeSlotKey(refDepth(ref), refSlot(ref))
if (!scopeSlotMap.containsKey(key)) { if (!scopeSlotMap.containsKey(key)) {
scopeSlotMap[key] = scopeSlotMap.size scopeSlotMap[key] = scopeSlotMap.size
@ -1387,12 +1738,21 @@ class BytecodeCompiler(
is AssignRef -> { is AssignRef -> {
val target = assignTarget(ref) val target = assignTarget(ref)
if (target != null) { if (target != null) {
val key = ScopeSlotKey(refDepth(target), refSlot(target)) val localKey = ScopeSlotKey(refScopeDepth(target), refSlot(target))
if (!scopeSlotMap.containsKey(key)) { val shouldLocalize = declaredLocalKeys.contains(localKey) ||
scopeSlotMap[key] = scopeSlotMap.size intLoopVarNames.contains(target.name)
} if (allowLocalSlots && !target.isDelegated && shouldLocalize) {
if (!scopeSlotNameMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(localKey)) {
scopeSlotNameMap[key] = target.name 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)) 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( class BytecodeStatement private constructor(
val original: Statement, val original: Statement,
private val function: BytecodeFunction, private val function: CmdFunction,
) : Statement(original.isStaticConst, original.isConst, original.returnType) { ) : Statement(original.isStaticConst, original.isConst, original.returnType) {
override val pos: Pos = original.pos override val pos: Pos = original.pos
override suspend fun execute(scope: Scope): Obj { 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 { companion object {
fun wrap(statement: Statement, nameHint: String, allowLocalSlots: Boolean): Statement { fun wrap(statement: Statement, nameHint: String, allowLocalSlots: Boolean): Statement {
if (statement is BytecodeStatement) return 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 compiled = compiler.compileStatement(nameHint, statement)
val fn = compiled ?: run { val fn = compiled ?: run {
val builder = BytecodeBuilder() val builder = CmdBuilder()
val slot = 0 val slot = 0
val id = builder.addFallback(statement) val id = builder.addFallback(statement)
builder.emit(Opcode.EVAL_FALLBACK, id, slot) builder.emit(Opcode.EVAL_FALLBACK, id, slot)
builder.emit(Opcode.RET, 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) 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 package net.sergeych.lyng.bytecode
internal expect object BytecodeCallSiteCache { internal expect object CmdCallSiteCache {
fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> 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 package net.sergeych.lyng.bytecode
data class BytecodeFunction( data class CmdFunction(
val name: String, val name: String,
val localCount: Int, val localCount: Int,
val addrCount: Int,
val scopeSlotCount: Int, val scopeSlotCount: Int,
val scopeSlotDepths: IntArray, val scopeSlotDepths: IntArray,
val scopeSlotIndices: IntArray, val scopeSlotIndices: IntArray,
val scopeSlotNames: Array<String?>, val scopeSlotNames: Array<String?>,
val slotWidth: Int, val localSlotNames: Array<String?>,
val ipWidth: Int, val localSlotMutables: BooleanArray,
val constIdWidth: Int, val localSlotDepths: IntArray,
val constants: List<BytecodeConst>, val constants: List<BytecodeConst>,
val fallbackStatements: List<net.sergeych.lyng.Statement>, val fallbackStatements: List<net.sergeych.lyng.Statement>,
val code: ByteArray, val cmds: Array<Cmd>,
) { ) {
init { 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(scopeSlotDepths.size == scopeSlotCount) { "scopeSlotDepths size mismatch" }
require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" } require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" }
require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames 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), SET_INDEX(0xA3),
EVAL_FALLBACK(0xB0), 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 { companion object {

View File

@ -1991,6 +1991,10 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
scope.assign(rec, name, newValue) scope.assign(rec, name, newValue)
return return
} }
scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx)?.let { rec ->
scope.assign(rec, name, newValue)
return
}
scope[name]?.let { stored -> scope[name]?.let { stored ->
scope.assign(stored, name, newValue) scope.assign(stored, name, newValue)
return return
@ -2007,6 +2011,10 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
return return
} }
} }
scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx)?.let { rec ->
scope.assign(rec, name, newValue)
return
}
scope[name]?.let { stored -> scope[name]?.let { stored ->
scope.assign(stored, name, newValue) scope.assign(stored, name, newValue)
return return
@ -2417,6 +2425,7 @@ class LocalSlotRef(
val name: String, val name: String,
internal val slot: Int, internal val slot: Int,
internal val depth: Int, internal val depth: Int,
internal val scopeDepth: Int,
internal val isMutable: Boolean, internal val isMutable: Boolean,
internal val isDelegated: Boolean, internal val isDelegated: Boolean,
private val atPos: Pos, private val atPos: Pos,

View File

@ -19,10 +19,10 @@ import net.sergeych.lyng.IfStatement
import net.sergeych.lyng.Pos import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement 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.BytecodeCompiler
import net.sergeych.lyng.bytecode.BytecodeConst 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.bytecode.Opcode
import net.sergeych.lyng.obj.BinaryOpRef import net.sergeych.lyng.obj.BinaryOpRef
import net.sergeych.lyng.obj.BinOp import net.sergeych.lyng.obj.BinOp
@ -45,10 +45,10 @@ import net.sergeych.lyng.obj.toLong
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class BytecodeVmTest { class CmdVmTest {
@Test @Test
fun addsIntConstants() = kotlinx.coroutines.test.runTest { fun addsIntConstants() = kotlinx.coroutines.test.runTest {
val builder = BytecodeBuilder() val builder = CmdBuilder()
val k0 = builder.addConst(BytecodeConst.IntVal(2)) val k0 = builder.addConst(BytecodeConst.IntVal(2))
val k1 = builder.addConst(BytecodeConst.IntVal(3)) val k1 = builder.addConst(BytecodeConst.IntVal(3))
builder.emit(Opcode.CONST_INT, k0, 0) builder.emit(Opcode.CONST_INT, k0, 0)
@ -56,7 +56,7 @@ class BytecodeVmTest {
builder.emit(Opcode.ADD_INT, 0, 1, 2) builder.emit(Opcode.ADD_INT, 0, 1, 2)
builder.emit(Opcode.RET, 2) builder.emit(Opcode.RET, 2)
val fn = builder.build("addInts", localCount = 3) 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()) assertEquals(5, result.toInt())
} }
@ -80,7 +80,7 @@ class BytecodeVmTest {
) )
val ifStmt = IfStatement(cond, thenStmt, elseStmt, net.sergeych.lyng.Pos.builtIn) val ifStmt = IfStatement(cond, thenStmt, elseStmt, net.sergeych.lyng.Pos.builtIn)
val fn = BytecodeCompiler().compileStatement("ifTest", ifStmt) ?: error("bytecode compile failed") 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()) assertEquals(10, result.toInt())
} }
@ -104,7 +104,7 @@ class BytecodeVmTest {
error("bytecode compile failed for ifNoElse") error("bytecode compile failed for ifNoElse")
} }
}!! }!!
val result = BytecodeVm().execute(fn, Scope(), emptyList()) val result = CmdVm().execute(fn, Scope(), emptyList())
assertEquals(ObjVoid, result) assertEquals(ObjVoid, result)
} }
@ -120,7 +120,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val fn = BytecodeCompiler().compileExpression("andShort", expr) ?: error("bytecode compile failed") 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()) assertEquals(false, result.toBool())
} }
@ -136,7 +136,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val fn = BytecodeCompiler().compileExpression("orShort", expr) ?: error("bytecode compile failed") 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()) assertEquals(true, result.toBool())
} }
@ -151,7 +151,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val fn = BytecodeCompiler().compileExpression("realPlus", expr) ?: error("bytecode compile failed") 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()) assertEquals(5.75, result.toDouble())
} }
@ -163,7 +163,7 @@ class BytecodeVmTest {
scope.args[0].toLong() + scope.args[1].toLong() scope.args[0].toLong() + scope.args[1].toLong()
) )
} }
val builder = BytecodeBuilder() val builder = CmdBuilder()
val fnId = builder.addConst(BytecodeConst.ObjRef(callable)) val fnId = builder.addConst(BytecodeConst.ObjRef(callable))
val arg0 = builder.addConst(BytecodeConst.IntVal(2L)) val arg0 = builder.addConst(BytecodeConst.IntVal(2L))
val arg1 = builder.addConst(BytecodeConst.IntVal(3L)) 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.CALL_SLOT, 0, 1, 2, 3)
builder.emit(Opcode.RET, 3) builder.emit(Opcode.RET, 3)
val fn = builder.build("callSlot", localCount = 4) 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()) assertEquals(5, result.toInt())
} }
@ -188,7 +188,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val ltFn = BytecodeCompiler().compileExpression("mixedLt", ltExpr) ?: error("bytecode compile failed") 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()) assertEquals(true, ltResult.toBool())
val eqExpr = ExpressionStatement( val eqExpr = ExpressionStatement(
@ -200,7 +200,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val eqFn = BytecodeCompiler().compileExpression("mixedEq", eqExpr) ?: error("bytecode compile failed") 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()) assertEquals(true, eqResult.toBool())
} }
@ -224,7 +224,7 @@ class BytecodeVmTest {
) )
val expr = ExpressionStatement(callRef, Pos.builtIn) val expr = ExpressionStatement(callRef, Pos.builtIn)
val fn = BytecodeCompiler().compileExpression("tailBlockArgs", expr) ?: error("bytecode compile failed") 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()) assertEquals(true, result.toBool())
} }
@ -249,7 +249,7 @@ class BytecodeVmTest {
) )
val expr = ExpressionStatement(callRef, Pos.builtIn) val expr = ExpressionStatement(callRef, Pos.builtIn)
val fn = BytecodeCompiler().compileExpression("namedArgs", expr) ?: error("bytecode compile failed") 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()) assertEquals(5, result.toInt())
} }
@ -275,7 +275,7 @@ class BytecodeVmTest {
) )
val expr = ExpressionStatement(callRef, Pos.builtIn) val expr = ExpressionStatement(callRef, Pos.builtIn)
val fn = BytecodeCompiler().compileExpression("splatArgs", expr) ?: error("bytecode compile failed") 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()) assertEquals(3, result.toInt())
} }
@ -290,7 +290,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val fn = BytecodeCompiler().compileExpression("mixedPlus", expr) ?: error("bytecode compile failed") 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()) assertEquals(5.5, result.toDouble())
} }
@ -305,13 +305,13 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val fn = BytecodeCompiler().compileExpression("mixedNeq", expr) ?: error("bytecode compile failed") 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()) assertEquals(true, result.toBool())
} }
@Test @Test
fun localSlotTypeTrackingEnablesArithmetic() = kotlinx.coroutines.test.runTest { 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( val assign = AssignRef(
slotRef, slotRef,
ConstRef(ObjInt.of(2).asReadonly), ConstRef(ObjInt.of(2).asReadonly),
@ -327,13 +327,13 @@ class BytecodeVmTest {
) )
val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed") val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed")
val scope = Scope().apply { applySlotPlan(mapOf("a" to 0)) } 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()) assertEquals(4, result.toInt())
} }
@Test @Test
fun parentScopeSlotAccessWorks() = kotlinx.coroutines.test.runTest { 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( val expr = ExpressionStatement(
BinaryOpRef( BinaryOpRef(
BinOp.PLUS, BinOp.PLUS,
@ -348,7 +348,7 @@ class BytecodeVmTest {
setSlotValue(0, ObjInt.of(3)) setSlotValue(0, ObjInt.of(3))
} }
val child = Scope(parent) val child = Scope(parent)
val result = BytecodeVm().execute(fn, child, emptyList()) val result = CmdVm().execute(fn, child, emptyList())
assertEquals(5, result.toInt()) assertEquals(5, result.toInt())
} }
@ -363,7 +363,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val fn = BytecodeCompiler().compileExpression("objEq", expr) ?: error("bytecode compile failed") 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()) assertEquals(true, result.toBool())
} }
@ -379,7 +379,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val eqFn = BytecodeCompiler().compileExpression("objRefEq", eqExpr) ?: error("bytecode compile failed") 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()) assertEquals(true, eqResult.toBool())
val neqExpr = ExpressionStatement( val neqExpr = ExpressionStatement(
@ -391,7 +391,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val neqFn = BytecodeCompiler().compileExpression("objRefNeq", neqExpr) ?: error("bytecode compile failed") 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()) assertEquals(true, neqResult.toBool())
} }
@ -406,7 +406,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val ltFn = BytecodeCompiler().compileExpression("objLt", ltExpr) ?: error("bytecode compile failed") 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()) assertEquals(true, ltResult.toBool())
val gteExpr = ExpressionStatement( val gteExpr = ExpressionStatement(
@ -418,7 +418,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val gteFn = BytecodeCompiler().compileExpression("objGte", gteExpr) ?: error("bytecode compile failed") 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()) assertEquals(true, gteResult.toBool())
} }
@ -433,7 +433,7 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val fn = BytecodeCompiler().compileExpression("objPlus", expr) ?: error("bytecode compile failed") 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) 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.ForInStatement
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
import net.sergeych.lyng.Statement 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.bytecode.BytecodeStatement
import net.sergeych.lyng.obj.ObjInt import net.sergeych.lyng.obj.ObjInt
import kotlin.time.TimeSource import kotlin.time.TimeSource
@ -56,8 +56,11 @@ class NestedRangeBenchmarkTest {
val scope = Script.newScope() val scope = Script.newScope()
scope.eval(script) scope.eval(script)
val fnDisasm = scope.disassembleSymbol("naiveCountHappyNumbers") 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 start = TimeSource.Monotonic.markNow()
val result = scope.eval("naiveCountHappyNumbers()") as ObjInt val result = scope.eval("naiveCountHappyNumbers()") as ObjInt
val elapsedMs = start.elapsedNow().inWholeMilliseconds val elapsedMs = start.elapsedNow().inWholeMilliseconds
@ -83,13 +86,13 @@ class NestedRangeBenchmarkTest {
"$slotName@${fn.scopeSlotDepths[idx]}:${fn.scopeSlotIndices[idx]}" "$slotName@${fn.scopeSlotDepths[idx]}:${fn.scopeSlotIndices[idx]}"
} }
println("[DEBUG_LOG] [BENCH] nested-happy slots depth=$depth: ${slots.joinToString(", ")}") println("[DEBUG_LOG] [BENCH] nested-happy slots depth=$depth: ${slots.joinToString(", ")}")
val disasm = BytecodeDisassembler.disassemble(fn) val disasm = CmdDisassembler.disassemble(fn)
println("[DEBUG_LOG] [BENCH] nested-happy bytecode depth=$depth:\n$disasm") println("[DEBUG_LOG] [BENCH] nested-happy cmd depth=$depth:\n$disasm")
current = original.body current = original.body
depth += 1 depth += 1
} }
if (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 x
} }
(1..100).map { launch { dosomething() } }.forEach { (1..50).map { launch { dosomething() } }.forEach {
assertEquals(5050, it.await()) assertEquals(5050, it.await())
} }
assertEquals( 100, ac.getCounter() ) assertEquals( 50, ac.getCounter() )
""".trimIndent() """.trimIndent()
) )

View File

@ -43,4 +43,4 @@ actual object PerfDefaults {
actual val ARG_SMALL_ARITY_12: Boolean = false actual val ARG_SMALL_ARITY_12: Boolean = false
actual val INDEX_PIC_SIZE_4: Boolean = false actual val INDEX_PIC_SIZE_4: Boolean = false
actual val RANGE_FAST_ITER: Boolean = false actual val RANGE_FAST_ITER: Boolean = false
} }

View File

@ -16,10 +16,10 @@
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
internal actual object BytecodeCallSiteCache { internal actual object CmdCallSiteCache {
private val cache = mutableMapOf<BytecodeFunction, MutableMap<Int, MethodCallSite>>() 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() } return cache.getOrPut(fn) { mutableMapOf() }
} }
} }

View File

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

View File

@ -18,12 +18,12 @@ package net.sergeych.lyng.bytecode
import java.util.IdentityHashMap import java.util.IdentityHashMap
internal actual object BytecodeCallSiteCache { internal actual object CmdCallSiteCache {
private val cache = ThreadLocal.withInitial { 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() val map = cache.get()
return map.getOrPut(fn) { mutableMapOf() } return map.getOrPut(fn) { mutableMapOf() }
} }

View File

@ -43,4 +43,4 @@ actual object PerfDefaults {
actual val ARG_SMALL_ARITY_12: Boolean = false actual val ARG_SMALL_ARITY_12: Boolean = false
actual val INDEX_PIC_SIZE_4: Boolean = false actual val INDEX_PIC_SIZE_4: Boolean = false
actual val RANGE_FAST_ITER: Boolean = false actual val RANGE_FAST_ITER: Boolean = false
} }

View File

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

View File

@ -43,4 +43,4 @@ actual object PerfDefaults {
actual val ARG_SMALL_ARITY_12: Boolean = false actual val ARG_SMALL_ARITY_12: Boolean = false
actual val INDEX_PIC_SIZE_4: Boolean = false actual val INDEX_PIC_SIZE_4: Boolean = false
actual val RANGE_FAST_ITER: Boolean = false actual val RANGE_FAST_ITER: Boolean = false
} }

View File

@ -16,10 +16,10 @@
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
internal actual object BytecodeCallSiteCache { internal actual object CmdCallSiteCache {
private val cache = mutableMapOf<BytecodeFunction, MutableMap<Int, MethodCallSite>>() 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() } return cache.getOrPut(fn) { mutableMapOf() }
} }
} }