Add bytecode slot metadata and compile-time mutability

This commit is contained in:
Sergey Chernov 2026-01-26 01:09:02 +03:00
parent b4598bff98
commit 059e366787
11 changed files with 577 additions and 138 deletions

View File

@ -10,6 +10,7 @@ interpreter and fall back to the existing AST execution when needed.
### Frame metadata ### Frame metadata
- localCount: number of local slots for this function (fixed at compile time). - localCount: number of local slots for this function (fixed at compile time).
- argCount: number of arguments passed at call time. - argCount: number of arguments passed at call time.
- scopeSlotNames: optional debug names for scope slots (locals/params), aligned to slot mapping.
- argBase = localCount. - argBase = localCount.
### Slot layout ### Slot layout
@ -25,6 +26,10 @@ slots[localCount .. localCount+argCount-1] arguments
- param i => slot localCount + i - param i => slot localCount + i
- variadic extra => slot localCount + declaredParamCount + k - variadic extra => slot localCount + declaredParamCount + k
### Debug metadata (optional)
- scopeSlotNames: array sized scopeSlotCount, each entry nullable.
- Intended for disassembly/debug tooling; VM semantics do not depend on it.
## 2) Slot ID Width ## 2) Slot ID Width
Per frame, select: Per frame, select:
@ -113,6 +118,13 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
- DIV_REAL S, S -> S - DIV_REAL S, S -> S
- NEG_REAL S -> S - NEG_REAL S -> S
### Arithmetic: OBJ
- ADD_OBJ S, S -> S
- SUB_OBJ S, S -> S
- MUL_OBJ S, S -> S
- DIV_OBJ S, S -> S
- MOD_OBJ S, S -> S
### Bitwise: INT ### Bitwise: INT
- AND_INT S, S -> S - AND_INT S, S -> S
- OR_INT S, S -> S - OR_INT S, S -> S

View File

@ -18,6 +18,7 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.Compiler.Companion.compile import net.sergeych.lyng.Compiler.Companion.compile
import net.sergeych.lyng.bytecode.BytecodeStatement
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
@ -44,8 +45,9 @@ class Compiler(
private val currentLocalNames: MutableSet<String>? private val currentLocalNames: MutableSet<String>?
get() = localNamesStack.lastOrNull() get() = localNamesStack.lastOrNull()
private data class SlotPlan(val slots: MutableMap<String, Int>, var nextIndex: Int) private data class SlotEntry(val index: Int, val isMutable: Boolean, val isDelegated: Boolean)
private data class SlotLocation(val slot: Int, val depth: Int) private data class SlotPlan(val slots: MutableMap<String, SlotEntry>, var nextIndex: Int)
private data class SlotLocation(val slot: Int, val depth: Int, val isMutable: Boolean, val isDelegated: Boolean)
private val slotPlanStack = mutableListOf<SlotPlan>() private val slotPlanStack = mutableListOf<SlotPlan>()
// Track declared local variables count per function for precise capacity hints // Track declared local variables count per function for precise capacity hints
@ -62,19 +64,20 @@ class Compiler(
} }
} }
private fun declareLocalName(name: String) { private fun declareLocalName(name: String, isMutable: Boolean, isDelegated: Boolean = false) {
// Add to current function's local set; only count if it was newly added (avoid duplicates) // Add to current function's local set; only count if it was newly added (avoid duplicates)
val added = currentLocalNames?.add(name) == true val added = currentLocalNames?.add(name) == true
if (added && localDeclCountStack.isNotEmpty()) { if (added && localDeclCountStack.isNotEmpty()) {
localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1 localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1
} }
declareSlotName(name) declareSlotName(name, isMutable, isDelegated)
} }
private fun declareSlotName(name: String) { private fun declareSlotName(name: String, isMutable: Boolean, isDelegated: Boolean) {
if (codeContexts.lastOrNull() is CodeContext.ClassBody) return
val plan = slotPlanStack.lastOrNull() ?: return val plan = slotPlanStack.lastOrNull() ?: return
if (plan.slots.containsKey(name)) return if (plan.slots.containsKey(name)) return
plan.slots[name] = plan.nextIndex plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated)
plan.nextIndex += 1 plan.nextIndex += 1
} }
@ -87,14 +90,38 @@ class Compiler(
idx++ idx++
} }
} }
return SlotPlan(map, idx) val entries = mutableMapOf<String, SlotEntry>()
for ((name, index) in map) {
entries[name] = SlotEntry(index, isMutable = false, isDelegated = false)
}
return SlotPlan(entries, idx)
}
private fun markDelegatedSlot(name: String) {
val plan = slotPlanStack.lastOrNull() ?: return
val entry = plan.slots[name] ?: return
if (!entry.isDelegated) {
plan.slots[name] = entry.copy(isDelegated = true)
}
}
private fun slotPlanIndices(plan: SlotPlan): Map<String, Int> {
if (plan.slots.isEmpty()) return emptyMap()
val result = LinkedHashMap<String, Int>(plan.slots.size)
for ((name, entry) in plan.slots) {
result[name] = entry.index
}
return result
} }
private fun lookupSlotLocation(name: String): SlotLocation? { private fun lookupSlotLocation(name: String): SlotLocation? {
for (i in slotPlanStack.indices.reversed()) { for (i in slotPlanStack.indices.reversed()) {
val slot = slotPlanStack[i].slots[name] ?: continue val slot = slotPlanStack[i].slots[name] ?: continue
val depth = slotPlanStack.size - 1 - i val depth = slotPlanStack.size - 1 - i
return SlotLocation(slot, depth) if (codeContexts.any { it is CodeContext.ClassBody } && depth > 1) {
return null
}
return SlotLocation(slot.index, depth, slot.isMutable, slot.isDelegated)
} }
return null return null
} }
@ -339,6 +366,12 @@ class Compiler(
private var lastAnnotation: (suspend (Scope, ObjString, Statement) -> Statement)? = null private var lastAnnotation: (suspend (Scope, ObjString, Statement) -> Statement)? = null
private var isTransientFlag: Boolean = false private var isTransientFlag: Boolean = false
private var lastLabel: String? = null private var lastLabel: String? = null
private val useBytecodeStatements: Boolean = true
private fun wrapBytecode(stmt: Statement): Statement {
if (!useBytecodeStatements) return stmt
return BytecodeStatement.wrap(stmt, "stmt@${stmt.pos}", allowLocalSlots = true)
}
private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? { private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
lastAnnotation = null lastAnnotation = null
@ -348,16 +381,16 @@ class Compiler(
val t = cc.next() val t = cc.next()
return when (t.type) { return when (t.type) {
Token.Type.ID, Token.Type.OBJECT -> { Token.Type.ID, Token.Type.OBJECT -> {
parseKeywordStatement(t) parseKeywordStatement(t)?.let { wrapBytecode(it) }
?: run { ?: run {
cc.previous() cc.previous()
parseExpression() parseExpression()?.let { wrapBytecode(it) }
} }
} }
Token.Type.PLUS2, Token.Type.MINUS2 -> { Token.Type.PLUS2, Token.Type.MINUS2 -> {
cc.previous() cc.previous()
parseExpression() parseExpression()?.let { wrapBytecode(it) }
} }
Token.Type.ATLABEL -> { Token.Type.ATLABEL -> {
@ -389,9 +422,9 @@ class Compiler(
Token.Type.LBRACE -> { Token.Type.LBRACE -> {
cc.previous() cc.previous()
if (braceMeansLambda) if (braceMeansLambda)
parseExpression() parseExpression()?.let { wrapBytecode(it) }
else else
parseBlock() wrapBytecode(parseBlock())
} }
Token.Type.RBRACE, Token.Type.RBRACKET -> { Token.Type.RBRACE, Token.Type.RBRACKET -> {
@ -404,7 +437,7 @@ class Compiler(
else -> { else -> {
// could be expression // could be expression
cc.previous() cc.previous()
parseExpression() parseExpression()?.let { wrapBytecode(it) }
} }
} }
} }
@ -800,7 +833,7 @@ class Compiler(
} }
label?.let { cc.labels.remove(it) } label?.let { cc.labels.remove(it) }
val paramSlotPlanSnapshot = if (paramSlotPlan.slots.isEmpty()) emptyMap() else paramSlotPlan.slots.toMap() val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
return ValueFnRef { closureScope -> return ValueFnRef { closureScope ->
val stmt = object : Statement() { val stmt = object : Statement() {
override val pos: Pos = body.pos override val pos: Pos = body.pos
@ -1424,7 +1457,14 @@ class Compiler(
val slotLoc = lookupSlotLocation(t.value) val slotLoc = lookupSlotLocation(t.value)
val inClassCtx = codeContexts.any { it is CodeContext.ClassBody } val inClassCtx = codeContexts.any { it is CodeContext.ClassBody }
when { when {
slotLoc != null -> LocalSlotRef(t.value, slotLoc.slot, slotLoc.depth, t.pos) slotLoc != null -> LocalSlotRef(
t.value,
slotLoc.slot,
slotLoc.depth,
slotLoc.isMutable,
slotLoc.isDelegated,
t.pos
)
PerfFlags.EMIT_FAST_LOCAL_REFS && (currentLocalNames?.contains(t.value) == true) -> PerfFlags.EMIT_FAST_LOCAL_REFS && (currentLocalNames?.contains(t.value) == true) ->
FastLocalVarRef(t.value, t.pos) FastLocalVarRef(t.value, t.pos)
inClassCtx -> ImplicitThisMemberRef(t.value, t.pos) inClassCtx -> ImplicitThisMemberRef(t.value, t.pos)
@ -1798,7 +1838,7 @@ class Compiler(
private suspend fun parseWhenStatement(): Statement { private suspend fun parseWhenStatement(): Statement {
// has a value, when(value) ? // has a value, when(value) ?
var t = cc.nextNonWhitespace() var t = cc.nextNonWhitespace()
return if (t.type == Token.Type.LPAREN) { val stmt = if (t.type == Token.Type.LPAREN) {
// when(value) // when(value)
val value = parseStatement() ?: throw ScriptError(cc.currentPos(), "when(value) expected") val value = parseStatement() ?: throw ScriptError(cc.currentPos(), "when(value) expected")
cc.skipTokenOfType(Token.Type.RPAREN) cc.skipTokenOfType(Token.Type.RPAREN)
@ -1919,13 +1959,14 @@ class Compiler(
// when { cond -> ... } // when { cond -> ... }
TODO("when without object is not yet implemented") TODO("when without object is not yet implemented")
} }
return wrapBytecode(stmt)
} }
private suspend fun parseThrowStatement(start: Pos): Statement { private suspend fun parseThrowStatement(start: Pos): Statement {
val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected") val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected")
// Important: bind the created statement to the position of the `throw` keyword so that // Important: bind the created statement to the position of the `throw` keyword so that
// any raised error reports the correct source location. // any raised error reports the correct source location.
return object : Statement() { val stmt = object : Statement() {
override val pos: Pos = start override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
var errorObject = throwStatement.execute(scope) var errorObject = throwStatement.execute(scope)
@ -1953,6 +1994,7 @@ class Compiler(
return ObjVoid return ObjVoid
} }
} }
return wrapBytecode(stmt)
} }
private data class CatchBlockData( private data class CatchBlockData(
@ -2185,9 +2227,15 @@ class Compiler(
miniSink?.onEnterClass(node) miniSink?.onEnterClass(node)
} }
val bodyStart = nextBody.pos val bodyStart = nextBody.pos
val st = withLocalNames(emptySet()) { val classSlotPlan = SlotPlan(mutableMapOf(), 0)
slotPlanStack.add(classSlotPlan)
val st = try {
withLocalNames(emptySet()) {
parseScript() parseScript()
} }
} finally {
slotPlanStack.removeLast()
}
val rbTok = cc.next() val rbTok = cc.next()
if (rbTok.type != Token.Type.RBRACE) throw ScriptError(rbTok.pos, "unbalanced braces in object body") if (rbTok.type != Token.Type.RBRACE) throw ScriptError(rbTok.pos, "unbalanced braces in object body")
classBodyRange = MiniRange(bodyStart, rbTok.pos) classBodyRange = MiniRange(bodyStart, rbTok.pos)
@ -2324,9 +2372,15 @@ class Compiler(
} }
// parse body // parse body
val bodyStart = next.pos val bodyStart = next.pos
val st = withLocalNames(constructorArgsDeclaration?.params?.map { it.name }?.toSet() ?: emptySet()) { val classSlotPlan = SlotPlan(mutableMapOf(), 0)
slotPlanStack.add(classSlotPlan)
val st = try {
withLocalNames(constructorArgsDeclaration?.params?.map { it.name }?.toSet() ?: emptySet()) {
parseScript() parseScript()
} }
} finally {
slotPlanStack.removeLast()
}
val rbTok = cc.next() val rbTok = cc.next()
if (rbTok.type != Token.Type.RBRACE) throw ScriptError(rbTok.pos, "unbalanced braces in class body") if (rbTok.type != Token.Type.RBRACE) throw ScriptError(rbTok.pos, "unbalanced braces in class body")
classBodyRange = MiniRange(bodyStart, rbTok.pos) classBodyRange = MiniRange(bodyStart, rbTok.pos)
@ -2480,7 +2534,7 @@ class Compiler(
val namesForLoop = (currentLocalNames?.toSet() ?: emptySet()) + tVar.value val namesForLoop = (currentLocalNames?.toSet() ?: emptySet()) + tVar.value
val loopSlotPlan = SlotPlan(mutableMapOf(), 0) val loopSlotPlan = SlotPlan(mutableMapOf(), 0)
slotPlanStack.add(loopSlotPlan) slotPlanStack.add(loopSlotPlan)
declareSlotName(tVar.value) declareSlotName(tVar.value, isMutable = true, isDelegated = false)
val (canBreak, body, elseStatement) = try { val (canBreak, body, elseStatement) = try {
withLocalNames(namesForLoop) { withLocalNames(namesForLoop) {
val loopParsed = cc.parseLoop { val loopParsed = cc.parseLoop {
@ -2500,7 +2554,7 @@ class Compiler(
} finally { } finally {
slotPlanStack.removeLast() slotPlanStack.removeLast()
} }
val loopSlotPlanSnapshot = if (loopSlotPlan.slots.isEmpty()) emptyMap() else loopSlotPlan.slots.toMap() val loopSlotPlanSnapshot = slotPlanIndices(loopSlotPlan)
return object : Statement() { return object : Statement() {
override val pos: Pos = body.pos override val pos: Pos = body.pos
@ -2805,7 +2859,7 @@ class Compiler(
var result: Obj = ObjVoid var result: Obj = ObjVoid
var wasBroken = false var wasBroken = false
while (condition.execute(scope).toBool()) { while (condition.execute(scope).toBool()) {
val loopScope = scope.createChildScope() val loopScope = scope.createChildScope().apply { skipScopeCreation = true }
if (canBreak) { if (canBreak) {
try { try {
result = body.execute(loopScope) result = body.execute(loopScope)
@ -2962,7 +3016,7 @@ class Compiler(
val t2 = cc.nextNonWhitespace() val t2 = cc.nextNonWhitespace()
// we generate different statements: optimization // we generate different statements: optimization
return if (t2.type == Token.Type.ID && t2.value == "else") { val stmt = if (t2.type == Token.Type.ID && t2.value == "else") {
val elseBody = val elseBody =
parseStatement() ?: throw ScriptError(pos, "Bad else statement: expected statement") parseStatement() ?: throw ScriptError(pos, "Bad else statement: expected statement")
IfStatement(condition, ifBody, elseBody, start) IfStatement(condition, ifBody, elseBody, start)
@ -2970,6 +3024,7 @@ class Compiler(
cc.previous() cc.previous()
IfStatement(condition, ifBody, null, start) IfStatement(condition, ifBody, null, start)
} }
return wrapBytecode(stmt)
} }
private suspend fun parseFunctionDeclaration( private suspend fun parseFunctionDeclaration(
@ -3115,7 +3170,7 @@ class Compiler(
var closure: Scope? = null var closure: Scope? = null
val paramSlotPlanSnapshot = if (paramSlotPlan.slots.isEmpty()) emptyMap() else paramSlotPlan.slots.toMap() val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
val fnBody = object : Statement() { val fnBody = object : Statement() {
override val pos: Pos = t.pos override val pos: Pos = t.pos
override suspend fun execute(callerContext: Scope): Obj { override suspend fun execute(callerContext: Scope): Obj {
@ -3323,7 +3378,7 @@ class Compiler(
} finally { } finally {
slotPlanStack.removeLast() slotPlanStack.removeLast()
} }
val planSnapshot = if (blockSlotPlan.slots.isEmpty()) emptyMap() else blockSlotPlan.slots.toMap() val planSnapshot = slotPlanIndices(blockSlotPlan)
val stmt = object : Statement() { val stmt = object : Statement() {
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
@ -3333,7 +3388,8 @@ class Compiler(
return block.execute(target) return block.execute(target)
} }
} }
return stmt.also { val wrapped = wrapBytecode(stmt)
return wrapped.also {
val t1 = cc.next() val t1 = cc.next()
if (t1.type != Token.Type.RBRACE) if (t1.type != Token.Type.RBRACE)
throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }") throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }")
@ -3368,7 +3424,7 @@ class Compiler(
// Register all names in the pattern // Register all names in the pattern
pattern.forEachVariableWithPos { name, namePos -> pattern.forEachVariableWithPos { name, namePos ->
declareLocalName(name) declareLocalName(name, isMutable)
val declRange = MiniRange(namePos, namePos) val declRange = MiniRange(namePos, namePos)
val node = MiniValDecl( val node = MiniValDecl(
range = declRange, range = declRange,
@ -3526,7 +3582,7 @@ class Compiler(
val effectiveEqToken = if (isProperty) null else eqToken val effectiveEqToken = if (isProperty) null else eqToken
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals // Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
if (!isStatic) declareLocalName(name) if (!isStatic) declareLocalName(name, isMutable)
val isDelegate = if (isAbstract || actualExtern) { val isDelegate = if (isAbstract || actualExtern) {
if (!isProperty && (effectiveEqToken?.type == Token.Type.ASSIGN || effectiveEqToken?.type == Token.Type.BY)) if (!isProperty && (effectiveEqToken?.type == Token.Type.ASSIGN || effectiveEqToken?.type == Token.Type.BY))
@ -3561,6 +3617,10 @@ class Compiler(
else parseStatement(true) else parseStatement(true)
?: throw ScriptError(effectiveEqToken!!.pos, "Expected initializer expression") ?: throw ScriptError(effectiveEqToken!!.pos, "Expected initializer expression")
if (!isStatic && isDelegate) {
markDelegatedSlot(name)
}
// Emit MiniValDecl for this declaration (before execution wiring), attach doc if any // Emit MiniValDecl for this declaration (before execution wiring), attach doc if any
run { run {
val declRange = MiniRange(pendingDeclStart ?: start, cc.currentPos()) val declRange = MiniRange(pendingDeclStart ?: start, cc.currentPos())
@ -3839,7 +3899,7 @@ class Compiler(
} }
// Register the local name so subsequent identifiers can be emitted as fast locals // Register the local name so subsequent identifiers can be emitted as fast locals
if (!isStatic) declareLocalName(name) if (!isStatic) declareLocalName(name, isMutable)
if (isDelegate) { if (isDelegate) {
val declaringClassName = declaringClassNameCaptured val declaringClassName = declaringClassNameCaptured

View File

@ -56,10 +56,22 @@ class BytecodeBuilder {
return fallbackStatements.lastIndex return fallbackStatements.lastIndex
} }
fun build(name: String, localCount: Int): BytecodeFunction { 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 { val slotWidth = when {
localCount < 256 -> 1 totalSlots < 256 -> 1
localCount < 65536 -> 2 totalSlots < 65536 -> 2
else -> 4 else -> 4
} }
val constIdWidth = if (constPool.size < 65536) 2 else 4 val constIdWidth = if (constPool.size < 65536) 2 else 4
@ -102,6 +114,10 @@ class BytecodeBuilder {
return BytecodeFunction( return BytecodeFunction(
name = name, name = name,
localCount = localCount, localCount = localCount,
scopeSlotCount = scopeSlotCount,
scopeSlotDepths = scopeSlotDepths,
scopeSlotIndices = scopeSlotIndices,
scopeSlotNames = if (scopeSlotNames.isEmpty()) Array(scopeSlotCount) { null } else scopeSlotNames,
slotWidth = slotWidth, slotWidth = slotWidth,
ipWidth = ipWidth, ipWidth = ipWidth,
constIdWidth = constIdWidth, constIdWidth = constIdWidth,
@ -135,6 +151,7 @@ class BytecodeBuilder {
Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_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_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.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 -> Opcode.AND_BOOL, Opcode.OR_BOOL ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->

View File

@ -23,12 +23,21 @@ import net.sergeych.lyng.Statement
import net.sergeych.lyng.ToBoolStatement import net.sergeych.lyng.ToBoolStatement
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
class BytecodeCompiler { class BytecodeCompiler(
private val builder = BytecodeBuilder() private val allowLocalSlots: Boolean = true,
) {
private var builder = BytecodeBuilder()
private var nextSlot = 0 private var nextSlot = 0
private var scopeSlotCount = 0
private var scopeSlotDepths = IntArray(0)
private var scopeSlotIndices = IntArray(0)
private var scopeSlotNames = emptyArray<String?>()
private val scopeSlotMap = LinkedHashMap<ScopeSlotKey, Int>()
private val scopeSlotNameMap = LinkedHashMap<ScopeSlotKey, String>()
private val slotTypes = mutableMapOf<Int, SlotType>() private val slotTypes = mutableMapOf<Int, SlotType>()
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): BytecodeFunction? { fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): BytecodeFunction? {
prepareCompilation(stmt)
return when (stmt) { return when (stmt) {
is ExpressionStatement -> compileExpression(name, stmt) is ExpressionStatement -> compileExpression(name, stmt)
is net.sergeych.lyng.IfStatement -> compileIf(name, stmt) is net.sergeych.lyng.IfStatement -> compileIf(name, stmt)
@ -37,10 +46,11 @@ class BytecodeCompiler {
} }
fun compileExpression(name: String, stmt: ExpressionStatement): BytecodeFunction? { fun compileExpression(name: String, stmt: ExpressionStatement): BytecodeFunction? {
prepareCompilation(stmt)
val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null
builder.emit(Opcode.RET, value.slot) builder.emit(Opcode.RET, value.slot)
val localCount = maxOf(nextSlot, value.slot + 1) val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
return builder.build(name, localCount) return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
} }
private data class CompiledValue(val slot: Int, val type: SlotType) private data class CompiledValue(val slot: Int, val type: SlotType)
@ -51,9 +61,11 @@ class BytecodeCompiler {
return when (ref) { return when (ref) {
is ConstRef -> compileConst(ref.constValue) is ConstRef -> compileConst(ref.constValue)
is LocalSlotRef -> { is LocalSlotRef -> {
if (!allowLocalSlots) return null
if (ref.isDelegated) return null
if (ref.name.isEmpty()) return null if (ref.name.isEmpty()) return null
if (refDepth(ref) != 0) return null val mapped = scopeSlotMap[ScopeSlotKey(refDepth(ref), refSlot(ref))] ?: return null
CompiledValue(refSlot(ref), slotTypes[refSlot(ref)] ?: SlotType.UNKNOWN) CompiledValue(mapped, slotTypes[mapped] ?: SlotType.UNKNOWN)
} }
is BinaryOpRef -> compileBinary(ref) is BinaryOpRef -> compileBinary(ref)
is UnaryOpRef -> compileUnary(ref) is UnaryOpRef -> compileUnary(ref)
@ -146,6 +158,7 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.INT) CompiledValue(out, SlotType.INT)
} }
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out) SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out)
SlotType.OBJ -> null
else -> null else -> null
} }
} }
@ -156,9 +169,15 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.REAL) CompiledValue(out, SlotType.REAL)
} }
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out) SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out)
SlotType.OBJ -> null
else -> null else -> null
} }
} }
SlotType.OBJ -> {
if (b.type != SlotType.OBJ) return null
builder.emit(Opcode.ADD_OBJ, a.slot, b.slot, out)
CompiledValue(out, SlotType.OBJ)
}
else -> null else -> null
} }
BinOp.MINUS -> when (a.type) { BinOp.MINUS -> when (a.type) {
@ -169,6 +188,7 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.INT) CompiledValue(out, SlotType.INT)
} }
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out) SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out)
SlotType.OBJ -> null
else -> null else -> null
} }
} }
@ -179,9 +199,15 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.REAL) CompiledValue(out, SlotType.REAL)
} }
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out) SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out)
SlotType.OBJ -> null
else -> null else -> null
} }
} }
SlotType.OBJ -> {
if (b.type != SlotType.OBJ) return null
builder.emit(Opcode.SUB_OBJ, a.slot, b.slot, out)
CompiledValue(out, SlotType.OBJ)
}
else -> null else -> null
} }
BinOp.STAR -> when (a.type) { BinOp.STAR -> when (a.type) {
@ -192,6 +218,7 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.INT) CompiledValue(out, SlotType.INT)
} }
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out) SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out)
SlotType.OBJ -> null
else -> null else -> null
} }
} }
@ -202,9 +229,15 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.REAL) CompiledValue(out, SlotType.REAL)
} }
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out) SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out)
SlotType.OBJ -> null
else -> null else -> null
} }
} }
SlotType.OBJ -> {
if (b.type != SlotType.OBJ) return null
builder.emit(Opcode.MUL_OBJ, a.slot, b.slot, out)
CompiledValue(out, SlotType.OBJ)
}
else -> null else -> null
} }
BinOp.SLASH -> when (a.type) { BinOp.SLASH -> when (a.type) {
@ -215,6 +248,7 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.INT) CompiledValue(out, SlotType.INT)
} }
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out) SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out)
SlotType.OBJ -> null
else -> null else -> null
} }
} }
@ -225,16 +259,32 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.REAL) CompiledValue(out, SlotType.REAL)
} }
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out) SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out)
SlotType.OBJ -> null
else -> null else -> null
} }
} }
SlotType.OBJ -> {
if (b.type != SlotType.OBJ) return null
builder.emit(Opcode.DIV_OBJ, a.slot, b.slot, out)
CompiledValue(out, SlotType.OBJ)
}
else -> null else -> null
} }
BinOp.PERCENT -> { BinOp.PERCENT -> {
if (a.type != SlotType.INT) return null return when (a.type) {
SlotType.INT -> {
if (b.type != SlotType.INT) return null
builder.emit(Opcode.MOD_INT, a.slot, b.slot, out) builder.emit(Opcode.MOD_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.INT) CompiledValue(out, SlotType.INT)
} }
SlotType.OBJ -> {
if (b.type != SlotType.OBJ) return null
builder.emit(Opcode.MOD_OBJ, a.slot, b.slot, out)
CompiledValue(out, SlotType.OBJ)
}
else -> null
}
}
BinOp.EQ -> { BinOp.EQ -> {
compileCompareEq(a, b, out) compileCompareEq(a, b, out)
} }
@ -522,9 +572,11 @@ class BytecodeCompiler {
private fun compileAssign(ref: AssignRef): CompiledValue? { private fun compileAssign(ref: AssignRef): CompiledValue? {
val target = assignTarget(ref) ?: return null val target = assignTarget(ref) ?: return null
if (refDepth(target) != 0) return null if (!allowLocalSlots) return null
if (!target.isMutable || target.isDelegated) return null
if (refDepth(target) > 0) return null
val value = compileRef(assignValue(ref)) ?: return null val value = compileRef(assignValue(ref)) ?: return null
val slot = refSlot(target) val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null
when (value.type) { when (value.type) {
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot) SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot) SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot)
@ -563,8 +615,8 @@ class BytecodeCompiler {
builder.mark(endLabel) builder.mark(endLabel)
builder.emit(Opcode.RET, resultSlot) builder.emit(Opcode.RET, resultSlot)
val localCount = maxOf(nextSlot, resultSlot + 1) val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
return builder.build(name, localCount) return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
} }
private fun compileStatementValue(stmt: Statement): CompiledValue? { private fun compileStatementValue(stmt: Statement): CompiledValue? {
@ -620,4 +672,71 @@ class BytecodeCompiler {
slotTypes[slot] = type slotTypes[slot] = type
} }
} }
private fun prepareCompilation(stmt: Statement) {
builder = BytecodeBuilder()
nextSlot = 0
slotTypes.clear()
scopeSlotMap.clear()
if (allowLocalSlots) {
collectScopeSlots(stmt)
}
scopeSlotCount = scopeSlotMap.size
scopeSlotDepths = IntArray(scopeSlotCount)
scopeSlotIndices = IntArray(scopeSlotCount)
scopeSlotNames = arrayOfNulls(scopeSlotCount)
for ((key, index) in scopeSlotMap) {
scopeSlotDepths[index] = key.depth
scopeSlotIndices[index] = key.slot
scopeSlotNames[index] = scopeSlotNameMap[key]
}
nextSlot = scopeSlotCount
}
private fun collectScopeSlots(stmt: Statement) {
when (stmt) {
is ExpressionStatement -> collectScopeSlotsRef(stmt.ref)
is IfStatement -> {
collectScopeSlots(stmt.condition)
collectScopeSlots(stmt.ifBody)
stmt.elseBody?.let { collectScopeSlots(it) }
}
else -> {}
}
}
private fun collectScopeSlotsRef(ref: ObjRef) {
when (ref) {
is LocalSlotRef -> {
val key = ScopeSlotKey(refDepth(ref), refSlot(ref))
if (!scopeSlotMap.containsKey(key)) {
scopeSlotMap[key] = scopeSlotMap.size
}
if (!scopeSlotNameMap.containsKey(key)) {
scopeSlotNameMap[key] = ref.name
}
}
is BinaryOpRef -> {
collectScopeSlotsRef(binaryLeft(ref))
collectScopeSlotsRef(binaryRight(ref))
}
is UnaryOpRef -> collectScopeSlotsRef(unaryOperand(ref))
is AssignRef -> {
val target = assignTarget(ref)
if (target != null) {
val key = ScopeSlotKey(refDepth(target), refSlot(target))
if (!scopeSlotMap.containsKey(key)) {
scopeSlotMap[key] = scopeSlotMap.size
}
if (!scopeSlotNameMap.containsKey(key)) {
scopeSlotNameMap[key] = target.name
}
}
collectScopeSlotsRef(assignValue(ref))
}
else -> {}
}
}
private data class ScopeSlotKey(val depth: Int, val slot: Int)
} }

View File

@ -38,7 +38,8 @@ object BytecodeDisassembler {
OperandKind.SLOT -> { OperandKind.SLOT -> {
val v = decoder.readSlot(code, ip) val v = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
operands += "s$v" val name = if (v < fn.scopeSlotCount) fn.scopeSlotNames[v] else null
operands += if (name != null) "s$v($name)" else "s$v"
} }
OperandKind.CONST -> { OperandKind.CONST -> {
val v = decoder.readConstId(code, ip, fn.constIdWidth) val v = decoder.readConstId(code, ip, fn.constIdWidth)
@ -103,6 +104,7 @@ object BytecodeDisassembler {
Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_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_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.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 -> Opcode.AND_BOOL, Opcode.OR_BOOL ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET -> Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->

View File

@ -19,6 +19,10 @@ package net.sergeych.lyng.bytecode
data class BytecodeFunction( data class BytecodeFunction(
val name: String, val name: String,
val localCount: Int, val localCount: Int,
val scopeSlotCount: Int,
val scopeSlotDepths: IntArray,
val scopeSlotIndices: IntArray,
val scopeSlotNames: Array<String?>,
val slotWidth: Int, val slotWidth: Int,
val ipWidth: Int, val ipWidth: Int,
val constIdWidth: Int, val constIdWidth: Int,
@ -30,5 +34,8 @@ data class BytecodeFunction(
require(slotWidth == 1 || slotWidth == 2 || slotWidth == 4) { "slotWidth must be 1,2,4" } 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(ipWidth == 2 || ipWidth == 4) { "ipWidth must be 2 or 4" }
require(constIdWidth == 2 || constIdWidth == 4) { "constIdWidth 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" }
} }
} }

View File

@ -0,0 +1,50 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.obj.Obj
class BytecodeStatement private constructor(
val original: Statement,
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 BytecodeVm().execute(function, scope, emptyList())
}
companion object {
fun wrap(statement: Statement, nameHint: String, allowLocalSlots: Boolean): Statement {
if (statement is BytecodeStatement) return statement
val compiler = BytecodeCompiler(allowLocalSlots = allowLocalSlots)
val compiled = compiler.compileStatement(nameHint, statement)
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)
}
}
}

View File

@ -47,7 +47,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val c = fn.constants[constId] as? BytecodeConst.IntVal val c = fn.constants[constId] as? BytecodeConst.IntVal
?: error("CONST_INT expects IntVal at $constId") ?: error("CONST_INT expects IntVal at $constId")
frame.setInt(dst, c.value) setInt(fn, frame, scope, dst, c.value)
} }
Opcode.CONST_REAL -> { Opcode.CONST_REAL -> {
val constId = decoder.readConstId(code, ip, fn.constIdWidth) val constId = decoder.readConstId(code, ip, fn.constIdWidth)
@ -56,7 +56,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val c = fn.constants[constId] as? BytecodeConst.RealVal val c = fn.constants[constId] as? BytecodeConst.RealVal
?: error("CONST_REAL expects RealVal at $constId") ?: error("CONST_REAL expects RealVal at $constId")
frame.setReal(dst, c.value) setReal(fn, frame, scope, dst, c.value)
} }
Opcode.CONST_BOOL -> { Opcode.CONST_BOOL -> {
val constId = decoder.readConstId(code, ip, fn.constIdWidth) val constId = decoder.readConstId(code, ip, fn.constIdWidth)
@ -65,7 +65,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val c = fn.constants[constId] as? BytecodeConst.Bool val c = fn.constants[constId] as? BytecodeConst.Bool
?: error("CONST_BOOL expects Bool at $constId") ?: error("CONST_BOOL expects Bool at $constId")
frame.setBool(dst, c.value) setBool(fn, frame, scope, dst, c.value)
} }
Opcode.CONST_OBJ -> { Opcode.CONST_OBJ -> {
val constId = decoder.readConstId(code, ip, fn.constIdWidth) val constId = decoder.readConstId(code, ip, fn.constIdWidth)
@ -76,76 +76,76 @@ class BytecodeVm {
is BytecodeConst.ObjRef -> { is BytecodeConst.ObjRef -> {
val obj = c.value val obj = c.value
when (obj) { when (obj) {
is ObjInt -> frame.setInt(dst, obj.value) is ObjInt -> setInt(fn, frame, scope, dst, obj.value)
is ObjReal -> frame.setReal(dst, obj.value) is ObjReal -> setReal(fn, frame, scope, dst, obj.value)
is ObjBool -> frame.setBool(dst, obj.value) is ObjBool -> setBool(fn, frame, scope, dst, obj.value)
else -> frame.setObj(dst, obj) else -> setObj(fn, frame, scope, dst, obj)
} }
} }
is BytecodeConst.StringVal -> frame.setObj(dst, ObjString(c.value)) is BytecodeConst.StringVal -> setObj(fn, frame, scope, dst, ObjString(c.value))
else -> error("CONST_OBJ expects ObjRef/StringVal at $constId") else -> error("CONST_OBJ expects ObjRef/StringVal at $constId")
} }
} }
Opcode.CONST_NULL -> { Opcode.CONST_NULL -> {
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setObj(dst, ObjNull) setObj(fn, frame, scope, dst, ObjNull)
} }
Opcode.MOVE_INT -> { Opcode.MOVE_INT -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(src)) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, src))
} }
Opcode.MOVE_REAL -> { Opcode.MOVE_REAL -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setReal(dst, frame.getReal(src)) setReal(fn, frame, scope, dst, getReal(fn, frame, scope, src))
} }
Opcode.MOVE_BOOL -> { Opcode.MOVE_BOOL -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getBool(src)) setBool(fn, frame, scope, dst, getBool(fn, frame, scope, src))
} }
Opcode.MOVE_OBJ -> { Opcode.MOVE_OBJ -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setObj(dst, frame.getObj(src)) setObj(fn, frame, scope, dst, getObj(fn, frame, scope, src))
} }
Opcode.INT_TO_REAL -> { Opcode.INT_TO_REAL -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setReal(dst, frame.getInt(src).toDouble()) setReal(fn, frame, scope, dst, getInt(fn, frame, scope, src).toDouble())
} }
Opcode.REAL_TO_INT -> { Opcode.REAL_TO_INT -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getReal(src).toLong()) setInt(fn, frame, scope, dst, getReal(fn, frame, scope, src).toLong())
} }
Opcode.BOOL_TO_INT -> { Opcode.BOOL_TO_INT -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, if (frame.getBool(src)) 1L else 0L) setInt(fn, frame, scope, dst, if (getBool(fn, frame, scope, src)) 1L else 0L)
} }
Opcode.INT_TO_BOOL -> { Opcode.INT_TO_BOOL -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(src) != 0L) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, src) != 0L)
} }
Opcode.ADD_INT -> { Opcode.ADD_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -154,7 +154,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) + frame.getInt(b)) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) + getInt(fn, frame, scope, b))
} }
Opcode.SUB_INT -> { Opcode.SUB_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -163,7 +163,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) - frame.getInt(b)) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) - getInt(fn, frame, scope, b))
} }
Opcode.MUL_INT -> { Opcode.MUL_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -172,7 +172,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) * frame.getInt(b)) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) * getInt(fn, frame, scope, b))
} }
Opcode.DIV_INT -> { Opcode.DIV_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -181,7 +181,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) / frame.getInt(b)) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) / getInt(fn, frame, scope, b))
} }
Opcode.MOD_INT -> { Opcode.MOD_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -190,24 +190,24 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) % frame.getInt(b)) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) % getInt(fn, frame, scope, b))
} }
Opcode.NEG_INT -> { Opcode.NEG_INT -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, -frame.getInt(src)) setInt(fn, frame, scope, dst, -getInt(fn, frame, scope, src))
} }
Opcode.INC_INT -> { Opcode.INC_INT -> {
val slot = decoder.readSlot(code, ip) val slot = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(slot, frame.getInt(slot) + 1L) setInt(fn, frame, scope, slot, getInt(fn, frame, scope, slot) + 1L)
} }
Opcode.DEC_INT -> { Opcode.DEC_INT -> {
val slot = decoder.readSlot(code, ip) val slot = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(slot, frame.getInt(slot) - 1L) setInt(fn, frame, scope, slot, getInt(fn, frame, scope, slot) - 1L)
} }
Opcode.ADD_REAL -> { Opcode.ADD_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -216,7 +216,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setReal(dst, frame.getReal(a) + frame.getReal(b)) setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) + getReal(fn, frame, scope, b))
} }
Opcode.SUB_REAL -> { Opcode.SUB_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -225,7 +225,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setReal(dst, frame.getReal(a) - frame.getReal(b)) setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) - getReal(fn, frame, scope, b))
} }
Opcode.MUL_REAL -> { Opcode.MUL_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -234,7 +234,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setReal(dst, frame.getReal(a) * frame.getReal(b)) setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) * getReal(fn, frame, scope, b))
} }
Opcode.DIV_REAL -> { Opcode.DIV_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -243,14 +243,14 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setReal(dst, frame.getReal(a) / frame.getReal(b)) setReal(fn, frame, scope, dst, getReal(fn, frame, scope, a) / getReal(fn, frame, scope, b))
} }
Opcode.NEG_REAL -> { Opcode.NEG_REAL -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setReal(dst, -frame.getReal(src)) setReal(fn, frame, scope, dst, -getReal(fn, frame, scope, src))
} }
Opcode.AND_INT -> { Opcode.AND_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -259,7 +259,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) and frame.getInt(b)) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) and getInt(fn, frame, scope, b))
} }
Opcode.OR_INT -> { Opcode.OR_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -268,7 +268,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) or frame.getInt(b)) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) or getInt(fn, frame, scope, b))
} }
Opcode.XOR_INT -> { Opcode.XOR_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -277,7 +277,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) xor frame.getInt(b)) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) xor getInt(fn, frame, scope, b))
} }
Opcode.SHL_INT -> { Opcode.SHL_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -286,7 +286,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) shl frame.getInt(b).toInt()) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) shl getInt(fn, frame, scope, b).toInt())
} }
Opcode.SHR_INT -> { Opcode.SHR_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -295,7 +295,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) shr frame.getInt(b).toInt()) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) shr getInt(fn, frame, scope, b).toInt())
} }
Opcode.USHR_INT -> { Opcode.USHR_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -304,14 +304,14 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(a) ushr frame.getInt(b).toInt()) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, a) ushr getInt(fn, frame, scope, b).toInt())
} }
Opcode.INV_INT -> { Opcode.INV_INT -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setInt(dst, frame.getInt(src).inv()) setInt(fn, frame, scope, dst, getInt(fn, frame, scope, src).inv())
} }
Opcode.CMP_LT_INT -> { Opcode.CMP_LT_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -320,7 +320,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a) < frame.getInt(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) < getInt(fn, frame, scope, b))
} }
Opcode.CMP_LTE_INT -> { Opcode.CMP_LTE_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -329,7 +329,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a) <= frame.getInt(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) <= getInt(fn, frame, scope, b))
} }
Opcode.CMP_GT_INT -> { Opcode.CMP_GT_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -338,7 +338,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a) > frame.getInt(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) > getInt(fn, frame, scope, b))
} }
Opcode.CMP_GTE_INT -> { Opcode.CMP_GTE_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -347,7 +347,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a) >= frame.getInt(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) >= getInt(fn, frame, scope, b))
} }
Opcode.CMP_EQ_INT -> { Opcode.CMP_EQ_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -356,7 +356,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a) == frame.getInt(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) == getInt(fn, frame, scope, b))
} }
Opcode.CMP_NEQ_INT -> { Opcode.CMP_NEQ_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -365,7 +365,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a) != frame.getInt(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a) != getInt(fn, frame, scope, b))
} }
Opcode.CMP_EQ_REAL -> { Opcode.CMP_EQ_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -374,7 +374,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) == frame.getReal(b)) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) == getReal(fn, frame, scope, b))
} }
Opcode.CMP_NEQ_REAL -> { Opcode.CMP_NEQ_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -383,7 +383,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) != frame.getReal(b)) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) != getReal(fn, frame, scope, b))
} }
Opcode.CMP_LT_REAL -> { Opcode.CMP_LT_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -392,7 +392,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) < frame.getReal(b)) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) < getReal(fn, frame, scope, b))
} }
Opcode.CMP_LTE_REAL -> { Opcode.CMP_LTE_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -401,7 +401,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) <= frame.getReal(b)) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) <= getReal(fn, frame, scope, b))
} }
Opcode.CMP_GT_REAL -> { Opcode.CMP_GT_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -410,7 +410,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) > frame.getReal(b)) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) > getReal(fn, frame, scope, b))
} }
Opcode.CMP_GTE_REAL -> { Opcode.CMP_GTE_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -419,7 +419,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) >= frame.getReal(b)) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) >= getReal(fn, frame, scope, b))
} }
Opcode.CMP_EQ_BOOL -> { Opcode.CMP_EQ_BOOL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -428,7 +428,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getBool(a) == frame.getBool(b)) setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) == getBool(fn, frame, scope, b))
} }
Opcode.CMP_NEQ_BOOL -> { Opcode.CMP_NEQ_BOOL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -437,7 +437,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getBool(a) != frame.getBool(b)) setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) != getBool(fn, frame, scope, b))
} }
Opcode.CMP_EQ_INT_REAL -> { Opcode.CMP_EQ_INT_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -446,7 +446,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a).toDouble() == frame.getReal(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() == getReal(fn, frame, scope, b))
} }
Opcode.CMP_EQ_REAL_INT -> { Opcode.CMP_EQ_REAL_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -455,7 +455,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) == frame.getInt(b).toDouble()) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) == getInt(fn, frame, scope, b).toDouble())
} }
Opcode.CMP_LT_INT_REAL -> { Opcode.CMP_LT_INT_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -464,7 +464,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a).toDouble() < frame.getReal(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() < getReal(fn, frame, scope, b))
} }
Opcode.CMP_LT_REAL_INT -> { Opcode.CMP_LT_REAL_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -473,7 +473,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) < frame.getInt(b).toDouble()) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) < getInt(fn, frame, scope, b).toDouble())
} }
Opcode.CMP_LTE_INT_REAL -> { Opcode.CMP_LTE_INT_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -482,7 +482,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a).toDouble() <= frame.getReal(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() <= getReal(fn, frame, scope, b))
} }
Opcode.CMP_LTE_REAL_INT -> { Opcode.CMP_LTE_REAL_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -491,7 +491,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) <= frame.getInt(b).toDouble()) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) <= getInt(fn, frame, scope, b).toDouble())
} }
Opcode.CMP_GT_INT_REAL -> { Opcode.CMP_GT_INT_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -500,7 +500,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a).toDouble() > frame.getReal(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() > getReal(fn, frame, scope, b))
} }
Opcode.CMP_GT_REAL_INT -> { Opcode.CMP_GT_REAL_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -509,7 +509,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) > frame.getInt(b).toDouble()) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) > getInt(fn, frame, scope, b).toDouble())
} }
Opcode.CMP_GTE_INT_REAL -> { Opcode.CMP_GTE_INT_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -518,7 +518,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a).toDouble() >= frame.getReal(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() >= getReal(fn, frame, scope, b))
} }
Opcode.CMP_GTE_REAL_INT -> { Opcode.CMP_GTE_REAL_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -527,7 +527,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) >= frame.getInt(b).toDouble()) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) >= getInt(fn, frame, scope, b).toDouble())
} }
Opcode.CMP_NEQ_INT_REAL -> { Opcode.CMP_NEQ_INT_REAL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -536,7 +536,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a).toDouble() != frame.getReal(b)) setBool(fn, frame, scope, dst, getInt(fn, frame, scope, a).toDouble() != getReal(fn, frame, scope, b))
} }
Opcode.CMP_NEQ_REAL_INT -> { Opcode.CMP_NEQ_REAL_INT -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -545,7 +545,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getReal(a) != frame.getInt(b).toDouble()) setBool(fn, frame, scope, dst, getReal(fn, frame, scope, a) != getInt(fn, frame, scope, b).toDouble())
} }
Opcode.CMP_EQ_OBJ -> { Opcode.CMP_EQ_OBJ -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -554,7 +554,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a).equals(scope, frame.getObj(b))) setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).equals(scope, getObj(fn, frame, scope, b)))
} }
Opcode.CMP_NEQ_OBJ -> { Opcode.CMP_NEQ_OBJ -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -563,7 +563,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, !frame.getObj(a).equals(scope, frame.getObj(b))) setBool(fn, frame, scope, dst, !getObj(fn, frame, scope, a).equals(scope, getObj(fn, frame, scope, b)))
} }
Opcode.CMP_REF_EQ_OBJ -> { Opcode.CMP_REF_EQ_OBJ -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -572,7 +572,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a) === frame.getObj(b)) setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a) === getObj(fn, frame, scope, b))
} }
Opcode.CMP_REF_NEQ_OBJ -> { Opcode.CMP_REF_NEQ_OBJ -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -581,7 +581,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a) !== frame.getObj(b)) setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a) !== getObj(fn, frame, scope, b))
} }
Opcode.CMP_LT_OBJ -> { Opcode.CMP_LT_OBJ -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -590,7 +590,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a).compareTo(scope, frame.getObj(b)) < 0) setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) < 0)
} }
Opcode.CMP_LTE_OBJ -> { Opcode.CMP_LTE_OBJ -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -599,7 +599,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a).compareTo(scope, frame.getObj(b)) <= 0) setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) <= 0)
} }
Opcode.CMP_GT_OBJ -> { Opcode.CMP_GT_OBJ -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -608,7 +608,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a).compareTo(scope, frame.getObj(b)) > 0) setBool(fn, frame, scope, dst, getObj(fn, frame, scope, a).compareTo(scope, getObj(fn, frame, scope, b)) > 0)
} }
Opcode.CMP_GTE_OBJ -> { Opcode.CMP_GTE_OBJ -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -617,14 +617,59 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getObj(a).compareTo(scope, frame.getObj(b)) >= 0) 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 -> { Opcode.NOT_BOOL -> {
val src = decoder.readSlot(code, ip) val src = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, !frame.getBool(src)) setBool(fn, frame, scope, dst, !getBool(fn, frame, scope, src))
} }
Opcode.AND_BOOL -> { Opcode.AND_BOOL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -633,7 +678,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getBool(a) && frame.getBool(b)) setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) && getBool(fn, frame, scope, b))
} }
Opcode.OR_BOOL -> { Opcode.OR_BOOL -> {
val a = decoder.readSlot(code, ip) val a = decoder.readSlot(code, ip)
@ -642,7 +687,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val dst = decoder.readSlot(code, ip) val dst = decoder.readSlot(code, ip)
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getBool(a) || frame.getBool(b)) setBool(fn, frame, scope, dst, getBool(fn, frame, scope, a) || getBool(fn, frame, scope, b))
} }
Opcode.JMP -> { Opcode.JMP -> {
val target = decoder.readIp(code, ip, fn.ipWidth) val target = decoder.readIp(code, ip, fn.ipWidth)
@ -653,7 +698,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val target = decoder.readIp(code, ip, fn.ipWidth) val target = decoder.readIp(code, ip, fn.ipWidth)
ip += fn.ipWidth ip += fn.ipWidth
if (!frame.getBool(cond)) { if (!getBool(fn, frame, scope, cond)) {
ip = target ip = target
} }
} }
@ -662,7 +707,7 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
val target = decoder.readIp(code, ip, fn.ipWidth) val target = decoder.readIp(code, ip, fn.ipWidth)
ip += fn.ipWidth ip += fn.ipWidth
if (frame.getBool(cond)) { if (getBool(fn, frame, scope, cond)) {
ip = target ip = target
} }
} }
@ -675,15 +720,15 @@ class BytecodeVm {
?: error("Fallback statement not found: $id") ?: error("Fallback statement not found: $id")
val result = stmt.execute(scope) val result = stmt.execute(scope)
when (result) { when (result) {
is ObjInt -> frame.setInt(dst, result.value) is ObjInt -> setInt(fn, frame, scope, dst, result.value)
is ObjReal -> frame.setReal(dst, result.value) is ObjReal -> setReal(fn, frame, scope, dst, result.value)
is ObjBool -> frame.setBool(dst, result.value) is ObjBool -> setBool(fn, frame, scope, dst, result.value)
else -> frame.setObj(dst, result) else -> setObj(fn, frame, scope, dst, result)
} }
} }
Opcode.RET -> { Opcode.RET -> {
val slot = decoder.readSlot(code, ip) val slot = decoder.readSlot(code, ip)
return slotToObj(frame, slot) return slotToObj(fn, frame, scope, slot)
} }
Opcode.RET_VOID -> return ObjVoid Opcode.RET_VOID -> return ObjVoid
else -> error("Opcode not implemented: $op") else -> error("Opcode not implemented: $op")
@ -692,13 +737,96 @@ class BytecodeVm {
return ObjVoid return ObjVoid
} }
private fun slotToObj(frame: BytecodeFrame, slot: Int): Obj { private fun slotToObj(fn: BytecodeFunction, frame: BytecodeFrame, scope: Scope, slot: Int): Obj {
return when (frame.getSlotTypeCode(slot)) { if (slot < fn.scopeSlotCount) {
SlotType.INT.code -> ObjInt.of(frame.getInt(slot)) return resolveScope(scope, fn.scopeSlotDepths[slot]).getSlotRecord(fn.scopeSlotIndices[slot]).value
SlotType.REAL.code -> ObjReal.of(frame.getReal(slot)) }
SlotType.BOOL.code -> if (frame.getBool(slot)) ObjTrue else ObjFalse val local = slot - fn.scopeSlotCount
SlotType.OBJ.code -> frame.getObj(slot) 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 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

@ -95,6 +95,11 @@ enum class Opcode(val code: Int) {
CMP_LTE_OBJ(0x74), CMP_LTE_OBJ(0x74),
CMP_GT_OBJ(0x75), CMP_GT_OBJ(0x75),
CMP_GTE_OBJ(0x76), CMP_GTE_OBJ(0x76),
ADD_OBJ(0x77),
SUB_OBJ(0x78),
MUL_OBJ(0x79),
DIV_OBJ(0x7A),
MOD_OBJ(0x7B),
JMP(0x80), JMP(0x80),
JMP_IF_TRUE(0x81), JMP_IF_TRUE(0x81),

View File

@ -2405,6 +2405,8 @@ class LocalSlotRef(
val name: String, val name: String,
internal val slot: Int, internal val slot: Int,
internal val depth: Int, internal val depth: Int,
internal val isMutable: Boolean,
internal val isDelegated: Boolean,
private val atPos: Pos, private val atPos: Pos,
) : ObjRef { ) : ObjRef {
override fun forEachVariable(block: (String) -> Unit) { override fun forEachVariable(block: (String) -> Unit) {

View File

@ -210,7 +210,7 @@ class BytecodeVmTest {
@Test @Test
fun localSlotTypeTrackingEnablesArithmetic() = kotlinx.coroutines.test.runTest { fun localSlotTypeTrackingEnablesArithmetic() = kotlinx.coroutines.test.runTest {
val slotRef = LocalSlotRef("a", 0, 0, net.sergeych.lyng.Pos.builtIn) val slotRef = LocalSlotRef("a", 0, 0, true, false, net.sergeych.lyng.Pos.builtIn)
val assign = AssignRef( val assign = AssignRef(
slotRef, slotRef,
ConstRef(ObjInt.of(2).asReadonly), ConstRef(ObjInt.of(2).asReadonly),
@ -225,10 +225,32 @@ class BytecodeVmTest {
net.sergeych.lyng.Pos.builtIn net.sergeych.lyng.Pos.builtIn
) )
val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed") val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed")
val result = BytecodeVm().execute(fn, Scope(), emptyList()) val scope = Scope().apply { applySlotPlan(mapOf("a" to 0)) }
val result = BytecodeVm().execute(fn, scope, emptyList())
assertEquals(4, result.toInt()) assertEquals(4, result.toInt())
} }
@Test
fun parentScopeSlotAccessWorks() = kotlinx.coroutines.test.runTest {
val parentRef = LocalSlotRef("a", 0, 1, true, false, net.sergeych.lyng.Pos.builtIn)
val expr = ExpressionStatement(
BinaryOpRef(
BinOp.PLUS,
parentRef,
ConstRef(ObjInt.of(2).asReadonly)
),
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("parentSlotAdd", expr) ?: error("bytecode compile failed")
val parent = Scope().apply {
applySlotPlan(mapOf("a" to 0))
setSlotValue(0, ObjInt.of(3))
}
val child = Scope(parent)
val result = BytecodeVm().execute(fn, child, emptyList())
assertEquals(5, result.toInt())
}
@Test @Test
fun objectEqualityUsesBytecodeOps() = kotlinx.coroutines.test.runTest { fun objectEqualityUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
val expr = ExpressionStatement( val expr = ExpressionStatement(
@ -298,4 +320,19 @@ class BytecodeVmTest {
val gteResult = BytecodeVm().execute(gteFn, Scope(), emptyList()) val gteResult = BytecodeVm().execute(gteFn, Scope(), emptyList())
assertEquals(true, gteResult.toBool()) assertEquals(true, gteResult.toBool())
} }
@Test
fun objectArithmeticUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
val expr = ExpressionStatement(
BinaryOpRef(
BinOp.PLUS,
ConstRef(ObjString("a").asReadonly),
ConstRef(ObjString("b").asReadonly),
),
net.sergeych.lyng.Pos.builtIn
)
val fn = BytecodeCompiler().compileExpression("objPlus", expr) ?: error("bytecode compile failed")
val result = BytecodeVm().execute(fn, Scope(), emptyList())
assertEquals("ab", (result as ObjString).value)
}
} }