JVM multithreaded scope pool now turned on by default

This commit is contained in:
sergeych committed 2025-11-10 23:08:58 +01:00
1 parent 38c1b3c209
commit 2af5852d44
16 files changed
+490 -35

No files matched your search

@@ -31,7 +31,7 @@ import net.sergeych.lyng.obj.ObjList
for (pa in this) {
if (pa.isSplat) { hasSplat = true; break }
count++
if (count > 3) break
if (count > 8) break
}
if (!hasSplat && count == this.size) {
val quick = when (count) {
@@ -63,6 +63,36 @@ import net.sergeych.lyng.obj.ObjList
val a4 = this.elementAt(4).value.execute(scope)
Arguments(listOf(a0, a1, a2, a3, a4), tailBlockMode)
}
6 -> {
val a0 = this.elementAt(0).value.execute(scope)
val a1 = this.elementAt(1).value.execute(scope)
val a2 = this.elementAt(2).value.execute(scope)
val a3 = this.elementAt(3).value.execute(scope)
val a4 = this.elementAt(4).value.execute(scope)
val a5 = this.elementAt(5).value.execute(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5), tailBlockMode)
}
7 -> {
val a0 = this.elementAt(0).value.execute(scope)
val a1 = this.elementAt(1).value.execute(scope)
val a2 = this.elementAt(2).value.execute(scope)
val a3 = this.elementAt(3).value.execute(scope)
val a4 = this.elementAt(4).value.execute(scope)
val a5 = this.elementAt(5).value.execute(scope)
val a6 = this.elementAt(6).value.execute(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6), tailBlockMode)
}
8 -> {
val a0 = this.elementAt(0).value.execute(scope)
val a1 = this.elementAt(1).value.execute(scope)
val a2 = this.elementAt(2).value.execute(scope)
val a3 = this.elementAt(3).value.execute(scope)
val a4 = this.elementAt(4).value.execute(scope)
val a5 = this.elementAt(5).value.execute(scope)
val a6 = this.elementAt(6).value.execute(scope)
val a7 = this.elementAt(7).value.execute(scope)
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7), tailBlockMode)
}
else -> null
}
if (quick != null) return quick
@@ -1,35 +1,12 @@
package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjVoid
/**
* Simple, portable scope frame pool. JVM-first optimization; for now it uses a small
* global deque. It is only used when [PerfFlags.SCOPE_POOL] is true.
*
* NOTE: This implementation is not thread-safe. It is acceptable for current single-threaded
* script execution and JVM tests. If we need cross-thread safety later, we will introduce
* platform-specific implementations.
* Expect/actual portable scope frame pool. Used only when [PerfFlags.SCOPE_POOL] is true.
* JVM actual provides a ThreadLocal-backed pool; other targets may use a simple global deque.
*/
object ScopePool {
private const val MAX_POOL_SIZE = 64
private val pool = ArrayDeque<Scope>(MAX_POOL_SIZE)
fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope {
val s = if (pool.isNotEmpty()) pool.removeLast() else Scope(parent, args, pos, thisObj)
// If we reused a scope, reset its state to behave as a fresh child frame
if (s.parent !== parent || s.args !== args || s.pos !== pos || s.thisObj !== thisObj) {
s.resetForReuse(parent, args, pos, thisObj)
} else {
// Even if equal by reference, refresh frameId to guarantee uniqueness
s.frameId = nextFrameId()
}
return s
}
fun release(scope: Scope) {
// Scrub sensitive references to avoid accidental retention
scope.resetForReuse(parent = null, args = Arguments.EMPTY, pos = Pos.builtIn, thisObj = ObjVoid)
if (pool.size < MAX_POOL_SIZE) pool.addLast(scope)
}
expect object ScopePool {
fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope
fun release(scope: Scope)
}
@@ -304,7 +304,10 @@ open class Obj {
}
fun autoInstanceScope(parent: Scope): Scope {
val scope = parent.createChildScope(newThisObj = this, args = parent.args)
// Create a stable instance scope whose parent is the provided parent scope directly,
// not a transient child that could be pooled and reset. This preserves proper name
// resolution (e.g., stdlib functions like sqrt) even when call frame pooling is enabled.
val scope = Scope(parent, parent.args, parent.pos, this)
for (m in objClass.members) {
scope.objects[m.key] = m.value
}
@@ -68,7 +68,11 @@ open class ObjClass(
override suspend fun callOn(scope: Scope): Obj {
val instance = ObjInstance(this)
instance.instanceScope = scope.createChildScope(newThisObj = instance, args = scope.args)
// Avoid capturing a transient (pooled) call frame as the parent of the instance scope.
// Bind instance scope to the caller's parent chain directly so name resolution (e.g., stdlib like sqrt)
// remains stable even when call frames are pooled and reused.
val stableParent = scope.parent
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
if (instanceConstructor != null) {
instanceConstructor!!.execute(instance.instanceScope)
}
@@ -37,7 +37,9 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
thisAs<ObjDeferred>().deferred.isCompleted.toObj()
}
addFn("isActive") {
thisAs<ObjDeferred>().deferred.isActive.toObj()
val d = thisAs<ObjDeferred>().deferred
// Cross-engine tolerant: treat any not-yet-completed deferred as active.
(!d.isCompleted).toObj()
}
addFn("isCancelled") {
thisAs<ObjDeferred>().deferred.isCancelled.toObj()