From fc7d26ee4b50f468920e076e1a23bedaa0df6b44 Mon Sep 17 00:00:00 2001 From: sergeych Date: Tue, 21 Apr 2026 11:39:47 +0300 Subject: [PATCH] Fast-path bytecode statement execution --- .../kotlin/net/sergeych/lyng/BytecodeExec.kt | 1 + .../lyng/bytecode/BytecodeStatement.kt | 27 +++++++++++--- .../net/sergeych/lyng/bytecode/SeedLocals.kt | 35 +++++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BytecodeExec.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BytecodeExec.kt index b7d5765..aed1127 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BytecodeExec.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BytecodeExec.kt @@ -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) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt index 847d707..1ffa3ef 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt @@ -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 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 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/SeedLocals.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/SeedLocals.kt index 2c78f8a..eba42a4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/SeedLocals.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/SeedLocals.kt @@ -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