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] Force delegated locals into local slots (even module) and avoid scope-slot resolution.
|
||||||
- [x] Drop opcode/runtime support for `ASSIGN_SCOPE_SLOT`.
|
- [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)
|
## Interpreter Removal (next)
|
||||||
|
|
||||||
- [x] Step 25: Replace Statement-based declaration calls in bytecode.
|
- [ ] Step 25: Replace Statement-based declaration calls in bytecode.
|
||||||
- [x] Add bytecode const/op for class/enum/function declarations (no `Statement` objects in constants).
|
- [ ] Add bytecode const/op for class/enum/function declarations (no `Statement` objects in constants).
|
||||||
- [x] Replace `emitStatementCall` usage for `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement`.
|
- [ ] Replace `emitStatementCall` usage for `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement`.
|
||||||
- [x] Add JVM disasm coverage to ensure module init has no `CALL_SLOT` to `Callable@...` for declarations.
|
- [ ] 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).
|
- [ ] 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.
|
- [ ] 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.
|
- [ ] 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 callableReturnTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
||||||
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||||
private val lambdaReturnTypeByRef: MutableMap<ObjRef, 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 classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
|
||||||
private val classScopeMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
|
private val classScopeMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
|
||||||
private val classScopeCallableMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
|
private val classScopeCallableMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
|
||||||
@ -1561,7 +1563,8 @@ class Compiler(
|
|||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
) as BytecodeStatement
|
) as BytecodeStatement
|
||||||
unwrapped to bytecodeStmt.bytecodeFunction()
|
unwrapped to bytecodeStmt.bytecodeFunction()
|
||||||
} else {
|
} else {
|
||||||
@ -1857,7 +1860,8 @@ class Compiler(
|
|||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1892,7 +1896,8 @@ class Compiler(
|
|||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2929,6 +2934,22 @@ class Compiler(
|
|||||||
if (returnClass != null) {
|
if (returnClass != null) {
|
||||||
lambdaReturnTypeByRef[ref] = returnClass
|
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
|
return ref
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ class BytecodeCompiler(
|
|||||||
private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||||
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||||
|
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
) {
|
) {
|
||||||
private var builder = CmdBuilder()
|
private var builder = CmdBuilder()
|
||||||
private var nextSlot = 0
|
private var nextSlot = 0
|
||||||
@ -607,6 +608,9 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun compileValueFnRef(ref: ValueFnRef): CompiledValue? {
|
private fun compileValueFnRef(ref: ValueFnRef): CompiledValue? {
|
||||||
|
lambdaCaptureEntriesByRef[ref]?.let { captures ->
|
||||||
|
builder.addConst(BytecodeConst.CaptureTable(captures))
|
||||||
|
}
|
||||||
val id = builder.addConst(BytecodeConst.ValueFn(ref.valueFn()))
|
val id = builder.addConst(BytecodeConst.ValueFn(ref.valueFn()))
|
||||||
val slot = allocSlot()
|
val slot = allocSlot()
|
||||||
builder.emit(Opcode.MAKE_VALUE_FN, id, slot)
|
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 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 DeclExec(val executable: net.sergeych.lyng.DeclExecutable) : BytecodeConst()
|
||||||
data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : 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(
|
data class ExtensionPropertyDecl(
|
||||||
val extTypeName: String,
|
val extTypeName: String,
|
||||||
val property: ObjProperty,
|
val property: ObjProperty,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.bytecode
|
|||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ValueFnRef
|
||||||
|
|
||||||
class BytecodeStatement private constructor(
|
class BytecodeStatement private constructor(
|
||||||
val original: Statement,
|
val original: Statement,
|
||||||
@ -50,6 +51,7 @@ class BytecodeStatement private constructor(
|
|||||||
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||||
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||||
|
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
): Statement {
|
): Statement {
|
||||||
if (statement is BytecodeStatement) return statement
|
if (statement is BytecodeStatement) return statement
|
||||||
val hasUnsupported = containsUnsupportedStatement(statement)
|
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||||
@ -73,7 +75,8 @@ class BytecodeStatement private constructor(
|
|||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
val compiled = compiler.compileStatement(nameHint, statement)
|
val compiled = compiler.compileStatement(nameHint, statement)
|
||||||
val fn = compiled ?: throw BytecodeCompileException(
|
val fn = compiled ?: throw BytecodeCompileException(
|
||||||
|
|||||||
@ -50,6 +50,23 @@ object CmdDisassembler {
|
|||||||
}
|
}
|
||||||
out.append('\n')
|
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()
|
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