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 isTransientFlag: Boolean = false
private var lastLabel: String? = null private var lastLabel: String? = null
private val useBytecodeStatements: Boolean = true private val useBytecodeStatements: Boolean = true
private val returnLabelStack = ArrayDeque<Set<String>>()
private fun wrapBytecode(stmt: Statement): Statement { private fun wrapBytecode(stmt: Statement): Statement {
if (!useBytecodeStatements) return stmt if (!useBytecodeStatements) return stmt
val allowLocals = codeContexts.lastOrNull() is CodeContext.Function 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 { private fun wrapFunctionBytecode(stmt: Statement, name: String): Statement {
if (!useBytecodeStatements) return stmt 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 { private fun containsUnsupportedForBytecode(stmt: Statement): Boolean {
@ -392,13 +405,27 @@ class Compiler(
(target.elseBody?.let { containsUnsupportedForBytecode(it) } ?: false) (target.elseBody?.let { containsUnsupportedForBytecode(it) } ?: false)
} }
is ForInStatement -> { is ForInStatement -> {
target.constRange == null || target.canBreak || target.constRange == null ||
containsUnsupportedForBytecode(target.source) || containsUnsupportedForBytecode(target.source) ||
containsUnsupportedForBytecode(target.body) || containsUnsupportedForBytecode(target.body) ||
(target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false) (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 BlockStatement -> target.statements().any { containsUnsupportedForBytecode(it) }
is VarDeclStatement -> target.initializer?.let { containsUnsupportedForBytecode(it) } ?: false 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 else -> true
} }
} }
@ -430,6 +457,59 @@ class Compiler(
val elseBody = stmt.elseBody?.let { unwrapBytecodeDeep(it) } val elseBody = stmt.elseBody?.let { unwrapBytecodeDeep(it) }
IfStatement(cond, ifBody, elseBody, stmt.pos) 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 else -> stmt
} }
} }
@ -885,8 +965,14 @@ class Compiler(
slotPlanStack.add(paramSlotPlan) slotPlanStack.add(paramSlotPlan)
val parsedBody = try { val parsedBody = try {
inCodeContext(CodeContext.Function("<lambda>")) { inCodeContext(CodeContext.Function("<lambda>")) {
withLocalNames(slotParamNames.toSet()) { val returnLabels = label?.let { setOf(it) } ?: emptySet()
parseBlock(skipLeadingBrace = true) returnLabelStack.addLast(returnLabels)
try {
withLocalNames(slotParamNames.toSet()) {
parseBlock(skipLeadingBrace = true)
}
} finally {
returnLabelStack.removeLast()
} }
} }
} finally { } finally {
@ -2031,37 +2117,7 @@ class Compiler(
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 return wrapBytecode(ThrowStatement(throwStatement, start))
// 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)
} }
private data class CatchBlockData( private data class CatchBlockData(
@ -2692,7 +2748,11 @@ class Compiler(
slotPlanStack.add(loopSlotPlan) slotPlanStack.add(loopSlotPlan)
val (canBreak, parsedBody) = try { val (canBreak, parsedBody) = try {
cc.parseLoop { cc.parseLoop {
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement") if (cc.current().type == Token.Type.LBRACE) {
parseLoopBlock()
} else {
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement")
}
} }
} finally { } finally {
slotPlanStack.removeLast() slotPlanStack.removeLast()
@ -2722,36 +2782,8 @@ class Compiler(
cc.previous() cc.previous()
null null
} }
val loopPlanSnapshot = slotPlanIndices(loopSlotPlan)
return object : Statement() { return DoWhileStatement(body, condition, elseStatement, label, loopPlanSnapshot, body.pos)
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
}
}
} }
private suspend fun parseWhileStatement(): Statement { private suspend fun parseWhileStatement(): Statement {
@ -2765,7 +2797,7 @@ class Compiler(
slotPlanStack.add(loopSlotPlan) slotPlanStack.add(loopSlotPlan)
val (canBreak, parsedBody) = try { val (canBreak, parsedBody) = try {
cc.parseLoop { 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") else parseStatement() ?: throw ScriptError(start, "Bad while statement: expected statement")
} }
} finally { } finally {
@ -2782,34 +2814,8 @@ class Compiler(
cc.previous() cc.previous()
null null
} }
return object : Statement() { val loopPlanSnapshot = slotPlanIndices(loopSlotPlan)
override val pos: Pos = body.pos return WhileStatement(condition, body, elseStatement, label, canBreak, loopPlanSnapshot, 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
}
}
} }
private suspend fun parseBreakStatement(start: Pos): Statement { private suspend fun parseBreakStatement(start: Pos): Statement {
@ -2843,17 +2849,7 @@ class Compiler(
cc.addBreak() cc.addBreak()
return object : Statement() { return BreakStatement(label, resultExpr, start)
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
)
}
}
} }
private fun parseContinueStatement(start: Pos): Statement { private fun parseContinueStatement(start: Pos): Statement {
@ -2870,15 +2866,7 @@ class Compiler(
} }
cc.addBreak() cc.addBreak()
return object : Statement() { return ContinueStatement(label, start)
override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
throw LoopBreakContinueException(
doContinue = true,
label = label,
)
}
}
} }
private suspend fun parseReturnStatement(start: Pos): Statement { private suspend fun parseReturnStatement(start: Pos): Statement {
@ -2907,13 +2895,7 @@ class Compiler(
parseExpression() parseExpression()
} else null } else null
return object : Statement() { return ReturnStatement(label, resultExpr, start)
override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
val returnValue = resultExpr?.execute(scope) ?: ObjVoid
throw ReturnException(returnValue, label)
}
}
} }
private fun ensureRparen(): Pos { private fun ensureRparen(): Pos {
@ -3065,32 +3047,41 @@ class Compiler(
localDeclCountStack.add(0) localDeclCountStack.add(0)
slotPlanStack.add(paramSlotPlan) slotPlanStack.add(paramSlotPlan)
val parsedFnStatements = try { val parsedFnStatements = try {
if (actualExtern) val returnLabels = buildSet {
object : Statement() { add(name)
override val pos: Pos = start outerLabel?.let { add(it) }
override suspend fun execute(scope: Scope): Obj { }
scope.raiseError("extern function not provided: $name") returnLabelStack.addLast(returnLabels)
} try {
} if (actualExtern)
else if (isAbstract || isDelegated) { object : Statement() {
null override val pos: Pos = start
} else override suspend fun execute(scope: Scope): Obj {
withLocalNames(paramNames) { scope.raiseError("extern function not provided: $name")
val next = cc.peekNextNonWhitespace()
if (next.type == Token.Type.ASSIGN) {
cc.nextNonWhitespace() // consume '='
if (cc.peekNextNonWhitespace().value == "return")
throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function")
val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression")
// Shorthand function returns the expression value
object : Statement() {
override val pos: Pos = expr.pos
override suspend fun execute(scope: Scope): Obj = expr.execute(scope)
} }
} else {
parseBlock()
} }
} else if (isAbstract || isDelegated) {
null
} else
withLocalNames(paramNames) {
val next = cc.peekNextNonWhitespace()
if (next.type == Token.Type.ASSIGN) {
cc.nextNonWhitespace() // consume '='
if (cc.peekNextNonWhitespace().value == "return")
throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function")
val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression")
// Shorthand function returns the expression value
object : Statement() {
override val pos: Pos = expr.pos
override suspend fun execute(scope: Scope): Obj = expr.execute(scope)
}
} else {
parseBlock()
}
}
} finally {
returnLabelStack.removeLast()
}
} finally { } finally {
slotPlanStack.removeLast() 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( private suspend fun parseVarDeclaration(
isMutable: Boolean, isMutable: Boolean,
visibility: Visibility, visibility: Visibility,

View File

@ -16,6 +16,7 @@
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Visibility import net.sergeych.lyng.Visibility
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
@ -25,6 +26,7 @@ sealed class BytecodeConst {
data class IntVal(val value: Long) : BytecodeConst() data class IntVal(val value: Long) : BytecodeConst()
data class RealVal(val value: Double) : BytecodeConst() data class RealVal(val value: Double) : BytecodeConst()
data class StringVal(val value: String) : BytecodeConst() data class StringVal(val value: String) : BytecodeConst()
data class PosVal(val pos: Pos) : BytecodeConst()
data class ObjRef(val value: Obj) : BytecodeConst() data class ObjRef(val value: Obj) : BytecodeConst()
data class SlotPlan(val plan: Map<String, Int>) : BytecodeConst() data class SlotPlan(val plan: Map<String, Int>) : BytecodeConst()
data class LocalDecl( data class LocalDecl(

View File

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

View File

@ -60,6 +60,7 @@ class CmdBuilder {
name: String, name: String,
localCount: Int, localCount: Int,
addrCount: Int = 0, addrCount: Int = 0,
returnLabels: Set<String> = emptySet(),
scopeSlotDepths: IntArray = IntArray(0), scopeSlotDepths: IntArray = IntArray(0),
scopeSlotIndices: IntArray = IntArray(0), scopeSlotIndices: IntArray = IntArray(0),
scopeSlotNames: Array<String?> = emptyArray(), scopeSlotNames: Array<String?> = emptyArray(),
@ -100,6 +101,7 @@ class CmdBuilder {
name = name, name = name,
localCount = localCount, localCount = localCount,
addrCount = addrCount, addrCount = addrCount,
returnLabels = returnLabels,
scopeSlotCount = scopeSlotCount, scopeSlotCount = scopeSlotCount,
scopeSlotDepths = scopeSlotDepths, scopeSlotDepths = scopeSlotDepths,
scopeSlotIndices = scopeSlotIndices, 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.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 -> Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.RET_LABEL, Opcode.THROW ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.RESOLVE_SCOPE_SLOT -> Opcode.RESOLVE_SCOPE_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ADDR) listOf(OperandKind.SLOT, OperandKind.ADDR)
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_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_BOOL -> CmdConstBool(operands[0], operands[1])
Opcode.CONST_NULL -> CmdConstNull(operands[0]) Opcode.CONST_NULL -> CmdConstNull(operands[0])
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1]) 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.RESOLVE_SCOPE_SLOT -> CmdResolveScopeSlot(operands[0], operands[1])
Opcode.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1]) Opcode.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1])
Opcode.STORE_OBJ_ADDR -> CmdStoreObjAddr(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 CmdJmpIfTrue -> Opcode.JMP_IF_TRUE to intArrayOf(cmd.cond, cmd.target)
is CmdJmpIfFalse -> Opcode.JMP_IF_FALSE 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 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 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 CmdPushScope -> Opcode.PUSH_SCOPE to intArrayOf(cmd.planId)
is CmdPopScope -> Opcode.POP_SCOPE to intArrayOf() is CmdPopScope -> Opcode.POP_SCOPE to intArrayOf()
is CmdPushSlotPlan -> Opcode.PUSH_SLOT_PLAN to intArrayOf(cmd.planId) 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.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 -> Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.RET_LABEL, Opcode.THROW ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.RESOLVE_SCOPE_SLOT -> Opcode.RESOLVE_SCOPE_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ADDR) listOf(OperandKind.SLOT, OperandKind.ADDR)
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_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 name: String,
val localCount: Int, val localCount: Int,
val addrCount: Int, val addrCount: Int,
val returnLabels: Set<String>,
val scopeSlotCount: Int, val scopeSlotCount: Int,
val scopeSlotDepths: IntArray, val scopeSlotDepths: IntArray,
val scopeSlotIndices: IntArray, val scopeSlotIndices: IntArray,

View File

@ -18,6 +18,9 @@ package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Arguments import net.sergeych.lyng.Arguments
import net.sergeych.lyng.PerfFlags 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.Scope
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
@ -49,7 +52,7 @@ class CmdNop : Cmd() {
class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() { class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.getObj(src)) frame.setObj(dst, frame.slotToObj(src))
return 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() { class CmdCmpEqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdCmpNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdCmpRefEqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpRefEqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdCmpRefNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpRefNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 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() { class CmdCmpLtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdCmpLteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpLteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdCmpGtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpGtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdCmpGteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpGteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdAddObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdAddObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdSubObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdSubObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdMulObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdMulObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdDivObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdDivObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 return
} }
} }
class CmdModObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdModObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { 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 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() { class CmdRetVoid : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.vm.result = ObjVoid 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() { class CmdPushScope(internal val planId: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val planConst = frame.fn.constants[planId] as? BytecodeConst.SlotPlan val planConst = frame.fn.constants[planId] as? BytecodeConst.SlotPlan
@ -963,10 +1074,29 @@ class CmdGetField(
internal val fieldId: Int, internal val fieldId: Int,
internal val dst: Int, internal val dst: Int,
) : Cmd() { ) : Cmd() {
private var rKey: Long = 0L
private var rVer: Int = -1
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot) val receiver = frame.slotToObj(recvSlot)
val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal
?: error("GET_FIELD expects StringVal at $fieldId") ?: 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 val result = receiver.readField(frame.scope, nameConst.value).value
frame.storeObjResult(dst, result) frame.storeObjResult(dst, result)
return return
@ -978,10 +1108,29 @@ class CmdSetField(
internal val fieldId: Int, internal val fieldId: Int,
internal val valueSlot: Int, internal val valueSlot: Int,
) : Cmd() { ) : Cmd() {
private var wKey: Long = 0L
private var wVer: Int = -1
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot) val receiver = frame.slotToObj(recvSlot)
val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal
?: error("SET_FIELD expects StringVal at $fieldId") ?: 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)) receiver.writeField(frame.scope, nameConst.value, frame.slotToObj(valueSlot))
return 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() { fun syncFrameToScope() {
val names = fn.localSlotNames val names = fn.localSlotNames
if (names.isEmpty()) return if (names.isEmpty()) return

View File

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

View File

@ -1336,6 +1336,9 @@ class IndexRef(
private val index: ObjRef, private val index: ObjRef,
private val isOptional: Boolean, private val isOptional: Boolean,
) : ObjRef { ) : 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 // 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 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 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.ObjInt
import net.sergeych.lyng.obj.ObjIterable import net.sergeych.lyng.obj.ObjIterable
import net.sergeych.lyng.obj.ObjNull import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjException
import net.sergeych.lyng.obj.ObjRange import net.sergeych.lyng.obj.ObjRange
import net.sergeych.lyng.obj.ObjRecord import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.obj.ObjVoid import net.sergeych.lyng.obj.ObjVoid
import net.sergeych.lyng.obj.toBool import net.sergeych.lyng.obj.toBool
import net.sergeych.lyng.obj.toInt 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( class ToBoolStatement(
val expr: Statement, val expr: Statement,
override val pos: Pos, 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 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. - 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. - 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 Tests
- ./gradlew :lynglib:jvmTest - ./gradlew :lynglib:jvmTest