Fast-path bytecode statement execution

This commit is contained in:
Sergey Chernov 2026-04-21 11:39:47 +03:00
parent a61b5a31be
commit fc7d26ee4b
3 changed files with 58 additions and 5 deletions

View File

@ -28,6 +28,7 @@ internal suspend fun executeBytecodeWithSeed(scope: Scope, stmt: Statement, labe
else -> null
} ?: scope.raiseIllegalState("$label requires bytecode statement")
scope.pos = bytecode.pos
bytecode.callOnFast(scope)?.let { return it }
return CmdVm().execute(bytecode.bytecodeFunction(), scope, scope.args) { frame, _ ->
seedFrameLocalsFromScope(frame, scope)
}

View File

@ -23,19 +23,36 @@ import net.sergeych.lyng.obj.*
class BytecodeStatement private constructor(
val original: Statement,
private val function: CmdFunction,
) : Statement(original.isStaticConst, original.isConst, original.returnType) {
) : Statement(original.isStaticConst, original.isConst, original.returnType), BytecodeCallable {
override val pos: Pos = original.pos
private val declaredLocalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) {
function.constants
.mapNotNull { it as? BytecodeConst.LocalDecl }
.mapTo(mutableSetOf()) { it.name }
}
override fun callOnFast(scope: Scope): Obj? {
scope.pos = pos
if (!function.fastOnly) return null
if (!canFastSeedUndeclaredLocals(function, declaredLocalNames, emptySet())) return null
val binder: (CmdFrame, Arguments) -> Unit = { frame, _ ->
if (!trySeedFrameLocalsFromScopeFast(frame, scope)) {
scope.raiseIllegalState("fast local seeding is not available")
}
}
return CmdVm().executeFastOnlyNoSuspend(function, scope, scope.args, binder)
}
override suspend fun execute(scope: Scope): Obj {
scope.pos = pos
val declaredNames = function.constants
.mapNotNull { it as? BytecodeConst.LocalDecl }
.mapTo(mutableSetOf()) { it.name }
val fastResult = callOnFast(scope)
if (fastResult != null) return fastResult
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
val localNames = frame.fn.localSlotNames
for (i in localNames.indices) {
val name = localNames[i] ?: continue
if (declaredNames.contains(name)) continue
if (declaredLocalNames.contains(name)) continue
val slotType = frame.getLocalSlotTypeCode(i)
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
continue

View File

@ -17,7 +17,11 @@
package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Scope
import net.sergeych.lyng.FrameSlotRef
import net.sergeych.lyng.RecordSlotRef
import net.sergeych.lyng.ScopeSlotRef
import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjProperty
internal fun canFastSeedUndeclaredLocals(
fn: CmdFunction,
@ -35,6 +39,37 @@ internal fun canFastSeedUndeclaredLocals(
return true
}
internal fun trySeedFrameLocalsFromScopeFast(frame: CmdFrame, scope: Scope): Boolean {
val localNames = frame.fn.localSlotNames
if (localNames.isEmpty()) return true
val base = frame.fn.scopeSlotCount
for (i in localNames.indices) {
val name = localNames[i] ?: continue
val slotType = frame.getLocalSlotTypeCode(i)
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) continue
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) continue
val record = scope.getLocalRecordDirect(name)
?: scope.chainLookupIgnoreClosure(name, followClosure = true)
?: continue
val value = when {
record.type == ObjRecord.Type.Delegated -> return false
record.type == ObjRecord.Type.Property -> return false
record.value is ObjProperty -> return false
else -> when (val direct = record.value) {
is FrameSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
is RecordSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
else -> direct
}
}
if (value is FrameSlotRef && value.refersTo(frame.frame, i)) {
continue
}
frame.setObjUnchecked(base + i, value)
}
return true
}
internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
val localNames = frame.fn.localSlotNames
if (localNames.isEmpty()) return