diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index 4b83f0f..de46c68 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -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] JVM tests must be green before commit. - [x] Step 24D: Eliminate `ClosureScope` usage on bytecode execution paths. - - [ ] Avoid `ClosureScope` in bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty). - - [ ] Keep interpreter path using `ClosureScope` until interpreter removal. - - [ ] JVM tests must be green before commit. + - [x] Avoid `ClosureScope` in bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty). + - [x] Keep interpreter path using `ClosureScope` until interpreter removal. + - [x] JVM tests must be green before commit. - [x] Step 24E: Isolate interpreter-only capture logic. - [ ] Mark `resolveCaptureRecord` paths as interpreter-only. - [ ] Guard or delete any bytecode path that tries to sync captures into scopes. diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt index e2b8658..59ae862 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt @@ -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(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) : Scope(callScope, thisObj = applied.thisObj) { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 2d5ac81..b116c31 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -6929,8 +6929,15 @@ class Compiler( // 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 // the closure is in the class initialization and we needn't more: - val context = closureBox.closure?.let { ClosureScope(callerContext, it) } - ?: callerContext + val context = closureBox.closure?.let { closure -> + if (fnStatements is BytecodeStatement) { + callerContext.applyClosureForBytecode(closure).also { + it.args = callerContext.args + } + } else { + ClosureScope(callerContext, closure) + } + } ?: callerContext // Capacity hint: parameters + declared locals + small overhead val capacityHint = paramNames.size + fnLocalDecls + 4 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt index 646b29e..bad5dc5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/FunctionDeclStatement.kt @@ -192,7 +192,14 @@ internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): override val pos: Pos = spec.startPos override suspend fun execute(scope: Scope): Obj { 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)) return result } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 265ca37..ea3cdc0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -836,17 +836,7 @@ open class Scope( ClosureScope(this, closure, preferredThisType) internal fun applyClosureForBytecode(closure: Scope, preferredThisType: String? = null): Scope { - val context = createChildScope(newThisObj = closure.thisObj) - val desired = preferredThisType?.let { typeName -> - thisVariants.firstOrNull { it.objClass.className == typeName } - } - val merged = ArrayList(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 + return BytecodeClosureScope(this, closure, preferredThisType) } /** diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index cb10310..0923a05 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -2004,6 +2004,8 @@ class CmdFrame( 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) { queue.add(current.applied) } @@ -2023,6 +2025,28 @@ class CmdFrame( 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) { + queue.add(current.applied) + } + } + return null + } + + private fun findScopeWithRecord(scope: Scope, name: String): Scope? { + val visited = HashSet(16) + val queue = ArrayDeque() + 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) { queue.add(current.applied) }