Compare commits

..

No commits in common. "54d882ce89a606851a90abcfb071d90168823545" and "059e36678768f887ec8f3d96e24ec841cd56ad78" have entirely different histories.

87 changed files with 1965 additions and 7495 deletions

View File

@ -30,10 +30,6 @@ slots[localCount .. localCount+argCount-1] arguments
- scopeSlotNames: array sized scopeSlotCount, each entry nullable.
- Intended for disassembly/debug tooling; VM semantics do not depend on it.
### Constant pool extras
- SlotPlan: map of name -> slot index, used by PUSH_SCOPE to pre-allocate and map loop locals.
- CallArgsPlan: ordered argument specs (name/splat) + tailBlock flag, used when argCount has the plan flag set.
## 2) Slot ID Width
Per frame, select:
@ -59,7 +55,6 @@ Behavior:
Other calls:
- CALL_VIRTUAL recvSlot, methodId, argBase, argCount, dst
- CALL_FALLBACK stmtId, argBase, argCount, dst
- CALL_SLOT calleeSlot, argBase, argCount, dst
## 4) Binary Encoding Layout
@ -84,10 +79,6 @@ Common operand patterns:
- I: jump target
- F S C S: fnId, argBase slot, argCount, dst slot
Arg count flag:
- If high bit of C is set (0x8000), the low 15 bits encode a CallArgsPlan constId.
- When not set, C is the raw positional count and tailBlockMode=false.
## 5) Opcode Table
Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
@ -98,7 +89,6 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
- MOVE_INT S -> S
- MOVE_REAL S -> S
- MOVE_BOOL S -> S
- BOX_OBJ S -> S
- CONST_OBJ K -> S
- CONST_INT K -> S
- CONST_REAL K -> S
@ -193,18 +183,11 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
- JMP_IF_FALSE S, I
- RET S
- RET_VOID
- PUSH_SCOPE K
- POP_SCOPE
### Scope setup
- PUSH_SCOPE uses const `SlotPlan` (name -> slot index) to create a child scope and apply slot mapping.
- POP_SCOPE restores the parent scope.
### Calls
- CALL_DIRECT F, S, C, S
- CALL_VIRTUAL S, M, S, C, S
- CALL_FALLBACK T, S, C, S
- CALL_SLOT S, S, C, S
### Object access (optional, later)
- GET_FIELD S, M -> S

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych"
version = "1.3.0-SNAPSHOT"
version = "1.2.1-SNAPSHOT"
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below

View File

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

View File

@ -1,30 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng.bytecode
import java.util.IdentityHashMap
internal actual object CmdCallSiteCache {
private val cache = ThreadLocal.withInitial {
IdentityHashMap<CmdFunction, MutableMap<Int, MethodCallSite>>()
}
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
val map = cache.get()
return map.getOrPut(fn) { mutableMapOf() }
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* 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
import net.sergeych.lyng.obj.Obj
class BlockStatement(
val block: Script,
val slotPlan: Map<String, Int>,
private val startPos: Pos,
) : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos)
if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan)
return block.execute(target)
}
fun statements(): List<Statement> = block.debugStatements()
}

View File

@ -1,23 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* 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
import net.sergeych.lyng.bytecode.BytecodeStatement
interface BytecodeBodyProvider {
fun bytecodeBody(): BytecodeStatement?
}

View File

@ -1,30 +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
import net.sergeych.lyng.obj.Obj
class ClassDeclStatement(
private val delegate: Statement,
private val startPos: Pos,
) : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +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
import net.sergeych.lyng.obj.Obj
class EnumDeclStatement(
private val delegate: Statement,
private val startPos: Pos,
) : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope)
}
}

View File

@ -1,50 +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
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjProperty
import net.sergeych.lyng.obj.ObjRecord
class ExtensionPropertyDeclStatement(
val extTypeName: String,
val property: ObjProperty,
val visibility: Visibility,
val setterVisibility: Visibility?,
private val startPos: Pos,
) : Statement() {
override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj {
val type = context[extTypeName]?.value ?: context.raiseSymbolNotFound("class $extTypeName not found")
if (type !is ObjClass) context.raiseClassCastError("$extTypeName is not the class instance")
context.addExtension(
type,
property.name,
ObjRecord(
property,
isMutable = false,
visibility = visibility,
writeVisibility = setterVisibility,
declaringClass = null,
type = ObjRecord.Type.Property
)
)
return property
}
}

View File

@ -1,30 +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
import net.sergeych.lyng.obj.Obj
class FunctionDeclStatement(
private val delegate: Statement,
private val startPos: Pos,
) : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope)
}
}

View File

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

View File

@ -18,8 +18,6 @@
package net.sergeych.lyng
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.bytecode.CmdDisassembler
import net.sergeych.lyng.bytecode.BytecodeStatement
import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.pacman.ImportProvider
@ -339,8 +337,6 @@ open class Scope(
internal val objects = mutableMapOf<String, ObjRecord>()
internal fun getLocalRecordDirect(name: String): ObjRecord? = objects[name]
open operator fun get(name: String): ObjRecord? {
if (name == "this") return thisObj.asReadonly
@ -383,8 +379,6 @@ open class Scope(
fun setSlotValue(index: Int, newValue: Obj) {
slots[index].value = newValue
}
val slotCount: Int
get() = slots.size
fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
fun allocateSlotFor(name: String, record: ObjRecord): Int {
@ -416,48 +410,6 @@ open class Scope(
}
}
fun applySlotPlanWithSnapshot(plan: Map<String, Int>): Map<String, Int?> {
if (plan.isEmpty()) return emptyMap()
val maxIndex = plan.values.maxOrNull() ?: return emptyMap()
if (slots.size <= maxIndex) {
val targetSize = maxIndex + 1
while (slots.size < targetSize) {
slots.add(ObjRecord(ObjUnset, isMutable = true))
}
}
val snapshot = LinkedHashMap<String, Int?>(plan.size)
for ((name, idx) in plan) {
snapshot[name] = nameToSlot[name]
nameToSlot[name] = idx
}
return snapshot
}
fun restoreSlotPlan(snapshot: Map<String, Int?>) {
if (snapshot.isEmpty()) return
for ((name, idx) in snapshot) {
if (idx == null) {
nameToSlot.remove(name)
} else {
nameToSlot[name] = idx
}
}
}
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.
*/
@ -663,15 +615,6 @@ open class Scope(
}
}
fun disassembleSymbol(name: String): String {
val record = get(name) ?: return "$name is not found"
val stmt = record.value as? Statement ?: return "$name is not a compiled body"
val bytecode = (stmt as? BytecodeStatement)?.bytecodeFunction()
?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
?: return "$name is not a compiled body"
return CmdDisassembler.disassemble(bytecode)
}
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
val newFn = object : Statement() {
override val pos: Pos = Pos.builtIn

View File

@ -43,8 +43,6 @@ class Script(
return lastResult
}
internal fun debugStatements(): List<Statement> = statements
suspend fun execute() = execute(
defaultImportManager.newStdScope()
)
@ -464,4 +462,4 @@ class Script(
}
}
}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* 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
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjRecord
class VarDeclStatement(
val name: String,
val isMutable: Boolean,
val visibility: Visibility,
val initializer: Statement?,
val isTransient: Boolean,
val slotIndex: Int?,
val slotDepth: Int?,
private val startPos: Pos,
) : Statement() {
override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj {
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjNull
context.addItem(
name,
isMutable,
initValue,
visibility,
recordType = ObjRecord.Type.Other,
isTransient = isTransient
)
return initValue
}
}

View File

@ -0,0 +1,216 @@
/*
* 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 -> emptyList()
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL,
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.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_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

@ -16,10 +16,7 @@
package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Visibility
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjProperty
sealed class BytecodeConst {
object Null : BytecodeConst()
@ -27,25 +24,5 @@ sealed class BytecodeConst {
data class IntVal(val value: Long) : BytecodeConst()
data class RealVal(val value: Double) : BytecodeConst()
data class StringVal(val value: String) : BytecodeConst()
data class PosVal(val pos: Pos) : BytecodeConst()
data class ObjRef(val value: Obj) : BytecodeConst()
data class Ref(val value: net.sergeych.lyng.obj.ObjRef) : BytecodeConst()
data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst()
data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst()
data class ValueFn(val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord) : BytecodeConst()
data class SlotPlan(val plan: Map<String, Int>) : BytecodeConst()
data class ExtensionPropertyDecl(
val extTypeName: String,
val property: ObjProperty,
val visibility: Visibility,
val setterVisibility: Visibility?,
) : BytecodeConst()
data class LocalDecl(
val name: String,
val isMutable: Boolean,
val visibility: Visibility,
val isTransient: Boolean,
) : BytecodeConst()
data class CallArgsPlan(val tailBlock: Boolean, val specs: List<CallArgSpec>) : BytecodeConst()
data class CallArgSpec(val name: String?, val isSplat: Boolean)
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng.bytecode
interface BytecodeDecoder {
fun readOpcode(code: ByteArray, ip: Int): Opcode
fun readSlot(code: ByteArray, ip: Int): Int
fun readConstId(code: ByteArray, ip: Int, width: Int): Int
fun readIp(code: ByteArray, ip: Int, width: Int): Int
}
object Decoder8 : BytecodeDecoder {
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
Opcode.fromCode(code[ip].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

@ -0,0 +1,132 @@
/*
* 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 -> emptyList()
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL,
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.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_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

@ -1,28 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Pos
class BytecodeFallbackException(
message: String,
val pos: Pos? = null,
) : RuntimeException(message) {
override fun toString(): String =
pos?.let { "${super.toString()} at $it" } ?: super.toString()
}

View File

@ -16,29 +16,26 @@
package net.sergeych.lyng.bytecode
data class CmdFunction(
data class BytecodeFunction(
val name: String,
val localCount: Int,
val addrCount: Int,
val returnLabels: Set<String>,
val scopeSlotCount: Int,
val scopeSlotDepths: IntArray,
val scopeSlotIndices: IntArray,
val scopeSlotNames: Array<String?>,
val localSlotNames: Array<String?>,
val localSlotMutables: BooleanArray,
val localSlotDepths: IntArray,
val slotWidth: Int,
val ipWidth: Int,
val constIdWidth: Int,
val constants: List<BytecodeConst>,
val fallbackStatements: List<net.sergeych.lyng.Statement>,
val cmds: Array<Cmd>,
val code: ByteArray,
) {
init {
require(slotWidth == 1 || slotWidth == 2 || slotWidth == 4) { "slotWidth must be 1,2,4" }
require(ipWidth == 2 || ipWidth == 4) { "ipWidth must be 2 or 4" }
require(constIdWidth == 2 || constIdWidth == 4) { "constIdWidth must be 2 or 4" }
require(scopeSlotDepths.size == scopeSlotCount) { "scopeSlotDepths size mismatch" }
require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" }
require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames size mismatch" }
require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" }
require(localSlotNames.size == localSlotDepths.size) { "localSlot depth metadata size mismatch" }
require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" }
require(addrCount >= 0) { "addrCount must be non-negative" }
}
}

View File

@ -20,174 +20,31 @@ import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.RangeRef
class BytecodeStatement private constructor(
val original: Statement,
private val function: CmdFunction,
private val function: BytecodeFunction,
) : Statement(original.isStaticConst, original.isConst, original.returnType) {
override val pos: Pos = original.pos
override suspend fun execute(scope: Scope): Obj {
return CmdVm().execute(function, scope, emptyList())
return BytecodeVm().execute(function, scope, emptyList())
}
internal fun bytecodeFunction(): CmdFunction = function
companion object {
fun wrap(
statement: Statement,
nameHint: String,
allowLocalSlots: Boolean,
returnLabels: Set<String> = emptySet(),
rangeLocalNames: Set<String> = emptySet(),
): Statement {
fun wrap(statement: Statement, nameHint: String, allowLocalSlots: Boolean): Statement {
if (statement is BytecodeStatement) return statement
val hasUnsupported = containsUnsupportedStatement(statement)
if (hasUnsupported) {
val statementName = statement::class.qualifiedName ?: statement.javaClass.name
throw BytecodeFallbackException(
"Bytecode fallback: unsupported statement $statementName in '$nameHint'",
statement.pos
)
}
val safeLocals = allowLocalSlots
val compiler = BytecodeCompiler(
allowLocalSlots = safeLocals,
returnLabels = returnLabels,
rangeLocalNames = rangeLocalNames
)
val compiler = BytecodeCompiler(allowLocalSlots = allowLocalSlots)
val compiled = compiler.compileStatement(nameHint, statement)
val fn = compiled ?: throw BytecodeFallbackException(
"Bytecode fallback: failed to compile '$nameHint'",
statement.pos
)
val fn = compiled ?: run {
val builder = BytecodeBuilder()
val slot = 0
val id = builder.addFallback(statement)
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
builder.emit(Opcode.RET, slot)
builder.build(nameHint, localCount = 1)
}
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 -> {
val unsupported = containsUnsupportedStatement(target.source) ||
containsUnsupportedStatement(target.body) ||
(target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false)
unsupported
}
is net.sergeych.lyng.WhileStatement -> {
containsUnsupportedStatement(target.condition) ||
containsUnsupportedStatement(target.body) ||
(target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false)
}
is net.sergeych.lyng.DoWhileStatement -> {
containsUnsupportedStatement(target.body) ||
containsUnsupportedStatement(target.condition) ||
(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
is net.sergeych.lyng.BreakStatement ->
target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false
is net.sergeych.lyng.ContinueStatement -> false
is net.sergeych.lyng.ReturnStatement ->
target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false
is net.sergeych.lyng.ThrowStatement ->
containsUnsupportedStatement(target.throwExpr)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> false
is net.sergeych.lyng.ClassDeclStatement -> false
is net.sergeych.lyng.FunctionDeclStatement -> false
is net.sergeych.lyng.EnumDeclStatement -> 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
)
}
is net.sergeych.lyng.WhileStatement -> {
net.sergeych.lyng.WhileStatement(
unwrapDeep(stmt.condition),
unwrapDeep(stmt.body),
stmt.elseStatement?.let { unwrapDeep(it) },
stmt.label,
stmt.canBreak,
stmt.loopSlotPlan,
stmt.pos
)
}
is net.sergeych.lyng.DoWhileStatement -> {
net.sergeych.lyng.DoWhileStatement(
unwrapDeep(stmt.body),
unwrapDeep(stmt.condition),
stmt.elseStatement?.let { unwrapDeep(it) },
stmt.label,
stmt.loopSlotPlan,
stmt.pos
)
}
is net.sergeych.lyng.BreakStatement -> {
val resultExpr = stmt.resultExpr?.let { unwrapDeep(it) }
net.sergeych.lyng.BreakStatement(stmt.label, resultExpr, stmt.pos)
}
is net.sergeych.lyng.ContinueStatement ->
net.sergeych.lyng.ContinueStatement(stmt.label, stmt.pos)
is net.sergeych.lyng.ReturnStatement -> {
val resultExpr = stmt.resultExpr?.let { unwrapDeep(it) }
net.sergeych.lyng.ReturnStatement(stmt.label, resultExpr, stmt.pos)
}
is net.sergeych.lyng.ThrowStatement ->
net.sergeych.lyng.ThrowStatement(unwrapDeep(stmt.throwExpr), stmt.pos)
else -> stmt
}
}
}
}

View File

@ -0,0 +1,832 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.*
class BytecodeVm {
suspend fun execute(fn: BytecodeFunction, scope: Scope, args: List<Obj>): Obj {
val frame = BytecodeFrame(fn.localCount, args.size)
for (i in args.indices) {
frame.setObj(frame.argBase + i, args[i])
}
val decoder = when (fn.slotWidth) {
1 -> Decoder8
2 -> Decoder16
4 -> Decoder32
else -> error("Unsupported slot width: ${fn.slotWidth}")
}
var ip = 0
val code = fn.code
while (ip < code.size) {
val op = decoder.readOpcode(code, ip)
ip += 1
when (op) {
Opcode.NOP -> {
// no-op
}
Opcode.CONST_INT -> {
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
ip += fn.constIdWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
val c = fn.constants[constId] as? BytecodeConst.IntVal
?: error("CONST_INT expects IntVal at $constId")
setInt(fn, frame, scope, dst, c.value)
}
Opcode.CONST_REAL -> {
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
ip += fn.constIdWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
val c = fn.constants[constId] as? BytecodeConst.RealVal
?: error("CONST_REAL expects RealVal at $constId")
setReal(fn, frame, scope, dst, c.value)
}
Opcode.CONST_BOOL -> {
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
ip += fn.constIdWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
val c = fn.constants[constId] as? BytecodeConst.Bool
?: error("CONST_BOOL expects Bool at $constId")
setBool(fn, frame, scope, dst, c.value)
}
Opcode.CONST_OBJ -> {
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
ip += fn.constIdWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
when (val c = fn.constants[constId]) {
is BytecodeConst.ObjRef -> {
val obj = c.value
when (obj) {
is ObjInt -> setInt(fn, frame, scope, dst, obj.value)
is ObjReal -> setReal(fn, frame, scope, dst, obj.value)
is ObjBool -> setBool(fn, frame, scope, dst, obj.value)
else -> setObj(fn, frame, scope, dst, obj)
}
}
is BytecodeConst.StringVal -> setObj(fn, frame, scope, dst, ObjString(c.value))
else -> error("CONST_OBJ expects ObjRef/StringVal at $constId")
}
}
Opcode.CONST_NULL -> {
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setObj(fn, frame, scope, dst, ObjNull)
}
Opcode.MOVE_INT -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, src))
}
Opcode.MOVE_REAL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setReal(fn, frame, scope, dst, getReal(fn, frame, scope, src))
}
Opcode.MOVE_BOOL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getBool(fn, frame, scope, src))
}
Opcode.MOVE_OBJ -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setObj(fn, frame, scope, dst, getObj(fn, frame, scope, src))
}
Opcode.INT_TO_REAL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setReal(fn, frame, scope, dst, getInt(fn, frame, scope, src).toDouble())
}
Opcode.REAL_TO_INT -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getReal(fn, frame, scope, src).toLong())
}
Opcode.BOOL_TO_INT -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, if (getBool(fn, frame, scope, src)) 1L else 0L)
}
Opcode.INT_TO_BOOL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, src) != 0L)
}
Opcode.ADD_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) + getInt(fn, frame, scope, b))
}
Opcode.SUB_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) - getInt(fn, frame, scope, b))
}
Opcode.MUL_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) * getInt(fn, frame, scope, b))
}
Opcode.DIV_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) / getInt(fn, frame, scope, b))
}
Opcode.MOD_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) % getInt(fn, frame, scope, b))
}
Opcode.NEG_INT -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, -getInt(fn, frame, scope, src))
}
Opcode.INC_INT -> {
val slot = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, slot, getInt(fn, frame, scope, slot) + 1L)
}
Opcode.DEC_INT -> {
val slot = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, slot, getInt(fn, frame, scope, slot) - 1L)
}
Opcode.ADD_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) + getReal(fn, frame, scope, b))
}
Opcode.SUB_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) - getReal(fn, frame, scope, b))
}
Opcode.MUL_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) * getReal(fn, frame, scope, b))
}
Opcode.DIV_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) / getReal(fn, frame, scope, b))
}
Opcode.NEG_REAL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setReal(fn, frame, scope, dst, -getReal(fn, frame, scope, src))
}
Opcode.AND_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) and getInt(fn, frame, scope, b))
}
Opcode.OR_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) or getInt(fn, frame, scope, b))
}
Opcode.XOR_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) xor getInt(fn, frame, scope, b))
}
Opcode.SHL_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) shl getInt(fn, frame, scope, b).toInt())
}
Opcode.SHR_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) shr getInt(fn, frame, scope, b).toInt())
}
Opcode.USHR_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) ushr getInt(fn, frame, scope, b).toInt())
}
Opcode.INV_INT -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setInt(fn, frame, scope, dst, getInt(fn, frame, scope, src).inv())
}
Opcode.CMP_LT_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) < getInt(fn, frame, scope, b))
}
Opcode.CMP_LTE_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) <= getInt(fn, frame, scope, b))
}
Opcode.CMP_GT_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) > getInt(fn, frame, scope, b))
}
Opcode.CMP_GTE_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) >= getInt(fn, frame, scope, b))
}
Opcode.CMP_EQ_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) == getInt(fn, frame, scope, b))
}
Opcode.CMP_NEQ_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) != getInt(fn, frame, scope, b))
}
Opcode.CMP_EQ_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) == getReal(fn, frame, scope, b))
}
Opcode.CMP_NEQ_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) != getReal(fn, frame, scope, b))
}
Opcode.CMP_LT_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) < getReal(fn, frame, scope, b))
}
Opcode.CMP_LTE_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) <= getReal(fn, frame, scope, b))
}
Opcode.CMP_GT_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) > getReal(fn, frame, scope, b))
}
Opcode.CMP_GTE_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) >= getReal(fn, frame, scope, b))
}
Opcode.CMP_EQ_BOOL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) == getBool(fn, frame, scope, b))
}
Opcode.CMP_NEQ_BOOL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) != getBool(fn, frame, scope, b))
}
Opcode.CMP_EQ_INT_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() == getReal(fn, frame, scope, b))
}
Opcode.CMP_EQ_REAL_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) == getInt(fn, frame, scope, b).toDouble())
}
Opcode.CMP_LT_INT_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() < getReal(fn, frame, scope, b))
}
Opcode.CMP_LT_REAL_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) < getInt(fn, frame, scope, b).toDouble())
}
Opcode.CMP_LTE_INT_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() <= getReal(fn, frame, scope, b))
}
Opcode.CMP_LTE_REAL_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) <= getInt(fn, frame, scope, b).toDouble())
}
Opcode.CMP_GT_INT_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() > getReal(fn, frame, scope, b))
}
Opcode.CMP_GT_REAL_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) > getInt(fn, frame, scope, b).toDouble())
}
Opcode.CMP_GTE_INT_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() >= getReal(fn, frame, scope, b))
}
Opcode.CMP_GTE_REAL_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) >= getInt(fn, frame, scope, b).toDouble())
}
Opcode.CMP_NEQ_INT_REAL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() != getReal(fn, frame, scope, b))
}
Opcode.CMP_NEQ_REAL_INT -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) != getInt(fn, frame, scope, b).toDouble())
}
Opcode.CMP_EQ_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).equals(scope, getObj(fn, frame, scope, b)))
}
Opcode.CMP_NEQ_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, !getObj(fn, frame, scope, a).equals(scope, getObj(fn, frame, scope, b)))
}
Opcode.CMP_REF_EQ_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a) === getObj(fn, frame, scope, b))
}
Opcode.CMP_REF_NEQ_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a) !== getObj(fn, frame, scope, b))
}
Opcode.CMP_LT_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) < 0)
}
Opcode.CMP_LTE_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) <= 0)
}
Opcode.CMP_GT_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) > 0)
}
Opcode.CMP_GTE_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) >= 0)
}
Opcode.ADD_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).plus(scope, getObj(fn, frame, scope, b)))
}
Opcode.SUB_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).minus(scope, getObj(fn, frame, scope, b)))
}
Opcode.MUL_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).mul(scope, getObj(fn, frame, scope, b)))
}
Opcode.DIV_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).div(scope, getObj(fn, frame, scope, b)))
}
Opcode.MOD_OBJ -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setObj(fn, frame, scope, dst, getObj(fn, frame, scope, a).mod(scope, getObj(fn, frame, scope, b)))
}
Opcode.NOT_BOOL -> {
val src = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, !getBool(fn, frame, scope, src))
}
Opcode.AND_BOOL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) && getBool(fn, frame, scope, b))
}
Opcode.OR_BOOL -> {
val a = decoder.readSlot(code, ip)
ip += fn.slotWidth
val b = decoder.readSlot(code, ip)
ip += fn.slotWidth
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) || getBool(fn, frame, scope, b))
}
Opcode.JMP -> {
val target = decoder.readIp(code, ip, fn.ipWidth)
ip = target
}
Opcode.JMP_IF_FALSE -> {
val cond = decoder.readSlot(code, ip)
ip += fn.slotWidth
val target = decoder.readIp(code, ip, fn.ipWidth)
ip += fn.ipWidth
if (!getBool(fn, frame, scope, cond)) {
ip = target
}
}
Opcode.JMP_IF_TRUE -> {
val cond = decoder.readSlot(code, ip)
ip += fn.slotWidth
val target = decoder.readIp(code, ip, fn.ipWidth)
ip += fn.ipWidth
if (getBool(fn, frame, scope, cond)) {
ip = target
}
}
Opcode.EVAL_FALLBACK -> {
val id = decoder.readConstId(code, ip, 2)
ip += 2
val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth
val stmt = fn.fallbackStatements.getOrNull(id)
?: error("Fallback statement not found: $id")
val result = stmt.execute(scope)
when (result) {
is ObjInt -> setInt(fn, frame, scope, dst, result.value)
is ObjReal -> setReal(fn, frame, scope, dst, result.value)
is ObjBool -> setBool(fn, frame, scope, dst, result.value)
else -> setObj(fn, frame, scope, dst, result)
}
}
Opcode.RET -> {
val slot = decoder.readSlot(code, ip)
return slotToObj(fn, frame, scope, slot)
}
Opcode.RET_VOID -> return ObjVoid
else -> error("Opcode not implemented: $op")
}
}
return ObjVoid
}
private fun slotToObj(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Obj {
if (slot < fn.scopeSlotCount) {
return resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value
}
val local = slot - fn.scopeSlotCount
return when (frame.getSlotTypeCode(local)) {
SlotType.INT.code -> ObjInt.of(frame.getInt(local))
SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
SlotType.OBJ.code -> frame.getObj(local)
else -> ObjVoid
}
}
private fun getObj(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Obj {
return if (slot < fn.scopeSlotCount) {
resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value
} else {
frame.getObj(slot - fn.scopeSlotCount)
}
}
private fun setObj(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int, value: Obj) {
if (slot < fn.scopeSlotCount) {
setScopeSlotValue(scope, fn.scopeSlotDepths[slot], fn.scopeSlotIndices[slot], value)
} else {
frame.setObj(slot - fn.scopeSlotCount, value)
}
}
private fun getInt(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Long {
return if (slot < fn.scopeSlotCount) {
resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value.toLong()
} else {
frame.getInt(slot - fn.scopeSlotCount)
}
}
private fun setInt(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int, value: Long) {
if (slot < fn.scopeSlotCount) {
setScopeSlotValue(scope, fn.scopeSlotDepths[slot], fn.scopeSlotIndices[slot], ObjInt.of(value))
} else {
frame.setInt(slot - fn.scopeSlotCount, value)
}
}
private fun getReal(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Double {
return if (slot < fn.scopeSlotCount) {
resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value.toDouble()
} else {
frame.getReal(slot - fn.scopeSlotCount)
}
}
private fun setReal(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int, value: Double) {
if (slot < fn.scopeSlotCount) {
setScopeSlotValue(scope, fn.scopeSlotDepths[slot], fn.scopeSlotIndices[slot], ObjReal.of(value))
} else {
frame.setReal(slot - fn.scopeSlotCount, value)
}
}
private fun getBool(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Boolean {
return if (slot < fn.scopeSlotCount) {
resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value.toBool()
} else {
frame.getBool(slot - fn.scopeSlotCount)
}
}
private fun setBool(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int, value: Boolean) {
if (slot < fn.scopeSlotCount) {
setScopeSlotValue(scope, fn.scopeSlotDepths[slot], fn.scopeSlotIndices[slot], if (value) ObjTrue else ObjFalse)
} else {
frame.setBool(slot - fn.scopeSlotCount, value)
}
}
private fun setScopeSlotValue(scope: Scope, depth: Int, index: Int, value: Obj) {
val target = resolveScope(scope, depth)
target.setSlotValue(index, value)
}
private fun resolveScope(scope: Scope, depth: Int): Scope {
if (depth == 0) return scope
val next = when (scope) {
is net.sergeych.lyng.ClosureScope -> scope.closureScope
else -> scope.parent
}
return next?.let { resolveScope(it, depth - 1) }
?: error("Scope depth $depth is out of range")
}
}

View File

@ -1,389 +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 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,
returnLabels: Set<String> = emptySet(),
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,
returnLabels = returnLabels,
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.OBJ_TO_BOOL,
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT,
Opcode.ASSERT_IS ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.CHECK_IS ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.RANGE_INT_BOUNDS ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.RET_LABEL, Opcode.THROW ->
listOf(OperandKind.CONST, 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, Opcode.DECL_EXT_PROPERTY ->
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.CONTAINS_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_NAME ->
listOf(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.LIST_LITERAL ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_THIS_MEMBER ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.SET_THIS_MEMBER ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
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.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
Opcode.RET_LABEL -> CmdRetLabel(operands[0], operands[1])
Opcode.THROW -> CmdThrow(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.CONTAINS_OBJ -> CmdContainsObj(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.DECL_EXT_PROPERTY -> CmdDeclExtProperty(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_NAME -> CmdGetName(operands[0], operands[1])
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
Opcode.GET_THIS_MEMBER -> CmdGetThisMember(operands[0], operands[1])
Opcode.SET_THIS_MEMBER -> CmdSetThisMember(operands[0], operands[1])
Opcode.EVAL_FALLBACK -> CmdEvalFallback(operands[0], operands[1])
Opcode.EVAL_REF -> CmdEvalRef(operands[0], operands[1])
Opcode.EVAL_STMT -> CmdEvalStmt(operands[0], operands[1])
Opcode.EVAL_VALUE_FN -> CmdEvalValueFn(operands[0], operands[1])
}
}
}

View File

@ -1,21 +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
internal expect object CmdCallSiteCache {
fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite>
}

View File

@ -1,282 +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 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 CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
is CmdAssertIs -> Opcode.ASSERT_IS to intArrayOf(cmd.objSlot, cmd.typeSlot)
is CmdRangeIntBounds -> Opcode.RANGE_INT_BOUNDS to intArrayOf(cmd.src, cmd.startSlot, cmd.endSlot, cmd.okSlot)
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 CmdContainsObj -> Opcode.CONTAINS_OBJ to intArrayOf(cmd.target, cmd.value, 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 CmdRetLabel -> Opcode.RET_LABEL to intArrayOf(cmd.labelId, cmd.slot)
is CmdRetVoid -> Opcode.RET_VOID to intArrayOf()
is CmdThrow -> Opcode.THROW to intArrayOf(cmd.posId, cmd.slot)
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 CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY 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 CmdGetName -> Opcode.GET_NAME to intArrayOf(cmd.nameId, cmd.dst)
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 CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
is CmdGetThisMember -> Opcode.GET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.dst)
is CmdSetThisMember -> Opcode.SET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.valueSlot)
is CmdEvalFallback -> Opcode.EVAL_FALLBACK to intArrayOf(cmd.id, cmd.dst)
is CmdEvalRef -> Opcode.EVAL_REF to intArrayOf(cmd.id, cmd.dst)
is CmdEvalStmt -> Opcode.EVAL_STMT to intArrayOf(cmd.id, cmd.dst)
is CmdEvalValueFn -> Opcode.EVAL_VALUE_FN 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.OBJ_TO_BOOL, Opcode.ASSERT_IS ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.CHECK_IS ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.RANGE_INT_BOUNDS ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.RET_LABEL, Opcode.THROW ->
listOf(OperandKind.CONST, 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, Opcode.DECL_EXT_PROPERTY ->
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.CONTAINS_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_NAME ->
listOf(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.LIST_LITERAL ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_THIS_MEMBER ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.SET_THIS_MEMBER ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
listOf(OperandKind.ID, OperandKind.SLOT)
}
}
}

View File

@ -1,241 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Arguments
import net.sergeych.lyng.ExecutionError
import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.PerfStats
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Visibility
import net.sergeych.lyng.canAccessMember
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjIllegalAccessException
import net.sergeych.lyng.obj.ObjInstance
import net.sergeych.lyng.obj.ObjProperty
import net.sergeych.lyng.obj.ObjRecord
class MethodCallSite(private val name: String) {
private var mKey1: Long = 0L; private var mVer1: Int = -1
private var mInvoker1: (suspend (Obj, Scope, Arguments) -> Obj)? = null
private var mKey2: Long = 0L; private var mVer2: Int = -1
private var mInvoker2: (suspend (Obj, Scope, Arguments) -> Obj)? = null
private var mKey3: Long = 0L; private var mVer3: Int = -1
private var mInvoker3: (suspend (Obj, Scope, Arguments) -> Obj)? = null
private var mKey4: Long = 0L; private var mVer4: Int = -1
private var mInvoker4: (suspend (Obj, Scope, Arguments) -> Obj)? = null
private var mAccesses: Int = 0; private var mMisses: Int = 0; private var mPromotedTo4: Boolean = false
private var mFreezeWindowsLeft: Int = 0
private var mWindowAccesses: Int = 0
private var mWindowMisses: Int = 0
private inline fun size4MethodsEnabled(): Boolean =
PerfFlags.METHOD_PIC_SIZE_4 ||
((PerfFlags.PIC_ADAPTIVE_2_TO_4 || PerfFlags.PIC_ADAPTIVE_METHODS_ONLY) && mPromotedTo4 && mFreezeWindowsLeft == 0)
private fun noteMethodHit() {
if (!(PerfFlags.PIC_ADAPTIVE_2_TO_4 || PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
val a = (mAccesses + 1).coerceAtMost(1_000_000)
mAccesses = a
if (PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
if (mWindowAccesses >= 256) endHeuristicWindow()
}
}
private fun noteMethodMiss() {
if (!(PerfFlags.PIC_ADAPTIVE_2_TO_4 || PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
val a = (mAccesses + 1).coerceAtMost(1_000_000)
mAccesses = a
mMisses = (mMisses + 1).coerceAtMost(1_000_000)
if (!mPromotedTo4 && mFreezeWindowsLeft == 0 && a >= 256) {
if (mMisses * 100 / a > 20) mPromotedTo4 = true
mAccesses = 0; mMisses = 0
}
if (PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
mWindowMisses = (mWindowMisses + 1).coerceAtMost(1_000_000)
if (mWindowAccesses >= 256) endHeuristicWindow()
}
}
private fun endHeuristicWindow() {
val accesses = mWindowAccesses
val misses = mWindowMisses
mWindowAccesses = 0
mWindowMisses = 0
if (mFreezeWindowsLeft > 0) {
mFreezeWindowsLeft = (mFreezeWindowsLeft - 1).coerceAtLeast(0)
return
}
if (mPromotedTo4 && accesses >= 256) {
val rate = misses * 100 / accesses
if (rate >= 25) {
mPromotedTo4 = false
mFreezeWindowsLeft = 4
}
}
}
suspend fun invoke(scope: Scope, base: Obj, callArgs: Arguments): Obj {
if (PerfFlags.METHOD_PIC) {
val (key, ver) = when (base) {
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
is ObjClass -> base.classId to base.layoutVersion
else -> 0L to -1
}
if (key != 0L) {
mInvoker1?.let { inv ->
if (key == mKey1 && ver == mVer1) {
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicHit++
noteMethodHit()
return inv(base, scope, callArgs)
}
}
mInvoker2?.let { inv ->
if (key == mKey2 && ver == mVer2) {
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicHit++
noteMethodHit()
val tK = mKey2; val tV = mVer2; val tI = mInvoker2
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
return inv(base, scope, callArgs)
}
}
if (size4MethodsEnabled()) mInvoker3?.let { inv ->
if (key == mKey3 && ver == mVer3) {
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicHit++
noteMethodHit()
val tK = mKey3; val tV = mVer3; val tI = mInvoker3
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
return inv(base, scope, callArgs)
}
}
if (size4MethodsEnabled()) mInvoker4?.let { inv ->
if (key == mKey4 && ver == mVer4) {
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicHit++
noteMethodHit()
val tK = mKey4; val tV = mVer4; val tI = mInvoker4
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
return inv(base, scope, callArgs)
}
}
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicMiss++
noteMethodMiss()
val result = try {
base.invokeInstanceMethod(scope, name, callArgs)
} catch (e: ExecutionError) {
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
mKey1 = key; mVer1 = ver; mInvoker1 = { _, sc, _ ->
sc.raiseError(e.message ?: "method not found: $name")
}
throw e
}
if (size4MethodsEnabled()) {
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
}
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
when (base) {
is ObjInstance -> {
val cls0 = base.objClass
val keyInScope = cls0.publicMemberResolution[name]
val methodSlot = if (keyInScope != null) cls0.methodSlotForKey(keyInScope) else null
val fastRec = if (methodSlot != null) {
val idx = methodSlot.slot
if (idx >= 0 && idx < base.methodSlots.size) base.methodSlots[idx] else null
} else if (keyInScope != null) {
base.methodRecordForKey(keyInScope) ?: base.instanceScope.objects[keyInScope]
} else null
val resolved = if (fastRec != null) null else cls0.resolveInstanceMember(name)
val targetRec = when {
fastRec != null && fastRec.type == ObjRecord.Type.Fun -> fastRec
resolved != null && resolved.record.type == ObjRecord.Type.Fun && !resolved.record.isAbstract -> resolved.record
else -> null
}
if (targetRec != null) {
val visibility = targetRec.visibility
val decl = targetRec.declaringClass ?: (resolved?.declaringClass ?: cls0)
if (methodSlot != null && targetRec.type == ObjRecord.Type.Fun) {
val slotIndex = methodSlot.slot
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
val inst = obj as ObjInstance
if (inst.objClass === cls0) {
val rec = if (slotIndex >= 0 && slotIndex < inst.methodSlots.size) inst.methodSlots[slotIndex] else null
if (rec != null && rec.type == ObjRecord.Type.Fun && !rec.isAbstract) {
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) {
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
}
rec.value.invoke(inst.instanceScope, inst, a, decl)
} else {
obj.invokeInstanceMethod(sc, name, a)
}
} else {
obj.invokeInstanceMethod(sc, name, a)
}
}
} else {
val callable = targetRec.value
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
val inst = obj as ObjInstance
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) {
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
}
callable.invoke(inst.instanceScope, inst, a)
}
}
} else {
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
obj.invokeInstanceMethod(sc, name, a)
}
}
}
is ObjClass -> {
val clsScope = base.classScope
val rec = clsScope?.get(name)
if (rec != null) {
val callable = rec.value
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
callable.invoke(sc, obj, a)
}
} else {
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
obj.invokeInstanceMethod(sc, name, a)
}
}
}
else -> {
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
obj.invokeInstanceMethod(sc, name, a)
}
}
}
return result
}
}
return base.invokeInstanceMethod(scope, name, callArgs)
}
}

View File

@ -27,16 +27,11 @@ enum class Opcode(val code: Int) {
CONST_REAL(0x07),
CONST_BOOL(0x08),
CONST_NULL(0x09),
BOX_OBJ(0x0A),
RANGE_INT_BOUNDS(0x0B),
INT_TO_REAL(0x10),
REAL_TO_INT(0x11),
BOOL_TO_INT(0x12),
INT_TO_BOOL(0x13),
OBJ_TO_BOOL(0x14),
CHECK_IS(0x15),
ASSERT_IS(0x16),
ADD_INT(0x20),
SUB_INT(0x21),
@ -105,49 +100,23 @@ enum class Opcode(val code: Int) {
MUL_OBJ(0x79),
DIV_OBJ(0x7A),
MOD_OBJ(0x7B),
CONTAINS_OBJ(0x7C),
JMP(0x80),
JMP_IF_TRUE(0x81),
JMP_IF_FALSE(0x82),
RET(0x83),
RET_VOID(0x84),
RET_LABEL(0xBA),
PUSH_SCOPE(0x85),
POP_SCOPE(0x86),
PUSH_SLOT_PLAN(0x87),
POP_SLOT_PLAN(0x88),
DECL_LOCAL(0x89),
DECL_EXT_PROPERTY(0x8A),
CALL_DIRECT(0x90),
CALL_VIRTUAL(0x91),
CALL_FALLBACK(0x92),
CALL_SLOT(0x93),
GET_FIELD(0xA0),
SET_FIELD(0xA1),
GET_INDEX(0xA2),
SET_INDEX(0xA3),
GET_NAME(0xA4),
LIST_LITERAL(0xA5),
GET_THIS_MEMBER(0xA6),
SET_THIS_MEMBER(0xA7),
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),
THROW(0xBB),
EVAL_REF(0xBC),
EVAL_STMT(0xBD),
EVAL_VALUE_FN(0xBE),
;
companion object {

View File

@ -166,7 +166,8 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
}
del = del ?: scope.raiseError("Internal error: delegated property $name has no delegate")
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
return obj.copy(value = res, type = ObjRecord.Type.Other)
obj.value = res
return obj
}
// Map member template to instance storage if applicable

View File

@ -66,8 +66,6 @@ sealed interface ObjRef {
/** Runtime-computed read-only reference backed by a lambda. */
class ValueFnRef(private val fn: suspend (Scope) -> ObjRecord) : ObjRef {
internal fun valueFn(): suspend (Scope) -> ObjRecord = fn
override suspend fun get(scope: Scope): ObjRecord = fn(scope)
}
@ -387,9 +385,9 @@ class BinaryOpRef(internal val op: BinOp, internal val left: ObjRef, internal va
/** Conditional (ternary) operator reference: cond ? a : b */
class ConditionalRef(
internal val condition: ObjRef,
internal val ifTrue: ObjRef,
internal val ifFalse: ObjRef
private val condition: ObjRef,
private val ifTrue: ObjRef,
private val ifFalse: ObjRef
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
return evalCondition(scope).get(scope)
@ -663,9 +661,9 @@ class QualifiedThisMethodSlotCallRef(
/** Assignment compound op: target op= value */
class AssignOpRef(
internal val op: BinOp,
internal val target: ObjRef,
internal val value: ObjRef,
private val op: BinOp,
private val target: ObjRef,
private val value: ObjRef,
private val atPos: Pos,
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
@ -725,9 +723,9 @@ class AssignOpRef(
/** Pre/post ++/-- on l-values */
class IncDecRef(
internal val target: ObjRef,
internal val isIncrement: Boolean,
internal val isPost: Boolean,
private val target: ObjRef,
private val isIncrement: Boolean,
private val isPost: Boolean,
private val atPos: Pos,
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
@ -753,7 +751,7 @@ class IncDecRef(
}
/** Elvis operator reference: a ?: b */
class ElvisRef(internal val left: ObjRef, internal val right: ObjRef) : ObjRef {
class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
val a = left.evalValue(scope)
val r = if (a != ObjNull) a else right.evalValue(scope)
@ -1157,17 +1155,6 @@ class FieldRef(
else -> 0L to -1 // no caching for primitives/dynamics without stable shape
}
private suspend fun resolveValue(scope: Scope, base: Obj, rec: ObjRecord): Obj {
if (rec.type == ObjRecord.Type.Delegated || rec.value is ObjProperty || rec.type == ObjRecord.Type.Property) {
val receiver = rec.receiver ?: base
return receiver.resolveRecord(scope, rec, name, rec.declaringClass).value
}
if (rec.receiver != null && rec.declaringClass != null) {
return rec.receiver!!.resolveRecord(scope, rec, name, rec.declaringClass).value
}
return rec.value
}
override suspend fun evalValue(scope: Scope): Obj {
// Mirror get(), but return raw Obj to avoid transient ObjRecord on R-value paths
val fieldPic = PerfFlags.FIELD_PIC
@ -1185,14 +1172,14 @@ class FieldRef(
if (key != 0L) {
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
if (picCounters) PerfStats.fieldPicHit++
return resolveValue(scope, base, g(base, scope))
return g(base, scope).value
} }
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
if (picCounters) PerfStats.fieldPicHit++
val tK = rKey2; val tV = rVer2; val tG = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tK; rVer1 = tV; rGetter1 = tG
return resolveValue(scope, base, g(base, scope))
return g(base, scope).value
} }
if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
if (picCounters) PerfStats.fieldPicHit++
@ -1200,7 +1187,7 @@ class FieldRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tK; rVer1 = tV; rGetter1 = tG
return resolveValue(scope, base, g(base, scope))
return g(base, scope).value
} }
if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
if (picCounters) PerfStats.fieldPicHit++
@ -1209,17 +1196,16 @@ class FieldRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tK; rVer1 = tV; rGetter1 = tG
return resolveValue(scope, base, g(base, scope))
return g(base, scope).value
} }
if (picCounters) PerfStats.fieldPicMiss++
val rec = base.readField(scope, name)
// install primary generic getter for this shape
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
return resolveValue(scope, base, rec)
return rec.value
}
}
val rec = base.readField(scope, name)
return resolveValue(scope, base, rec)
return base.readField(scope, name).value
}
}
@ -1338,9 +1324,6 @@ class IndexRef(
private val index: ObjRef,
private val isOptional: Boolean,
) : ObjRef {
internal val targetRef: ObjRef get() = target
internal val indexRef: ObjRef get() = index
internal val optionalRef: Boolean get() = isOptional
// Tiny 4-entry PIC for index reads (guarded implicitly by RVAL_FASTPATH); move-to-front on hits
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (suspend (Obj, Scope, Obj) -> Obj)? = null
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: (suspend (Obj, Scope, Obj) -> Obj)? = null
@ -1584,22 +1567,21 @@ class StatementRef(internal val statement: Statement) : ObjRef {
* Direct function call reference: f(args) and optional f?(args).
*/
class CallRef(
internal val target: ObjRef,
internal val args: List<ParsedArgument>,
internal val tailBlock: Boolean,
internal val isOptionalInvoke: Boolean,
private val target: ObjRef,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptionalInvoke: Boolean,
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
val usePool = PerfFlags.SCOPE_POOL
val callee = target.evalValue(scope)
if (callee == ObjNull && isOptionalInvoke) return ObjNull.asReadonly
val callArgs = args.toArguments(scope, tailBlock)
val usePool = PerfFlags.SCOPE_POOL && callee !is Statement
val result: Obj = if (usePool) {
scope.withChildFrame(callArgs) { child ->
callee.callOn(child)
}
} else {
// Pooling for Statement callables (lambdas) can still perturb closure semantics; keep safe path for now.
callee.callOn(scope.createChildScope(scope.pos, callArgs))
}
return result.asReadonly
@ -1610,11 +1592,11 @@ class CallRef(
* Instance method call reference: obj.method(args) and optional obj?.method(args).
*/
class MethodCallRef(
internal val receiver: ObjRef,
internal val name: String,
internal val args: List<ParsedArgument>,
internal val tailBlock: Boolean,
internal val isOptional: Boolean,
private val receiver: ObjRef,
private val name: String,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean,
) : ObjRef {
// 4-entry PIC for method invocations (guarded by PerfFlags.METHOD_PIC)
private var mKey1: Long = 0L; private var mVer1: Int = -1; private var mInvoker1: (suspend (Obj, Scope, Arguments) -> Obj)? = null
@ -1867,11 +1849,6 @@ class ThisMethodSlotCallRef(
private val tailBlock: Boolean,
private val isOptional: Boolean
) : ObjRef {
internal fun methodName(): String = name
internal fun arguments(): List<ParsedArgument> = args
internal fun hasTailBlock(): Boolean = tailBlock
internal fun optionalInvoke(): Boolean = isOptional
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
override suspend fun evalValue(scope: Scope): Obj {
@ -2002,10 +1979,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
scope.assign(rec, name, newValue)
return
}
scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx)?.let { rec ->
scope.assign(rec, name, newValue)
return
}
scope[name]?.let { stored ->
scope.assign(stored, name, newValue)
return
@ -2022,10 +1995,6 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
return
}
}
scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx)?.let { rec ->
scope.assign(rec, name, newValue)
return
}
scope[name]?.let { stored ->
scope.assign(stored, name, newValue)
return
@ -2436,7 +2405,6 @@ class LocalSlotRef(
val name: String,
internal val slot: Int,
internal val depth: Int,
internal val scopeDepth: Int,
internal val isMutable: Boolean,
internal val isDelegated: Boolean,
private val atPos: Pos,
@ -2523,8 +2491,6 @@ class LocalSlotRef(
}
class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
internal fun entries(): List<ListEntry> = entries
override fun forEachVariable(block: (String) -> Unit) {
for (e in entries) {
when (e) {
@ -2674,9 +2640,9 @@ class RangeRef(
/** Assignment if null op: target ?= value */
class AssignIfNullRef(
internal val target: ObjRef,
internal val value: ObjRef,
internal val atPos: Pos,
private val target: ObjRef,
private val value: ObjRef,
private val atPos: Pos,
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
return evalValue(scope).asReadonly

View File

@ -19,17 +19,8 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjIterable
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjException
import net.sergeych.lyng.obj.ObjRange
import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.obj.ObjVoid
import net.sergeych.lyng.obj.toBool
import net.sergeych.lyng.obj.toInt
import net.sergeych.lyng.obj.toLong
fun String.toSource(name: String = "eval"): Source = Source(name, this)
@ -88,357 +79,6 @@ class IfStatement(
}
}
data class ConstIntRange(val start: Long, val endExclusive: Long)
class ForInStatement(
val loopVarName: String,
val source: Statement,
val constRange: ConstIntRange?,
val body: Statement,
val elseStatement: Statement?,
val label: String?,
val canBreak: Boolean,
val loopSlotPlan: Map<String, Int>,
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
val forContext = scope.createChildScope(pos)
if (loopSlotPlan.isNotEmpty()) {
forContext.applySlotPlan(loopSlotPlan)
}
val loopSO = forContext.addItem(loopVarName, true, ObjNull)
val loopSlotIndex = forContext.getSlotIndexOf(loopVarName) ?: -1
if (constRange != null && PerfFlags.PRIMITIVE_FASTOPS) {
return loopIntRange(
forContext,
constRange.start,
constRange.endExclusive,
loopSO,
loopSlotIndex,
body,
elseStatement,
label,
canBreak
)
}
val sourceObj = source.execute(forContext)
return if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) {
loopIntRange(
forContext,
sourceObj.start!!.toLong(),
if (sourceObj.isEndInclusive) sourceObj.end!!.toLong() + 1 else sourceObj.end!!.toLong(),
loopSO,
loopSlotIndex,
body,
elseStatement,
label,
canBreak
)
} else if (sourceObj.isInstanceOf(ObjIterable)) {
loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
} else {
val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() }
.getOrElse {
throw ScriptError(
pos,
"object is not enumerable: no size in $sourceObj",
it
)
}
var result: Obj = ObjVoid
var breakCaught = false
if (size > 0) {
var current = runCatching { sourceObj.getAt(forContext, ObjInt.of(0)) }
.getOrElse {
throw ScriptError(
pos,
"object is not enumerable: no index access for ${sourceObj.inspect(scope)}",
it
)
}
var index = 0
while (true) {
loopSO.value = current
try {
result = body.execute(forContext)
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
breakCaught = true
if (lbe.doContinue) continue
result = lbe.result
break
} else {
throw lbe
}
}
if (++index >= size) break
current = sourceObj.getAt(forContext, ObjInt.of(index.toLong()))
}
}
if (!breakCaught && elseStatement != null) {
result = elseStatement.execute(scope)
}
result
}
}
private suspend fun loopIntRange(
forScope: Scope,
start: Long,
end: Long,
loopVar: ObjRecord,
loopSlotIndex: Int,
body: Statement,
elseStatement: Statement?,
label: String?,
catchBreak: Boolean,
): Obj {
var result: Obj = ObjVoid
val cacheLow = ObjInt.CACHE_LOW
val cacheHigh = ObjInt.CACHE_HIGH
val useCache = start >= cacheLow && end <= cacheHigh + 1
val cache = if (useCache) ObjInt.cacheArray() else null
val useSlot = loopSlotIndex >= 0
if (catchBreak) {
if (useCache && cache != null) {
var i = start
while (i < end) {
val v = cache[(i - cacheLow).toInt()]
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
try {
result = body.execute(forScope)
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
if (lbe.doContinue) {
i++
continue
}
return lbe.result
}
throw lbe
}
i++
}
} else {
for (i in start..<end) {
val v = ObjInt.of(i)
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
try {
result = body.execute(forScope)
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
if (lbe.doContinue) continue
return lbe.result
}
throw lbe
}
}
}
} else {
if (useCache && cache != null) {
var i = start
while (i < end) {
val v = cache[(i - cacheLow).toInt()]
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
result = body.execute(forScope)
i++
}
} else {
for (i in start..<end) {
val v = ObjInt.of(i)
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
result = body.execute(forScope)
}
}
}
return elseStatement?.execute(forScope) ?: result
}
private suspend fun loopIterable(
forScope: Scope,
sourceObj: Obj,
loopVar: ObjRecord,
body: Statement,
elseStatement: Statement?,
label: String?,
catchBreak: Boolean,
): Obj {
var result: Obj = ObjVoid
var breakCaught = false
sourceObj.enumerate(forScope) { item ->
loopVar.value = item
if (catchBreak) {
try {
result = body.execute(forScope)
true
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
breakCaught = true
if (lbe.doContinue) true else {
result = lbe.result
false
}
} else {
throw lbe
}
}
} else {
result = body.execute(forScope)
true
}
}
if (!breakCaught && elseStatement != null) {
result = elseStatement.execute(forScope)
}
return result
}
}
class WhileStatement(
val condition: Statement,
val body: Statement,
val elseStatement: Statement?,
val label: String?,
val canBreak: Boolean,
val loopSlotPlan: Map<String, Int>,
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
var result: Obj = ObjVoid
var wasBroken = false
while (condition.execute(scope).toBool()) {
val loopScope = scope.createChildScope().apply { skipScopeCreation = true }
if (canBreak) {
try {
result = body.execute(loopScope)
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
if (lbe.doContinue) continue
result = lbe.result
wasBroken = true
break
} else {
throw lbe
}
}
} else {
result = body.execute(loopScope)
}
}
if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) }
return result
}
}
class DoWhileStatement(
val body: Statement,
val condition: Statement,
val elseStatement: Statement?,
val label: String?,
val loopSlotPlan: Map<String, Int>,
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
var wasBroken = false
var result: Obj = ObjVoid
while (true) {
val doScope = scope.createChildScope().apply { skipScopeCreation = true }
try {
result = body.execute(doScope)
} catch (e: LoopBreakContinueException) {
if (e.label == label || e.label == null) {
if (!e.doContinue) {
result = e.result
wasBroken = true
break
}
// continue: fall through to condition check
} else {
throw e
}
}
if (!condition.execute(doScope).toBool()) {
break
}
}
if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) }
return result
}
}
class BreakStatement(
val label: String?,
val resultExpr: Statement?,
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
val returnValue = resultExpr?.execute(scope)
throw LoopBreakContinueException(
doContinue = false,
label = label,
result = returnValue ?: ObjVoid
)
}
}
class ContinueStatement(
val label: String?,
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
throw LoopBreakContinueException(
doContinue = true,
label = label,
)
}
}
class ReturnStatement(
val label: String?,
val resultExpr: Statement?,
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
val returnValue = resultExpr?.execute(scope) ?: ObjVoid
throw ReturnException(returnValue, label)
}
}
class ThrowStatement(
val throwExpr: Statement,
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
var errorObject = throwExpr.execute(scope)
val throwScope = scope.createChildScope(pos = pos)
if (errorObject is ObjString) {
errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() }
}
if (!errorObject.isInstanceOf(ObjException.Root)) {
throwScope.raiseError("this is not an exception object: $errorObject")
}
if (errorObject is ObjException) {
errorObject = ObjException(
errorObject.exceptionClass,
throwScope,
errorObject.message,
errorObject.extraData,
errorObject.useStackTrace
).apply { getStackTrace() }
throwScope.raiseError(errorObject)
} else {
val msg = errorObject.invokeInstanceMethod(scope, "message").toString(scope).value
throwScope.raiseError(errorObject, pos, msg)
}
return ObjVoid
}
}
class ToBoolStatement(
val expr: Statement,
override val pos: Pos,

View File

@ -21,7 +21,6 @@ import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.binding.Binder
import net.sergeych.lyng.binding.SymbolKind
import net.sergeych.lyng.miniast.MiniAstBuilder
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

View File

@ -23,7 +23,6 @@ package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.binding.Binder
import net.sergeych.lyng.miniast.MiniAstBuilder
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

View File

@ -18,7 +18,6 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import net.sergeych.lyng.obj.ObjInt
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails

View File

@ -1,74 +0,0 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Test
class BytecodeRecentOpsTest {
@Test
fun listLiteralWithSpread() = runTest {
eval(
"""
val a = [1, 2, 3]
val b = [0, ...a, 4]
assertEquals(5, b.size)
assertEquals(0, b[0])
assertEquals(1, b[1])
assertEquals(4, b[4])
""".trimIndent()
)
}
@Test
fun valueFnRefViaClassOperator() = runTest {
eval(
"""
val c = 1::class
assertEquals("Int", c.className)
""".trimIndent()
)
}
@Test
fun implicitThisCompoundAssign() = runTest {
eval(
"""
class C {
var x = 1
fun add(n) { x += n }
fun calc() { add(2); x }
}
val c = C()
assertEquals(3, c.calc())
""".trimIndent()
)
}
@Test
fun optionalCompoundAssignEvaluatesRhsOnce() = runTest {
eval(
"""
var count = 0
fun inc() { count = count + 1; return 3 }
class Box(var v)
var b = Box(1)
b?.v += inc()
assertEquals(4, b.v)
assertEquals(1, count)
""".trimIndent()
)
}
@Test
fun optionalIndexCompoundAssignEvaluatesRhsOnce() = runTest {
eval(
"""
var count = 0
fun inc() { count = count + 1; return 2 }
var a = [1, 2, 3]
a?[1] += inc()
assertEquals(4, a[1])
assertEquals(1, count)
""".trimIndent()
)
}
}

View File

@ -16,17 +16,14 @@
import net.sergeych.lyng.ExpressionStatement
import net.sergeych.lyng.IfStatement
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.bytecode.CmdBuilder
import net.sergeych.lyng.bytecode.BytecodeBuilder
import net.sergeych.lyng.bytecode.BytecodeCompiler
import net.sergeych.lyng.bytecode.BytecodeConst
import net.sergeych.lyng.bytecode.CmdVm
import net.sergeych.lyng.bytecode.BytecodeVm
import net.sergeych.lyng.bytecode.Opcode
import net.sergeych.lyng.obj.BinaryOpRef
import net.sergeych.lyng.obj.BinOp
import net.sergeych.lyng.obj.CallRef
import net.sergeych.lyng.obj.ConstRef
import net.sergeych.lyng.obj.LocalSlotRef
import net.sergeych.lyng.obj.ObjFalse
@ -41,15 +38,13 @@ import net.sergeych.lyng.obj.ObjVoid
import net.sergeych.lyng.obj.toBool
import net.sergeych.lyng.obj.toDouble
import net.sergeych.lyng.obj.toInt
import net.sergeych.lyng.obj.toLong
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
class CmdVmTest {
class BytecodeVmTest {
@Test
fun addsIntConstants() = kotlinx.coroutines.test.runTest {
val builder = CmdBuilder()
val builder = BytecodeBuilder()
val k0 = builder.addConst(BytecodeConst.IntVal(2))
val k1 = builder.addConst(BytecodeConst.IntVal(3))
builder.emit(Opcode.CONST_INT, k0, 0)
@ -57,7 +52,7 @@ class CmdVmTest {
builder.emit(Opcode.ADD_INT, 0, 1, 2)
builder.emit(Opcode.RET, 2)
val fn = builder.build("addInts", localCount = 3)
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(5, result.toInt())
}
@ -81,7 +76,7 @@ class CmdVmTest {
)
val ifStmt = IfStatement(cond, thenStmt, elseStmt, net.sergeych.lyng.Pos.builtIn)
val fn = BytecodeCompiler().compileStatement("ifTest", ifStmt) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(10, result.toInt())
}
@ -105,7 +100,7 @@ class CmdVmTest {
error("bytecode compile failed for ifNoElse")
}
}!!
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(ObjVoid, result)
}
@ -121,7 +116,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("andShort", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(false, result.toBool())
}
@ -137,7 +132,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("orShort", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(true, result.toBool())
}
@ -152,32 +147,10 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("realPlus", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(5.75, result.toDouble())
}
@Test
fun callSlotInvokesCallable() = kotlinx.coroutines.test.runTest {
val callable = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope) = ObjInt.of(
scope.args[0].toLong() + scope.args[1].toLong()
)
}
val builder = CmdBuilder()
val fnId = builder.addConst(BytecodeConst.ObjRef(callable))
val arg0 = builder.addConst(BytecodeConst.IntVal(2L))
val arg1 = builder.addConst(BytecodeConst.IntVal(3L))
builder.emit(Opcode.CONST_OBJ, fnId, 0)
builder.emit(Opcode.CONST_INT, arg0, 1)
builder.emit(Opcode.CONST_INT, arg1, 2)
builder.emit(Opcode.CALL_SLOT, 0, 1, 2, 3)
builder.emit(Opcode.RET, 3)
val fn = builder.build("callSlot", localCount = 4)
val result = CmdVm().execute(fn, Scope(), emptyList())
assertEquals(5, result.toInt())
}
@Test
fun mixedIntRealComparisonUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
val ltExpr = ExpressionStatement(
@ -189,7 +162,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val ltFn = BytecodeCompiler().compileExpression("mixedLt", ltExpr) ?: error("bytecode compile failed")
val ltResult = CmdVm().execute(ltFn, Scope(), emptyList())
val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList())
assertEquals(true, ltResult.toBool())
val eqExpr = ExpressionStatement(
@ -201,85 +174,10 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val eqFn = BytecodeCompiler().compileExpression("mixedEq", eqExpr) ?: error("bytecode compile failed")
val eqResult = CmdVm().execute(eqFn, Scope(), emptyList())
val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList())
assertEquals(true, eqResult.toBool())
}
@Test
fun callWithTailBlockKeepsTailBlockMode() = kotlinx.coroutines.test.runTest {
val callable = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope) =
if (scope.args.tailBlockMode) ObjTrue else ObjFalse
}
val callRef = CallRef(
ConstRef(callable.asReadonly),
listOf(
net.sergeych.lyng.ParsedArgument(
ExpressionStatement(ConstRef(ObjInt.of(1).asReadonly), Pos.builtIn),
Pos.builtIn
)
),
tailBlock = true,
isOptionalInvoke = false
)
val expr = ExpressionStatement(callRef, Pos.builtIn)
val fn = BytecodeCompiler().compileExpression("tailBlockArgs", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
assertEquals(true, result.toBool())
}
@Test
fun callWithNamedArgumentsUsesPlan() = kotlinx.coroutines.test.runTest {
val callable = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope) =
(scope.args.named["x"] as ObjInt)
}
val callRef = CallRef(
ConstRef(callable.asReadonly),
listOf(
net.sergeych.lyng.ParsedArgument(
ExpressionStatement(ConstRef(ObjInt.of(5).asReadonly), Pos.builtIn),
Pos.builtIn,
name = "x"
)
),
tailBlock = false,
isOptionalInvoke = false
)
val expr = ExpressionStatement(callRef, Pos.builtIn)
val fn = BytecodeCompiler().compileExpression("namedArgs", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
assertEquals(5, result.toInt())
}
@Test
fun callWithSplatArgumentsUsesPlan() = kotlinx.coroutines.test.runTest {
val callable = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope) =
ObjInt.of(scope.args.size.toLong())
}
val list = ObjList(mutableListOf<net.sergeych.lyng.obj.Obj>(ObjInt.of(1), ObjInt.of(2), ObjInt.of(3)))
val callRef = CallRef(
ConstRef(callable.asReadonly),
listOf(
net.sergeych.lyng.ParsedArgument(
ExpressionStatement(ConstRef(list.asReadonly), Pos.builtIn),
Pos.builtIn,
isSplat = true
)
),
tailBlock = false,
isOptionalInvoke = false
)
val expr = ExpressionStatement(callRef, Pos.builtIn)
val fn = BytecodeCompiler().compileExpression("splatArgs", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
assertEquals(3, result.toInt())
}
@Test
fun mixedIntRealArithmeticUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
val expr = ExpressionStatement(
@ -291,7 +189,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("mixedPlus", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(5.5, result.toDouble())
}
@ -306,13 +204,13 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("mixedNeq", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(true, result.toBool())
}
@Test
fun localSlotTypeTrackingEnablesArithmetic() = kotlinx.coroutines.test.runTest {
val slotRef = LocalSlotRef("a", 0, 0, 0, true, false, net.sergeych.lyng.Pos.builtIn)
val slotRef = LocalSlotRef("a", 0, 0, true, false, net.sergeych.lyng.Pos.builtIn)
val assign = AssignRef(
slotRef,
ConstRef(ObjInt.of(2).asReadonly),
@ -328,13 +226,13 @@ class CmdVmTest {
)
val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed")
val scope = Scope().apply { applySlotPlan(mapOf("a" to 0)) }
val result = CmdVm().execute(fn, scope, emptyList())
val result = BytecodeVm().execute(fn, scope, emptyList())
assertEquals(4, result.toInt())
}
@Test
fun parentScopeSlotAccessWorks() = kotlinx.coroutines.test.runTest {
val parentRef = LocalSlotRef("a", 0, 1, 0, true, false, net.sergeych.lyng.Pos.builtIn)
val parentRef = LocalSlotRef("a", 0, 1, true, false, net.sergeych.lyng.Pos.builtIn)
val expr = ExpressionStatement(
BinaryOpRef(
BinOp.PLUS,
@ -349,7 +247,7 @@ class CmdVmTest {
setSlotValue(0, ObjInt.of(3))
}
val child = Scope(parent)
val result = CmdVm().execute(fn, child, emptyList())
val result = BytecodeVm().execute(fn, child, emptyList())
assertEquals(5, result.toInt())
}
@ -364,7 +262,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("objEq", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals(true, result.toBool())
}
@ -380,7 +278,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val eqFn = BytecodeCompiler().compileExpression("objRefEq", eqExpr) ?: error("bytecode compile failed")
val eqResult = CmdVm().execute(eqFn, Scope(), emptyList())
val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList())
assertEquals(true, eqResult.toBool())
val neqExpr = ExpressionStatement(
@ -392,7 +290,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val neqFn = BytecodeCompiler().compileExpression("objRefNeq", neqExpr) ?: error("bytecode compile failed")
val neqResult = CmdVm().execute(neqFn, Scope(), emptyList())
val neqResult = BytecodeVm().execute(neqFn, Scope(), emptyList())
assertEquals(true, neqResult.toBool())
}
@ -407,7 +305,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val ltFn = BytecodeCompiler().compileExpression("objLt", ltExpr) ?: error("bytecode compile failed")
val ltResult = CmdVm().execute(ltFn, Scope(), emptyList())
val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList())
assertEquals(true, ltResult.toBool())
val gteExpr = ExpressionStatement(
@ -419,7 +317,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val gteFn = BytecodeCompiler().compileExpression("objGte", gteExpr) ?: error("bytecode compile failed")
val gteResult = CmdVm().execute(gteFn, Scope(), emptyList())
val gteResult = BytecodeVm().execute(gteFn, Scope(), emptyList())
assertEquals(true, gteResult.toBool())
}
@ -434,7 +332,7 @@ class CmdVmTest {
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("objPlus", expr) ?: error("bytecode compile failed")
val result = CmdVm().execute(fn, Scope(), emptyList())
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals("ab", (result as ObjString).value)
}
}

View File

@ -17,10 +17,8 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
@Ignore("TODO(bytecode-only): uses fallback")
class TestCoroutines {
@Test
@ -187,4 +185,4 @@ class TestCoroutines {
// }.toList())
""".trimIndent())
}
}
}

View File

@ -21,13 +21,11 @@ import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.obj.*
import net.sergeych.lynon.lynonDecodeAny
import net.sergeych.lynon.lynonEncodeAny
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue
@Ignore("TODO(bytecode-only): uses fallback")
class EmbeddingExceptionTest {
@Test

View File

@ -21,10 +21,8 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
@Ignore("TODO(bytecode-only): uses fallback")
class MIC3MroTest {
@Test

View File

@ -21,12 +21,10 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertFails
import kotlin.test.assertTrue
@Ignore("TODO(bytecode-only): uses fallback")
class MIDiagnosticsTest {
@Test

View File

@ -17,10 +17,8 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
@Ignore("TODO(bytecode-only): uses fallback")
class MIQualifiedDispatchTest {
@Test

View File

@ -23,7 +23,6 @@ import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.ExecutionError
import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

View File

@ -23,7 +23,6 @@ package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.highlight.offsetOf
import net.sergeych.lyng.miniast.*
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

View File

@ -22,11 +22,9 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.ExecutionError
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertFailsWith
@Ignore("TODO(bytecode-only): uses fallback")
class NamedArgsTest {
@Test

View File

@ -17,12 +17,7 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Benchmarks
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.ForInStatement
import net.sergeych.lyng.Script
import net.sergeych.lyng.Statement
import net.sergeych.lyng.bytecode.CmdDisassembler
import net.sergeych.lyng.bytecode.BytecodeStatement
import net.sergeych.lyng.eval
import net.sergeych.lyng.obj.ObjInt
import kotlin.time.TimeSource
import kotlin.test.Test
@ -32,68 +27,25 @@ class NestedRangeBenchmarkTest {
@Test
fun benchmarkHappyNumbersNestedRanges() = runTest {
if (!Benchmarks.enabled) return@runTest
val bodyScript = """
var count = 0
for( n1 in 0..9 )
for( n2 in 0..9 )
for( n3 in 0..9 )
for( n4 in 0..9 )
for( n5 in 0..9 )
for( n6 in 0..9 )
if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
count
""".trimIndent()
val compiled = Compiler.compile(bodyScript)
dumpNestedLoopBytecode(compiled.debugStatements())
val script = """
fun naiveCountHappyNumbers() {
$bodyScript
var count = 0
for( n1 in 0..9 )
for( n2 in 0..9 )
for( n3 in 0..9 )
for( n4 in 0..9 )
for( n5 in 0..9 )
for( n6 in 0..9 )
if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
count
}
naiveCountHappyNumbers()
""".trimIndent()
val scope = Script.newScope()
scope.eval(script)
val fnDisasm = scope.disassembleSymbol("naiveCountHappyNumbers")
println("[DEBUG_LOG] [BENCH] nested-happy function naiveCountHappyNumbers cmd:\n$fnDisasm")
runMode(scope)
}
private suspend fun runMode(scope: net.sergeych.lyng.Scope) {
val start = TimeSource.Monotonic.markNow()
val result = scope.eval("naiveCountHappyNumbers()") as ObjInt
val result = eval(script) as ObjInt
val elapsedMs = start.elapsedNow().inWholeMilliseconds
println("[DEBUG_LOG] [BENCH] nested-happy elapsed=${elapsedMs} ms")
assertEquals(55252L, result.value)
}
private fun dumpNestedLoopBytecode(statements: List<Statement>) {
var current: Statement? = statements.firstOrNull { stmt ->
stmt is BytecodeStatement && stmt.original is ForInStatement
}
var depth = 1
while (current is BytecodeStatement && current.original is ForInStatement) {
val original = current.original as ForInStatement
println(
"[DEBUG_LOG] [BENCH] nested-happy loop depth=$depth " +
"constRange=${original.constRange} canBreak=${original.canBreak} " +
"loopSlotPlan=${original.loopSlotPlan}"
)
val fn = current.bytecodeFunction()
val slots = fn.scopeSlotNames.mapIndexed { idx, name ->
val slotName = name ?: "s$idx"
"$slotName@${fn.scopeSlotDepths[idx]}:${fn.scopeSlotIndices[idx]}"
}
println("[DEBUG_LOG] [BENCH] nested-happy slots depth=$depth: ${slots.joinToString(", ")}")
val disasm = CmdDisassembler.disassemble(fn)
println("[DEBUG_LOG] [BENCH] nested-happy cmd depth=$depth:\n$disasm")
current = original.body
depth += 1
}
if (depth == 1) {
println("[DEBUG_LOG] [BENCH] nested-happy cmd: <not found>")
}
}
}

View File

@ -22,12 +22,10 @@ import net.sergeych.lyng.eval
import net.sergeych.lyng.obj.ObjInstance
import net.sergeych.lyng.obj.ObjList
import net.sergeych.lyng.toSource
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
@Ignore("TODO(bytecode-only): uses fallback")
class OOTest {
@Test
fun testClassProps() = runTest {
@ -928,4 +926,4 @@ class OOTest {
assertEquals(5, t.x)
""".trimIndent())
}
}
}

View File

@ -2,11 +2,9 @@ package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import net.sergeych.lynon.lynonEncodeAny
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertFailsWith
@Ignore("TODO(bytecode-only): uses fallback")
class ObjectExpressionTest {
@Test

View File

@ -21,10 +21,8 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
@Ignore("TODO(bytecode-only): uses fallback")
class ParallelLocalScopeTest {
@Test

View File

@ -2,7 +2,6 @@ import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.eval
import net.sergeych.lyng.obj.toInt
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

View File

@ -4,7 +4,6 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
class ScopeCycleRegressionTest {

View File

@ -18,7 +18,6 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals

View File

@ -54,7 +54,6 @@ import kotlin.time.Instant
* limitations under the License.
*
*/
@Ignore("TODO(bytecode-only): uses fallback")
class ScriptTest {
@Test
fun testVersion() {
@ -2823,10 +2822,10 @@ class ScriptTest {
x
}
(1..50).map { launch { dosomething() } }.forEach {
(1..100).map { launch { dosomething() } }.forEach {
assertEquals(5050, it.await())
}
assertEquals( 50, ac.getCounter() )
assertEquals( 100, ac.getCounter() )
""".trimIndent()
)
@ -3225,8 +3224,7 @@ class ScriptTest {
@Test
fun testDateTimeComprehensive() = runTest {
eval(
"""
eval("""
import lyng.time
import lyng.serialization
@ -3321,14 +3319,12 @@ class ScriptTest {
val dtParsedZ = DateTime.parseRFC3339("2024-05-20T15:30:45Z")
assertEquals(dtParsedZ.timeZone, "Z")
assertEquals(dtParsedZ.hour, 15)
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testInstantComponents() = runTest {
eval(
"""
eval("""
import lyng.time
val t1 = Instant("1970-05-06T07:11:56Z")
val dt = t1.toDateTime("Z")
@ -3354,8 +3350,7 @@ class ScriptTest {
assertEquals(dt4.year, 1971)
assertEquals(dt.toInstant(), t1)
""".trimIndent()
)
""".trimIndent())
}
@Test
@ -3866,7 +3861,7 @@ class ScriptTest {
}
// @Test
// @Test
fun testMinimumOptimization() = runTest {
for (i in 1..200) {
bm {
@ -3994,7 +3989,6 @@ class ScriptTest {
)
}
@Test
fun testParserOverflow() = runTest {
try {
@ -4312,12 +4306,10 @@ class ScriptTest {
@Test
fun testStringMul() = runTest {
eval(
"""
eval("""
assertEquals("hellohello", "hello"*2)
assertEquals("", "hello"*0)
""".trimIndent()
)
""".trimIndent())
}
@Test
@ -4701,8 +4693,7 @@ class ScriptTest {
@Test
fun testFunMiniDeclaration() = runTest {
eval(
"""
eval("""
class T(x) {
fun method() = x + 1
}
@ -4710,14 +4701,12 @@ class ScriptTest {
assertEquals(11, T(10).method())
assertEquals(2, median(1,3))
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testUserClassExceptions() = runTest {
eval(
"""
eval("""
val x = try { throw IllegalAccessException("test1") } catch { it }
assertEquals("test1", x.message)
assert( x is IllegalAccessException)
@ -4731,41 +4720,35 @@ class ScriptTest {
assert( y is X)
assert( y is Exception )
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testTodo() = runTest {
eval(
"""
eval("""
assertThrows(NotImplementedException) {
TODO()
}
val x = try { TODO("check me") } catch { it }
assertEquals("check me", x.message)
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testOptOnNullAssignment() = runTest {
eval(
"""
eval("""
var x = null
assertEquals(null, x)
x ?= 1
assertEquals(1, x)
x ?= 2
assertEquals(1, x)
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testUserExceptionClass() = runTest {
eval(
"""
eval("""
class UserException : Exception("user exception")
val x = try { throw UserException() } catch { it }
assertEquals("user exception", x.message)
@ -4783,14 +4766,12 @@ class ScriptTest {
assert( t is X )
assert( t is Exception )
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testExceptionToString() = runTest {
eval(
"""
eval("""
class MyEx(m) : Exception(m)
val e = MyEx("custom error")
val s = e.toString()
@ -4799,14 +4780,11 @@ class ScriptTest {
val e2 = try { throw e } catch { it }
assert( e2 === e )
assertEquals("custom error", e2.message)
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testAssertThrowsUserException() = runTest {
eval(
"""
eval("""
class MyEx : Exception
class DerivedEx : MyEx
@ -4821,38 +4799,25 @@ class ScriptTest {
assert(caught != null)
assertEquals("Expected DerivedEx, got MyEx", caught.message)
assert(caught.message == "Expected DerivedEx, got MyEx")
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testRaiseAsError() = runTest {
var x = evalNamed(
"tc1", """
var x = evalNamed( "tc1","""
IllegalArgumentException("test3")
""".trimIndent()
)
var x1 = try {
x.raiseAsExecutionError()
} catch (e: ExecutionError) {
e
}
""".trimIndent())
var x1 = try { x.raiseAsExecutionError() } catch(e: ExecutionError) { e }
println(x1.message)
assertTrue { "tc1:1" in x1.message!! }
assertTrue { "test3" in x1.message!! }
// With user exception classes it should be the same at top level:
x = evalNamed(
"tc2", """
x = evalNamed("tc2","""
class E: Exception("test4")
E()
""".trimIndent()
)
x1 = try {
x.raiseAsExecutionError()
} catch (e: ExecutionError) {
e
}
""".trimIndent())
x1 = try { x.raiseAsExecutionError() } catch(e: ExecutionError) { e }
println(x1.message)
assertContains(x1.message!!, "test4")
// the reported error message should include proper trace, which must include
@ -4863,37 +4828,31 @@ class ScriptTest {
@Test
fun testFilterStackTrace() = runTest {
var x = try {
evalNamed(
"tc1", """
evalNamed( "tc1","""
fun f2() = throw IllegalArgumentException("test3")
fun f1() = f2()
f1()
""".trimIndent()
)
""".trimIndent())
fail("this should throw")
} catch (x: ExecutionError) {
}
catch(x: ExecutionError) {
x
}
assertEquals(
"""
assertEquals("""
tc1:1:12: test3
at tc1:1:12: fun f2() = throw IllegalArgumentException("test3")
at tc1:2:12: fun f1() = f2()
at tc1:3:1: f1()
""".trimIndent(), x.errorObject.getLyngExceptionMessageWithStackTrace()
)
""".trimIndent(),x.errorObject.getLyngExceptionMessageWithStackTrace())
}
@Test
fun testLyngToKotlinExceptionHelpers() = runTest {
var x = evalNamed(
"tc1", """
var x = evalNamed( "tc1","""
IllegalArgumentException("test3")
""".trimIndent()
)
assertEquals(
"""
""".trimIndent())
assertEquals("""
tc1:1:1: test3
at tc1:1:1: IllegalArgumentException("test3")
""".trimIndent(),
@ -4903,8 +4862,7 @@ class ScriptTest {
@Test
fun testMapIteralAmbiguity() = runTest {
eval(
"""
eval("""
val m = { a: 1, b: { foo: "bar" } }
assertEquals(1, m["a"])
assertEquals("bar", m["b"]["foo"])
@ -4912,14 +4870,12 @@ class ScriptTest {
val m2 = { a: 1, b: { bar: } }
assert( m2["b"] is Map )
assertEquals("foobar", m2["b"]["bar"])
""".trimIndent()
)
""".trimIndent())
}
@Test
fun realWorldCaptureProblem() = runTest {
eval(
"""
eval("""
// 61755f07-630c-4181-8d50-1b044d96e1f4
class T {
static var f1 = null
@ -4938,14 +4894,12 @@ class ScriptTest {
println("2- "+T.f1::class)
println("2- "+T.f1)
assert(T.f1 == "foo")
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testLazyLocals() = runTest() {
eval(
"""
eval("""
class T {
val x by lazy {
val c = "c"
@ -4955,14 +4909,11 @@ class ScriptTest {
val t = T()
assertEquals("c!", t.x)
assertEquals("c!", t.x)
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testGetterLocals() = runTest() {
eval(
"""
eval("""
class T {
val x get() {
val c = "c"
@ -4972,14 +4923,12 @@ class ScriptTest {
val t = T()
assertEquals("c!", t.x)
assertEquals("c!", t.x)
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testMethodLocals() = runTest() {
eval(
"""
eval("""
class T {
fun x() {
val c = "c"
@ -4989,14 +4938,12 @@ class ScriptTest {
val t = T()
assertEquals("c!", t.x())
assertEquals("c!", t.x())
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testContrcuctorMagicIdBug() = runTest() {
eval(
"""
eval("""
interface SomeI {
abstract fun x()
}
@ -5009,14 +4956,12 @@ class ScriptTest {
val t = T("c")
assertEquals("c!", t.x())
assertEquals("c!", t.x())
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testLambdaLocals() = runTest() {
eval(
"""
eval("""
class T {
val l = { x ->
val c = x + ":"
@ -5024,14 +4969,12 @@ class ScriptTest {
}
}
assertEquals("r:r", T().l("r"))
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testTypedArgsWithInitializers() = runTest {
eval(
"""
eval("""
fun f(a: String = "foo") = a + "!"
fun g(a: String? = null) = a ?: "!!"
assertEquals(f(), "foo!")
@ -5040,14 +4983,12 @@ class ScriptTest {
class T(b: Int=42,c: String?=null)
assertEquals(42, T().b)
assertEquals(null, T().c)
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testArgsPriorityWithSplash() = runTest {
eval(
"""
eval("""
class A {
val tags get() = ["foo"]
@ -5056,14 +4997,12 @@ class ScriptTest {
fun f2(tags...) = f1(...tags)
}
assertEquals(["bar"], A().f2("bar"))
"""
)
""")
}
@Test
fun testClamp() = runTest {
eval(
"""
eval("""
// Global clamp
assertEquals(5, clamp(5, 0..10))
assertEquals(0, clamp(-5, 0..10))
@ -5094,164 +5033,15 @@ class ScriptTest {
assertEquals(5.5, 5.5.clamp(0.0..10.0))
assertEquals(0.0, (-1.5).clamp(0.0..10.0))
assertEquals(10.0, 15.5.clamp(0.0..10.0))
""".trimIndent()
)
""".trimIndent())
}
@Test
fun testEmptySpreadList() = runTest {
eval(
"""
eval("""
fun t(a, tags=[]) { [a, ...tags] }
assertEquals( [1], t(1) )
""".trimIndent()
)
}
@Test
fun testForInIterableDisasm() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun type(x) {
when(x) {
"42", 42 -> "answer to the great question"
is Real, is Int -> "number"
is String -> {
for( d in x ) {
if( d !in '0'..'9' )
break "unknown"
}
else "number"
}
}
}
""".trimIndent()
)
println("[DEBUG_LOG] type disasm:\n${scope.disassembleSymbol("type")}")
val r1 = scope.eval("""type("12%")""")
val r2 = scope.eval("""type("153")""")
println("[DEBUG_LOG] type(\"12%\")=${r1.inspect(scope)}")
println("[DEBUG_LOG] type(\"153\")=${r2.inspect(scope)}")
}
@Test
fun testForInIterableBytecode() = runTest {
val result = eval(
"""
fun sumAll(x) {
var s = 0
for (i in x) s += i
s
}
sumAll([1,2,3]) + sumAll(0..3)
""".trimIndent()
)
assertEquals(ObjInt(12), result)
}
@Test
fun testForInIterableUnknownTypeDisasm() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun countAll(x) {
var c = 0
for (i in x) c++
c
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("countAll")
println("[DEBUG_LOG] countAll disasm:\n$disasm")
assertFalse(disasm.contains("not a compiled body"))
assertFalse(disasm.contains("EVAL_FALLBACK"))
val r1 = scope.eval("countAll([1,2,3])")
val r2 = scope.eval("countAll(0..3)")
assertEquals(ObjInt(3), r1)
assertEquals(ObjInt(4), r2)
}
@Test
fun testReturnBreakValueBytecodeDisasm() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun firstPositive() {
for (i in 0..5)
if (i > 0) return i
-1
}
fun firstEvenOrMinus() {
val r = for (i in 1..7)
if (i % 2 == 0) break i
r
}
""".trimIndent()
)
val disasmReturn = scope.disassembleSymbol("firstPositive")
val disasmBreak = scope.disassembleSymbol("firstEvenOrMinus")
println("[DEBUG_LOG] firstPositive disasm:\n$disasmReturn")
println("[DEBUG_LOG] firstEvenOrMinus disasm:\n$disasmBreak")
assertFalse(disasmReturn.contains("not a compiled body"))
assertFalse(disasmBreak.contains("not a compiled body"))
assertFalse(disasmReturn.contains("EVAL_FALLBACK"))
assertFalse(disasmBreak.contains("EVAL_FALLBACK"))
assertEquals(ObjInt(1), scope.eval("firstPositive()"))
assertEquals(ObjInt(2), scope.eval("firstEvenOrMinus()"))
}
@Test
fun testInOperatorBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun inList(x, xs) { x in xs }
""".trimIndent()
)
val disasm = scope.disassembleSymbol("inList")
assertFalse(disasm.contains("not a compiled body"))
assertEquals(ObjTrue, scope.eval("inList(2, [1,2,3])"))
assertEquals(ObjFalse, scope.eval("inList(5, [1,2,3])"))
}
@Test
fun testIsOperatorBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun isInt(x) { x is Int }
""".trimIndent()
)
val disasm = scope.disassembleSymbol("isInt")
assertFalse(disasm.contains("not a compiled body"))
assertEquals(ObjTrue, scope.eval("isInt(42)"))
assertEquals(ObjFalse, scope.eval("isInt(\"42\")"))
}
@Test
fun testFilterBug() = runTest {
eval(
"""
var filterCalledWith = []
var callCount = 0
fun Iterable.drop2(n) {
var cnt = 0
filter {
filterCalledWith.add( { cnt:, n:, value: it } )
println("%d of %d = %s:%s"(cnt, n, it, cnt >= n))
println(callCount++)
cnt++ >= n
}
}
val result = [1,2,3,4,5,6].drop2(4)
println(callCount)
println(result)
println(filterCalledWith)
assertEquals(6, callCount)
assertEquals([5,6], result)
""".trimIndent()
)
""".trimIndent())
}
}

View File

@ -17,10 +17,8 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
@Ignore("TODO(bytecode-only): uses fallback")
class StdlibTest {
@Test
fun testIterableFilter() = runTest {
@ -133,4 +131,4 @@ class StdlibTest {
assertEquals(31, p.age)
""".trimIndent())
}
}
}

View File

@ -17,7 +17,6 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
/*
@ -37,7 +36,6 @@ import kotlin.test.Test
*
*/
@Ignore("TODO(bytecode-only): uses fallback")
class TestInheritance {
@Test
@ -197,4 +195,4 @@ assertEquals(null, (buzz as? Foo)?.runA())
""")
}
}
}

View File

@ -17,7 +17,6 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
class TypesTest {
@ -91,4 +90,4 @@ class TypesTest {
assertNotEquals(Point(0,1), Point(0,1).apply { c = 1 } )
""".trimIndent())
}
}
}

View File

@ -17,7 +17,6 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import kotlin.test.Ignore
import kotlin.test.Test
class ValReassignRegressionTest {

View File

@ -18,10 +18,8 @@
package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import kotlin.test.Ignore
import kotlin.test.Test
@Ignore("TODO(bytecode-only): uses fallback")
class DelegationTest {
@Test

View File

@ -1,10 +1,8 @@
package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import kotlin.test.Ignore
import kotlin.test.Test
@Ignore("TODO(bytecode-only): uses fallback")
class OperatorOverloadingTest {
@Test
fun testBinaryOverloading() = runTest {

View File

@ -1,10 +1,8 @@
package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import kotlin.test.Ignore
import kotlin.test.Test
@Ignore("TODO(bytecode-only): uses fallback")
class PropsTest {
@Test

View File

@ -24,13 +24,11 @@ import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.toBool
import net.sergeych.lynon.lynonDecodeAny
import net.sergeych.lynon.lynonEncodeAny
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
@Ignore("TODO(bytecode-only): uses fallback")
class TransientTest {
@Test

View File

@ -20,7 +20,6 @@ package net.sergeych.lyng.miniast
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.binding.Binder
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals

View File

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

View File

@ -1,25 +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
internal actual object CmdCallSiteCache {
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
return cache.getOrPut(fn) { mutableMapOf() }
}
}

View File

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

View File

@ -1,30 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng.bytecode
import java.util.IdentityHashMap
internal actual object CmdCallSiteCache {
private val cache = ThreadLocal.withInitial {
IdentityHashMap<CmdFunction, MutableMap<Int, MethodCallSite>>()
}
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
val map = cache.get()
return map.getOrPut(fn) { mutableMapOf() }
}
}

View File

@ -30,7 +30,6 @@ import java.nio.file.Files.readAllLines
import java.nio.file.Paths
import kotlin.io.path.absolutePathString
import kotlin.io.path.extension
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.fail
@ -248,7 +247,6 @@ suspend fun runDocTests(fileName: String, bookMode: Boolean = false) {
println("tests passed: $count")
}
@Ignore("TODO(bytecode-only): uses fallback")
class BookTest {
@Test
@ -359,4 +357,4 @@ class BookTest {
fun testJson() = runBlocking {
runDocTests("../docs/json_and_kotlin_serialization.md")
}
}
}

View File

@ -25,13 +25,11 @@ import net.sergeych.lyng.obj.*
import net.sergeych.lynon.*
import java.nio.file.Files
import java.nio.file.Path
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@Ignore("TODO(bytecode-only): uses fallback")
class LynonTests {
@Test
@ -796,3 +794,4 @@ class Wallet( id, ownerKey, balance=0, createdAt=Instant.now().truncateToSecond(
}

View File

@ -24,7 +24,6 @@ import net.sergeych.lyng.pacman.InlineSourcesImportProvider
import net.sergeych.lyng.toSource
import net.sergeych.lynon.BitArray
import net.sergeych.lynon.BitList
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertNotEquals
@ -100,4 +99,4 @@ class OtherTests {
}
}
}

View File

@ -21,12 +21,10 @@ import net.sergeych.lyng.PerfStats
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjInt
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@Ignore("TODO(bytecode-only): uses fallback")
class PicInvalidationJvmTest {
@Test
fun fieldPicInvalidatesOnClassLayoutChange() = runBlocking {

View File

@ -22,7 +22,6 @@ import net.sergeych.lyng.Scope
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.extension
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.time.Clock
@ -41,7 +40,6 @@ suspend fun executeSampleTests(fileName: String) {
}
}
@Ignore("TODO(bytecode-only): uses fallback")
class SamplesTest {
@Test
@ -51,4 +49,4 @@ class SamplesTest {
if (s.extension == "lyng") executeSampleTests(s.toString())
}
}
}
}

View File

@ -20,11 +20,9 @@ import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjList
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
@Ignore("TODO(bytecode-only): uses fallback")
class ScriptSubsetJvmTest {
private suspend fun evalInt(code: String): Long = (Scope().eval(code) as ObjInt).value
private suspend fun evalList(code: String): List<Any?> = (Scope().eval(code) as ObjList).list.map { (it as? ObjInt)?.value ?: it }

View File

@ -21,14 +21,12 @@ import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.ObjBool
import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjList
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
/**
* JVM-only fast functional subset additions. Keep each test quick (< ~1s) and deterministic.
*/
@Ignore("TODO(bytecode-only): uses fallback")
class ScriptSubsetJvmTest_Additions3 {
private suspend fun evalInt(code: String): Long = (Scope().eval(code) as ObjInt).value
private suspend fun evalBool(code: String): Boolean = (Scope().eval(code) as ObjBool).value

View File

@ -20,7 +20,6 @@ import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjList
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ -29,7 +28,6 @@ import kotlin.test.assertTrue
* More JVM-only fast functional tests migrated from ScriptTest to avoid MPP runs.
* Keep each test fast (<1s) and deterministic.
*/
@Ignore("TODO(bytecode-only): uses fallback")
class ScriptSubsetJvmTest_Additions4 {
private suspend fun evalInt(code: String): Long = (Scope().eval(code) as ObjInt).value
private suspend fun evalList(code: String): List<Any?> = (Scope().eval(code) as ObjList).list.map { (it as? ObjInt)?.value ?: it }

View File

@ -19,7 +19,6 @@ import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.ObjInt
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@ -28,7 +27,6 @@ import kotlin.test.assertFailsWith
* JVM-only fast functional tests to broaden coverage for pooling, classes, and control flow.
* Keep each test fast (<1s) and deterministic.
*/
@Ignore("TODO(bytecode-only): uses fallback")
class ScriptSubsetJvmTest_Additions5 {
private suspend fun evalInt(code: String): Long = (Scope().eval(code) as ObjInt).value
@ -76,7 +74,6 @@ class ScriptSubsetJvmTest_Additions5 {
assertEquals(3L, r)
}
@Ignore("TODO(bytecode+closure): pooled lambda calls duplicate side effects; re-enable after fixing call semantics")
@Test
fun pooled_frames_closure_this_capture_jvm_only() = runBlocking {
val code = """

View File

@ -19,7 +19,6 @@ import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjList
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
@ -27,7 +26,6 @@ import kotlin.test.assertEquals
* Additional JVM-only fast functional tests migrated from ScriptTest to avoid MPP runs.
* Keep each test fast (<1s) and with clear assertions.
*/
@Ignore("TODO(bytecode-only): uses fallback")
class ScriptSubsetJvmTest_Additions {
private suspend fun evalInt(code: String): Long = (Scope().eval(code) as ObjInt).value
private suspend fun evalList(code: String): List<Any?> = (Scope().eval(code) as ObjList).list.map { (it as? ObjInt)?.value ?: it }
@ -105,7 +103,6 @@ class ScriptSubsetJvmTest_Additions {
}
@Ignore("TODO(bytecode-only): uses fallback")
class ScriptSubsetJvmTest_Additions2 {
private suspend fun evalInt(code: String): Long = (Scope().eval(code) as ObjInt).value

View File

@ -5,7 +5,6 @@
import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScriptError
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.fail

View File

@ -18,13 +18,11 @@
package net.sergeych.lyng.miniast
import kotlinx.coroutines.runBlocking
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@Ignore("TODO(bytecode-only): uses fallback")
class CompletionEngineLightTest {
private fun names(items: List<CompletionItem>): List<String> = items.map { it.name }

View File

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

View File

@ -1,26 +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
@kotlin.native.concurrent.ThreadLocal
internal actual object CmdCallSiteCache {
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
return cache.getOrPut(fn) { mutableMapOf() }
}
}

View File

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

View File

@ -1,25 +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
internal actual object CmdCallSiteCache {
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
return cache.getOrPut(fn) { mutableMapOf() }
}
}

View File

@ -249,6 +249,20 @@ fun List.sort() {
sortWith { a, b -> a <=> b }
}
/* Represents a single stack trace element. */
class StackTraceEntry(
val sourceName: String,
val line: Int,
val column: Int,
val sourceString: String
) {
val at by lazy { "%s:%s:%s"(sourceName,line+1,column+1) }
/* Formatted representation: source:line:column: text. */
override fun toString() {
"%s: %s"(at, sourceString.trim())
}
}
/* Print this exception and its stack trace to standard output. */
fun Exception.printStackTrace() {
println(this)
@ -323,17 +337,3 @@ class lazy(creatorParam) : Delegate {
value
}
}
/* Represents a single stack trace element. */
class StackTraceEntry(
val sourceName: String,
val line: Int,
val column: Int,
val sourceString: String
) {
val at by lazy { "%s:%s:%s"(sourceName,line+1,column+1) }
/* Formatted representation: source:line:column: text. */
override fun toString() {
"%s: %s"(at, sourceString.trim())
}
}

View File

@ -1,18 +0,0 @@
# Bytecode method call-site cache
Changes
- Added per-thread bytecode method call-site caches via BytecodeCallSiteCache expect/actuals.
- Bytecode VM now reuses per-function call-site maps to preserve method PIC hits across repeated bytecode executions.
- Removed unused methodCallSites property from BytecodeFunction.
Why
- Fixes JVM PIC invalidation test by allowing method PIC hits when bytecode bodies are invoked repeatedly (e.g., loop bodies compiled to bytecode statements).
- Avoids cross-thread mutable map sharing on native by using thread-local storage.
Tests
- ./gradlew :lynglib:jvmTest
- ./gradlew :lynglib:allTests -x :lynglib:jvmTest
Benchmark
- ./gradlew :lynglib:jvmTest --tests NestedRangeBenchmarkTest -Dbenchmarks=true
- nested-happy elapsed=1266 ms

View File

@ -1,15 +0,0 @@
# Bytecode call-site PIC + fallback gating
Changes
- Added method call PIC path in bytecode VM with new CALL_SLOT/CALL_VIRTUAL opcodes.
- Fixed FieldRef property/delegate resolution to avoid bypassing ObjRecord delegation.
- Prevent delegated ObjRecord mutation by returning a resolved copy.
- Restricted bytecode call compilation to args that are ExpressionStatement (no splat/named/tail-block), fallback otherwise.
Rationale
- Fixes JVM test regressions and avoids premature evaluation of Statement args.
- Keeps delegated/property semantics identical to interpreter.
Tests
- ./gradlew :lynglib:jvmTest
- ./gradlew :lynglib:allTests -x :lynglib:jvmTest

View File

@ -1,12 +0,0 @@
# Bytecode expression + for-in loop support
Changes
- Added bytecode compilation for conditional/elvis expressions, inc/dec, and compound assignments where safe.
- Added ForInStatement and ConstIntRange to keep for-loop structure explicit (no anonymous Statement).
- Added PUSH_SCOPE/POP_SCOPE opcodes with SlotPlan constants to create loop scopes in bytecode.
- Bytecode compiler emits int-range for-in loops when const range is known and no break/continue.
- Temporary: CmdGetField/CmdSetField maintain lightweight PIC counters for regression tests; remove or guard under a flag once bytecode becomes the sole execution path.
Tests
- ./gradlew :lynglib:jvmTest
- ./gradlew :lynglib:allTests -x :lynglib:jvmTest