Extend generic fast callable entry points

This commit is contained in:
Sergey Chernov 2026-04-21 17:26:53 +03:00
parent 6c91b77a85
commit ee634c8dff
3 changed files with 82 additions and 19 deletions

View File

@ -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) }

View File

@ -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")

View File

@ -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(
"<generic-invoke-unary>",
"""
val delta = 2
{ x -> x + delta }
""".trimIndent()
),
Script.defaultImportManager
)
val nullaryLambda = Compiler.compile(
Source(
"<generic-invoke-nullary>",
"""
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("<generic-invoke-pos>", ""), 0, 0),
ObjString("receiver"),
Arguments(ObjInt.of(40))
).toInt()
)
}
@Test
fun statementCallUsesPreparedLambdaFastPath() = runTest {
val unaryLambda = Compiler.compile(
Source(
"<statement-call-unary>",
"""
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<ScriptError> {