Step 26C: remove forceScopeSlots

This commit is contained in:
Sergey Chernov 2026-02-11 21:02:48 +03:00
parent b9af80a1b2
commit 6efdfc1964
2 changed files with 16 additions and 97 deletions

View File

@ -123,7 +123,7 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
- [ ] Step 26: Bytecode-backed lambdas (remove `ValueFnRef` runtime execution).
- [x] Compile lambda bodies to bytecode and emit an opcode to create a callable from bytecode + capture plan.
- [x] Remove `containsValueFnRef` helper now that lambdas are bytecode-backed.
- [ ] Remove `forceScopeSlots` branches once no bytecode paths depend on scope slots.
- [x] Remove `forceScopeSlots` branches once no bytecode paths depend on scope slots.
- [x] Add JVM tests for captured locals and delegated locals inside lambdas on the bytecode path.
- [ ] Step 27: Remove interpreter opcodes and constants from bytecode runtime.
- [ ] Delete `BytecodeConst.ValueFn`, `CmdMakeValueFn`, and `MAKE_VALUE_FN`.

View File

@ -70,7 +70,6 @@ class BytecodeCompiler(
private val intLoopVarNames = LinkedHashSet<String>()
private val valueFnRefs = LinkedHashSet<ValueFnRef>()
private val loopStack = ArrayDeque<LoopContext>()
private var forceScopeSlots = false
private var currentPos: Pos? = null
private data class LoopContext(
@ -372,7 +371,6 @@ class BytecodeCompiler(
return CompiledValue(slot, resolved)
}
if (allowLocalSlots) {
if (!forceScopeSlots) {
val localIndex = localSlotIndexByName[ref.name]
if (localIndex != null) {
val slot = scopeSlotCount + localIndex
@ -383,13 +381,6 @@ class BytecodeCompiler(
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
}
if (forceScopeSlots) {
scopeSlotIndexByName[ref.name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
}
}
null
}
@ -402,7 +393,6 @@ class BytecodeCompiler(
return CompiledValue(slot, resolved)
}
if (allowLocalSlots) {
if (!forceScopeSlots) {
val localIndex = localSlotIndexByName[ref.name]
if (localIndex != null) {
val slot = scopeSlotCount + localIndex
@ -413,13 +403,6 @@ class BytecodeCompiler(
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
}
if (forceScopeSlots) {
scopeSlotIndexByName[ref.name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
}
}
null
}
@ -3620,22 +3603,15 @@ class BytecodeCompiler(
return CompiledValue(slot, resolved)
}
if (!allowLocalSlots) return null
if (!forceScopeSlots) {
scopeSlotIndexByName[name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
localSlotIndexByName[name]?.let { localIndex ->
val slot = scopeSlotCount + localIndex
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
return null
}
scopeSlotIndexByName[name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
localSlotIndexByName[name]?.let { localIndex ->
val slot = scopeSlotCount + localIndex
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
return null
}
@ -4560,7 +4536,7 @@ class BytecodeCompiler(
emitInlineStatements(stmt.statements(), needResult)
private fun shouldInlineBlock(stmt: BlockStatement): Boolean {
return allowLocalSlots && !forceScopeSlots
return allowLocalSlots
}
private fun compileInlineBlock(name: String, stmt: net.sergeych.lyng.InlineBlockStatement): CmdFunction? {
@ -4585,7 +4561,7 @@ class BytecodeCompiler(
private fun compileLoopBody(stmt: Statement, needResult: Boolean): CompiledValue? {
val target = if (stmt is BytecodeStatement) stmt.original else stmt
if (target is BlockStatement) {
val useInline = !forceScopeSlots && target.slotPlan.isEmpty() && target.captureSlots.isEmpty()
val useInline = target.slotPlan.isEmpty() && target.captureSlots.isEmpty()
return if (useInline) emitInlineBlock(target, needResult) else emitBlock(target, needResult)
}
return compileStatementValueOrFallback(target, needResult)
@ -4650,7 +4626,7 @@ class BytecodeCompiler(
updateSlotObjClass(localSlot, stmt.initializer, stmt.initializerObjClass)
updateNameObjClassFromSlot(stmt.name, localSlot)
val shadowedScopeSlot = scopeSlotIndexByName.containsKey(stmt.name)
if (forceScopeSlots || !shadowedScopeSlot) {
if (!shadowedScopeSlot) {
val declId = builder.addConst(
BytecodeConst.LocalDecl(
stmt.name,
@ -4867,14 +4843,14 @@ class BytecodeCompiler(
slot
}
var emitDeclLocal = usedOverride
if (useLoopScope && !forceScopeSlots) {
if (useLoopScope) {
val loopVarOnly = loopSlotPlan.size == 1 && loopSlotPlan.containsKey(stmt.loopVarName)
val loopVarIsLocal = loopSlotId >= scopeSlotCount
if (loopVarOnly && loopVarIsLocal) {
useLoopScope = false
}
}
if (useLoopScope && allowLocalSlots && !forceScopeSlots) {
if (useLoopScope && allowLocalSlots) {
val needsScope = allowedScopeNames?.let { names ->
loopSlotPlan.keys.any { names.contains(it) }
} == true
@ -4883,7 +4859,7 @@ class BytecodeCompiler(
}
}
emitDeclLocal = emitDeclLocal && useLoopScope
if (!forceScopeSlots && loopSlotId < scopeSlotCount) {
if (loopSlotId < scopeSlotCount) {
val localSlot = allocSlot()
loopSlotOverrides[stmt.loopVarName] = localSlot
usedOverride = true
@ -5738,7 +5714,7 @@ class BytecodeCompiler(
val loopKeys = loopSlotOverrides.keys.sorted().joinToString(prefix = "[", postfix = "]")
val localKeys = localSlotIndexByName.keys.sorted().joinToString(prefix = "[", postfix = "]")
val scopeKeys = scopeSlotIndexByName.keys.sorted().joinToString(prefix = "[", postfix = "]")
val info = " ref=$refKind loopSlots=$loopKeys localSlots=$localKeys scopeSlots=$scopeKeys forceScopeSlots=$forceScopeSlots"
val info = " ref=$refKind loopSlots=$loopKeys localSlots=$localKeys scopeSlots=$scopeKeys"
throw BytecodeCompileException("Unresolved name '$name'.$info", pos)
}
val refInfo = when (ref) {
@ -6169,10 +6145,6 @@ class BytecodeCompiler(
val nameIndex = localSlotIndexByName[ref.name]
if (nameIndex != null) return scopeSlotCount + nameIndex
}
if (forceScopeSlots) {
val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
return scopeSlotMap[scopeKey]
}
val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
val localIndex = localSlotIndexByKey[localKey]
if (localIndex != null) return scopeSlotCount + localIndex
@ -6223,7 +6195,6 @@ class BytecodeCompiler(
valueFnRefs.clear()
addrSlotByScopeSlot.clear()
loopStack.clear()
forceScopeSlots = false
if (slotTypeByScopeId.isNotEmpty()) {
for ((scopeId, slots) in slotTypeByScopeId) {
for ((slotIndex, cls) in slots) {
@ -6366,7 +6337,7 @@ class BytecodeCompiler(
}
}
val isModuleSlot = isModuleSlot(scopeId, stmt.name)
if (allowLocalSlots && !forceScopeSlots && slotIndex != null && !isModuleSlot) {
if (allowLocalSlots && slotIndex != null && !isModuleSlot) {
val key = ScopeSlotKey(scopeId, slotIndex)
declaredLocalKeys.add(key)
if (!localSlotInfoMap.containsKey(key)) {
@ -6463,56 +6434,6 @@ class BytecodeCompiler(
collectLoopSlotPlans(stmt.original, scopeDepth)
return
}
if (forceScopeSlots) {
when (stmt) {
is net.sergeych.lyng.ForInStatement -> {
collectLoopSlotPlans(stmt.source, scopeDepth)
val loopDepth = scopeDepth + 1
collectLoopSlotPlans(stmt.body, loopDepth)
stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) }
}
is net.sergeych.lyng.WhileStatement -> {
collectLoopSlotPlans(stmt.condition, scopeDepth)
val loopDepth = scopeDepth + 1
collectLoopSlotPlans(stmt.body, loopDepth)
stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) }
}
is net.sergeych.lyng.DoWhileStatement -> {
val loopDepth = scopeDepth + 1
collectLoopSlotPlans(stmt.body, loopDepth)
collectLoopSlotPlans(stmt.condition, loopDepth)
stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) }
}
is BlockStatement -> {
val nextDepth = scopeDepth + 1
for (child in stmt.statements()) {
collectLoopSlotPlans(child, nextDepth)
}
}
is net.sergeych.lyng.InlineBlockStatement -> {
for (child in stmt.statements()) {
collectLoopSlotPlans(child, scopeDepth)
}
}
is IfStatement -> {
collectLoopSlotPlans(stmt.condition, scopeDepth)
collectLoopSlotPlans(stmt.ifBody, scopeDepth)
stmt.elseBody?.let { collectLoopSlotPlans(it, scopeDepth) }
}
is VarDeclStatement -> {
stmt.initializer?.let { collectLoopSlotPlans(it, scopeDepth) }
}
is ExpressionStatement -> {}
is net.sergeych.lyng.ReturnStatement -> {
stmt.resultExpr?.let { collectLoopSlotPlans(it, scopeDepth) }
}
is net.sergeych.lyng.ThrowStatement -> {
collectLoopSlotPlans(stmt.throwExpr, scopeDepth)
}
else -> {}
}
return
}
when (stmt) {
is net.sergeych.lyng.ForInStatement -> {
collectLoopSlotPlans(stmt.source, scopeDepth)
@ -6682,9 +6603,8 @@ class BytecodeCompiler(
captureSlotKeys.add(key)
return
}
val shouldLocalize = ref.isDelegated || !forceScopeSlots || intLoopVarNames.contains(ref.name)
val isModuleSlot = if (ref.isDelegated) false else isModuleSlot(scopeId, ref.name)
if (allowLocalSlots && shouldLocalize && !isModuleSlot) {
if (allowLocalSlots && !isModuleSlot) {
if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated)
}
@ -6731,9 +6651,8 @@ class BytecodeCompiler(
}
captureSlotKeys.add(key)
} else {
val shouldLocalize = target.isDelegated || !forceScopeSlots || intLoopVarNames.contains(target.name)
val isModuleSlot = if (target.isDelegated) false else isModuleSlot(scopeId, target.name)
if (allowLocalSlots && shouldLocalize && !isModuleSlot) {
if (allowLocalSlots && !isModuleSlot) {
if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated)
}