diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt index 761dc52..0b064e8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -724,41 +724,39 @@ open class Obj { scope.raiseNotImplemented() } - suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj { - val usePool = PerfFlags.SCOPE_POOL && this !is Statement + private suspend fun invokeWithBoundScope( + scope: Scope, + args: Arguments, + thisObj: Obj, + declaringClass: ObjClass? = null, + atPos: Pos = scope.pos + ): Obj { + val usePool = PerfFlags.SCOPE_POOL && this !is Statement && atPos == scope.pos return if (usePool) { scope.withChildFrame(args, newThisObj = thisObj) { child -> if (declaringClass != null) child.currentClassCtx = declaringClass (this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child) } } else { - val child = scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also { + val child = scope.createChildScope(atPos, args = args, newThisObj = thisObj).also { if (declaringClass != null) it.currentClassCtx = declaringClass } (this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child) } } + suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj { + return invokeWithBoundScope(scope, args, thisObj, declaringClass) + } + suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj = - callOn( - scope.createChildScope( - scope.pos, - args = Arguments(args.toList()), - newThisObj = thisObj - ) - ) + invokeWithBoundScope(scope, Arguments(args.toList()), thisObj) suspend fun invoke(scope: Scope, thisObj: Obj): Obj = - callOn( - scope.createChildScope( - scope.pos, - args = Arguments.EMPTY, - newThisObj = thisObj - ) - ) + invokeWithBoundScope(scope, Arguments.EMPTY, thisObj) suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj = - callOn(scope.createChildScope(atPos, args = args, newThisObj = thisObj)) + invokeWithBoundScope(scope, args, thisObj, atPos = atPos) val asReadonly: ObjRecord by lazy { ObjRecord(this, false) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt index 3987570..4c468dc 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt @@ -58,7 +58,10 @@ abstract class Statement( val type = ObjClass("Callable") } - suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.createChildScope(args = Arguments(*args))) + suspend fun call(scope: Scope, vararg args: Obj): Obj { + val child = scope.createChildScope(args = Arguments(*args)) + return (this as? BytecodeCallable)?.callOnFast(child) ?: execute(child) + } protected fun bytecodeOnly(scope: Scope, label: String): Nothing { return scope.raiseIllegalState("bytecode-only execution is required; $label needs compiled bytecode") diff --git a/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt b/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt index 5641b56..e21ab60 100644 --- a/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt +++ b/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt @@ -17,10 +17,12 @@ import kotlinx.coroutines.test.runTest import net.sergeych.lyng.Compiler import net.sergeych.lyng.Arguments +import net.sergeych.lyng.Pos import net.sergeych.lyng.Scope import net.sergeych.lyng.Script import net.sergeych.lyng.ScriptError import net.sergeych.lyng.Source +import net.sergeych.lyng.Statement import net.sergeych.lyng.asFacade import net.sergeych.lyng.obj.ObjInt import net.sergeych.lyng.obj.ObjString @@ -88,6 +90,66 @@ class CompilerVmReviewRegressionTest { assertEquals(42, scope.asFacade().call(callable, Arguments(ObjInt.of(40))).toInt()) } + @Test + fun genericInvokeHelpersUsePreparedLambdaEntryPoints() = runTest { + val unaryLambda = Compiler.compile( + Source( + "", + """ + val delta = 2 + { x -> x + delta } + """.trimIndent() + ), + Script.defaultImportManager + ) + val nullaryLambda = Compiler.compile( + Source( + "", + """ + val base = 7 + { base } + """.trimIndent() + ), + Script.defaultImportManager + ) + + val unaryScope = Script.newScope() + val nullaryScope = Script.newScope() + val unaryCallable = unaryLambda.execute(unaryScope) + val nullaryCallable = nullaryLambda.execute(nullaryScope) + + assertEquals(42, unaryCallable.invoke(unaryScope, ObjString("receiver"), ObjInt.of(40)).toInt()) + assertEquals(7, nullaryCallable.invoke(nullaryScope, ObjString("receiver")).toInt()) + assertEquals( + 42, + unaryCallable.invoke( + unaryScope, + Pos(Source("", ""), 0, 0), + ObjString("receiver"), + Arguments(ObjInt.of(40)) + ).toInt() + ) + } + + @Test + fun statementCallUsesPreparedLambdaFastPath() = runTest { + val unaryLambda = Compiler.compile( + Source( + "", + """ + val delta = 2 + { x -> x + delta } + """.trimIndent() + ), + Script.defaultImportManager + ) + + val scope = Script.newScope() + val callable = unaryLambda.execute(scope) as Statement + + assertEquals(42, callable.call(scope, ObjInt.of(40)).toInt()) + } + @Test fun subjectlessWhenReportsScriptError() = runTest { val ex = assertFailsWith {