Step 24A: bytecode capture tables for lambdas
This commit is contained in:
parent
3ce9029162
commit
8f1c660f4e
@ -85,12 +85,37 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
|
||||
- [x] Force delegated locals into local slots (even module) and avoid scope-slot resolution.
|
||||
- [x] Drop opcode/runtime support for `ASSIGN_SCOPE_SLOT`.
|
||||
|
||||
## Frame-Only Execution (new, before interpreter removal)
|
||||
|
||||
- [x] Step 24A: Bytecode capture tables for lambdas (compiler only).
|
||||
- [x] Emit per-lambda capture tables containing (ownerFrameKind, ownerSlotId).
|
||||
- [x] Create captures only when detected; do not allocate scope slots.
|
||||
- [x] Add disassembler output for capture tables.
|
||||
- [x] JVM tests must be green before commit.
|
||||
- [ ] Step 24B: Frame-slot captures in bytecode runtime.
|
||||
- [ ] Build lambdas from bytecode + capture table (no capture names).
|
||||
- [ ] Read captured values via `FrameSlotRef` only.
|
||||
- [ ] Forbid `resolveCaptureRecord` in bytecode paths; keep only in interpreter.
|
||||
- [ ] JVM tests must be green before commit.
|
||||
- [ ] Step 24C: Remove scope local mirroring in bytecode execution.
|
||||
- [ ] Remove/disable any bytecode runtime code that writes locals into Scope for execution.
|
||||
- [ ] Keep Scope creation only for reflection/Kotlin interop paths.
|
||||
- [ ] JVM tests must be green before commit.
|
||||
- [ ] 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.
|
||||
- [ ] 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.
|
||||
- [ ] JVM tests must be green before commit.
|
||||
|
||||
## Interpreter Removal (next)
|
||||
|
||||
- [x] Step 25: Replace Statement-based declaration calls in bytecode.
|
||||
- [x] Add bytecode const/op for class/enum/function declarations (no `Statement` objects in constants).
|
||||
- [x] Replace `emitStatementCall` usage for `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement`.
|
||||
- [x] Add JVM disasm coverage to ensure module init has no `CALL_SLOT` to `Callable@...` for declarations.
|
||||
- [ ] Step 25: Replace Statement-based declaration calls in bytecode.
|
||||
- [ ] Add bytecode const/op for class/enum/function declarations (no `Statement` objects in constants).
|
||||
- [ ] Replace `emitStatementCall` usage for `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement`.
|
||||
- [ ] Add JVM disasm coverage to ensure module init has no `CALL_SLOT` to `Callable@...` for declarations.
|
||||
- [ ] Step 26: Bytecode-backed lambdas (remove `ValueFnRef` runtime execution).
|
||||
- [ ] Compile lambda bodies to bytecode and emit an opcode to create a callable from bytecode + capture plan.
|
||||
- [ ] Remove `containsValueFnRef`/`forceScopeSlots` workaround once lambdas are bytecode.
|
||||
|
||||
@ -168,6 +168,8 @@ class Compiler(
|
||||
private val callableReturnTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
||||
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||
private val lambdaReturnTypeByRef: MutableMap<ObjRef, ObjClass> = mutableMapOf()
|
||||
private val lambdaCaptureEntriesByRef: MutableMap<ValueFnRef, List<net.sergeych.lyng.bytecode.LambdaCaptureEntry>> =
|
||||
mutableMapOf()
|
||||
private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
|
||||
private val classScopeMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
|
||||
private val classScopeCallableMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
|
||||
@ -1561,7 +1563,8 @@ class Compiler(
|
||||
classFieldTypesByName = classFieldTypesByName,
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
) as BytecodeStatement
|
||||
unwrapped to bytecodeStmt.bytecodeFunction()
|
||||
} else {
|
||||
@ -1857,7 +1860,8 @@ class Compiler(
|
||||
classFieldTypesByName = classFieldTypesByName,
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
}
|
||||
|
||||
@ -1892,7 +1896,8 @@ class Compiler(
|
||||
classFieldTypesByName = classFieldTypesByName,
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
}
|
||||
|
||||
@ -2929,6 +2934,22 @@ class Compiler(
|
||||
if (returnClass != null) {
|
||||
lambdaReturnTypeByRef[ref] = returnClass
|
||||
}
|
||||
val moduleScopeId = moduleSlotPlan()?.id
|
||||
val captureEntries = captureSlots.map { capture ->
|
||||
val owner = capturePlan.captureOwners[capture.name]
|
||||
?: error("Missing capture owner for ${capture.name}")
|
||||
val kind = if (moduleScopeId != null && owner.scopeId == moduleScopeId) {
|
||||
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.MODULE
|
||||
} else {
|
||||
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL
|
||||
}
|
||||
net.sergeych.lyng.bytecode.LambdaCaptureEntry(
|
||||
ownerKind = kind,
|
||||
ownerScopeId = owner.scopeId,
|
||||
ownerSlotId = owner.slot
|
||||
)
|
||||
}
|
||||
lambdaCaptureEntriesByRef[ref] = captureEntries
|
||||
return ref
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@ class BytecodeCompiler(
|
||||
private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||
) {
|
||||
private var builder = CmdBuilder()
|
||||
private var nextSlot = 0
|
||||
@ -607,6 +608,9 @@ class BytecodeCompiler(
|
||||
}
|
||||
|
||||
private fun compileValueFnRef(ref: ValueFnRef): CompiledValue? {
|
||||
lambdaCaptureEntriesByRef[ref]?.let { captures ->
|
||||
builder.addConst(BytecodeConst.CaptureTable(captures))
|
||||
}
|
||||
val id = builder.addConst(BytecodeConst.ValueFn(ref.valueFn()))
|
||||
val slot = allocSlot()
|
||||
builder.emit(Opcode.MAKE_VALUE_FN, id, slot)
|
||||
|
||||
@ -36,6 +36,7 @@ sealed class BytecodeConst {
|
||||
data class ValueFn(val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord) : BytecodeConst()
|
||||
data class DeclExec(val executable: net.sergeych.lyng.DeclExecutable) : BytecodeConst()
|
||||
data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst()
|
||||
data class CaptureTable(val entries: List<LambdaCaptureEntry>) : BytecodeConst()
|
||||
data class ExtensionPropertyDecl(
|
||||
val extTypeName: String,
|
||||
val property: ObjProperty,
|
||||
|
||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.bytecode
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ValueFnRef
|
||||
|
||||
class BytecodeStatement private constructor(
|
||||
val original: Statement,
|
||||
@ -50,6 +51,7 @@ class BytecodeStatement private constructor(
|
||||
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||
): Statement {
|
||||
if (statement is BytecodeStatement) return statement
|
||||
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||
@ -73,7 +75,8 @@ class BytecodeStatement private constructor(
|
||||
classFieldTypesByName = classFieldTypesByName,
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
val compiled = compiler.compileStatement(nameHint, statement)
|
||||
val fn = compiled ?: throw BytecodeCompileException(
|
||||
|
||||
@ -50,6 +50,23 @@ object CmdDisassembler {
|
||||
}
|
||||
out.append('\n')
|
||||
}
|
||||
val captureConsts = fn.constants.withIndex().mapNotNull { (idx, constVal) ->
|
||||
val table = constVal as? BytecodeConst.CaptureTable ?: return@mapNotNull null
|
||||
idx to table
|
||||
}
|
||||
if (captureConsts.isNotEmpty()) {
|
||||
out.append("consts:\n")
|
||||
for ((idx, table) in captureConsts) {
|
||||
val entries = if (table.entries.isEmpty()) {
|
||||
"[]"
|
||||
} else {
|
||||
table.entries.joinToString(prefix = "[", postfix = "]") { entry ->
|
||||
"${entry.ownerKind}#${entry.ownerScopeId}:${entry.ownerSlotId}"
|
||||
}
|
||||
}
|
||||
out.append("k").append(idx).append(" CAPTURE_TABLE ").append(entries).append('\n')
|
||||
}
|
||||
}
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
enum class CaptureOwnerFrameKind { MODULE, LOCAL }
|
||||
|
||||
data class LambdaCaptureEntry(
|
||||
val ownerKind: CaptureOwnerFrameKind,
|
||||
val ownerScopeId: Int,
|
||||
val ownerSlotId: Int,
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user