diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index a74abab..1f3504e 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -36,8 +36,8 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [x] Support `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement` in bytecode compilation. - [x] Keep a mixed execution path for declarations (module bytecode calls statement bodies via `CALL_SLOT`). - [x] Ensure module object member refs compile as instance access (not class-scope). -- [ ] Step 11: Destructuring assignment bytecode. - - [ ] Handle `[a, b] = expr` (AssignRef target `ListLiteralRef`) without interpreter fallback. +- [x] Step 11: Destructuring assignment bytecode. + - [x] Handle `[a, b] = expr` (AssignRef target `ListLiteralRef`) without interpreter fallback. - [ ] Step 12: Optional member assign-ops and inc/dec in bytecode. - [ ] Support `a?.b += 1` and `a?.b++` for `FieldRef` targets. - [x] Fix post-inc return value for object slots stored in scope frames. diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 970efff..39d5e8b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -1949,9 +1949,12 @@ class Compiler( is CastRef -> containsUnsupportedRef(ref.castValueRef()) || containsUnsupportedRef(ref.castTypeRef()) is net.sergeych.lyng.obj.TypeDeclRef -> false is AssignRef -> { - if (ref.target is ListLiteralRef) return true val target = ref.target as? LocalSlotRef - (target?.isDelegated == true) || containsUnsupportedRef(ref.value) + if (target != null) { + (target.isDelegated) || containsUnsupportedRef(ref.value) + } else { + containsUnsupportedRef(ref.target) || containsUnsupportedRef(ref.value) + } } is AssignOpRef -> containsUnsupportedRef(ref.target) || containsUnsupportedRef(ref.value) is AssignIfNullRef -> 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 b5b58a0..13d5e06 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -1700,6 +1700,15 @@ class BytecodeCompiler( updateNameObjClassFromSlot(nameTarget, slot) return value } + val listTarget = ref.target as? ListLiteralRef + if (listTarget != null) { + val value = compileRef(assignValue(ref)) ?: return null + val valueObj = ensureObjSlot(value) + val declId = builder.addConst(BytecodeConst.DestructureAssign(listTarget, callSitePos())) + builder.emit(Opcode.ASSIGN_DESTRUCTURE, declId, valueObj.slot) + updateSlotType(valueObj.slot, SlotType.OBJ) + return CompiledValue(valueObj.slot, SlotType.OBJ) + } val value = compileRef(assignValue(ref)) ?: return null val target = ref.target if (target is ClassScopeMemberRef) { @@ -6252,7 +6261,11 @@ class BytecodeCompiler( is CastRef -> containsValueFnRef(ref.castValueRef()) || containsValueFnRef(ref.castTypeRef()) is AssignRef -> { val target = assignTarget(ref) - (target != null && containsValueFnRef(target)) || containsValueFnRef(assignValue(ref)) + if (target != null) { + containsValueFnRef(target) || containsValueFnRef(assignValue(ref)) + } else { + containsValueFnRef(ref.target) || containsValueFnRef(assignValue(ref)) + } } is AssignOpRef -> containsValueFnRef(ref.target) || containsValueFnRef(ref.value) is AssignIfNullRef -> containsValueFnRef(ref.target) || containsValueFnRef(ref.value) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt index 2d6cf0b..07a947b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt @@ -61,6 +61,10 @@ sealed class BytecodeConst { val isTransient: Boolean, val pos: Pos, ) : BytecodeConst() + data class DestructureAssign( + val pattern: ListLiteralRef, + val pos: Pos, + ) : BytecodeConst() data class CallArgsPlan(val tailBlock: Boolean, val specs: List) : BytecodeConst() data class CallArgSpec(val name: String?, val isSplat: Boolean) } 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 ce31952..851f0a7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -147,7 +147,8 @@ class CmdBuilder { listOf(OperandKind.CONST) Opcode.PUSH_TRY -> listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) - Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE -> + Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, + Opcode.ASSIGN_DESTRUCTURE -> listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL, @@ -398,6 +399,7 @@ class CmdBuilder { Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1]) Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1]) Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3]) + Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1]) Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4]) Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.CALL_DYNAMIC_MEMBER -> CmdCallDynamicMember(operands[0], operands[1], operands[2], operands[3], operands[4]) 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 fcd653f..915a368 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -193,6 +193,7 @@ object CmdDisassembler { is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot) is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst) + is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallDynamicMember -> Opcode.CALL_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.argBase, cmd.argCount, cmd.dst) @@ -254,7 +255,8 @@ object CmdDisassembler { listOf(OperandKind.CONST) Opcode.PUSH_TRY -> listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) - Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE -> + Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, + Opcode.ASSIGN_DESTRUCTURE -> listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL, 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 f01b626..ff6350f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -1272,7 +1272,26 @@ class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cm scope.updateSlotFor(name, immutableRec) } } - frame.storeObjResult(slot, ObjVoid) + if (slot >= frame.fn.scopeSlotCount) { + frame.storeObjResult(slot, ObjVoid) + } + return + } +} + +class CmdAssignDestructure(internal val constId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncFrameToScope(useRefs = true) + } + val decl = frame.fn.constants[constId] as? BytecodeConst.DestructureAssign + ?: error("ASSIGN_DESTRUCTURE expects DestructureAssign at $constId") + val value = frame.slotToObj(slot) + decl.pattern.setAt(decl.pos, frame.ensureScope(), value) + if (frame.fn.localSlotNames.isNotEmpty()) { + frame.syncScopeToFrame() + } + frame.storeObjResult(slot, value) return } } 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 2fd2a58..869cdf6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -132,6 +132,7 @@ enum class Opcode(val code: Int) { CLEAR_PENDING_THROWABLE(0x8F), CALL_DIRECT(0x90), + ASSIGN_DESTRUCTURE(0x91), CALL_MEMBER_SLOT(0x92), CALL_SLOT(0x93),