Step 24D: bytecode closure scope

This commit is contained in:
Sergey Chernov 2026-02-11 11:36:19 +03:00
parent 1271f347bd
commit c14c7d43d9
6 changed files with 71 additions and 17 deletions

View File

@ -102,9 +102,9 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
- [x] Keep Scope creation only for reflection/Kotlin interop paths. - [x] Keep Scope creation only for reflection/Kotlin interop paths.
- [x] JVM tests must be green before commit. - [x] JVM tests must be green before commit.
- [x] Step 24D: Eliminate `ClosureScope` usage on bytecode execution paths. - [x] Step 24D: Eliminate `ClosureScope` usage on bytecode execution paths.
- [ ] Avoid `ClosureScope` in bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty). - [x] Avoid `ClosureScope` in bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty).
- [ ] Keep interpreter path using `ClosureScope` until interpreter removal. - [x] Keep interpreter path using `ClosureScope` until interpreter removal.
- [ ] JVM tests must be green before commit. - [x] JVM tests must be green before commit.
- [x] Step 24E: Isolate interpreter-only capture logic. - [x] Step 24E: Isolate interpreter-only capture logic.
- [ ] Mark `resolveCaptureRecord` paths as interpreter-only. - [ ] Mark `resolveCaptureRecord` paths as interpreter-only.
- [ ] Guard or delete any bytecode path that tries to sync captures into scopes. - [ ] Guard or delete any bytecode path that tries to sync captures into scopes.

View File

@ -84,6 +84,32 @@ class ClosureScope(
} }
} }
/**
* Bytecode-oriented closure scope that keeps the call scope parent chain for stack traces
* while carrying the lexical closure for `this` variants and module resolution.
* Unlike [ClosureScope], it does not override name lookup.
*/
class BytecodeClosureScope(
val callScope: Scope,
val closureScope: Scope,
private val preferredThisType: String? = null
) :
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
init {
val desired = preferredThisType?.let { typeName ->
callScope.thisVariants.firstOrNull { it.objClass.className == typeName }
}
val primaryThis = closureScope.thisObj
val merged = ArrayList<Obj>(callScope.thisVariants.size + closureScope.thisVariants.size + 1)
desired?.let { merged.add(it) }
merged.addAll(callScope.thisVariants)
merged.addAll(closureScope.thisVariants)
setThisVariants(primaryThis, merged)
this.currentClassCtx = closureScope.currentClassCtx ?: callScope.currentClassCtx
}
}
class ApplyScope(val callScope: Scope, val applied: Scope) : class ApplyScope(val callScope: Scope, val applied: Scope) :
Scope(callScope, thisObj = applied.thisObj) { Scope(callScope, thisObj = applied.thisObj) {

View File

@ -6929,8 +6929,15 @@ class Compiler(
// restore closure where the function was defined, and making a copy of it // restore closure where the function was defined, and making a copy of it
// for local space. If there is no closure, we are in, say, class context where // for local space. If there is no closure, we are in, say, class context where
// the closure is in the class initialization and we needn't more: // the closure is in the class initialization and we needn't more:
val context = closureBox.closure?.let { ClosureScope(callerContext, it) } val context = closureBox.closure?.let { closure ->
?: callerContext if (fnStatements is BytecodeStatement) {
callerContext.applyClosureForBytecode(closure).also {
it.args = callerContext.args
}
} else {
ClosureScope(callerContext, closure)
}
} ?: callerContext
// Capacity hint: parameters + declared locals + small overhead // Capacity hint: parameters + declared locals + small overhead
val capacityHint = paramNames.size + fnLocalDecls + 4 val capacityHint = paramNames.size + fnLocalDecls + 4

View File

@ -192,7 +192,14 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec):
override val pos: Pos = spec.startPos override val pos: Pos = spec.startPos
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val result = (scope.thisObj as? ObjInstance)?.let { i -> val result = (scope.thisObj as? ObjInstance)?.let { i ->
compiledFnBody.execute(ClosureScope(scope, i.instanceScope)) val execScope = if (compiledFnBody is net.sergeych.lyng.bytecode.BytecodeStatement) {
scope.applyClosureForBytecode(i.instanceScope).also {
it.args = scope.args
}
} else {
ClosureScope(scope, i.instanceScope)
}
compiledFnBody.execute(execScope)
} ?: compiledFnBody.execute(scope.thisObj.autoInstanceScope(scope)) } ?: compiledFnBody.execute(scope.thisObj.autoInstanceScope(scope))
return result return result
} }

View File

@ -836,17 +836,7 @@ open class Scope(
ClosureScope(this, closure, preferredThisType) ClosureScope(this, closure, preferredThisType)
internal fun applyClosureForBytecode(closure: Scope, preferredThisType: String? = null): Scope { internal fun applyClosureForBytecode(closure: Scope, preferredThisType: String? = null): Scope {
val context = createChildScope(newThisObj = closure.thisObj) return BytecodeClosureScope(this, closure, preferredThisType)
val desired = preferredThisType?.let { typeName ->
thisVariants.firstOrNull { it.objClass.className == typeName }
}
val merged = ArrayList<Obj>(thisVariants.size + closure.thisVariants.size + 1)
desired?.let { merged.add(it) }
merged.addAll(thisVariants)
merged.addAll(closure.thisVariants)
context.setThisVariants(closure.thisObj, merged)
context.currentClassCtx = closure.currentClassCtx ?: currentClassCtx
return context
} }
/** /**

View File

@ -2004,6 +2004,8 @@ class CmdFrame(
current.parent?.let { queue.add(it) } current.parent?.let { queue.add(it) }
if (current is ClosureScope) { if (current is ClosureScope) {
queue.add(current.closureScope) queue.add(current.closureScope)
} else if (current is BytecodeClosureScope) {
queue.add(current.closureScope)
} else if (current is ApplyScope) { } else if (current is ApplyScope) {
queue.add(current.applied) queue.add(current.applied)
} }
@ -2023,6 +2025,28 @@ class CmdFrame(
current.parent?.let { queue.add(it) } current.parent?.let { queue.add(it) }
if (current is ClosureScope) { if (current is ClosureScope) {
queue.add(current.closureScope) queue.add(current.closureScope)
} else if (current is BytecodeClosureScope) {
queue.add(current.closureScope)
} else if (current is ApplyScope) {
queue.add(current.applied)
}
}
return null
}
private fun findScopeWithRecord(scope: Scope, name: String): Scope? {
val visited = HashSet<Scope>(16)
val queue = ArrayDeque<Scope>()
queue.add(scope)
while (queue.isNotEmpty()) {
val current = queue.removeFirst()
if (!visited.add(current)) continue
if (current.getLocalRecordDirect(name) != null) return current
current.parent?.let { queue.add(it) }
if (current is ClosureScope) {
queue.add(current.closureScope)
} else if (current is BytecodeClosureScope) {
queue.add(current.closureScope)
} else if (current is ApplyScope) { } else if (current is ApplyScope) {
queue.add(current.applied) queue.add(current.applied)
} }