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() scope.raiseNotImplemented()
} }
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj { private suspend fun invokeWithBoundScope(
val usePool = PerfFlags.SCOPE_POOL && this !is Statement 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) { return if (usePool) {
scope.withChildFrame(args, newThisObj = thisObj) { child -> scope.withChildFrame(args, newThisObj = thisObj) { child ->
if (declaringClass != null) child.currentClassCtx = declaringClass if (declaringClass != null) child.currentClassCtx = declaringClass
(this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child) (this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child)
} }
} else { } 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 if (declaringClass != null) it.currentClassCtx = declaringClass
} }
(this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child) (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 = suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
callOn( invokeWithBoundScope(scope, Arguments(args.toList()), thisObj)
scope.createChildScope(
scope.pos,
args = Arguments(args.toList()),
newThisObj = thisObj
)
)
suspend fun invoke(scope: Scope, thisObj: Obj): Obj = suspend fun invoke(scope: Scope, thisObj: Obj): Obj =
callOn( invokeWithBoundScope(scope, Arguments.EMPTY, thisObj)
scope.createChildScope(
scope.pos,
args = Arguments.EMPTY,
newThisObj = thisObj
)
)
suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj = 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) } val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }

View File

@ -58,7 +58,10 @@ abstract class Statement(
val type = ObjClass("Callable") 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 { protected fun bytecodeOnly(scope: Scope, label: String): Nothing {
return scope.raiseIllegalState("bytecode-only execution is required; $label needs compiled bytecode") return scope.raiseIllegalState("bytecode-only execution is required; $label needs compiled bytecode")

View File

@ -17,10 +17,12 @@
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Compiler import net.sergeych.lyng.Compiler
import net.sergeych.lyng.Arguments import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.Source import net.sergeych.lyng.Source
import net.sergeych.lyng.Statement
import net.sergeych.lyng.asFacade import net.sergeych.lyng.asFacade
import net.sergeych.lyng.obj.ObjInt import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjString import net.sergeych.lyng.obj.ObjString
@ -88,6 +90,66 @@ class CompilerVmReviewRegressionTest {
assertEquals(42, scope.asFacade().call(callable, Arguments(ObjInt.of(40))).toInt()) 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 @Test
fun subjectlessWhenReportsScriptError() = runTest { fun subjectlessWhenReportsScriptError() = runTest {
val ex = assertFailsWith<ScriptError> { val ex = assertFailsWith<ScriptError> {