Use fast compiled callbacks in dynamic and flow helpers

This commit is contained in:
Sergey Chernov 2026-04-21 18:06:31 +03:00
parent c80900c503
commit 3be2892025
5 changed files with 52 additions and 11 deletions

View File

@ -809,7 +809,8 @@ open class ObjClass(
if (initStmt is net.sergeych.lyng.Statement) { if (initStmt is net.sergeych.lyng.Statement) {
executeBytecodeWithSeed(instance.instanceScope, initStmt, "instance init") executeBytecodeWithSeed(instance.instanceScope, initStmt, "instance init")
} else { } else {
initStmt.callOn(instance.instanceScope) (initStmt as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(instance.instanceScope)
?: initStmt.callOn(instance.instanceScope)
} }
} }
} finally { } finally {
@ -821,13 +822,14 @@ open class ObjClass(
c.instanceConstructor?.let { ctor -> c.instanceConstructor?.let { ctor ->
val execScope = val execScope =
instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance) instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance)
ctor.callOn(execScope) (ctor as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(execScope) ?: ctor.callOn(execScope)
} }
} }
} }
suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj { suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj {
return callOn(scope.createChildScope(Arguments(*plainArgs))) val child = scope.createChildScope(Arguments(*plainArgs))
return (this as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: callOn(child)
} }

View File

@ -142,7 +142,7 @@ object ObjDecimalSupport {
} }
val child = requireScope().createChildScope() val child = requireScope().createChildScope()
child.addConst(decimalContextVar, context) child.addConst(decimalContextVar, context)
block.callOn(child) (block as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: block.callOn(child)
} }
registerBuiltinConversions(decimalClass) registerBuiltinConversions(decimalClass)
registerInterop(decimalClass) registerInterop(decimalClass)

View File

@ -64,13 +64,19 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
return (callback as? BytecodeLambdaCallable)?.rebindClosure(context) ?: callback return (callback as? BytecodeLambdaCallable)?.rebindClosure(context) ?: callback
} }
private suspend fun callCallback(callback: Obj, child: Scope): Obj {
return (callback as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: callback.callOn(child)
}
/** /**
* Use read callback to dynamically resolve the field name. Note that it does not work * Use read callback to dynamically resolve the field name. Note that it does not work
* with method invocation which is implemented separately in [invokeInstanceMethod] below. * with method invocation which is implemented separately in [invokeInstanceMethod] below.
*/ */
override suspend fun readField(scope: Scope, name: String): ObjRecord { override suspend fun readField(scope: Scope, name: String): ObjRecord {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
return readCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name))))?.let { return readCallback?.let { callback ->
callCallback(callback, execBase.createChildScope(Arguments(ObjString(name))))
}?.let {
if (writeCallback != null) if (writeCallback != null)
it.asMutable it.asMutable
else else
@ -90,26 +96,34 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
onNotFoundResult: (suspend () -> Obj?)? onNotFoundResult: (suspend () -> Obj?)?
): Obj { ): Obj {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
val over = readCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name)))) val over = readCallback?.let { callback ->
callCallback(callback, execBase.createChildScope(Arguments(ObjString(name))))
}
return over?.invoke(scope, scope.thisObj, args) return over?.invoke(scope, scope.thisObj, args)
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult) ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
} }
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
writeCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name), newValue))) writeCallback?.let { callback ->
callCallback(callback, execBase.createChildScope(Arguments(ObjString(name), newValue)))
}
?: super.writeField(scope, name, newValue) ?: super.writeField(scope, name, newValue)
} }
override suspend fun getAt(scope: Scope, index: Obj): Obj { override suspend fun getAt(scope: Scope, index: Obj): Obj {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
return readCallback?.callOn(execBase.createChildScope(Arguments(index))) return readCallback?.let { callback ->
callCallback(callback, execBase.createChildScope(Arguments(index)))
}
?: super.getAt(scope, index) ?: super.getAt(scope, index)
} }
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) { override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
writeCallback?.callOn(execBase.createChildScope(Arguments(index, newValue))) writeCallback?.let { callback ->
callCallback(callback, execBase.createChildScope(Arguments(index, newValue)))
}
?: super.putAt(scope, index, newValue) ?: super.putAt(scope, index, newValue)
} }
@ -124,7 +138,7 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
// Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames. // Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames.
// Module scope should stay late-bound to allow extern class rebinding and similar updates. // Module scope should stay late-bound to allow extern class rebinding and similar updates.
delegate.builderScope = if (scope is net.sergeych.lyng.ModuleScope) null else scope.snapshotForClosure() delegate.builderScope = if (scope is net.sergeych.lyng.ModuleScope) null else scope.snapshotForClosure()
builder.callOn(buildScope) (builder as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(buildScope) ?: builder.callOn(buildScope)
return delegate return delegate
} }

View File

@ -81,7 +81,7 @@ private suspend fun createLyngFlowInput(scope: Scope, producer: Obj, ownerSessio
val runProducer: suspend CoroutineScope.() -> Unit = { val runProducer: suspend CoroutineScope.() -> Unit = {
var failure: Throwable? = null var failure: Throwable? = null
try { try {
producer.callOn(builderScope) (producer as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(builderScope) ?: producer.callOn(builderScope)
} catch (x: ScriptFlowIsNoMoreCollected) { } catch (x: ScriptFlowIsNoMoreCollected) {
// premature flow closing, OK // premature flow closing, OK
} catch (x: Throwable) { } catch (x: Throwable) {

View File

@ -24,6 +24,7 @@ import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.Source import net.sergeych.lyng.Source
import net.sergeych.lyng.Statement import net.sergeych.lyng.Statement
import net.sergeych.lyng.asFacade import net.sergeych.lyng.asFacade
import net.sergeych.lyng.obj.ObjDynamic
import net.sergeych.lyng.obj.ObjInt import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjString import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.obj.toInt import net.sergeych.lyng.obj.toInt
@ -181,6 +182,30 @@ 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 dynamicCallbacksUsePreparedLambdaFastPath() = runTest {
val script = Compiler.compile(
Source(
"<dynamic-fast-callbacks>",
"""
var seen = ""
dynamic {
get { name -> name + seen }
set { name, value -> seen = name + "=" + value }
}
""".trimIndent()
),
Script.defaultImportManager
)
val scope = Script.newScope()
val dynamic = script.execute(scope) as ObjDynamic
assertEquals("foo", (dynamic.readField(scope, "foo").value as ObjString).value)
dynamic.writeField(scope, "foo", ObjInt.of(7))
assertEquals("barfoo=7", (dynamic.getAt(scope, ObjString("bar")) as ObjString).value)
}
@Test @Test
fun subjectlessWhenReportsScriptError() = runTest { fun subjectlessWhenReportsScriptError() = runTest {
val ex = assertFailsWith<ScriptError> { val ex = assertFailsWith<ScriptError> {