diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt index f4d0348..3ac54e5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt @@ -434,6 +434,57 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) } } + fun supportsFastFrameBinding(arguments: Arguments): Boolean { + if (arguments.named.isNotEmpty() || arguments.tailBlockMode) return false + return params.none { it.isEllipsis || it.defaultValue != null } + } + + fun assignToFrameFast( + scope: Scope, + arguments: Arguments = scope.args, + paramSlotPlan: Map, + frame: FrameAccess, + slotOffset: Int = 0 + ) { + if (!supportsFastFrameBinding(arguments)) { + scope.raiseIllegalState("fast frame binding is not supported for this call shape") + } + + fun slotFor(name: String): Int { + val full = paramSlotPlan[name] ?: scope.raiseIllegalState("parameter slot for '$name' is missing") + val slot = full - slotOffset + if (slot < 0) scope.raiseIllegalState("parameter slot for '$name' is out of range") + return slot + } + + fun assign(slot: Int, value: Obj) { + when (value) { + is net.sergeych.lyng.obj.ObjInt -> frame.setInt(slot, value.value) + is net.sergeych.lyng.obj.ObjReal -> frame.setReal(slot, value.value) + is net.sergeych.lyng.obj.ObjBool -> frame.setBool(slot, value.value) + else -> frame.setObj(slot, value) + } + } + + if (arguments.list.size > params.size) { + scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}") + } + if (arguments.list.size < params.size) { + for (i in arguments.list.size until params.size) { + val a = params[i] + if (!a.type.isNullable) { + scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}") + } + } + } + + for (i in params.indices) { + val slot = slotFor(params[i].name) + val value = if (i < arguments.list.size) arguments.list[i] else ObjNull + assign(slot, value.byValueCopy()) + } + } + /** * Single argument declaration descriptor. * diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index b934fb6..6c28c14 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -3545,6 +3545,11 @@ class Compiler( } val bytecodeFn = (fnStatements as? BytecodeStatement)?.bytecodeFunction() val inlineBodyRef = argsDeclaration?.let { null } ?: extractInlineLambdaBodyRef(body) + val supportsDirectInvokeFastPath = bytecodeFn != null && + bytecodeFn.scopeSlotCount == 0 && + expectedReceiverType == null && + !wrapAsExtensionCallable && + !containsDelegatedRefs(body) val ref = LambdaFnRef( valueFn = { closureScope -> val captureRecords = closureScope.captureRecords @@ -3628,6 +3633,7 @@ class Compiler( captureEntries = captureEntries, inferredReturnClass = returnClass, inlineBodyRef = inlineBodyRef, + supportsDirectInvokeFastPath = supportsDirectInvokeFastPath, preferredThisType = expectedReceiverType, wrapAsExtensionCallable = wrapAsExtensionCallable, returnLabels = returnLabels, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index fb956a6..774e13c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -899,6 +899,7 @@ class BytecodeCompiler( captureNames = captureNames, paramSlotPlan = ref.paramSlotPlan, argsDeclaration = ref.argsDeclaration, + supportsDirectInvokeFastPath = ref.supportsDirectInvokeFastPath, preferredThisType = ref.preferredThisType, wrapAsExtensionCallable = ref.wrapAsExtensionCallable, returnLabels = ref.returnLabels, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt index 7f19d16..bf8a7d5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt @@ -40,6 +40,7 @@ sealed class BytecodeConst { val captureNames: List, val paramSlotPlan: Map, val argsDeclaration: ArgsDeclaration?, + val supportsDirectInvokeFastPath: Boolean, val preferredThisType: String?, val wrapAsExtensionCallable: Boolean, val returnLabels: Set, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt index e15fd1c..2ab3faa 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -112,6 +112,7 @@ class CmdBuilder { } cmds.add(createCmd(ins.op, operands, scopeSlotCount, localSlotCaptures)) } + val cmdArray = cmds.toTypedArray() return CmdFunction( name = name, localCount = localCount, @@ -128,8 +129,9 @@ class CmdBuilder { localSlotDelegated = localSlotDelegated, localSlotCaptures = localSlotCaptures, constants = constPool.toList(), - cmds = cmds.toTypedArray(), - posByIp = posByInstr.toTypedArray() + cmds = cmdArray, + posByIp = posByInstr.toTypedArray(), + fastOnly = computeFastOnlyBytecode(scopeSlotCount, cmdArray) ) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt index 7955446..0045f0d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt @@ -35,6 +35,7 @@ data class CmdFunction( val constants: List, val cmds: Array, val posByIp: Array, + val fastOnly: Boolean = false, ) { init { require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" } @@ -71,3 +72,118 @@ data class CmdFunction( } } + +internal fun computeFastOnlyBytecode(scopeSlotCount: Int, cmds: Array): Boolean { + if (scopeSlotCount != 0) return false + return cmds.all(::supportsFastOnlyExecution) +} + +private fun supportsFastOnlyExecution(cmd: Cmd): Boolean { + return when (cmd) { + is CmdMoveIntLocal, + is CmdMoveRealLocal, + is CmdMoveBoolLocal, + is CmdConstObj, + is CmdConstInt, + is CmdConstIntLocal, + is CmdConstReal, + is CmdConstBool, + is CmdConstNull, + is CmdUnboxIntObjLocal, + is CmdUnboxRealObjLocal, + is CmdIntToRealLocal, + is CmdRealToIntLocal, + is CmdBoolToIntLocal, + is CmdIntToBoolLocal, + is CmdAddIntLocal, + is CmdSubIntLocal, + is CmdMulIntLocal, + is CmdDivIntLocal, + is CmdModIntLocal, + is CmdNegIntLocal, + is CmdIncIntLocal, + is CmdDecIntLocal, + is CmdAddRealLocal, + is CmdSubRealLocal, + is CmdMulRealLocal, + is CmdDivRealLocal, + is CmdNegRealLocal, + is CmdAndIntLocal, + is CmdOrIntLocal, + is CmdXorIntLocal, + is CmdShlIntLocal, + is CmdShrIntLocal, + is CmdUshrIntLocal, + is CmdInvIntLocal, + is CmdCmpEqIntLocal, + is CmdCmpNeqIntLocal, + is CmdCmpLtIntLocal, + is CmdCmpLteIntLocal, + is CmdCmpGtIntLocal, + is CmdCmpGteIntLocal, + is CmdCmpEqRealLocal, + is CmdCmpNeqRealLocal, + is CmdCmpLtRealLocal, + is CmdCmpLteRealLocal, + is CmdCmpGtRealLocal, + is CmdCmpGteRealLocal, + is CmdCmpEqBoolLocal, + is CmdCmpNeqBoolLocal, + is CmdCmpEqIntRealLocal, + is CmdCmpEqRealIntLocal, + is CmdCmpLtIntRealLocal, + is CmdCmpLtRealIntLocal, + is CmdCmpLteIntRealLocal, + is CmdCmpLteRealIntLocal, + is CmdCmpGtIntRealLocal, + is CmdCmpGtRealIntLocal, + is CmdCmpGteIntRealLocal, + is CmdCmpGteRealIntLocal, + is CmdCmpNeqIntRealLocal, + is CmdCmpNeqRealIntLocal, + is CmdCmpEqStrLocal, + is CmdCmpNeqStrLocal, + is CmdCmpLtStrLocal, + is CmdCmpLteStrLocal, + is CmdCmpGtStrLocal, + is CmdCmpGteStrLocal, + is CmdCmpEqIntObjLocal, + is CmdCmpNeqIntObjLocal, + is CmdCmpLtIntObjLocal, + is CmdCmpLteIntObjLocal, + is CmdCmpGtIntObjLocal, + is CmdCmpGteIntObjLocal, + is CmdCmpEqRealObjLocal, + is CmdCmpNeqRealObjLocal, + is CmdCmpLtRealObjLocal, + is CmdCmpLteRealObjLocal, + is CmdCmpGtRealObjLocal, + is CmdCmpGteRealObjLocal, + is CmdAddIntObjLocal, + is CmdSubIntObjLocal, + is CmdMulIntObjLocal, + is CmdDivIntObjLocal, + is CmdModIntObjLocal, + is CmdAddRealObjLocal, + is CmdSubRealObjLocal, + is CmdMulRealObjLocal, + is CmdDivRealObjLocal, + is CmdModRealObjLocal, + is CmdNotBoolLocal, + is CmdAndBoolLocal, + is CmdOrBoolLocal, + is CmdJmp, + is CmdJmpIfTrueLocal, + is CmdJmpIfFalseLocal, + is CmdJmpIfEqIntLocal, + is CmdJmpIfNeqIntLocal, + is CmdJmpIfLtIntLocal, + is CmdJmpIfLteIntLocal, + is CmdJmpIfGtIntLocal, + is CmdJmpIfGteIntLocal, + is CmdRet, + is CmdRetVoid -> true + + else -> false + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index 4606c8f..e7f8ec9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -57,6 +57,39 @@ class CmdVm { suspend fun execute(fn: CmdFunction, scope0: Scope, args: List): Obj { return execute(fn, scope0, Arguments.from(args)) } + + suspend fun executeFastOnly( + fn: CmdFunction, + scope0: Scope, + args: Arguments, + binder: ((CmdFrame, Arguments) -> Unit)? = null + ): Obj { + require(fn.fastOnly) { "fast-only execution requested for non-fast function ${fn.name}" } + result = null + val frame = CmdFrame(this, fn, scope0, args.list) + frame.applyCaptureRecords() + binder?.invoke(frame, args) + val cmds = fn.cmds + while (true) { + try { + while (result == null) { + val cmd = cmds[frame.ip++] + if (!cmd.performFast(frame)) { + error("fast-only command not supported: ${cmd::class.simpleName}") + } + } + break + } catch (e: Throwable) { + val throwable = frame.normalizeThrowable(e) + if (!frame.handleException(throwable)) { + frame.cancelIterators() + throw throwable + } + } + } + frame.cancelIterators() + return result ?: ObjVoid + } } sealed class Cmd { @@ -280,6 +313,11 @@ class CmdMakeRange( } class CmdConstNull(internal val dst: Int) : Cmd() { + override fun performFast(frame: CmdFrame): Boolean { + frame.setObj(dst, ObjNull) + return true + } + override suspend fun perform(frame: CmdFrame) { frame.setObj(dst, ObjNull) return @@ -2301,6 +2339,11 @@ class CmdJmpIfGteIntLocal(internal val a: Int, internal val b: Int, internal val } class CmdRet(internal val slot: Int) : Cmd() { + override fun performFast(frame: CmdFrame): Boolean { + frame.vm.result = frame.storedSlotObj(slot) + return true + } + override suspend fun perform(frame: CmdFrame) { frame.vm.result = frame.slotToObj(slot) return @@ -2322,6 +2365,11 @@ class CmdRetLabel(internal val labelId: Int, internal val slot: Int) : Cmd() { } class CmdRetVoid : Cmd() { + override fun performFast(frame: CmdFrame): Boolean { + frame.vm.result = ObjVoid + return true + } + override suspend fun perform(frame: CmdFrame) { frame.vm.result = ObjVoid return @@ -3220,7 +3268,12 @@ class CmdCallDirect( val result = if (PerfFlags.SCOPE_POOL) { frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) } } else { - callee.callOn(frame.ensureScope().createChildScope(frame.ensureScope().pos, args = args)) + val scope = frame.ensureScope() + if (callee is BytecodeLambdaCallable && callee.supportsDirectInvokeFastPath()) { + callee.invokeWithArgs(scope, args) + } else { + callee.callOn(scope.createChildScope(scope.pos, args = args)) + } } frame.storeObjResult(dst, result) return @@ -3258,7 +3311,11 @@ class CmdCallSlot( scope.raiseIllegalState("bytecode runtime cannot call non-bytecode Statement") } } - callee.callOn(scope.createChildScope(scope.pos, args = args)) + if (callee is BytecodeLambdaCallable && callee.supportsDirectInvokeFastPath()) { + callee.invokeWithArgs(scope, args) + } else { + callee.callOn(scope.createChildScope(scope.pos, args = args)) + } } frame.storeObjResult(dst, result) return @@ -3343,6 +3400,8 @@ class CmdListFillInt( for (i in 0 until size) { val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) { callable.invokeImplicitIntArg(scope, i.toLong()) + } else if (callable is BytecodeLambdaCallable && callable.supportsDirectInvokeFastPath()) { + callable.invokeWithArgs(scope, Arguments(ObjInt.of(i.toLong()))) } else { callable.callOn(scope.createChildScope(scope.pos, args = Arguments(ObjInt.of(i.toLong())))) } @@ -3862,6 +3921,7 @@ class CmdMakeLambda(internal val id: Int, internal val dst: Int) : Cmd() { captureNames = lambdaConst.captureNames, paramSlotPlan = lambdaConst.paramSlotPlan, argsDeclaration = lambdaConst.argsDeclaration, + supportsDirectInvokeFastPath = lambdaConst.supportsDirectInvokeFastPath, preferredThisType = lambdaConst.preferredThisType, returnLabels = lambdaConst.returnLabels, pos = lambdaConst.pos @@ -3883,10 +3943,18 @@ class BytecodeLambdaCallable( private val captureNames: List, private val paramSlotPlan: Map, private val argsDeclaration: ArgsDeclaration?, + private val supportsDirectInvokeFastPath: Boolean, private val preferredThisType: String?, private val returnLabels: Set, override val pos: Pos, ) : Statement(), BytecodeCallable { + private val slotPlanByName: Map by lazy(LazyThreadSafetyMode.NONE) { fn.localSlotPlanByName() } + private val declaredLocalNames: Set by lazy(LazyThreadSafetyMode.NONE) { + fn.constants + .mapNotNull { it as? BytecodeConst.LocalDecl } + .mapTo(mutableSetOf()) { it.name } + } + private fun freezeRecord(record: ObjRecord): ObjRecord { if (record.isMutable) return record val raw = record.value as Obj? @@ -3918,6 +3986,7 @@ class BytecodeLambdaCallable( captureNames = captureNames, paramSlotPlan = paramSlotPlan, argsDeclaration = argsDeclaration, + supportsDirectInvokeFastPath = supportsDirectInvokeFastPath, preferredThisType = preferredThisType, returnLabels = returnLabels, pos = pos @@ -3934,6 +4003,7 @@ class BytecodeLambdaCallable( captureNames = captureNames, paramSlotPlan = paramSlotPlan, argsDeclaration = argsDeclaration, + supportsDirectInvokeFastPath = supportsDirectInvokeFastPath, preferredThisType = preferredThisType, returnLabels = returnLabels, pos = pos @@ -3942,9 +4012,27 @@ class BytecodeLambdaCallable( fun supportsImplicitIntFillFastPath(): Boolean = argsDeclaration == null - suspend fun invokeImplicitIntArg(scope: Scope, arg: Long): Obj { - val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also { - it.args = Arguments.EMPTY + fun supportsDirectInvokeFastPath(): Boolean = supportsDirectInvokeFastPath + + private val supportsFastUndeclaredLocalInit: Boolean by lazy(LazyThreadSafetyMode.NONE) { + val parameterSlots = paramSlotPlan.values.toHashSet() + fn.localSlotNames.indices.all { localIndex -> + val name = fn.localSlotNames[localIndex] ?: return@all true + if (declaredLocalNames.contains(name)) return@all true + if (fn.localSlotCaptures.getOrNull(localIndex) == true) return@all true + parameterSlots.contains(fn.scopeSlotCount + localIndex) + } + } + + private fun supportsFastOnlyVm(arguments: Arguments): Boolean { + if (!supportsDirectInvokeFastPath || !fn.fastOnly) return false + if (!supportsFastUndeclaredLocalInit) return false + return argsDeclaration == null || argsDeclaration.supportsFastFrameBinding(arguments) + } + + private fun buildContext(callScope: Scope, args: Arguments): Scope { + val context = callScope.applyClosureForBytecode(closureScope, preferredThisType).also { + it.args = args } if (captureRecords != null) { context.captureRecords = captureRecords @@ -3952,98 +4040,129 @@ class BytecodeLambdaCallable( } else if (captureNames.isNotEmpty()) { closureScope.raiseIllegalState("bytecode lambda capture records missing") } - val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ -> - paramSlotPlan["it"]?.let { itSlot -> + return context + } + + private fun bindArgumentsFast(frame: CmdFrame, context: Scope, arguments: Arguments) { + if (argsDeclaration == null) { + val l = arguments.list + val itValue: Obj = when (l.size) { + 0 -> ObjVoid + 1 -> l[0] + else -> ObjList(l.toMutableList()) + } + val itSlot = slotPlanByName["it"] + if (itSlot != null) { + when (itValue) { + is ObjInt -> frame.frame.setInt(itSlot, itValue.value) + is ObjReal -> frame.frame.setReal(itSlot, itValue.value) + is ObjBool -> frame.frame.setBool(itSlot, itValue.value) + else -> frame.frame.setObj(itSlot, itValue) + } + } + } else { + argsDeclaration.assignToFrameFast( + context, + arguments, + slotPlanByName, + frame.frame + ) + } + } + + private suspend fun bindArguments(frame: CmdFrame, context: Scope, arguments: Arguments) { + if (argsDeclaration == null) { + bindArgumentsFast(frame, context, arguments) + } else { + argsDeclaration.assignToFrame( + context, + arguments, + slotPlanByName, + frame.frame + ) + } + } + + private suspend fun seedUndeclaredLocals(frame: CmdFrame, context: Scope) { + val localNames = frame.fn.localSlotNames + for (i in localNames.indices) { + val name = localNames[i] ?: continue + if (declaredLocalNames.contains(name)) 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 = context.getLocalRecordDirect(name) + ?: context.parent?.get(name) + ?: context.get(name) + ?: continue + val value = + if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { + context.resolve(record, name) + } else { + when (val direct = record.value) { + is FrameSlotRef -> direct.read() + is RecordSlotRef -> direct.read(context, name) + is ScopeSlotRef -> direct.read() + else -> direct + } + } + frame.frame.setObj(i, value) + } + } + + suspend fun invokeImplicitIntArg(scope: Scope, arg: Long): Obj { + val context = buildContext(scope, Arguments.EMPTY) + val fastBinder: (CmdFrame, Arguments) -> Unit = { frame, _ -> + slotPlanByName["it"]?.let { itSlot -> frame.frame.setInt(itSlot, arg) } } return try { - CmdVm().execute(fn, context, Arguments.EMPTY, binder) + val vm = CmdVm() + if (supportsFastOnlyVm(Arguments.EMPTY)) { + vm.executeFastOnly(fn, context, Arguments.EMPTY, fastBinder) + } else { + val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ -> + slotPlanByName["it"]?.let { itSlot -> + frame.frame.setInt(itSlot, arg) + } + } + vm.execute(fn, context, Arguments.EMPTY, binder) + } } catch (e: ReturnException) { if (e.label == null || returnLabels.contains(e.label)) e.result else throw e } } - override suspend fun execute(scope: Scope): Obj { - val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also { - it.args = scope.args - } - if (captureRecords != null) { - context.captureRecords = captureRecords - context.captureNames = captureNames - } else if (captureNames.isNotEmpty()) { - closureScope.raiseIllegalState("bytecode lambda capture records missing") - } - if (argsDeclaration == null) { - // Bound in the bytecode entry binder. - } else { - // args bound into frame slots in the bytecode entry binder - } + suspend fun invokeWithArgs(scope: Scope, args: Arguments): Obj { + val context = buildContext(scope, args) return try { - val declaredNames = fn.constants - .mapNotNull { it as? BytecodeConst.LocalDecl } - .mapTo(mutableSetOf()) { it.name } - val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments -> - val slotPlan = fn.localSlotPlanByName() - if (argsDeclaration == null) { - val l = arguments.list - val itValue: Obj = when (l.size) { - 0 -> ObjVoid - 1 -> l[0] - else -> ObjList(l.toMutableList()) - } - val itSlot = slotPlan["it"] - if (itSlot != null) { - when (itValue) { - is ObjInt -> frame.frame.setInt(itSlot, itValue.value) - is ObjReal -> frame.frame.setReal(itSlot, itValue.value) - is ObjBool -> frame.frame.setBool(itSlot, itValue.value) - else -> frame.frame.setObj(itSlot, itValue) - } - } - } else { - argsDeclaration.assignToFrame( - context, - arguments, - slotPlan, - frame.frame - ) + val vm = CmdVm() + if (supportsFastOnlyVm(args)) { + val binder: (CmdFrame, Arguments) -> Unit = { frame, arguments -> + bindArgumentsFast(frame, context, arguments) } - val localNames = frame.fn.localSlotNames - for (i in localNames.indices) { - val name = localNames[i] ?: continue - if (declaredNames.contains(name)) 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 = context.getLocalRecordDirect(name) - ?: context.parent?.get(name) - ?: context.get(name) - ?: continue - val value = - if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) { - context.resolve(record, name) - } else { - when (val direct = record.value) { - is FrameSlotRef -> direct.read() - is RecordSlotRef -> direct.read(context, name) - is ScopeSlotRef -> direct.read() - else -> direct - } - } - frame.frame.setObj(i, value) + vm.executeFastOnly(fn, context, args, binder) + } else { + val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments -> + bindArguments(frame, context, arguments) + seedUndeclaredLocals(frame, context) } + vm.execute(fn, context, args, binder) } - CmdVm().execute(fn, context, scope.args, binder) } catch (e: ReturnException) { if (e.label == null || returnLabels.contains(e.label)) e.result else throw e } } + + override suspend fun execute(scope: Scope): Obj { + return invokeWithArgs(scope, scope.args) + } } class CmdIterPush(internal val iterSlot: Int) : Cmd() { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/LambdaFnRef.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/LambdaFnRef.kt index 7ae1e83..25d5618 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/LambdaFnRef.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/LambdaFnRef.kt @@ -31,6 +31,7 @@ class LambdaFnRef( val captureEntries: List, val inferredReturnClass: ObjClass?, val inlineBodyRef: ObjRef?, + val supportsDirectInvokeFastPath: Boolean, val preferredThisType: String?, val wrapAsExtensionCallable: Boolean, val returnLabels: Set, diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/OptTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/OptTest.kt index 987d12e..bc26f27 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/OptTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/OptTest.kt @@ -17,6 +17,7 @@ package net.sergeych.lyng +import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest import net.sergeych.lyng.obj.toInt import kotlin.test.Test @@ -40,9 +41,11 @@ class OptTest { repeat(3) { pass -> val size = scope.eval("buildArray(200000)").toInt() assertEquals(200000, size, "warmup pass ${pass + 1} failed") + delay(100) } - val passes = 3 + + val passes = 4 var bestMs = Long.MAX_VALUE var totalMs = 0L repeat(passes) { pass ->