From dab0b9f1655957027699675db20a41c8eb9d4996 Mon Sep 17 00:00:00 2001 From: sergeych Date: Mon, 9 Feb 2026 20:23:25 +0300 Subject: [PATCH] Step 24: remove assign scope slot --- bytecode_migration_plan.md | 3 + .../kotlin/net/sergeych/lyng/Compiler.kt | 2 +- .../lyng/bytecode/BytecodeCompiler.kt | 98 ++++++++----------- .../net/sergeych/lyng/bytecode/CmdBuilder.kt | 3 - .../sergeych/lyng/bytecode/CmdDisassembler.kt | 3 - .../net/sergeych/lyng/bytecode/CmdRuntime.kt | 17 ---- .../net/sergeych/lyng/bytecode/Opcode.kt | 7 +- .../kotlin/BytecodeRecentOpsTest.kt | 25 +++++ 8 files changed, 71 insertions(+), 87 deletions(-) diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index 3982010..090c878 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -81,6 +81,9 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [x] Add bytecode ops to bind/get/set delegated locals without scope storage. - [x] Store delegated locals in frame slots and compile get/set/assign ops with new ops. - [x] Preserve reflection facade by syncing delegated locals into scope only when needed. +- [x] Step 24: Remove `ASSIGN_SCOPE_SLOT` now that delegated locals are always frame-backed. + - [x] Force delegated locals into local slots (even module) and avoid scope-slot resolution. + - [x] Drop opcode/runtime support for `ASSIGN_SCOPE_SLOT`. ## Notes diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 6fbd47c..4245928 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -1967,7 +1967,7 @@ class Compiler( is AssignRef -> { val target = ref.target as? LocalSlotRef if (target != null) { - (target.isDelegated) || containsUnsupportedRef(ref.value) + containsUnsupportedRef(ref.value) } else { containsUnsupportedRef(ref.target) || containsUnsupportedRef(ref.value) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index f08b016..1276b50 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -317,13 +317,7 @@ class BytecodeCompiler( if (!allowLocalSlots) return null if (ref.isDelegated) { val mapped = resolveSlot(ref) ?: return null - if (mapped < scopeSlotCount) { - val addrSlot = ensureScopeAddr(mapped) - val local = allocSlot() - builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, local) - updateSlotType(local, SlotType.OBJ) - return CompiledValue(local, SlotType.OBJ) - } + if (mapped < scopeSlotCount) return null val nameId = builder.addConst(BytecodeConst.StringVal(ref.name)) val local = allocSlot() builder.emit(Opcode.DELEGATED_GET_LOCAL, mapped, nameId, local) @@ -1692,13 +1686,9 @@ class BytecodeCompiler( if (localTarget.isDelegated) { val slot = resolveSlot(localTarget) ?: return null val value = compileRef(assignValue(ref)) ?: return null - if (slot >= scopeSlotCount) { - val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name)) - builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, value.slot) - updateSlotType(slot, SlotType.OBJ) - return value - } - builder.emit(Opcode.ASSIGN_SCOPE_SLOT, slot, value.slot) + if (slot < scopeSlotCount) return null + val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name)) + builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, value.slot) updateSlotType(slot, SlotType.OBJ) return value } @@ -2011,14 +2001,10 @@ class BytecodeCompiler( if (!allowLocalSlots) return compileEvalRef(ref) if (localTarget.isDelegated) { val slot = resolveSlot(localTarget) ?: return null + if (slot < scopeSlotCount) return null val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name)) val current = allocSlot() - if (slot >= scopeSlotCount) { - builder.emit(Opcode.DELEGATED_GET_LOCAL, slot, nameId, current) - } else { - val addrSlot = ensureScopeAddr(slot) - builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, current) - } + builder.emit(Opcode.DELEGATED_GET_LOCAL, slot, nameId, current) updateSlotType(current, SlotType.OBJ) val rhs = compileRef(ref.value) ?: return compileEvalRef(ref) val rhsObj = ensureObjSlot(rhs) @@ -2033,11 +2019,7 @@ class BytecodeCompiler( val result = allocSlot() builder.emit(objOp, current, rhsObj.slot, result) updateSlotType(result, SlotType.OBJ) - if (slot >= scopeSlotCount) { - builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, result) - } else { - builder.emit(Opcode.ASSIGN_SCOPE_SLOT, slot, result) - } + builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, result) updateSlotType(slot, SlotType.OBJ) return CompiledValue(result, SlotType.OBJ) } @@ -2326,14 +2308,10 @@ class BytecodeCompiler( if (!allowLocalSlots || !target.isMutable) return null if (target.isDelegated) { val slot = resolveSlot(target) ?: return null + if (slot < scopeSlotCount) return null val nameId = builder.addConst(BytecodeConst.StringVal(target.name)) val current = allocSlot() - if (slot >= scopeSlotCount) { - builder.emit(Opcode.DELEGATED_GET_LOCAL, slot, nameId, current) - } else { - val addrSlot = ensureScopeAddr(slot) - builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, current) - } + builder.emit(Opcode.DELEGATED_GET_LOCAL, slot, nameId, current) val nullSlot = allocSlot() builder.emit(Opcode.CONST_NULL, nullSlot) val cmpSlot = allocSlot() @@ -2347,11 +2325,7 @@ class BytecodeCompiler( builder.emit(Opcode.MOVE_OBJ, current, resultSlot) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.mark(assignLabel) - if (slot >= scopeSlotCount) { - builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, newValue.slot) - } else { - builder.emit(Opcode.ASSIGN_SCOPE_SLOT, slot, newValue.slot) - } + builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, newValue.slot) builder.emit(Opcode.MOVE_OBJ, newValue.slot, resultSlot) builder.mark(endLabel) updateSlotType(resultSlot, SlotType.OBJ) @@ -2840,14 +2814,10 @@ class BytecodeCompiler( if (!target.isMutable) return null if (target.isDelegated) { val slot = resolveSlot(target) ?: return null + if (slot < scopeSlotCount) return null val nameId = builder.addConst(BytecodeConst.StringVal(target.name)) val current = allocSlot() - if (slot >= scopeSlotCount) { - builder.emit(Opcode.DELEGATED_GET_LOCAL, slot, nameId, current) - } else { - val addrSlot = ensureScopeAddr(slot) - builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, current) - } + builder.emit(Opcode.DELEGATED_GET_LOCAL, slot, nameId, current) updateSlotType(current, SlotType.OBJ) val oneSlot = allocSlot() val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One)) @@ -2857,11 +2827,7 @@ class BytecodeCompiler( val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ builder.emit(op, current, oneSlot, result) updateSlotType(result, SlotType.OBJ) - if (slot >= scopeSlotCount) { - builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, result) - } else { - builder.emit(Opcode.ASSIGN_SCOPE_SLOT, slot, result) - } + builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, result) updateSlotType(slot, SlotType.OBJ) return if (wantResult && ref.isPost) { CompiledValue(current, SlotType.OBJ) @@ -4619,17 +4585,25 @@ class BytecodeCompiler( val value = compileStatementValueOrFallback(stmt.initializer) ?: return null val slotIndex = stmt.slotIndex val scopeId = stmt.scopeId ?: 0 - val isModuleSlot = isModuleSlot(scopeId, stmt.name) val localSlot = if (slotIndex != null) { val key = ScopeSlotKey(scopeId, slotIndex) localSlotIndexByKey[key]?.let { scopeSlotCount + it } } else { null } - if (allowLocalSlots && !isModuleSlot && localSlot != null) { - val nameId = builder.addConst(BytecodeConst.StringVal(stmt.name)) - val accessId = builder.addConst(BytecodeConst.StringVal(if (stmt.isMutable) "Var" else "Val")) - builder.emit(Opcode.BIND_DELEGATE_LOCAL, value.slot, nameId, accessId, localSlot) + if (allowLocalSlots && localSlot != null) { + if (value.slot != localSlot) { + emitMove(value, localSlot) + } + val declId = builder.addConst( + BytecodeConst.DelegatedDecl( + stmt.name, + stmt.isMutable, + stmt.visibility, + stmt.isTransient + ) + ) + builder.emit(Opcode.DECL_DELEGATED, declId, localSlot) updateSlotType(localSlot, SlotType.OBJ) return CompiledValue(localSlot, SlotType.OBJ) } @@ -6032,7 +6006,7 @@ class BytecodeCompiler( private fun resolveSlot(ref: LocalSlotRef): Int? { loopSlotOverrides[ref.name]?.let { return it } val scopeId = refScopeId(ref) - if (isModuleSlot(scopeId, ref.name)) { + if (!ref.isDelegated && isModuleSlot(scopeId, ref.name)) { val key = ScopeSlotKey(scopeId, refSlot(ref)) scopeSlotMap[key]?.let { return it } scopeSlotIndexByName[ref.name]?.let { return it } @@ -6050,6 +6024,13 @@ class BytecodeCompiler( val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) return scopeSlotMap[scopeKey] } + if (ref.isDelegated) { + val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) + val localIndex = localSlotIndexByKey[localKey] + if (localIndex != null) return scopeSlotCount + localIndex + val nameIndex = localSlotIndexByName[ref.name] + if (nameIndex != null) return scopeSlotCount + nameIndex + } if (forceScopeSlots) { val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) return scopeSlotMap[scopeKey] @@ -6236,8 +6217,7 @@ class BytecodeCompiler( is DelegatedVarDeclStatement -> { val slotIndex = stmt.slotIndex val scopeId = stmt.scopeId ?: 0 - val isModuleSlot = isModuleSlot(scopeId, stmt.name) - if (allowLocalSlots && !forceScopeSlots && slotIndex != null && !isModuleSlot) { + if (allowLocalSlots && slotIndex != null) { val key = ScopeSlotKey(scopeId, slotIndex) declaredLocalKeys.add(key) if (!localSlotInfoMap.containsKey(key)) { @@ -6528,8 +6508,8 @@ class BytecodeCompiler( } return } - val shouldLocalize = !forceScopeSlots || intLoopVarNames.contains(ref.name) - val isModuleSlot = isModuleSlot(scopeId, ref.name) + 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 (!localSlotInfoMap.containsKey(key)) { localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated) @@ -6577,8 +6557,8 @@ class BytecodeCompiler( scopeSlotNameMap[key] = target.name } } else { - val shouldLocalize = !forceScopeSlots || intLoopVarNames.contains(target.name) - val isModuleSlot = isModuleSlot(scopeId, target.name) + 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 (!localSlotInfoMap.containsKey(key)) { localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt index ee51dbf..9429f53 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -138,8 +138,6 @@ class CmdBuilder { listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.RESOLVE_SCOPE_SLOT -> listOf(OperandKind.SLOT, OperandKind.ADDR) - Opcode.ASSIGN_SCOPE_SLOT -> - listOf(OperandKind.SLOT, OperandKind.SLOT) Opcode.DELEGATED_GET_LOCAL -> listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) Opcode.DELEGATED_SET_LOCAL -> @@ -265,7 +263,6 @@ class CmdBuilder { Opcode.THROW -> CmdThrow(operands[0], operands[1]) Opcode.RETHROW_PENDING -> CmdRethrowPending() Opcode.RESOLVE_SCOPE_SLOT -> CmdResolveScopeSlot(operands[0], operands[1]) - Opcode.ASSIGN_SCOPE_SLOT -> CmdAssignScopeSlot(operands[0], operands[1]) Opcode.DELEGATED_GET_LOCAL -> CmdDelegatedGetLocal(operands[0], operands[1], operands[2]) Opcode.DELEGATED_SET_LOCAL -> CmdDelegatedSetLocal(operands[0], operands[1], operands[2]) Opcode.BIND_DELEGATE_LOCAL -> CmdBindDelegateLocal(operands[0], operands[1], operands[2], operands[3]) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt index 9bf1394..025189b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -84,7 +84,6 @@ object CmdDisassembler { cmd.dst ) is CmdResolveScopeSlot -> Opcode.RESOLVE_SCOPE_SLOT to intArrayOf(cmd.scopeSlot, cmd.addrSlot) - is CmdAssignScopeSlot -> Opcode.ASSIGN_SCOPE_SLOT to intArrayOf(cmd.scopeSlot, cmd.valueSlot) is CmdDelegatedGetLocal -> Opcode.DELEGATED_GET_LOCAL to intArrayOf(cmd.delegateSlot, cmd.nameId, cmd.dst) is CmdDelegatedSetLocal -> Opcode.DELEGATED_SET_LOCAL to intArrayOf(cmd.delegateSlot, cmd.nameId, cmd.valueSlot) is CmdBindDelegateLocal -> Opcode.BIND_DELEGATE_LOCAL to intArrayOf(cmd.delegateSlot, cmd.nameId, cmd.accessId, cmd.dst) @@ -247,8 +246,6 @@ object CmdDisassembler { listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.RESOLVE_SCOPE_SLOT -> listOf(OperandKind.SLOT, OperandKind.ADDR) - Opcode.ASSIGN_SCOPE_SLOT -> - listOf(OperandKind.SLOT, OperandKind.SLOT) Opcode.DELEGATED_GET_LOCAL -> listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) Opcode.DELEGATED_SET_LOCAL -> 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 cffc02a..47cf67d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -358,13 +358,6 @@ class CmdStoreBoolAddr(internal val src: Int, internal val addrSlot: Int) : Cmd( } } -class CmdAssignScopeSlot(internal val scopeSlot: Int, internal val valueSlot: Int) : Cmd() { - override suspend fun perform(frame: CmdFrame) { - frame.assignScopeSlot(scopeSlot, frame.slotToObj(valueSlot)) - return - } -} - class CmdDelegatedGetLocal( internal val delegateSlot: Int, internal val nameId: Int, @@ -2192,16 +2185,6 @@ class CmdFrame( } } - suspend fun assignScopeSlot(scopeSlot: Int, value: Obj) { - ensureScope() - val target = scopeTarget(scopeSlot) - val index = ensureScopeSlot(target, scopeSlot) - val name = fn.scopeSlotNames.getOrNull(scopeSlot) - ?: target.raiseSymbolNotFound("slot $scopeSlot") - val record = target.getSlotRecord(index) - target.assign(record, name, value) - } - suspend fun getInt(slot: Int): Long { return if (slot < fn.scopeSlotCount) { getScopeSlotValue(slot).toLong() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt index b775350..8bf8f13 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -161,10 +161,9 @@ enum class Opcode(val code: Int) { ITER_PUSH(0xBF), ITER_POP(0xC0), ITER_CANCEL(0xC1), - ASSIGN_SCOPE_SLOT(0xC2), - DELEGATED_GET_LOCAL(0xC3), - DELEGATED_SET_LOCAL(0xC4), - BIND_DELEGATE_LOCAL(0xC5), + DELEGATED_GET_LOCAL(0xC2), + DELEGATED_SET_LOCAL(0xC3), + BIND_DELEGATE_LOCAL(0xC4), ; companion object { diff --git a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt index 4ee85dd..e68094a 100644 --- a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt +++ b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt @@ -18,10 +18,12 @@ import kotlinx.coroutines.test.runTest import net.sergeych.lyng.Compiler import net.sergeych.lyng.ExecutionError +import net.sergeych.lyng.Pos import net.sergeych.lyng.Script import net.sergeych.lyng.ScriptError import net.sergeych.lyng.Source import net.sergeych.lyng.eval +import net.sergeych.lyng.toSource import net.sergeych.lyng.obj.toInt import kotlin.test.Test import kotlin.test.assertEquals @@ -218,6 +220,29 @@ class BytecodeRecentOpsTest { ) } + @Test + fun delegatedLocalDisasmUsesDelegateOps() = runTest { + val script = """ + class BoxDelegate(var v) : Delegate { + override fun getValue(thisRef: Object, name: String): Object = v + override fun setValue(thisRef: Object, name: String, value: Object) { v = value } + } + fun calc() { + var x by BoxDelegate(1) + x += 2 + x++ + return x + } + """.trimIndent() + val compiled = Compiler.compile(script.toSource(), Script.defaultImportManager) + val scope = Script.defaultImportManager.newModuleAt(Pos.builtIn) + compiled.execute(scope) + val disasm = scope.disassembleSymbol("calc") + assertTrue(disasm.contains("DELEGATED_GET_LOCAL"), disasm) + assertTrue(disasm.contains("DELEGATED_SET_LOCAL"), disasm) + assertTrue(disasm.contains("DECL_DELEGATED"), disasm) + } + @Test fun unionMemberDispatchSubtype() = runTest { eval(