Stabilize bytecode baseline for nested range benchmark

This commit is contained in:
Sergey Chernov 2026-01-28 06:35:04 +03:00
parent bef94d3bc5
commit 2311cfc224
12 changed files with 1400 additions and 251 deletions

View File

@ -370,16 +370,29 @@ class Compiler(
private var isTransientFlag: Boolean = false
private var lastLabel: String? = null
private val useBytecodeStatements: Boolean = true
private val returnLabelStack = ArrayDeque<Set<String>>()
private fun wrapBytecode(stmt: Statement): Statement {
if (!useBytecodeStatements) return stmt
val allowLocals = codeContexts.lastOrNull() is CodeContext.Function
return BytecodeStatement.wrap(stmt, "stmt@${stmt.pos}", allowLocalSlots = allowLocals)
val returnLabels = returnLabelStack.lastOrNull() ?: emptySet()
return BytecodeStatement.wrap(
stmt,
"stmt@${stmt.pos}",
allowLocalSlots = allowLocals,
returnLabels = returnLabels
)
}
private fun wrapFunctionBytecode(stmt: Statement, name: String): Statement {
if (!useBytecodeStatements) return stmt
return BytecodeStatement.wrap(stmt, "fn@$name", allowLocalSlots = true)
val returnLabels = returnLabelStack.lastOrNull() ?: emptySet()
return BytecodeStatement.wrap(
stmt,
"fn@$name",
allowLocalSlots = true,
returnLabels = returnLabels
)
}
private fun containsUnsupportedForBytecode(stmt: Statement): Boolean {
@ -392,13 +405,27 @@ class Compiler(
(target.elseBody?.let { containsUnsupportedForBytecode(it) } ?: false)
}
is ForInStatement -> {
target.constRange == null || target.canBreak ||
target.constRange == null ||
containsUnsupportedForBytecode(target.source) ||
containsUnsupportedForBytecode(target.body) ||
(target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false)
}
is WhileStatement -> {
containsUnsupportedForBytecode(target.condition) ||
containsUnsupportedForBytecode(target.body) ||
(target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false)
}
is DoWhileStatement -> {
containsUnsupportedForBytecode(target.body) ||
containsUnsupportedForBytecode(target.condition) ||
(target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false)
}
is BlockStatement -> target.statements().any { containsUnsupportedForBytecode(it) }
is VarDeclStatement -> target.initializer?.let { containsUnsupportedForBytecode(it) } ?: false
is BreakStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false
is ContinueStatement -> false
is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false
is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr)
else -> true
}
}
@ -430,6 +457,59 @@ class Compiler(
val elseBody = stmt.elseBody?.let { unwrapBytecodeDeep(it) }
IfStatement(cond, ifBody, elseBody, stmt.pos)
}
is ForInStatement -> {
val source = unwrapBytecodeDeep(stmt.source)
val body = unwrapBytecodeDeep(stmt.body)
val elseBody = stmt.elseStatement?.let { unwrapBytecodeDeep(it) }
ForInStatement(
stmt.loopVarName,
source,
stmt.constRange,
body,
elseBody,
stmt.label,
stmt.canBreak,
stmt.loopSlotPlan,
stmt.pos
)
}
is WhileStatement -> {
val condition = unwrapBytecodeDeep(stmt.condition)
val body = unwrapBytecodeDeep(stmt.body)
val elseBody = stmt.elseStatement?.let { unwrapBytecodeDeep(it) }
WhileStatement(
condition,
body,
elseBody,
stmt.label,
stmt.canBreak,
stmt.loopSlotPlan,
stmt.pos
)
}
is DoWhileStatement -> {
val body = unwrapBytecodeDeep(stmt.body)
val condition = unwrapBytecodeDeep(stmt.condition)
val elseBody = stmt.elseStatement?.let { unwrapBytecodeDeep(it) }
DoWhileStatement(
body,
condition,
elseBody,
stmt.label,
stmt.loopSlotPlan,
stmt.pos
)
}
is BreakStatement -> {
val resultExpr = stmt.resultExpr?.let { unwrapBytecodeDeep(it) }
BreakStatement(stmt.label, resultExpr, stmt.pos)
}
is ContinueStatement -> ContinueStatement(stmt.label, stmt.pos)
is ReturnStatement -> {
val resultExpr = stmt.resultExpr?.let { unwrapBytecodeDeep(it) }
ReturnStatement(stmt.label, resultExpr, stmt.pos)
}
is ThrowStatement -> ThrowStatement(unwrapBytecodeDeep(stmt.throwExpr), stmt.pos)
else -> stmt
}
}
@ -885,9 +965,15 @@ class Compiler(
slotPlanStack.add(paramSlotPlan)
val parsedBody = try {
inCodeContext(CodeContext.Function("<lambda>")) {
val returnLabels = label?.let { setOf(it) } ?: emptySet()
returnLabelStack.addLast(returnLabels)
try {
withLocalNames(slotParamNames.toSet()) {
parseBlock(skipLeadingBrace = true)
}
} finally {
returnLabelStack.removeLast()
}
}
} finally {
slotPlanStack.removeLast()
@ -2031,37 +2117,7 @@ class Compiler(
private suspend fun parseThrowStatement(start: Pos): Statement {
val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected")
// Important: bind the created statement to the position of the `throw` keyword so that
// any raised error reports the correct source location.
val stmt = object : Statement() {
override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
var errorObject = throwStatement.execute(scope)
// Rebind error scope to the throw-site position so ScriptError.pos is accurate
val throwScope = scope.createChildScope(pos = start)
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, start, msg)
}
return ObjVoid
}
}
return wrapBytecode(stmt)
return wrapBytecode(ThrowStatement(throwStatement, start))
}
private data class CatchBlockData(
@ -2692,8 +2748,12 @@ class Compiler(
slotPlanStack.add(loopSlotPlan)
val (canBreak, parsedBody) = try {
cc.parseLoop {
if (cc.current().type == Token.Type.LBRACE) {
parseLoopBlock()
} else {
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement")
}
}
} finally {
slotPlanStack.removeLast()
}
@ -2722,36 +2782,8 @@ class Compiler(
cc.previous()
null
}
return object : Statement() {
override val pos: Pos = body.pos
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
}
// for continue: just fall through to condition check below
} else {
throw e
}
}
if (!condition.execute(doScope).toBool()) {
break
}
}
if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) }
return result
}
}
val loopPlanSnapshot = slotPlanIndices(loopSlotPlan)
return DoWhileStatement(body, condition, elseStatement, label, loopPlanSnapshot, body.pos)
}
private suspend fun parseWhileStatement(): Statement {
@ -2765,7 +2797,7 @@ class Compiler(
slotPlanStack.add(loopSlotPlan)
val (canBreak, parsedBody) = try {
cc.parseLoop {
if (cc.current().type == Token.Type.LBRACE) parseBlock()
if (cc.current().type == Token.Type.LBRACE) parseLoopBlock()
else parseStatement() ?: throw ScriptError(start, "Bad while statement: expected statement")
}
} finally {
@ -2782,34 +2814,8 @@ class Compiler(
cc.previous()
null
}
return object : Statement() {
override val pos: Pos = body.pos
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
else {
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
}
}
val loopPlanSnapshot = slotPlanIndices(loopSlotPlan)
return WhileStatement(condition, body, elseStatement, label, canBreak, loopPlanSnapshot, body.pos)
}
private suspend fun parseBreakStatement(start: Pos): Statement {
@ -2843,17 +2849,7 @@ class Compiler(
cc.addBreak()
return object : Statement() {
override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
val returnValue = resultExpr?.execute(scope)// ?: ObjVoid
throw LoopBreakContinueException(
doContinue = false,
label = label,
result = returnValue ?: ObjVoid
)
}
}
return BreakStatement(label, resultExpr, start)
}
private fun parseContinueStatement(start: Pos): Statement {
@ -2870,15 +2866,7 @@ class Compiler(
}
cc.addBreak()
return object : Statement() {
override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
throw LoopBreakContinueException(
doContinue = true,
label = label,
)
}
}
return ContinueStatement(label, start)
}
private suspend fun parseReturnStatement(start: Pos): Statement {
@ -2907,13 +2895,7 @@ class Compiler(
parseExpression()
} else null
return object : Statement() {
override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
val returnValue = resultExpr?.execute(scope) ?: ObjVoid
throw ReturnException(returnValue, label)
}
}
return ReturnStatement(label, resultExpr, start)
}
private fun ensureRparen(): Pos {
@ -3065,6 +3047,12 @@ class Compiler(
localDeclCountStack.add(0)
slotPlanStack.add(paramSlotPlan)
val parsedFnStatements = try {
val returnLabels = buildSet {
add(name)
outerLabel?.let { add(it) }
}
returnLabelStack.addLast(returnLabels)
try {
if (actualExtern)
object : Statement() {
override val pos: Pos = start
@ -3091,6 +3079,9 @@ class Compiler(
parseBlock()
}
}
} finally {
returnLabelStack.removeLast()
}
} finally {
slotPlanStack.removeLast()
}
@ -3326,6 +3317,24 @@ class Compiler(
}
}
private suspend fun parseLoopBlock(): Statement {
val startPos = cc.currentPos()
val t = cc.next()
if (t.type != Token.Type.LBRACE)
throw ScriptError(t.pos, "Expected block body start: {")
val block = parseScript()
val stmt = BlockStatement(block, emptyMap(), startPos)
val wrapped = wrapBytecode(stmt)
return wrapped.also {
val t1 = cc.next()
if (t1.type != Token.Type.RBRACE)
throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }")
val range = MiniRange(startPos, t1.pos)
lastParsedBlockRange = range
miniSink?.onBlock(MiniBlock(range))
}
}
private suspend fun parseVarDeclaration(
isMutable: Boolean,
visibility: Visibility,

View File

@ -16,6 +16,7 @@
package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Visibility
import net.sergeych.lyng.obj.Obj
@ -25,6 +26,7 @@ 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 SlotPlan(val plan: Map<String, Int>) : BytecodeConst()
data class LocalDecl(

View File

@ -20,6 +20,7 @@ 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,
@ -34,12 +35,17 @@ class BytecodeStatement private constructor(
internal fun bytecodeFunction(): CmdFunction = function
companion object {
fun wrap(statement: Statement, nameHint: String, allowLocalSlots: Boolean): Statement {
fun wrap(
statement: Statement,
nameHint: String,
allowLocalSlots: Boolean,
returnLabels: Set<String> = emptySet(),
): Statement {
if (statement is BytecodeStatement) return statement
val hasUnsupported = containsUnsupportedStatement(statement)
if (hasUnsupported) return unwrapDeep(statement)
val safeLocals = allowLocalSlots
val compiler = BytecodeCompiler(allowLocalSlots = safeLocals)
val compiler = BytecodeCompiler(allowLocalSlots = safeLocals, returnLabels = returnLabels)
val compiled = compiler.compileStatement(nameHint, statement)
val fn = compiled ?: run {
val builder = CmdBuilder()
@ -51,6 +57,7 @@ class BytecodeStatement private constructor(
nameHint,
localCount = 1,
addrCount = 0,
returnLabels = returnLabels,
localSlotNames = emptyArray(),
localSlotMutables = BooleanArray(0),
localSlotDepths = IntArray(0)
@ -69,15 +76,35 @@ class BytecodeStatement private constructor(
(target.elseBody?.let { containsUnsupportedStatement(it) } ?: false)
}
is net.sergeych.lyng.ForInStatement -> {
target.constRange == null || target.canBreak ||
val rangeSource = target.source
val rangeRef = (rangeSource as? net.sergeych.lyng.ExpressionStatement)?.ref as? RangeRef
val hasRange = target.constRange != null || rangeRef != null
!hasRange ||
containsUnsupportedStatement(target.source) ||
containsUnsupportedStatement(target.body) ||
(target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false)
}
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)
else -> true
}
}
@ -126,6 +153,39 @@ class BytecodeStatement private constructor(
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

@ -60,6 +60,7 @@ class CmdBuilder {
name: String,
localCount: Int,
addrCount: Int = 0,
returnLabels: Set<String> = emptySet(),
scopeSlotDepths: IntArray = IntArray(0),
scopeSlotIndices: IntArray = IntArray(0),
scopeSlotNames: Array<String?> = emptyArray(),
@ -100,6 +101,7 @@ class CmdBuilder {
name = name,
localCount = localCount,
addrCount = addrCount,
returnLabels = returnLabels,
scopeSlotCount = scopeSlotCount,
scopeSlotDepths = scopeSlotDepths,
scopeSlotIndices = scopeSlotIndices,
@ -120,6 +122,8 @@ class CmdBuilder {
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.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 ->
@ -205,6 +209,8 @@ class CmdBuilder {
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
Opcode.CONST_NULL -> CmdConstNull(operands[0])
Opcode.BOX_OBJ -> CmdBoxObj(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])

View File

@ -160,7 +160,9 @@ object CmdDisassembler {
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)
@ -194,6 +196,8 @@ object CmdDisassembler {
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.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 ->

View File

@ -20,6 +20,7 @@ data class CmdFunction(
val name: String,
val localCount: Int,
val addrCount: Int,
val returnLabels: Set<String>,
val scopeSlotCount: Int,
val scopeSlotDepths: IntArray,
val scopeSlotIndices: IntArray,

View File

@ -18,6 +18,9 @@ package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Arguments
import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.PerfStats
import net.sergeych.lyng.Pos
import net.sergeych.lyng.ReturnException
import net.sergeych.lyng.Scope
import net.sergeych.lyng.obj.*
@ -49,7 +52,7 @@ class CmdNop : Cmd() {
class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.getObj(src))
frame.setObj(dst, frame.slotToObj(src))
return
}
}
@ -657,28 +660,28 @@ class CmdCmpNeqRealInt(internal val a: Int, internal val b: Int, internal val ds
class CmdCmpEqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.getObj(a) == frame.getObj(b))
frame.setBool(dst, frame.slotToObj(a) == frame.slotToObj(b))
return
}
}
class CmdCmpNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.getObj(a) != frame.getObj(b))
frame.setBool(dst, frame.slotToObj(a) != frame.slotToObj(b))
return
}
}
class CmdCmpRefEqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.getObj(a) === frame.getObj(b))
frame.setBool(dst, frame.slotToObj(a) === frame.slotToObj(b))
return
}
}
class CmdCmpRefNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.getObj(a) !== frame.getObj(b))
frame.setBool(dst, frame.slotToObj(a) !== frame.slotToObj(b))
return
}
}
@ -706,63 +709,148 @@ class CmdOrBool(internal val a: Int, internal val b: Int, internal val dst: Int)
class CmdCmpLtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) < 0)
frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) < 0)
return
}
}
class CmdCmpLteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) <= 0)
frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) <= 0)
return
}
}
class CmdCmpGtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) > 0)
frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) > 0)
return
}
}
class CmdCmpGteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) >= 0)
frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) >= 0)
return
}
}
class CmdAddObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.getObj(a).plus(frame.scope, frame.getObj(b)))
val scopeSlotCount = frame.fn.scopeSlotCount
if (a >= scopeSlotCount && b >= scopeSlotCount) {
val la = a - scopeSlotCount
val lb = b - scopeSlotCount
val ta = frame.frame.getSlotTypeCode(la)
val tb = frame.frame.getSlotTypeCode(lb)
if (ta == SlotType.INT.code && tb == SlotType.INT.code) {
frame.setInt(dst, frame.frame.getInt(la) + frame.frame.getInt(lb))
return
}
if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) {
val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble()
val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble()
frame.setReal(dst, av + bv)
return
}
}
frame.setObj(dst, frame.slotToObj(a).plus(frame.scope, frame.slotToObj(b)))
return
}
}
class CmdSubObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.getObj(a).minus(frame.scope, frame.getObj(b)))
val scopeSlotCount = frame.fn.scopeSlotCount
if (a >= scopeSlotCount && b >= scopeSlotCount) {
val la = a - scopeSlotCount
val lb = b - scopeSlotCount
val ta = frame.frame.getSlotTypeCode(la)
val tb = frame.frame.getSlotTypeCode(lb)
if (ta == SlotType.INT.code && tb == SlotType.INT.code) {
frame.setInt(dst, frame.frame.getInt(la) - frame.frame.getInt(lb))
return
}
if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) {
val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble()
val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble()
frame.setReal(dst, av - bv)
return
}
}
frame.setObj(dst, frame.slotToObj(a).minus(frame.scope, frame.slotToObj(b)))
return
}
}
class CmdMulObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.getObj(a).mul(frame.scope, frame.getObj(b)))
val scopeSlotCount = frame.fn.scopeSlotCount
if (a >= scopeSlotCount && b >= scopeSlotCount) {
val la = a - scopeSlotCount
val lb = b - scopeSlotCount
val ta = frame.frame.getSlotTypeCode(la)
val tb = frame.frame.getSlotTypeCode(lb)
if (ta == SlotType.INT.code && tb == SlotType.INT.code) {
frame.setInt(dst, frame.frame.getInt(la) * frame.frame.getInt(lb))
return
}
if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) {
val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble()
val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble()
frame.setReal(dst, av * bv)
return
}
}
frame.setObj(dst, frame.slotToObj(a).mul(frame.scope, frame.slotToObj(b)))
return
}
}
class CmdDivObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.getObj(a).div(frame.scope, frame.getObj(b)))
val scopeSlotCount = frame.fn.scopeSlotCount
if (a >= scopeSlotCount && b >= scopeSlotCount) {
val la = a - scopeSlotCount
val lb = b - scopeSlotCount
val ta = frame.frame.getSlotTypeCode(la)
val tb = frame.frame.getSlotTypeCode(lb)
if (ta == SlotType.INT.code && tb == SlotType.INT.code) {
frame.setInt(dst, frame.frame.getInt(la) / frame.frame.getInt(lb))
return
}
if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) {
val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble()
val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble()
frame.setReal(dst, av / bv)
return
}
}
frame.setObj(dst, frame.slotToObj(a).div(frame.scope, frame.slotToObj(b)))
return
}
}
class CmdModObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.getObj(a).mod(frame.scope, frame.getObj(b)))
val scopeSlotCount = frame.fn.scopeSlotCount
if (a >= scopeSlotCount && b >= scopeSlotCount) {
val la = a - scopeSlotCount
val lb = b - scopeSlotCount
val ta = frame.frame.getSlotTypeCode(la)
val tb = frame.frame.getSlotTypeCode(lb)
if (ta == SlotType.INT.code && tb == SlotType.INT.code) {
frame.setInt(dst, frame.frame.getInt(la) % frame.frame.getInt(lb))
return
}
if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) {
val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble()
val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble()
frame.setReal(dst, av % bv)
return
}
}
frame.setObj(dst, frame.slotToObj(a).mod(frame.scope, frame.slotToObj(b)))
return
}
}
@ -799,6 +887,20 @@ class CmdRet(internal val slot: Int) : Cmd() {
}
}
class CmdRetLabel(internal val labelId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val labelConst = frame.fn.constants.getOrNull(labelId) as? BytecodeConst.StringVal
?: error("RET_LABEL expects StringVal at $labelId")
val value = frame.slotToObj(slot)
if (frame.fn.returnLabels.contains(labelConst.value)) {
frame.vm.result = value
} else {
throw ReturnException(value, labelConst.value)
}
return
}
}
class CmdRetVoid : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.vm.result = ObjVoid
@ -806,6 +908,15 @@ class CmdRetVoid : Cmd() {
}
}
class CmdThrow(internal val posId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val posConst = frame.fn.constants.getOrNull(posId) as? BytecodeConst.PosVal
?: error("THROW expects PosVal at $posId")
frame.throwObj(posConst.pos, frame.slotToObj(slot))
return
}
}
class CmdPushScope(internal val planId: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val planConst = frame.fn.constants[planId] as? BytecodeConst.SlotPlan
@ -963,10 +1074,29 @@ class CmdGetField(
internal val fieldId: Int,
internal val dst: Int,
) : Cmd() {
private var rKey: Long = 0L
private var rVer: Int = -1
override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot)
val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal
?: error("GET_FIELD expects StringVal at $fieldId")
if (PerfFlags.FIELD_PIC) {
val (key, ver) = when (receiver) {
is ObjInstance -> receiver.objClass.classId to receiver.objClass.layoutVersion
is ObjClass -> receiver.classId to receiver.layoutVersion
else -> 0L to -1
}
if (key != 0L) {
if (key == rKey && ver == rVer) {
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicHit++
} else {
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicMiss++
rKey = key
rVer = ver
}
}
}
val result = receiver.readField(frame.scope, nameConst.value).value
frame.storeObjResult(dst, result)
return
@ -978,10 +1108,29 @@ class CmdSetField(
internal val fieldId: Int,
internal val valueSlot: Int,
) : Cmd() {
private var wKey: Long = 0L
private var wVer: Int = -1
override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot)
val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal
?: error("SET_FIELD expects StringVal at $fieldId")
if (PerfFlags.FIELD_PIC) {
val (key, ver) = when (receiver) {
is ObjInstance -> receiver.objClass.classId to receiver.objClass.layoutVersion
is ObjClass -> receiver.classId to receiver.layoutVersion
else -> 0L to -1
}
if (key != 0L) {
if (key == wKey && ver == wVer) {
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicSetHit++
} else {
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicSetMiss++
wKey = key
wVer = ver
}
}
}
receiver.writeField(frame.scope, nameConst.value, frame.slotToObj(valueSlot))
return
}
@ -1266,6 +1415,30 @@ class CmdFrame(
}
}
suspend fun throwObj(pos: Pos, value: Obj) {
var errorObject = value
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)
}
}
fun syncFrameToScope() {
val names = fn.localSlotNames
if (names.isEmpty()) return

View File

@ -107,6 +107,7 @@ enum class Opcode(val code: Int) {
JMP_IF_FALSE(0x82),
RET(0x83),
RET_VOID(0x84),
RET_LABEL(0xBA),
PUSH_SCOPE(0x85),
POP_SCOPE(0x86),
PUSH_SLOT_PLAN(0x87),
@ -133,6 +134,7 @@ enum class Opcode(val code: Int) {
STORE_REAL_ADDR(0xB7),
LOAD_BOOL_ADDR(0xB8),
STORE_BOOL_ADDR(0xB9),
THROW(0xBB),
;
companion object {

View File

@ -1336,6 +1336,9 @@ 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

View File

@ -22,8 +22,10 @@ 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
@ -297,6 +299,146 @@ class ForInStatement(
}
}
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

@ -5,6 +5,7 @@ Changes
- 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