From 3be28920256e837daff24712368df60a2ffd871a Mon Sep 17 00:00:00 2001 From: sergeych Date: Tue, 21 Apr 2026 18:06:31 +0300 Subject: [PATCH] Use fast compiled callbacks in dynamic and flow helpers --- .../kotlin/net/sergeych/lyng/obj/ObjClass.kt | 8 +++--- .../sergeych/lyng/obj/ObjDecimalSupport.kt | 2 +- .../net/sergeych/lyng/obj/ObjDynamic.kt | 26 ++++++++++++++----- .../kotlin/net/sergeych/lyng/obj/ObjFlow.kt | 2 +- .../kotlin/CompilerVmReviewRegressionTest.kt | 25 ++++++++++++++++++ 5 files changed, 52 insertions(+), 11 deletions(-) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt index d7d99ee..3d6efbb 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -809,7 +809,8 @@ open class ObjClass( if (initStmt is net.sergeych.lyng.Statement) { executeBytecodeWithSeed(instance.instanceScope, initStmt, "instance init") } else { - initStmt.callOn(instance.instanceScope) + (initStmt as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(instance.instanceScope) + ?: initStmt.callOn(instance.instanceScope) } } } finally { @@ -821,13 +822,14 @@ open class ObjClass( c.instanceConstructor?.let { ctor -> val execScope = 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 { - return callOn(scope.createChildScope(Arguments(*plainArgs))) + val child = scope.createChildScope(Arguments(*plainArgs)) + return (this as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: callOn(child) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt index b410e84..608537e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt @@ -142,7 +142,7 @@ object ObjDecimalSupport { } val child = requireScope().createChildScope() child.addConst(decimalContextVar, context) - block.callOn(child) + (block as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: block.callOn(child) } registerBuiltinConversions(decimalClass) registerInterop(decimalClass) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt index 1a602ba..a7e1be6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt @@ -64,13 +64,19 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n 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 * with method invocation which is implemented separately in [invokeInstanceMethod] below. */ override suspend fun readField(scope: Scope, name: String): ObjRecord { 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) it.asMutable else @@ -90,26 +96,34 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n onNotFoundResult: (suspend () -> Obj?)? ): Obj { 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) ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult) } override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { 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) } override suspend fun getAt(scope: Scope, index: Obj): Obj { 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) } override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) { 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) } @@ -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. // 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() - builder.callOn(buildScope) + (builder as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(buildScope) ?: builder.callOn(buildScope) return delegate } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt index cd71211..60fae77 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt @@ -81,7 +81,7 @@ private suspend fun createLyngFlowInput(scope: Scope, producer: Obj, ownerSessio val runProducer: suspend CoroutineScope.() -> Unit = { var failure: Throwable? = null try { - producer.callOn(builderScope) + (producer as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(builderScope) ?: producer.callOn(builderScope) } catch (x: ScriptFlowIsNoMoreCollected) { // premature flow closing, OK } catch (x: Throwable) { diff --git a/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt b/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt index 65dad3e..e36e04c 100644 --- a/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt +++ b/lynglib/src/commonTest/kotlin/CompilerVmReviewRegressionTest.kt @@ -24,6 +24,7 @@ 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.ObjDynamic import net.sergeych.lyng.obj.ObjInt import net.sergeych.lyng.obj.ObjString import net.sergeych.lyng.obj.toInt @@ -181,6 +182,30 @@ class CompilerVmReviewRegressionTest { assertEquals(42, scope.asFacade().call(callable, Arguments(ObjInt.of(40))).toInt()) } + @Test + fun dynamicCallbacksUsePreparedLambdaFastPath() = runTest { + val script = Compiler.compile( + Source( + "", + """ + 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 fun subjectlessWhenReportsScriptError() = runTest { val ex = assertFailsWith {