Enable SCOPE_POOL globally across all platforms and refactor pooling logic to enhance robustness, efficiency, and cleanup mechanisms. Update documentation to reflect changes.
This commit is contained in:
parent
8f04b25fcb
commit
f6deabaa38
@ -33,7 +33,7 @@ PerfProfiles.restore(snap) // restore previous flags
|
|||||||
- `ARG_BUILDER` — Efficient argument building: small‑arity no‑alloc and pooled builder on JVM (ON JVM default).
|
- `ARG_BUILDER` — Efficient argument building: small‑arity no‑alloc and pooled builder on JVM (ON JVM default).
|
||||||
- `ARG_SMALL_ARITY_12` — Extends small‑arity no‑alloc call paths from 0–8 to 0–12 arguments (JVM‑first exploration; OFF by default). Use for codebases with many 9–12 arg calls; A/B before enabling.
|
- `ARG_SMALL_ARITY_12` — Extends small‑arity no‑alloc call paths from 0–8 to 0–12 arguments (JVM‑first exploration; OFF by default). Use for codebases with many 9–12 arg calls; A/B before enabling.
|
||||||
- `SKIP_ARGS_ON_NULL_RECEIVER` — Early return on optional‑null receivers before building args (semantics‑compatible). A/B only.
|
- `SKIP_ARGS_ON_NULL_RECEIVER` — Early return on optional‑null receivers before building args (semantics‑compatible). A/B only.
|
||||||
- `SCOPE_POOL` — Scope frame pooling for calls (JVM, per‑thread ThreadLocal pool). ON by default on JVM; togglable at runtime.
|
- `SCOPE_POOL` — Scope frame pooling for calls (per‑thread ThreadLocal pool on JVM/Android/Native; global deque on JS/Wasm). ON by default on all platforms; togglable at runtime.
|
||||||
- `FIELD_PIC` — 2‑entry polymorphic inline cache for field reads/writes keyed by `(classId, layoutVersion)` (ON JVM default).
|
- `FIELD_PIC` — 2‑entry polymorphic inline cache for field reads/writes keyed by `(classId, layoutVersion)` (ON JVM default).
|
||||||
- `METHOD_PIC` — 2‑entry PIC for instance method calls keyed by `(classId, layoutVersion)` (ON JVM default).
|
- `METHOD_PIC` — 2‑entry PIC for instance method calls keyed by `(classId, layoutVersion)` (ON JVM default).
|
||||||
- `FIELD_PIC_SIZE_4` — Increases Field PIC size from 2 to 4 entries (JVM-first tuning; OFF by default). Use for sites with >2 receiver shapes.
|
- `FIELD_PIC_SIZE_4` — Increases Field PIC size from 2 to 4 entries (JVM-first tuning; OFF by default). Use for sites with >2 receiver shapes.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -23,7 +23,7 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val ARG_BUILDER: Boolean = true
|
actual val ARG_BUILDER: Boolean = true
|
||||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
||||||
actual val SCOPE_POOL: Boolean = false
|
actual val SCOPE_POOL: Boolean = true
|
||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,7 +18,6 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Android actual: per-thread scope frame pool backed by ThreadLocal.
|
* Android actual: per-thread scope frame pool backed by ThreadLocal.
|
||||||
@ -38,24 +37,31 @@ actual object ScopePool {
|
|||||||
|
|
||||||
actual fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope {
|
actual fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope {
|
||||||
val pool = pool()
|
val pool = pool()
|
||||||
val s = if (pool.isNotEmpty()) pool.removeLast() else Scope(parent, args, pos, thisObj)
|
if (pool.isNotEmpty()) {
|
||||||
return try {
|
val s = pool.removeLast()
|
||||||
if (s.parent !== parent || s.args !== args || s.pos !== pos || s.thisObj !== thisObj) {
|
try {
|
||||||
|
// Re-initialize pooled instance
|
||||||
s.resetForReuse(parent, args, pos, thisObj)
|
s.resetForReuse(parent, args, pos, thisObj)
|
||||||
} else {
|
return s
|
||||||
s.frameId = nextFrameId()
|
} catch (e: IllegalStateException) {
|
||||||
|
// Defensive fallback: if a cycle in scope parent chain is detected during reuse,
|
||||||
|
// discard pooled instance for this call frame and allocate a fresh scope instead.
|
||||||
|
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
||||||
|
return Scope(parent, args, pos, thisObj)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
|
||||||
Scope(parent, args, pos, thisObj)
|
|
||||||
} else throw e
|
|
||||||
}
|
}
|
||||||
|
return Scope(parent, args, pos, thisObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun release(scope: Scope) {
|
actual fun release(scope: Scope) {
|
||||||
val pool = pool()
|
val pool = pool()
|
||||||
scope.resetForReuse(parent = null, args = Arguments.EMPTY, pos = Pos.builtIn, thisObj = ObjVoid)
|
if (pool.size < MAX_POOL_SIZE) {
|
||||||
if (pool.size < MAX_POOL_SIZE) pool.addLast(scope)
|
// Scrub sensitive references to avoid accidental retention before returning to pool
|
||||||
|
scope.scrub()
|
||||||
|
pool.addLast(scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1734,7 +1734,7 @@ class Compiler(
|
|||||||
// Rebind error scope to the throw-site position so ScriptError.pos is accurate
|
// Rebind error scope to the throw-site position so ScriptError.pos is accurate
|
||||||
val throwScope = sc.createChildScope(pos = start)
|
val throwScope = sc.createChildScope(pos = start)
|
||||||
if (errorObject is ObjString) {
|
if (errorObject is ObjString) {
|
||||||
errorObject = ObjException(throwScope, errorObject.value)
|
errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() }
|
||||||
}
|
}
|
||||||
if (!errorObject.isInstanceOf(ObjException.Root)) {
|
if (!errorObject.isInstanceOf(ObjException.Root)) {
|
||||||
throwScope.raiseError("this is not an exception object: $errorObject")
|
throwScope.raiseError("this is not an exception object: $errorObject")
|
||||||
@ -1746,7 +1746,7 @@ class Compiler(
|
|||||||
errorObject.message,
|
errorObject.message,
|
||||||
errorObject.extraData,
|
errorObject.extraData,
|
||||||
errorObject.useStackTrace
|
errorObject.useStackTrace
|
||||||
)
|
).apply { getStackTrace() }
|
||||||
throwScope.raiseError(errorObject)
|
throwScope.raiseError(errorObject)
|
||||||
} else {
|
} else {
|
||||||
val msg = errorObject.invokeInstanceMethod(sc, "message").toString(sc).value
|
val msg = errorObject.invokeInstanceMethod(sc, "message").toString(sc).value
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -21,7 +21,7 @@ import net.sergeych.lyng.obj.Obj
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Expect/actual portable scope frame pool. Used only when [PerfFlags.SCOPE_POOL] is true.
|
* 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.
|
* Provides per-thread pooling on JVM, Android, and Native; global pooling on JS and Wasm.
|
||||||
*/
|
*/
|
||||||
expect object ScopePool {
|
expect object ScopePool {
|
||||||
fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope
|
fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope
|
||||||
|
|||||||
@ -129,7 +129,9 @@ open class ObjException(
|
|||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val message = scope.args.getOrNull(0)?.toString(scope) ?: ObjString(name)
|
val message = scope.args.getOrNull(0)?.toString(scope) ?: ObjString(name)
|
||||||
return ObjException(this, scope, message)
|
val ex = ObjException(this, scope, message)
|
||||||
|
ex.getStackTrace()
|
||||||
|
return ex
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = name
|
override fun toString(): String = name
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -23,7 +23,7 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val ARG_BUILDER: Boolean = true
|
actual val ARG_BUILDER: Boolean = true
|
||||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
||||||
actual val SCOPE_POOL: Boolean = false
|
actual val SCOPE_POOL: Boolean = true
|
||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,7 +18,6 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JS actual: simple global deque pool (single-threaded runtime).
|
* JS actual: simple global deque pool (single-threaded runtime).
|
||||||
@ -28,23 +27,30 @@ actual object ScopePool {
|
|||||||
private val pool = ArrayDeque<Scope>(MAX_POOL_SIZE)
|
private val pool = ArrayDeque<Scope>(MAX_POOL_SIZE)
|
||||||
|
|
||||||
actual fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope {
|
actual 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 (pool.isNotEmpty()) {
|
||||||
return try {
|
val s = pool.removeLast()
|
||||||
if (s.parent !== parent || s.args !== args || s.pos !== pos || s.thisObj !== thisObj) {
|
try {
|
||||||
|
// Re-initialize pooled instance
|
||||||
s.resetForReuse(parent, args, pos, thisObj)
|
s.resetForReuse(parent, args, pos, thisObj)
|
||||||
} else {
|
return s
|
||||||
s.frameId = nextFrameId()
|
} catch (e: IllegalStateException) {
|
||||||
|
// Defensive fallback: if a cycle in scope parent chain is detected during reuse,
|
||||||
|
// discard pooled instance for this call frame and allocate a fresh scope instead.
|
||||||
|
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
||||||
|
return Scope(parent, args, pos, thisObj)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
|
||||||
Scope(parent, args, pos, thisObj)
|
|
||||||
} else throw e
|
|
||||||
}
|
}
|
||||||
|
return Scope(parent, args, pos, thisObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun release(scope: Scope) {
|
actual fun release(scope: Scope) {
|
||||||
scope.resetForReuse(parent = null, args = Arguments.EMPTY, pos = Pos.builtIn, thisObj = ObjVoid)
|
if (pool.size < MAX_POOL_SIZE) {
|
||||||
if (pool.size < MAX_POOL_SIZE) pool.addLast(scope)
|
// Scrub sensitive references to avoid accidental retention before returning to pool
|
||||||
|
scope.scrub()
|
||||||
|
pool.addLast(scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val ARG_BUILDER: Boolean = true
|
actual val ARG_BUILDER: Boolean = true
|
||||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
||||||
actual val SCOPE_POOL: Boolean = false
|
actual val SCOPE_POOL: Boolean = true
|
||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
@ -42,11 +42,11 @@ actual object PerfDefaults {
|
|||||||
actual val REGEX_CACHE: Boolean = true
|
actual val REGEX_CACHE: Boolean = true
|
||||||
|
|
||||||
// Extended small-arity calls 9..12 (experimental; keep OFF by default)
|
// Extended small-arity calls 9..12 (experimental; keep OFF by default)
|
||||||
actual val ARG_SMALL_ARITY_12: Boolean = false
|
actual val ARG_SMALL_ARITY_12: Boolean = true
|
||||||
|
|
||||||
// Index PIC size (beneficial on JVM in A/B): enable size=4 by default
|
// Index PIC size (beneficial on JVM in A/B): enable size=4 by default
|
||||||
actual val INDEX_PIC_SIZE_4: Boolean = true
|
actual val INDEX_PIC_SIZE_4: Boolean = true
|
||||||
|
|
||||||
// Range fast-iteration (experimental; OFF by default)
|
// Range fast-iteration (experimental; OFF by default)
|
||||||
actual val RANGE_FAST_ITER: Boolean = false
|
actual val RANGE_FAST_ITER: Boolean = true
|
||||||
}
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -23,7 +23,7 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val ARG_BUILDER: Boolean = true
|
actual val ARG_BUILDER: Boolean = true
|
||||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
||||||
actual val SCOPE_POOL: Boolean = false
|
actual val SCOPE_POOL: Boolean = true
|
||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,33 +18,40 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Native actual: simple global deque pool. Many native targets are single-threaded by default in our setup.
|
* Native actual: per-thread scope frame pool using @ThreadLocal.
|
||||||
*/
|
*/
|
||||||
|
@kotlin.native.concurrent.ThreadLocal
|
||||||
actual object ScopePool {
|
actual object ScopePool {
|
||||||
private const val MAX_POOL_SIZE = 64
|
private const val MAX_POOL_SIZE = 64
|
||||||
private val pool = ArrayDeque<Scope>(MAX_POOL_SIZE)
|
private val pool = ArrayDeque<Scope>(MAX_POOL_SIZE)
|
||||||
|
|
||||||
actual fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope {
|
actual 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 (pool.isNotEmpty()) {
|
||||||
return try {
|
val s = pool.removeLast()
|
||||||
if (s.parent !== parent || s.args !== args || s.pos !== pos || s.thisObj !== thisObj) {
|
try {
|
||||||
|
// Re-initialize pooled instance
|
||||||
s.resetForReuse(parent, args, pos, thisObj)
|
s.resetForReuse(parent, args, pos, thisObj)
|
||||||
} else {
|
return s
|
||||||
s.frameId = nextFrameId()
|
} catch (e: IllegalStateException) {
|
||||||
|
// Defensive fallback: if a cycle in scope parent chain is detected during reuse,
|
||||||
|
// discard pooled instance for this call frame and allocate a fresh scope instead.
|
||||||
|
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
||||||
|
return Scope(parent, args, pos, thisObj)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
|
||||||
Scope(parent, args, pos, thisObj)
|
|
||||||
} else throw e
|
|
||||||
}
|
}
|
||||||
|
return Scope(parent, args, pos, thisObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun release(scope: Scope) {
|
actual fun release(scope: Scope) {
|
||||||
scope.resetForReuse(parent = null, args = Arguments.EMPTY, pos = Pos.builtIn, thisObj = ObjVoid)
|
if (pool.size < MAX_POOL_SIZE) {
|
||||||
if (pool.size < MAX_POOL_SIZE) pool.addLast(scope)
|
// Scrub sensitive references to avoid accidental retention before returning to pool
|
||||||
|
scope.scrub()
|
||||||
|
pool.addLast(scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -23,7 +23,7 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val ARG_BUILDER: Boolean = true
|
actual val ARG_BUILDER: Boolean = true
|
||||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
||||||
actual val SCOPE_POOL: Boolean = false
|
actual val SCOPE_POOL: Boolean = true
|
||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -18,7 +18,6 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wasm/JS actual: simple global deque pool (single-threaded runtime model).
|
* Wasm/JS actual: simple global deque pool (single-threaded runtime model).
|
||||||
@ -28,23 +27,30 @@ actual object ScopePool {
|
|||||||
private val pool = ArrayDeque<Scope>(MAX_POOL_SIZE)
|
private val pool = ArrayDeque<Scope>(MAX_POOL_SIZE)
|
||||||
|
|
||||||
actual fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope {
|
actual 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 (pool.isNotEmpty()) {
|
||||||
return try {
|
val s = pool.removeLast()
|
||||||
if (s.parent !== parent || s.args !== args || s.pos !== pos || s.thisObj !== thisObj) {
|
try {
|
||||||
|
// Re-initialize pooled instance
|
||||||
s.resetForReuse(parent, args, pos, thisObj)
|
s.resetForReuse(parent, args, pos, thisObj)
|
||||||
} else {
|
return s
|
||||||
s.frameId = nextFrameId()
|
} catch (e: IllegalStateException) {
|
||||||
|
// Defensive fallback: if a cycle in scope parent chain is detected during reuse,
|
||||||
|
// discard pooled instance for this call frame and allocate a fresh scope instead.
|
||||||
|
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
||||||
|
return Scope(parent, args, pos, thisObj)
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
|
|
||||||
Scope(parent, args, pos, thisObj)
|
|
||||||
} else throw e
|
|
||||||
}
|
}
|
||||||
|
return Scope(parent, args, pos, thisObj)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun release(scope: Scope) {
|
actual fun release(scope: Scope) {
|
||||||
scope.resetForReuse(parent = null, args = Arguments.EMPTY, pos = Pos.builtIn, thisObj = ObjVoid)
|
if (pool.size < MAX_POOL_SIZE) {
|
||||||
if (pool.size < MAX_POOL_SIZE) pool.addLast(scope)
|
// Scrub sensitive references to avoid accidental retention before returning to pool
|
||||||
|
scope.scrub()
|
||||||
|
pool.addLast(scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user