Step 3: bytecode try/catch/finally

This commit is contained in:
Sergey Chernov 2026-02-09 01:18:20 +03:00
parent 7ea67d4816
commit f5ced02505
10 changed files with 390 additions and 75 deletions

View File

@ -9,10 +9,10 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
- [x] Enable bytecode for `ImplicitThisMethodCallRef`, `QualifiedThisMethodSlotCallRef`, `QualifiedThisFieldSlotRef`. - [x] Enable bytecode for `ImplicitThisMethodCallRef`, `QualifiedThisMethodSlotCallRef`, `QualifiedThisFieldSlotRef`.
- [x] Keep unsupported cases blocked: `ClassScopeMemberRef`, dynamic receivers, delegated members. - [x] Keep unsupported cases blocked: `ClassScopeMemberRef`, dynamic receivers, delegated members.
- [x] JVM tests must be green before commit. - [x] JVM tests must be green before commit.
- [ ] Step 3: Bytecode support for `try/catch/finally`. - [x] Step 3: Bytecode support for `try/catch/finally`.
- [ ] Implement bytecode emission for try/catch and finally blocks. - [x] Implement bytecode emission for try/catch and finally blocks.
- [ ] Preserve existing error/stack semantics. - [x] Preserve existing error/stack semantics.
- [ ] JVM tests must be green before commit. - [x] JVM tests must be green before commit.
## Notes ## Notes

View File

@ -21,6 +21,7 @@ import net.sergeych.lyng.obj.Obj
class BlockStatement( class BlockStatement(
val block: Script, val block: Script,
val slotPlan: Map<String, Int>, val slotPlan: Map<String, Int>,
val scopeId: Int,
val captureSlots: List<CaptureSlot> = emptyList(), val captureSlots: List<CaptureSlot> = emptyList(),
private val startPos: Pos, private val startPos: Pos,
) : Statement() { ) : Statement() {

View File

@ -1897,7 +1897,11 @@ class Compiler(
is ContinueStatement -> false is ContinueStatement -> false
is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false
is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr) is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr)
is TryStatement -> true is TryStatement -> {
containsUnsupportedForBytecode(target.body) ||
target.catches.any { containsUnsupportedForBytecode(it.block) } ||
(target.finallyClause?.let { containsUnsupportedForBytecode(it) } ?: false)
}
is WhenStatement -> { is WhenStatement -> {
containsUnsupportedForBytecode(target.value) || containsUnsupportedForBytecode(target.value) ||
target.cases.any { case -> target.cases.any { case ->
@ -2099,7 +2103,7 @@ class Compiler(
is BlockStatement -> { is BlockStatement -> {
val unwrapped = stmt.statements().map { unwrapBytecodeDeep(it) } val unwrapped = stmt.statements().map { unwrapBytecodeDeep(it) }
val script = Script(stmt.block.pos, unwrapped) val script = Script(stmt.block.pos, unwrapped)
BlockStatement(script, stmt.slotPlan, stmt.captureSlots, stmt.pos) BlockStatement(script, stmt.slotPlan, stmt.scopeId, stmt.captureSlots, stmt.pos)
} }
is InlineBlockStatement -> { is InlineBlockStatement -> {
val unwrapped = stmt.statements().map { unwrapBytecodeDeep(it) } val unwrapped = stmt.statements().map { unwrapBytecodeDeep(it) }
@ -5388,19 +5392,12 @@ class Compiler(
for ((name, idx) in basePlan) { for ((name, idx) in basePlan) {
newPlan[name] = idx + 1 newPlan[name] = idx + 1
} }
return BlockStatement(stmt.block, newPlan, stmt.captureSlots, stmt.pos) return BlockStatement(stmt.block, newPlan, stmt.scopeId, stmt.captureSlots, stmt.pos)
} }
fun stripCatchCaptures(block: Statement): Statement { fun stripCatchCaptures(block: Statement): Statement {
val stmt = block as? BlockStatement ?: return block val stmt = block as? BlockStatement ?: return block
if (stmt.captureSlots.isEmpty()) return stmt if (stmt.captureSlots.isEmpty()) return stmt
return BlockStatement(stmt.block, stmt.slotPlan, emptyList(), stmt.pos) return BlockStatement(stmt.block, stmt.slotPlan, stmt.scopeId, emptyList(), stmt.pos)
}
fun resolveExceptionClass(scope: Scope, name: String): ObjClass {
val rec = scope[name]
val cls = rec?.value as? ObjClass
if (cls != null) return cls
if (name == "Exception") return ObjException.Root
scope.raiseSymbolNotFound("error class does not exist or is not a class: $name")
} }
fun resolveCatchVarClass(names: List<String>): ObjClass? { fun resolveCatchVarClass(names: List<String>): ObjClass? {
if (names.size == 1) { if (names.size == 1) {
@ -5507,55 +5504,15 @@ class Compiler(
throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both") throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both")
val stmtPos = body.pos val stmtPos = body.pos
val tryStatement = object : Statement() { val tryCatches = catches.map { cdata ->
override val pos: Pos = stmtPos TryStatement.CatchBlock(
override suspend fun execute(scope: Scope): Obj { catchVarName = cdata.catchVar.value,
var result: Obj = ObjVoid catchVarPos = cdata.catchVar.pos,
try { classNames = cdata.classNames,
// body is a parsed block, it already has separate context block = cdata.block
result = body.execute(scope) )
} catch (e: ReturnException) {
throw e
} catch (e: LoopBreakContinueException) {
throw e
} catch (e: Exception) {
// convert to appropriate exception
val caughtObj = when (e) {
is ExecutionError -> e.errorObject
else -> ObjUnknownException(scope, e.message ?: e.toString())
}
// let's see if we should catch it:
var isCaught = false
for (cdata in catches) {
var match: Obj? = null
for (exceptionClassName in cdata.classNames) {
val exObj = resolveExceptionClass(scope, exceptionClassName)
if (caughtObj.isInstanceOf(exObj)) {
match = caughtObj
break
}
}
if (match != null) {
val catchContext = scope.createChildScope(pos = cdata.catchVar.pos).apply {
skipScopeCreation = true
}
catchContext.addItem(cdata.catchVar.value, false, caughtObj)
result = cdata.block.execute(catchContext)
isCaught = true
break
}
}
// rethrow if not caught this exception
if (!isCaught)
throw e
} finally {
// finally clause does not alter result!
finallyClause?.execute(scope)
}
return result
}
} }
return TryStatement(tryStatement, stmtPos) return TryStatement(body, tryCatches, finallyClause, stmtPos)
} }
private fun parseEnumDeclaration(isExtern: Boolean = false): Statement { private fun parseEnumDeclaration(isExtern: Boolean = false): Statement {
@ -7164,7 +7121,7 @@ class Compiler(
} }
val planSnapshot = slotPlanIndices(blockSlotPlan) val planSnapshot = slotPlanIndices(blockSlotPlan)
val block = Script(startPos, listOf(expr)) val block = Script(startPos, listOf(expr))
val stmt = BlockStatement(block, planSnapshot, capturePlan.captures.toList(), startPos) val stmt = BlockStatement(block, planSnapshot, blockSlotPlan.id, capturePlan.captures.toList(), startPos)
resolutionSink?.exitScope(cc.currentPos()) resolutionSink?.exitScope(cc.currentPos())
return stmt return stmt
} }
@ -7480,7 +7437,7 @@ class Compiler(
slotPlanStack.removeLast() slotPlanStack.removeLast()
} }
val planSnapshot = slotPlanIndices(blockSlotPlan) val planSnapshot = slotPlanIndices(blockSlotPlan)
val stmt = BlockStatement(block, planSnapshot, capturePlan.captures.toList(), startPos) val stmt = BlockStatement(block, planSnapshot, blockSlotPlan.id, capturePlan.captures.toList(), startPos)
val wrapped = wrapBytecode(stmt) val wrapped = wrapBytecode(stmt)
return wrapped.also { return wrapped.also {
val t1 = cc.next() val t1 = cc.next()
@ -7511,7 +7468,7 @@ class Compiler(
slotPlanStack.removeLast() slotPlanStack.removeLast()
} }
val planSnapshot = slotPlanIndices(blockSlotPlan) val planSnapshot = slotPlanIndices(blockSlotPlan)
val stmt = BlockStatement(block, planSnapshot, capturePlan.captures.toList(), startPos) val stmt = BlockStatement(block, planSnapshot, blockSlotPlan.id, capturePlan.captures.toList(), startPos)
val wrapped = wrapBytecode(stmt) val wrapped = wrapBytecode(stmt)
return wrapped.also { return wrapped.also {
val t1 = cc.next() val t1 = cc.next()
@ -7541,7 +7498,7 @@ class Compiler(
slotPlanStack.removeLast() slotPlanStack.removeLast()
} }
val planSnapshot = slotPlanIndices(blockSlotPlan) val planSnapshot = slotPlanIndices(blockSlotPlan)
val stmt = BlockStatement(block, planSnapshot, capturePlan.captures.toList(), startPos) val stmt = BlockStatement(block, planSnapshot, blockSlotPlan.id, capturePlan.captures.toList(), startPos)
val wrapped = wrapBytecode(stmt) val wrapped = wrapBytecode(stmt)
val t1 = cc.next() val t1 = cc.next()
if (t1.type != Token.Type.RBRACE) if (t1.type != Token.Type.RBRACE)

View File

@ -17,14 +17,71 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjException
import net.sergeych.lyng.obj.ObjUnknownException
import net.sergeych.lyng.obj.ObjVoid
class TryStatement( class TryStatement(
private val delegate: Statement, val body: Statement,
val catches: List<CatchBlock>,
val finallyClause: Statement?,
private val startPos: Pos, private val startPos: Pos,
) : Statement() { ) : Statement() {
override val pos: Pos = startPos override val pos: Pos = startPos
data class CatchBlock(
val catchVarName: String,
val catchVarPos: Pos,
val classNames: List<String>,
val block: Statement
)
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope) var result: Obj = ObjVoid
try {
result = body.execute(scope)
} catch (e: ReturnException) {
throw e
} catch (e: LoopBreakContinueException) {
throw e
} catch (e: Exception) {
val caughtObj = when (e) {
is ExecutionError -> e.errorObject
else -> ObjUnknownException(scope, e.message ?: e.toString())
}
var isCaught = false
for (cdata in catches) {
var match: Obj? = null
for (exceptionClassName in cdata.classNames) {
val exObj = resolveExceptionClass(scope, exceptionClassName)
if (caughtObj.isInstanceOf(exObj)) {
match = caughtObj
break
}
}
if (match != null) {
val catchContext = scope.createChildScope(pos = cdata.catchVarPos).apply {
skipScopeCreation = true
}
catchContext.addItem(cdata.catchVarName, false, caughtObj)
result = cdata.block.execute(catchContext)
isCaught = true
break
}
}
if (!isCaught) throw e
} finally {
finallyClause?.execute(scope)
}
return result
}
private fun resolveExceptionClass(scope: Scope, name: String): ObjClass {
val rec = scope[name]
val cls = rec?.value as? ObjClass
if (cls != null) return cls
if (name == "Exception") return ObjException.Root
scope.raiseSymbolNotFound("error class does not exist or is not a class: $name")
} }
} }

View File

@ -133,7 +133,7 @@ class BytecodeCompiler(
is net.sergeych.lyng.ThrowStatement -> compileThrowStatement(name, stmt) is net.sergeych.lyng.ThrowStatement -> compileThrowStatement(name, stmt)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> compileExtensionPropertyDecl(name, stmt) is net.sergeych.lyng.ExtensionPropertyDeclStatement -> compileExtensionPropertyDecl(name, stmt)
is net.sergeych.lyng.TryStatement -> { is net.sergeych.lyng.TryStatement -> {
val value = emitStatementEval(stmt) val value = emitTry(stmt, true) ?: return null
builder.emit(Opcode.RET, value.slot) builder.emit(Opcode.RET, value.slot)
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
builder.build( builder.build(
@ -3314,12 +3314,13 @@ class BytecodeCompiler(
is net.sergeych.lyng.ClassDeclStatement -> emitStatementEval(target) is net.sergeych.lyng.ClassDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementEval(target) is net.sergeych.lyng.FunctionDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.EnumDeclStatement -> emitStatementEval(target) is net.sergeych.lyng.EnumDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.TryStatement -> emitStatementEval(target) is net.sergeych.lyng.TryStatement -> emitTry(target, true)
is net.sergeych.lyng.WhenStatement -> compileWhen(target, true) is net.sergeych.lyng.WhenStatement -> compileWhen(target, true)
is net.sergeych.lyng.BreakStatement -> compileBreak(target) is net.sergeych.lyng.BreakStatement -> compileBreak(target)
is net.sergeych.lyng.ContinueStatement -> compileContinue(target) is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
is net.sergeych.lyng.ReturnStatement -> compileReturn(target) is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
is net.sergeych.lyng.ThrowStatement -> compileThrow(target) is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
is net.sergeych.lyng.TryStatement -> emitTry(target, false)
else -> { else -> {
emitFallbackStatement(target) emitFallbackStatement(target)
} }
@ -3362,6 +3363,7 @@ class BytecodeCompiler(
is net.sergeych.lyng.ContinueStatement -> compileContinue(target) is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
is net.sergeych.lyng.ReturnStatement -> compileReturn(target) is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
is net.sergeych.lyng.ThrowStatement -> compileThrow(target) is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
is net.sergeych.lyng.TryStatement -> emitTry(target, false)
is net.sergeych.lyng.WhenStatement -> compileWhen(target, false) is net.sergeych.lyng.WhenStatement -> compileWhen(target, false)
else -> { else -> {
emitFallbackStatement(target) emitFallbackStatement(target)
@ -3423,6 +3425,167 @@ class BytecodeCompiler(
return result return result
} }
private fun emitTry(stmt: net.sergeych.lyng.TryStatement, needResult: Boolean): CompiledValue? {
val resultSlot = allocSlot()
updateSlotType(resultSlot, SlotType.OBJ)
val exceptionSlot = allocSlot()
updateSlotType(exceptionSlot, SlotType.OBJ)
val catchLabel = if (stmt.catches.isNotEmpty()) builder.label() else null
val finallyLabel = if (stmt.finallyClause != null) builder.label() else null
val endLabel = builder.label()
val catchOperand = catchLabel?.let { CmdBuilder.Operand.LabelRef(it) }
?: CmdBuilder.Operand.IntVal(-1)
val finallyOperand = finallyLabel?.let { CmdBuilder.Operand.LabelRef(it) }
?: CmdBuilder.Operand.IntVal(-1)
builder.emit(
Opcode.PUSH_TRY,
listOf(
CmdBuilder.Operand.IntVal(exceptionSlot),
catchOperand,
finallyOperand
)
)
val bodyValue = compileStatementValueOrFallback(stmt.body, needResult) ?: return null
if (needResult) {
emitMove(bodyValue, resultSlot)
}
if (finallyLabel != null) {
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(finallyLabel)))
} else {
builder.emit(Opcode.POP_TRY)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
}
if (catchLabel != null) {
builder.mark(catchLabel)
val catchBlockLabels = stmt.catches.map { builder.label() }
val noMatchLabel = builder.label()
for ((index, cdata) in stmt.catches.withIndex()) {
val handlerLabel = catchBlockLabels[index]
for (className in cdata.classNames) {
val classValue = compileCatchClassSlot(className) ?: return null
val checkSlot = allocSlot()
builder.emit(Opcode.CHECK_IS, exceptionSlot, classValue.slot, checkSlot)
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(checkSlot), CmdBuilder.Operand.LabelRef(handlerLabel))
)
}
}
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(noMatchLabel)))
for ((index, cdata) in stmt.catches.withIndex()) {
val handlerLabel = catchBlockLabels[index]
builder.mark(handlerLabel)
builder.emit(Opcode.CLEAR_PENDING_THROWABLE)
val catchValue = emitCatchBlock(cdata.block, cdata.catchVarName, exceptionSlot, needResult)
?: return null
if (needResult) {
emitMove(catchValue, resultSlot)
}
if (finallyLabel != null) {
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(finallyLabel)))
} else {
builder.emit(Opcode.POP_TRY)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
}
}
builder.mark(noMatchLabel)
if (finallyLabel != null) {
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(finallyLabel)))
} else {
builder.emit(Opcode.POP_TRY)
builder.emit(Opcode.RETHROW_PENDING)
}
}
if (finallyLabel != null) {
builder.mark(finallyLabel)
builder.emit(Opcode.POP_TRY)
stmt.finallyClause?.let { finallyClause ->
compileStatementValueOrFallback(finallyClause, false) ?: return null
}
builder.emit(Opcode.RETHROW_PENDING)
}
builder.mark(endLabel)
if (needResult) return CompiledValue(resultSlot, SlotType.OBJ)
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
val voidSlot = allocSlot()
builder.emit(Opcode.CONST_OBJ, voidId, voidSlot)
return CompiledValue(voidSlot, SlotType.OBJ)
}
private fun emitCatchBlock(
block: Statement,
catchVarName: String,
exceptionSlot: Int,
needResult: Boolean
): CompiledValue? {
val stmt = block as? BlockStatement
if (stmt == null) {
val declId = builder.addConst(BytecodeConst.LocalDecl(catchVarName, false, Visibility.Public, false))
builder.emit(Opcode.DECL_LOCAL, declId, exceptionSlot)
return compileStatementValueOrFallback(block, needResult)
}
val captureNames = if (stmt.captureSlots.isEmpty()) emptyList() else stmt.captureSlots.map { it.name }
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan, captureNames))
builder.emit(Opcode.PUSH_SCOPE, planId)
resetAddrCache()
val declId = builder.addConst(BytecodeConst.LocalDecl(catchVarName, false, Visibility.Public, false))
builder.emit(Opcode.DECL_LOCAL, declId, exceptionSlot)
val catchSlotIndex = stmt.slotPlan[catchVarName]
if (catchSlotIndex != null) {
val key = ScopeSlotKey(stmt.scopeId, catchSlotIndex)
val localIndex = localSlotIndexByKey[key]
if (localIndex != null) {
val localSlot = scopeSlotCount + localIndex
if (localSlot != exceptionSlot) {
emitMove(CompiledValue(exceptionSlot, SlotType.OBJ), localSlot)
}
updateSlotType(localSlot, SlotType.OBJ)
}
}
val statements = stmt.statements()
var lastValue: CompiledValue? = null
for ((index, statement) in statements.withIndex()) {
val isLast = index == statements.lastIndex
val wantResult = needResult && isLast
val value = compileStatementValueOrFallback(statement, wantResult) ?: return null
if (wantResult) {
lastValue = value
}
}
val result = if (needResult) {
lastValue ?: run {
val slot = allocSlot()
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
builder.emit(Opcode.CONST_OBJ, voidId, slot)
CompiledValue(slot, SlotType.OBJ)
}
} else {
lastValue ?: run {
val slot = allocSlot()
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
builder.emit(Opcode.CONST_OBJ, voidId, slot)
CompiledValue(slot, SlotType.OBJ)
}
}
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
return result
}
private fun compileCatchClassSlot(name: String): CompiledValue? {
val ref = LocalVarRef(name, Pos.builtIn)
val compiled = compileRef(ref)
if (compiled != null) {
return ensureObjSlot(compiled)
}
val cls = nameObjClass[name] ?: resolveTypeNameClass(name) ?: return null
val id = builder.addConst(BytecodeConst.ObjRef(cls))
val slot = allocSlot()
builder.emit(Opcode.CONST_OBJ, id, slot)
updateSlotType(slot, SlotType.OBJ)
return CompiledValue(slot, SlotType.OBJ)
}
private fun emitInlineStatements(statements: List<Statement>, needResult: Boolean): CompiledValue? { private fun emitInlineStatements(statements: List<Statement>, needResult: Boolean): CompiledValue? {
var lastValue: CompiledValue? = null var lastValue: CompiledValue? = null
for ((index, statement) in statements.withIndex()) { for ((index, statement) in statements.withIndex()) {
@ -5181,6 +5344,13 @@ class BytecodeCompiler(
} }
stmt.elseCase?.let { collectScopeSlots(it) } stmt.elseCase?.let { collectScopeSlots(it) }
} }
is net.sergeych.lyng.TryStatement -> {
collectScopeSlots(stmt.body)
for (catchBlock in stmt.catches) {
collectScopeSlots(catchBlock.block)
}
stmt.finallyClause?.let { collectScopeSlots(it) }
}
is net.sergeych.lyng.BreakStatement -> { is net.sergeych.lyng.BreakStatement -> {
stmt.resultExpr?.let { collectScopeSlots(it) } stmt.resultExpr?.let { collectScopeSlots(it) }
} }
@ -5362,6 +5532,13 @@ class BytecodeCompiler(
is net.sergeych.lyng.ThrowStatement -> { is net.sergeych.lyng.ThrowStatement -> {
collectLoopVarNames(stmt.throwExpr) collectLoopVarNames(stmt.throwExpr)
} }
is net.sergeych.lyng.TryStatement -> {
collectLoopVarNames(stmt.body)
for (catchBlock in stmt.catches) {
collectLoopVarNames(catchBlock.block)
}
stmt.finallyClause?.let { collectLoopVarNames(it) }
}
else -> {} else -> {}
} }
} }

View File

@ -126,7 +126,11 @@ class BytecodeStatement private constructor(
is net.sergeych.lyng.ClassDeclStatement -> false is net.sergeych.lyng.ClassDeclStatement -> false
is net.sergeych.lyng.FunctionDeclStatement -> false is net.sergeych.lyng.FunctionDeclStatement -> false
is net.sergeych.lyng.EnumDeclStatement -> false is net.sergeych.lyng.EnumDeclStatement -> false
is net.sergeych.lyng.TryStatement -> true is net.sergeych.lyng.TryStatement -> {
containsUnsupportedStatement(target.body) ||
target.catches.any { containsUnsupportedStatement(it.block) } ||
(target.finallyClause?.let { containsUnsupportedStatement(it) } ?: false)
}
is net.sergeych.lyng.WhenStatement -> { is net.sergeych.lyng.WhenStatement -> {
containsUnsupportedStatement(target.value) || containsUnsupportedStatement(target.value) ||
target.cases.any { case -> target.cases.any { case ->
@ -147,6 +151,7 @@ class BytecodeStatement private constructor(
net.sergeych.lyng.BlockStatement( net.sergeych.lyng.BlockStatement(
net.sergeych.lyng.Script(stmt.pos, unwrapped), net.sergeych.lyng.Script(stmt.pos, unwrapped),
stmt.slotPlan, stmt.slotPlan,
stmt.scopeId,
stmt.captureSlots, stmt.captureSlots,
stmt.pos stmt.pos
) )

View File

@ -119,7 +119,8 @@ class CmdBuilder {
private fun operandKinds(op: Opcode): List<OperandKind> { private fun operandKinds(op: Opcode): List<OperandKind> {
return when (op) { return when (op) {
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList() Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN, Opcode.POP_TRY,
Opcode.CLEAR_PENDING_THROWABLE, Opcode.RETHROW_PENDING -> emptyList()
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ, Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL, Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
Opcode.OBJ_TO_BOOL, Opcode.OBJ_TO_BOOL,
@ -144,6 +145,8 @@ class CmdBuilder {
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST) listOf(OperandKind.CONST)
Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE -> Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
@ -238,6 +241,7 @@ class CmdBuilder {
Opcode.MAKE_QUALIFIED_VIEW -> CmdMakeQualifiedView(operands[0], operands[1], operands[2]) Opcode.MAKE_QUALIFIED_VIEW -> CmdMakeQualifiedView(operands[0], operands[1], operands[2])
Opcode.RET_LABEL -> CmdRetLabel(operands[0], operands[1]) Opcode.RET_LABEL -> CmdRetLabel(operands[0], operands[1])
Opcode.THROW -> CmdThrow(operands[0], operands[1]) Opcode.THROW -> CmdThrow(operands[0], operands[1])
Opcode.RETHROW_PENDING -> CmdRethrowPending()
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])
@ -376,6 +380,9 @@ class CmdBuilder {
Opcode.POP_SCOPE -> CmdPopScope() Opcode.POP_SCOPE -> CmdPopScope()
Opcode.PUSH_SLOT_PLAN -> CmdPushSlotPlan(operands[0]) Opcode.PUSH_SLOT_PLAN -> CmdPushSlotPlan(operands[0])
Opcode.POP_SLOT_PLAN -> CmdPopSlotPlan() Opcode.POP_SLOT_PLAN -> CmdPopSlotPlan()
Opcode.PUSH_TRY -> CmdPushTry(operands[0], operands[1], operands[2])
Opcode.POP_TRY -> CmdPopTry()
Opcode.CLEAR_PENDING_THROWABLE -> CmdClearPendingThrowable()
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1]) Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1]) Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1]) Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])

View File

@ -180,10 +180,14 @@ object CmdDisassembler {
is CmdRetLabel -> Opcode.RET_LABEL to intArrayOf(cmd.labelId, 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 CmdThrow -> Opcode.THROW to intArrayOf(cmd.posId, cmd.slot)
is CmdRethrowPending -> Opcode.RETHROW_PENDING to intArrayOf()
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)
is CmdPopSlotPlan -> Opcode.POP_SLOT_PLAN to intArrayOf() is CmdPopSlotPlan -> Opcode.POP_SLOT_PLAN to intArrayOf()
is CmdPushTry -> Opcode.PUSH_TRY to intArrayOf(cmd.exceptionSlot, cmd.catchIp, cmd.finallyIp)
is CmdPopTry -> Opcode.POP_TRY to intArrayOf()
is CmdClearPendingThrowable -> Opcode.CLEAR_PENDING_THROWABLE to intArrayOf()
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot) is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot) is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
@ -214,7 +218,8 @@ object CmdDisassembler {
private fun operandKinds(op: Opcode): List<OperandKind> { private fun operandKinds(op: Opcode): List<OperandKind> {
return when (op) { return when (op) {
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN, Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN, Opcode.POP_TRY,
Opcode.CLEAR_PENDING_THROWABLE, Opcode.RETHROW_PENDING,
Opcode.ITER_POP, Opcode.ITER_CANCEL -> emptyList() Opcode.ITER_POP, Opcode.ITER_CANCEL -> emptyList()
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ, Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL, Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
@ -242,6 +247,8 @@ object CmdDisassembler {
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST) listOf(OperandKind.CONST)
Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE -> Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,

View File

@ -34,7 +34,14 @@ class CmdVm {
while (result == null) { while (result == null) {
val cmd = cmds[frame.ip] val cmd = cmds[frame.ip]
frame.ip += 1 frame.ip += 1
cmd.perform(frame) try {
cmd.perform(frame)
} catch (e: Throwable) {
if (!frame.handleException(e)) {
frame.cancelIterators()
throw e
}
}
} }
} catch (e: Throwable) { } catch (e: Throwable) {
frame.cancelIterators() frame.cancelIterators()
@ -1138,6 +1145,13 @@ class CmdThrow(internal val posId: Int, internal val slot: Int) : Cmd() {
} }
} }
class CmdRethrowPending : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.rethrowPending()
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
@ -1170,6 +1184,27 @@ class CmdPopSlotPlan : Cmd() {
} }
} }
class CmdPushTry(internal val exceptionSlot: Int, internal val catchIp: Int, internal val finallyIp: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.pushTry(exceptionSlot, catchIp, finallyIp)
return
}
}
class CmdPopTry : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.popTry()
return
}
}
class CmdClearPendingThrowable : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.clearPendingThrowable()
return
}
}
class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() { class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl
@ -1627,6 +1662,14 @@ class CmdFrame(
private var scopeDepth = 0 private var scopeDepth = 0
private var virtualDepth = 0 private var virtualDepth = 0
private val iterStack = ArrayDeque<Obj>() private val iterStack = ArrayDeque<Obj>()
internal data class TryHandler(
val exceptionSlot: Int,
val catchIp: Int,
val finallyIp: Int,
var inCatch: Boolean = false
)
internal val tryStack = ArrayDeque<TryHandler>()
private var pendingThrowable: Throwable? = null
internal val frame = BytecodeFrame(fn.localCount, args.size) internal val frame = BytecodeFrame(fn.localCount, args.size)
private val addrScopes: Array<Scope?> = arrayOfNulls(fn.addrCount) private val addrScopes: Array<Scope?> = arrayOfNulls(fn.addrCount)
@ -1709,6 +1752,63 @@ class CmdFrame(
return scope return scope
} }
fun handleException(t: Throwable): Boolean {
val handler = tryStack.lastOrNull() ?: return false
val finallyIp = handler.finallyIp
if (t is ReturnException || t is LoopBreakContinueException) {
if (finallyIp >= 0) {
pendingThrowable = t
ip = finallyIp
return true
}
return false
}
if (handler.inCatch) {
if (finallyIp >= 0) {
pendingThrowable = t
ip = finallyIp
return true
}
return false
}
handler.inCatch = true
pendingThrowable = t
if (handler.catchIp >= 0) {
val caughtObj = when (t) {
is ExecutionError -> t.errorObject
else -> ObjUnknownException(ensureScope(), t.message ?: t.toString())
}
storeObjResult(handler.exceptionSlot, caughtObj)
ip = handler.catchIp
return true
}
if (finallyIp >= 0) {
ip = finallyIp
return true
}
return false
}
fun pushTry(exceptionSlot: Int, catchIp: Int, finallyIp: Int) {
tryStack.addLast(TryHandler(exceptionSlot, catchIp, finallyIp))
}
fun popTry() {
if (tryStack.isNotEmpty()) {
tryStack.removeLast()
}
}
fun clearPendingThrowable() {
pendingThrowable = null
}
fun rethrowPending() {
val t = pendingThrowable ?: return
pendingThrowable = null
throw t
}
private fun posForIp(ip: Int): Pos? { private fun posForIp(ip: Int): Pos? {
if (ip < 0) return null if (ip < 0) return null
return fn.posByIp.getOrNull(ip) return fn.posByIp.getOrNull(ip)

View File

@ -127,6 +127,9 @@ enum class Opcode(val code: Int) {
DECL_EXT_PROPERTY(0x8A), DECL_EXT_PROPERTY(0x8A),
DECL_DELEGATED(0x8B), DECL_DELEGATED(0x8B),
DECL_DESTRUCTURE(0x8C), DECL_DESTRUCTURE(0x8C),
PUSH_TRY(0x8D),
POP_TRY(0x8E),
CLEAR_PENDING_THROWABLE(0x8F),
CALL_DIRECT(0x90), CALL_DIRECT(0x90),
CALL_MEMBER_SLOT(0x92), CALL_MEMBER_SLOT(0x92),
@ -148,6 +151,7 @@ enum class Opcode(val code: Int) {
LOAD_BOOL_ADDR(0xB8), LOAD_BOOL_ADDR(0xB8),
STORE_BOOL_ADDR(0xB9), STORE_BOOL_ADDR(0xB9),
THROW(0xBB), THROW(0xBB),
RETHROW_PENDING(0xBC),
ITER_PUSH(0xBF), ITER_PUSH(0xBF),
ITER_POP(0xC0), ITER_POP(0xC0),
ITER_CANCEL(0xC1), ITER_CANCEL(0xC1),