diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index 7bb03a4..3982010 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -77,6 +77,10 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [x] Support reads/writes/assign-ops/inc/dec for delegated locals (`LocalSlotRef.isDelegated`) in `BytecodeCompiler`. - [x] Remove `containsDelegatedRefs` guard once delegated locals are bytecode-safe. - [x] Add JVM tests that use delegated locals inside bytecode-compiled functions. +- [x] Step 23: Refactor delegated locals to keep delegate objects in frame slots. + - [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. ## Notes diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 511206a..6fbd47c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -7985,12 +7985,17 @@ class Compiler( ) { if (isDelegate) { val initExpr = initialExpression ?: throw ScriptError(start, "Delegate must be initialized") + val slotPlan = slotPlanStack.lastOrNull() + val slotIndex = slotPlan?.slots?.get(name)?.index + val scopeId = slotPlan?.id return DelegatedVarDeclStatement( name, isMutable, visibility, initExpr, isTransient, + slotIndex, + scopeId, start ) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DelegatedVarDeclStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DelegatedVarDeclStatement.kt index 531e653..1c63adb 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DelegatedVarDeclStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/DelegatedVarDeclStatement.kt @@ -28,6 +28,8 @@ class DelegatedVarDeclStatement( val visibility: Visibility, val initializer: Statement, val isTransient: Boolean, + val slotIndex: Int?, + val scopeId: Int?, private val startPos: Pos, ) : Statement() { override val pos: Pos = startPos 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 fddb4b3..f08b016 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -48,13 +48,14 @@ class BytecodeCompiler( private val scopeSlotIndexByName = LinkedHashMap() private val pendingScopeNameRefs = LinkedHashSet() private val addrSlotByScopeSlot = LinkedHashMap() - private data class LocalSlotInfo(val name: String, val isMutable: Boolean) + private data class LocalSlotInfo(val name: String, val isMutable: Boolean, val isDelegated: Boolean) private val localSlotInfoMap = LinkedHashMap() private val localSlotIndexByKey = LinkedHashMap() private val localSlotIndexByName = LinkedHashMap() private val loopSlotOverrides = LinkedHashMap() private var localSlotNames = emptyArray() private var localSlotMutables = BooleanArray(0) + private var localSlotDelegated = BooleanArray(0) private val declaredLocalKeys = LinkedHashSet() private val localRangeRefs = LinkedHashMap() private val slotTypes = mutableMapOf() @@ -98,7 +99,8 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables + localSlotMutables, + localSlotDelegated ) } is BlockStatement -> compileBlock(name, stmt) @@ -117,7 +119,8 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables + localSlotMutables, + localSlotDelegated ) } is DestructuringVarDeclStatement -> { @@ -133,7 +136,8 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables + localSlotMutables, + localSlotDelegated ) } is net.sergeych.lyng.ThrowStatement -> compileThrowStatement(name, stmt) @@ -151,7 +155,8 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables + localSlotMutables, + localSlotDelegated ) } is net.sergeych.lyng.ClassDeclStatement -> { @@ -167,7 +172,8 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables + localSlotMutables, + localSlotDelegated ) } is net.sergeych.lyng.FunctionDeclStatement -> { @@ -183,7 +189,8 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables + localSlotMutables, + localSlotDelegated ) } is net.sergeych.lyng.EnumDeclStatement -> { @@ -199,7 +206,8 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables + localSlotMutables, + localSlotDelegated ) } is net.sergeych.lyng.NopStatement -> { @@ -216,7 +224,8 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables + localSlotMutables, + localSlotDelegated ) } else -> null @@ -235,8 +244,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } private fun compileExtensionPropertyDecl( @@ -256,8 +266,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } fun compileExpression(name: String, stmt: ExpressionStatement): CmdFunction? { @@ -274,8 +285,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } private data class CompiledValue(val slot: Int, val type: SlotType) @@ -312,8 +324,11 @@ class BytecodeCompiler( updateSlotType(local, SlotType.OBJ) return CompiledValue(local, SlotType.OBJ) } - updateSlotType(mapped, SlotType.OBJ) - return CompiledValue(mapped, SlotType.OBJ) + val nameId = builder.addConst(BytecodeConst.StringVal(ref.name)) + val local = allocSlot() + builder.emit(Opcode.DELEGATED_GET_LOCAL, mapped, nameId, local) + updateSlotType(local, SlotType.OBJ) + return CompiledValue(local, SlotType.OBJ) } if (ref.name.isEmpty()) return null if (ref.captureOwnerScopeId == null && refScopeId(ref) == 0) { @@ -1676,8 +1691,13 @@ class BytecodeCompiler( if (!allowLocalSlots) return null if (localTarget.isDelegated) { val slot = resolveSlot(localTarget) ?: return null - if (slot >= scopeSlotCount) 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) updateSlotType(slot, SlotType.OBJ) return value @@ -1991,10 +2011,14 @@ class BytecodeCompiler( if (!allowLocalSlots) return compileEvalRef(ref) if (localTarget.isDelegated) { val slot = resolveSlot(localTarget) ?: return null - if (slot >= scopeSlotCount) return null - val addrSlot = ensureScopeAddr(slot) + val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name)) val current = allocSlot() - builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, current) + 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) + } updateSlotType(current, SlotType.OBJ) val rhs = compileRef(ref.value) ?: return compileEvalRef(ref) val rhsObj = ensureObjSlot(rhs) @@ -2009,7 +2033,11 @@ class BytecodeCompiler( val result = allocSlot() builder.emit(objOp, current, rhsObj.slot, result) updateSlotType(result, SlotType.OBJ) - builder.emit(Opcode.ASSIGN_SCOPE_SLOT, slot, result) + if (slot >= scopeSlotCount) { + builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, result) + } else { + builder.emit(Opcode.ASSIGN_SCOPE_SLOT, slot, result) + } updateSlotType(slot, SlotType.OBJ) return CompiledValue(result, SlotType.OBJ) } @@ -2298,10 +2326,14 @@ class BytecodeCompiler( if (!allowLocalSlots || !target.isMutable) return null if (target.isDelegated) { val slot = resolveSlot(target) ?: return null - if (slot >= scopeSlotCount) return null - val addrSlot = ensureScopeAddr(slot) + val nameId = builder.addConst(BytecodeConst.StringVal(target.name)) val current = allocSlot() - builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, current) + 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) + } val nullSlot = allocSlot() builder.emit(Opcode.CONST_NULL, nullSlot) val cmpSlot = allocSlot() @@ -2315,7 +2347,11 @@ class BytecodeCompiler( builder.emit(Opcode.MOVE_OBJ, current, resultSlot) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.mark(assignLabel) - builder.emit(Opcode.ASSIGN_SCOPE_SLOT, slot, newValue.slot) + 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.MOVE_OBJ, newValue.slot, resultSlot) builder.mark(endLabel) updateSlotType(resultSlot, SlotType.OBJ) @@ -2804,10 +2840,14 @@ class BytecodeCompiler( if (!target.isMutable) return null if (target.isDelegated) { val slot = resolveSlot(target) ?: return null - if (slot >= scopeSlotCount) return null - val addrSlot = ensureScopeAddr(slot) + val nameId = builder.addConst(BytecodeConst.StringVal(target.name)) val current = allocSlot() - builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, current) + 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) + } updateSlotType(current, SlotType.OBJ) val oneSlot = allocSlot() val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One)) @@ -2817,7 +2857,11 @@ class BytecodeCompiler( val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ builder.emit(op, current, oneSlot, result) updateSlotType(result, SlotType.OBJ) - builder.emit(Opcode.ASSIGN_SCOPE_SLOT, slot, result) + if (slot >= scopeSlotCount) { + builder.emit(Opcode.DELEGATED_SET_LOCAL, slot, nameId, result) + } else { + builder.emit(Opcode.ASSIGN_SCOPE_SLOT, slot, result) + } updateSlotType(slot, SlotType.OBJ) return if (wantResult && ref.isPost) { CompiledValue(current, SlotType.OBJ) @@ -3930,8 +3974,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): CmdFunction? { @@ -3947,8 +3992,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } private fun compileWhile(name: String, stmt: net.sergeych.lyng.WhileStatement): CmdFunction? { @@ -3965,8 +4011,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } private fun compileDoWhile(name: String, stmt: net.sergeych.lyng.DoWhileStatement): CmdFunction? { @@ -3983,8 +4030,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } private fun compileBlock(name: String, stmt: BlockStatement): CmdFunction? { @@ -4004,8 +4052,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } private fun compileVarDecl(name: String, stmt: VarDeclStatement): CmdFunction? { @@ -4021,8 +4070,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } private fun compileStatementValue(stmt: Statement): CompiledValue? { @@ -4431,8 +4481,9 @@ class BytecodeCompiler( scopeSlotNames, scopeSlotIsModule, localSlotNames, - localSlotMutables - ) + localSlotMutables, + localSlotDelegated + ) } private fun compileLoopBody(stmt: Statement, needResult: Boolean): CompiledValue? { @@ -4566,6 +4617,22 @@ class BytecodeCompiler( private fun emitDelegatedVarDecl(stmt: DelegatedVarDeclStatement): CompiledValue? { 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) + updateSlotType(localSlot, SlotType.OBJ) + return CompiledValue(localSlot, SlotType.OBJ) + } val declId = builder.addConst( BytecodeConst.DelegatedDecl( stmt.name, @@ -6026,6 +6093,7 @@ class BytecodeCompiler( pendingScopeNameRefs.clear() localSlotNames = emptyArray() localSlotMutables = BooleanArray(0) + localSlotDelegated = BooleanArray(0) declaredLocalKeys.clear() localRangeRefs.clear() intLoopVarNames.clear() @@ -6072,6 +6140,7 @@ class BytecodeCompiler( if (allowLocalSlots && localSlotInfoMap.isNotEmpty()) { val names = ArrayList(localSlotInfoMap.size) val mutables = BooleanArray(localSlotInfoMap.size) + val delegated = BooleanArray(localSlotInfoMap.size) var index = 0 for ((key, info) in localSlotInfoMap) { localSlotIndexByKey[key] = index @@ -6080,10 +6149,12 @@ class BytecodeCompiler( } names.add(info.name) mutables[index] = info.isMutable + delegated[index] = info.isDelegated index += 1 } localSlotNames = names.toTypedArray() localSlotMutables = mutables + localSlotDelegated = delegated } if (scopeSlotCount > 0) { for ((key, index) in scopeSlotMap) { @@ -6144,7 +6215,7 @@ class BytecodeCompiler( val key = ScopeSlotKey(scopeId, slotIndex) declaredLocalKeys.add(key) if (!localSlotInfoMap.containsKey(key)) { - localSlotInfoMap[key] = LocalSlotInfo(stmt.name, stmt.isMutable) + localSlotInfoMap[key] = LocalSlotInfo(stmt.name, stmt.isMutable, isDelegated = false) } if (!stmt.isMutable) { extractDeclaredRange(stmt.initializer)?.let { range -> @@ -6163,6 +6234,24 @@ class BytecodeCompiler( stmt.initializer?.let { collectScopeSlots(it) } } is DelegatedVarDeclStatement -> { + val slotIndex = stmt.slotIndex + val scopeId = stmt.scopeId ?: 0 + val isModuleSlot = isModuleSlot(scopeId, stmt.name) + if (allowLocalSlots && !forceScopeSlots && slotIndex != null && !isModuleSlot) { + val key = ScopeSlotKey(scopeId, slotIndex) + declaredLocalKeys.add(key) + if (!localSlotInfoMap.containsKey(key)) { + localSlotInfoMap[key] = LocalSlotInfo(stmt.name, stmt.isMutable, isDelegated = true) + } + } else if (slotIndex != null) { + val key = ScopeSlotKey(scopeId, slotIndex) + if (!scopeSlotMap.containsKey(key)) { + scopeSlotMap[key] = scopeSlotMap.size + } + if (!scopeSlotNameMap.containsKey(key)) { + scopeSlotNameMap[key] = stmt.name + } + } collectScopeSlots(stmt.initializer) } is IfStatement -> { @@ -6441,9 +6530,9 @@ class BytecodeCompiler( } val shouldLocalize = !forceScopeSlots || intLoopVarNames.contains(ref.name) val isModuleSlot = isModuleSlot(scopeId, ref.name) - if (allowLocalSlots && !ref.isDelegated && shouldLocalize && !isModuleSlot) { + if (allowLocalSlots && shouldLocalize && !isModuleSlot) { if (!localSlotInfoMap.containsKey(key)) { - localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable) + localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated) } return } @@ -6490,9 +6579,9 @@ class BytecodeCompiler( } else { val shouldLocalize = !forceScopeSlots || intLoopVarNames.contains(target.name) val isModuleSlot = isModuleSlot(scopeId, target.name) - if (allowLocalSlots && !target.isDelegated && shouldLocalize && !isModuleSlot) { + if (allowLocalSlots && shouldLocalize && !isModuleSlot) { if (!localSlotInfoMap.containsKey(key)) { - localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable) + localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated) } } else { if (!scopeSlotMap.containsKey(key)) { 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 85b3cf1..ee51dbf 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -68,7 +68,8 @@ class CmdBuilder { scopeSlotNames: Array = emptyArray(), scopeSlotIsModule: BooleanArray = BooleanArray(0), localSlotNames: Array = emptyArray(), - localSlotMutables: BooleanArray = BooleanArray(0) + localSlotMutables: BooleanArray = BooleanArray(0), + localSlotDelegated: BooleanArray = BooleanArray(0) ): CmdFunction { val scopeSlotCount = scopeSlotIndices.size require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) { @@ -78,6 +79,7 @@ class CmdBuilder { "scope slot module mapping size mismatch" } require(localSlotNames.size == localSlotMutables.size) { "local slot metadata size mismatch" } + require(localSlotNames.size == localSlotDelegated.size) { "local slot delegation size mismatch" } val labelIps = mutableMapOf() for ((label, idx) in labelPositions) { labelIps[label] = idx @@ -111,6 +113,7 @@ class CmdBuilder { scopeSlotIsModule = if (scopeSlotIsModule.isEmpty()) BooleanArray(scopeSlotCount) else scopeSlotIsModule, localSlotNames = localSlotNames, localSlotMutables = localSlotMutables, + localSlotDelegated = localSlotDelegated, constants = constPool.toList(), cmds = cmds.toTypedArray(), posByIp = posByInstr.toTypedArray() @@ -137,6 +140,12 @@ class CmdBuilder { 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 -> + listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) + Opcode.BIND_DELEGATE_LOCAL -> + listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.CONST, OperandKind.SLOT) Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR -> listOf(OperandKind.ADDR, OperandKind.SLOT) Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR -> @@ -257,6 +266,9 @@ class CmdBuilder { 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]) Opcode.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1]) Opcode.STORE_OBJ_ADDR -> CmdStoreObjAddr(operands[0], operands[1]) Opcode.LOAD_INT_ADDR -> CmdLoadIntAddr(operands[0], operands[1]) 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 4fd1f7d..9bf1394 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -85,6 +85,9 @@ object CmdDisassembler { ) 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) is CmdLoadObjAddr -> Opcode.LOAD_OBJ_ADDR to intArrayOf(cmd.addrSlot, cmd.dst) is CmdStoreObjAddr -> Opcode.STORE_OBJ_ADDR to intArrayOf(cmd.src, cmd.addrSlot) is CmdLoadIntAddr -> Opcode.LOAD_INT_ADDR to intArrayOf(cmd.addrSlot, cmd.dst) @@ -246,6 +249,12 @@ object CmdDisassembler { 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 -> + listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) + Opcode.BIND_DELEGATE_LOCAL -> + listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.CONST, OperandKind.SLOT) Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR -> listOf(OperandKind.ADDR, OperandKind.SLOT) Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR -> diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt index 39caedf..829a915 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt @@ -28,6 +28,7 @@ data class CmdFunction( val scopeSlotIsModule: BooleanArray, val localSlotNames: Array, val localSlotMutables: BooleanArray, + val localSlotDelegated: BooleanArray, val constants: List, val cmds: Array, val posByIp: Array, @@ -37,6 +38,7 @@ data class CmdFunction( require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames size mismatch" } require(scopeSlotIsModule.size == scopeSlotCount) { "scopeSlotIsModule size mismatch" } require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" } + require(localSlotNames.size == localSlotDelegated.size) { "localSlot delegation size mismatch" } require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" } require(addrCount >= 0) { "addrCount must be non-negative" } if (posByIp.isNotEmpty()) { 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 9943a8e..cffc02a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -365,6 +365,67 @@ class CmdAssignScopeSlot(internal val scopeSlot: Int, internal val valueSlot: In } } +class CmdDelegatedGetLocal( + internal val delegateSlot: Int, + internal val nameId: Int, + internal val dst: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal + ?: error("DELEGATED_GET_LOCAL expects StringVal at $nameId") + val delegate = frame.slotToObj(delegateSlot) + val scope = frame.ensureScope() + val rec = ObjRecord(ObjNull, isMutable = false, type = ObjRecord.Type.Delegated) + rec.delegate = delegate + val resolved = ObjVoid.resolveRecord(scope, rec, nameConst.value, null) + frame.storeObjResult(dst, resolved.value) + return + } +} + +class CmdDelegatedSetLocal( + internal val delegateSlot: Int, + internal val nameId: Int, + internal val valueSlot: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal + ?: error("DELEGATED_SET_LOCAL expects StringVal at $nameId") + val delegate = frame.slotToObj(delegateSlot) + val scope = frame.ensureScope() + val value = frame.slotToObj(valueSlot) + delegate.invokeInstanceMethod(scope, "setValue", Arguments(ObjNull, ObjString(nameConst.value), value)) + return + } +} + +class CmdBindDelegateLocal( + internal val delegateSlot: Int, + internal val nameId: Int, + internal val accessId: Int, + internal val dst: Int, +) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal + ?: error("BIND_DELEGATE_LOCAL expects StringVal at $nameId") + val accessConst = frame.fn.constants.getOrNull(accessId) as? BytecodeConst.StringVal + ?: error("BIND_DELEGATE_LOCAL expects StringVal at $accessId") + val delegate = frame.slotToObj(delegateSlot) + val scope = frame.ensureScope() + val bound = try { + delegate.invokeInstanceMethod( + scope, + "bind", + Arguments(ObjString(nameConst.value), ObjString(accessConst.value), ObjNull) + ) + } catch (_: Exception) { + delegate + } + frame.storeObjResult(dst, bound) + return + } +} + class CmdIntToReal(internal val src: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { frame.setReal(dst, frame.getReal(src)) @@ -2274,7 +2335,7 @@ class CmdFrame( } val local = slot - fn.scopeSlotCount val localName = fn.localSlotNames.getOrNull(local) - if (localName != null) { + if (localName != null && fn.localSlotDelegated.getOrNull(local) != true) { val rec = scope.getLocalRecordDirect(localName) ?: scope.localBindings[localName] if (rec != null && (rec.type == ObjRecord.Type.Delegated || rec.type == ObjRecord.Type.Property || rec.value is ObjProperty)) { return scope.resolve(rec, localName) @@ -2329,12 +2390,27 @@ class CmdFrame( val name = names[i] ?: continue if (scopeSlotNames.contains(name)) continue val target = resolveLocalScope(i) ?: continue + val isDelegated = fn.localSlotDelegated.getOrNull(i) == true val value = if (useRefs) FrameSlotRef(frame, i) else localSlotToObj(i) val rec = target.getLocalRecordDirect(name) if (rec == null) { val isMutable = fn.localSlotMutables.getOrElse(i) { true } - target.addItem(name, isMutable, value) + if (isDelegated) { + val delegatedRec = target.addItem( + name, + isMutable, + ObjNull, + recordType = ObjRecord.Type.Delegated + ) + delegatedRec.delegate = localSlotToObj(i) + } else { + target.addItem(name, isMutable, value) + } } else { + if (isDelegated && rec.type == ObjRecord.Type.Delegated) { + rec.delegate = localSlotToObj(i) + continue + } val existing = rec.value if (existing is FrameSlotRef && !useRefs) continue rec.value = value @@ -2349,6 +2425,11 @@ class CmdFrame( val name = names[i] ?: continue val target = resolveLocalScope(i) ?: continue val rec = target.getLocalRecordDirect(name) ?: continue + if (fn.localSlotDelegated.getOrNull(i) == true && rec.type == ObjRecord.Type.Delegated) { + val delegate = rec.delegate ?: ObjNull + frame.setObj(i, delegate) + continue + } val value = rec.value if (value is FrameSlotRef) { val resolved = value.read() 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 b866775..b775350 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -162,6 +162,9 @@ enum class Opcode(val code: Int) { ITER_POP(0xC0), ITER_CANCEL(0xC1), ASSIGN_SCOPE_SLOT(0xC2), + DELEGATED_GET_LOCAL(0xC3), + DELEGATED_SET_LOCAL(0xC4), + BIND_DELEGATE_LOCAL(0xC5), ; companion object {