Step 3: bytecode try/catch/finally
This commit is contained in:
parent
7ea67d4816
commit
f5ced02505
@ -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] Keep unsupported cases blocked: `ClassScopeMemberRef`, dynamic receivers, delegated members.
|
||||
- [x] JVM tests must be green before commit.
|
||||
- [ ] Step 3: Bytecode support for `try/catch/finally`.
|
||||
- [ ] Implement bytecode emission for try/catch and finally blocks.
|
||||
- [ ] Preserve existing error/stack semantics.
|
||||
- [ ] JVM tests must be green before commit.
|
||||
- [x] Step 3: Bytecode support for `try/catch/finally`.
|
||||
- [x] Implement bytecode emission for try/catch and finally blocks.
|
||||
- [x] Preserve existing error/stack semantics.
|
||||
- [x] JVM tests must be green before commit.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ import net.sergeych.lyng.obj.Obj
|
||||
class BlockStatement(
|
||||
val block: Script,
|
||||
val slotPlan: Map<String, Int>,
|
||||
val scopeId: Int,
|
||||
val captureSlots: List<CaptureSlot> = emptyList(),
|
||||
private val startPos: Pos,
|
||||
) : Statement() {
|
||||
|
||||
@ -1897,7 +1897,11 @@ class Compiler(
|
||||
is ContinueStatement -> false
|
||||
is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false
|
||||
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 -> {
|
||||
containsUnsupportedForBytecode(target.value) ||
|
||||
target.cases.any { case ->
|
||||
@ -2099,7 +2103,7 @@ class Compiler(
|
||||
is BlockStatement -> {
|
||||
val unwrapped = stmt.statements().map { unwrapBytecodeDeep(it) }
|
||||
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 -> {
|
||||
val unwrapped = stmt.statements().map { unwrapBytecodeDeep(it) }
|
||||
@ -5388,19 +5392,12 @@ class Compiler(
|
||||
for ((name, idx) in basePlan) {
|
||||
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 {
|
||||
val stmt = block as? BlockStatement ?: return block
|
||||
if (stmt.captureSlots.isEmpty()) return stmt
|
||||
return BlockStatement(stmt.block, stmt.slotPlan, 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")
|
||||
return BlockStatement(stmt.block, stmt.slotPlan, stmt.scopeId, emptyList(), stmt.pos)
|
||||
}
|
||||
fun resolveCatchVarClass(names: List<String>): ObjClass? {
|
||||
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")
|
||||
|
||||
val stmtPos = body.pos
|
||||
val tryStatement = object : Statement() {
|
||||
override val pos: Pos = stmtPos
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
var result: Obj = ObjVoid
|
||||
try {
|
||||
// body is a parsed block, it already has separate context
|
||||
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
|
||||
}
|
||||
val tryCatches = catches.map { cdata ->
|
||||
TryStatement.CatchBlock(
|
||||
catchVarName = cdata.catchVar.value,
|
||||
catchVarPos = cdata.catchVar.pos,
|
||||
classNames = cdata.classNames,
|
||||
block = cdata.block
|
||||
)
|
||||
}
|
||||
return TryStatement(tryStatement, stmtPos)
|
||||
return TryStatement(body, tryCatches, finallyClause, stmtPos)
|
||||
}
|
||||
|
||||
private fun parseEnumDeclaration(isExtern: Boolean = false): Statement {
|
||||
@ -7164,7 +7121,7 @@ class Compiler(
|
||||
}
|
||||
val planSnapshot = slotPlanIndices(blockSlotPlan)
|
||||
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())
|
||||
return stmt
|
||||
}
|
||||
@ -7480,7 +7437,7 @@ class Compiler(
|
||||
slotPlanStack.removeLast()
|
||||
}
|
||||
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)
|
||||
return wrapped.also {
|
||||
val t1 = cc.next()
|
||||
@ -7511,7 +7468,7 @@ class Compiler(
|
||||
slotPlanStack.removeLast()
|
||||
}
|
||||
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)
|
||||
return wrapped.also {
|
||||
val t1 = cc.next()
|
||||
@ -7541,7 +7498,7 @@ class Compiler(
|
||||
slotPlanStack.removeLast()
|
||||
}
|
||||
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 t1 = cc.next()
|
||||
if (t1.type != Token.Type.RBRACE)
|
||||
|
||||
@ -17,14 +17,71 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
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(
|
||||
private val delegate: Statement,
|
||||
val body: Statement,
|
||||
val catches: List<CatchBlock>,
|
||||
val finallyClause: Statement?,
|
||||
private val startPos: Pos,
|
||||
) : Statement() {
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@ class BytecodeCompiler(
|
||||
is net.sergeych.lyng.ThrowStatement -> compileThrowStatement(name, stmt)
|
||||
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> compileExtensionPropertyDecl(name, stmt)
|
||||
is net.sergeych.lyng.TryStatement -> {
|
||||
val value = emitStatementEval(stmt)
|
||||
val value = emitTry(stmt, true) ?: return null
|
||||
builder.emit(Opcode.RET, value.slot)
|
||||
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
|
||||
builder.build(
|
||||
@ -3314,12 +3314,13 @@ class BytecodeCompiler(
|
||||
is net.sergeych.lyng.ClassDeclStatement -> emitStatementEval(target)
|
||||
is net.sergeych.lyng.FunctionDeclStatement -> 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.BreakStatement -> compileBreak(target)
|
||||
is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
|
||||
is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
|
||||
is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
|
||||
is net.sergeych.lyng.TryStatement -> emitTry(target, false)
|
||||
else -> {
|
||||
emitFallbackStatement(target)
|
||||
}
|
||||
@ -3362,6 +3363,7 @@ class BytecodeCompiler(
|
||||
is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
|
||||
is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
|
||||
is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
|
||||
is net.sergeych.lyng.TryStatement -> emitTry(target, false)
|
||||
is net.sergeych.lyng.WhenStatement -> compileWhen(target, false)
|
||||
else -> {
|
||||
emitFallbackStatement(target)
|
||||
@ -3423,6 +3425,167 @@ class BytecodeCompiler(
|
||||
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? {
|
||||
var lastValue: CompiledValue? = null
|
||||
for ((index, statement) in statements.withIndex()) {
|
||||
@ -5181,6 +5344,13 @@ class BytecodeCompiler(
|
||||
}
|
||||
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 -> {
|
||||
stmt.resultExpr?.let { collectScopeSlots(it) }
|
||||
}
|
||||
@ -5362,6 +5532,13 @@ class BytecodeCompiler(
|
||||
is net.sergeych.lyng.ThrowStatement -> {
|
||||
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 -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,7 +126,11 @@ class BytecodeStatement private constructor(
|
||||
is net.sergeych.lyng.ClassDeclStatement -> false
|
||||
is net.sergeych.lyng.FunctionDeclStatement -> 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 -> {
|
||||
containsUnsupportedStatement(target.value) ||
|
||||
target.cases.any { case ->
|
||||
@ -147,6 +151,7 @@ class BytecodeStatement private constructor(
|
||||
net.sergeych.lyng.BlockStatement(
|
||||
net.sergeych.lyng.Script(stmt.pos, unwrapped),
|
||||
stmt.slotPlan,
|
||||
stmt.scopeId,
|
||||
stmt.captureSlots,
|
||||
stmt.pos
|
||||
)
|
||||
|
||||
@ -119,7 +119,8 @@ class CmdBuilder {
|
||||
|
||||
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||
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.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
||||
Opcode.OBJ_TO_BOOL,
|
||||
@ -144,6 +145,8 @@ class CmdBuilder {
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||
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 ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
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.RET_LABEL -> CmdRetLabel(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.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1])
|
||||
Opcode.STORE_OBJ_ADDR -> CmdStoreObjAddr(operands[0], operands[1])
|
||||
@ -376,6 +380,9 @@ class CmdBuilder {
|
||||
Opcode.POP_SCOPE -> CmdPopScope()
|
||||
Opcode.PUSH_SLOT_PLAN -> CmdPushSlotPlan(operands[0])
|
||||
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_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
|
||||
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
|
||||
|
||||
@ -180,10 +180,14 @@ object CmdDisassembler {
|
||||
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 CmdRethrowPending -> Opcode.RETHROW_PENDING to intArrayOf()
|
||||
is CmdPushScope -> Opcode.PUSH_SCOPE to intArrayOf(cmd.planId)
|
||||
is CmdPopScope -> Opcode.POP_SCOPE to intArrayOf()
|
||||
is CmdPushSlotPlan -> Opcode.PUSH_SLOT_PLAN to intArrayOf(cmd.planId)
|
||||
is CmdPopSlotPlan -> Opcode.POP_SLOT_PLAN to intArrayOf()
|
||||
is 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 CmdDeclDelegated -> Opcode.DECL_DELEGATED 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> {
|
||||
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.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,
|
||||
@ -242,6 +247,8 @@ object CmdDisassembler {
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||
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 ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||
|
||||
@ -34,7 +34,14 @@ class CmdVm {
|
||||
while (result == null) {
|
||||
val cmd = cmds[frame.ip]
|
||||
frame.ip += 1
|
||||
cmd.perform(frame)
|
||||
try {
|
||||
cmd.perform(frame)
|
||||
} catch (e: Throwable) {
|
||||
if (!frame.handleException(e)) {
|
||||
frame.cancelIterators()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
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() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
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() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl
|
||||
@ -1627,6 +1662,14 @@ class CmdFrame(
|
||||
private var scopeDepth = 0
|
||||
private var virtualDepth = 0
|
||||
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)
|
||||
private val addrScopes: Array<Scope?> = arrayOfNulls(fn.addrCount)
|
||||
@ -1709,6 +1752,63 @@ class CmdFrame(
|
||||
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? {
|
||||
if (ip < 0) return null
|
||||
return fn.posByIp.getOrNull(ip)
|
||||
|
||||
@ -127,6 +127,9 @@ enum class Opcode(val code: Int) {
|
||||
DECL_EXT_PROPERTY(0x8A),
|
||||
DECL_DELEGATED(0x8B),
|
||||
DECL_DESTRUCTURE(0x8C),
|
||||
PUSH_TRY(0x8D),
|
||||
POP_TRY(0x8E),
|
||||
CLEAR_PENDING_THROWABLE(0x8F),
|
||||
|
||||
CALL_DIRECT(0x90),
|
||||
CALL_MEMBER_SLOT(0x92),
|
||||
@ -148,6 +151,7 @@ enum class Opcode(val code: Int) {
|
||||
LOAD_BOOL_ADDR(0xB8),
|
||||
STORE_BOOL_ADDR(0xB9),
|
||||
THROW(0xBB),
|
||||
RETHROW_PENDING(0xBC),
|
||||
ITER_PUSH(0xBF),
|
||||
ITER_POP(0xC0),
|
||||
ITER_CANCEL(0xC1),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user