Add bytecode destructuring assignment

This commit is contained in:
Sergey Chernov 2026-02-09 09:58:34 +03:00
parent 780227a229
commit 694d15c69d
8 changed files with 52 additions and 8 deletions

View File

@ -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] Support `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement` in bytecode compilation.
- [x] Keep a mixed execution path for declarations (module bytecode calls statement bodies via `CALL_SLOT`). - [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). - [x] Ensure module object member refs compile as instance access (not class-scope).
- [ ] Step 11: Destructuring assignment bytecode. - [x] Step 11: Destructuring assignment bytecode.
- [ ] Handle `[a, b] = expr` (AssignRef target `ListLiteralRef`) without interpreter fallback. - [x] Handle `[a, b] = expr` (AssignRef target `ListLiteralRef`) without interpreter fallback.
- [ ] Step 12: Optional member assign-ops and inc/dec in bytecode. - [ ] Step 12: Optional member assign-ops and inc/dec in bytecode.
- [ ] Support `a?.b += 1` and `a?.b++` for `FieldRef` targets. - [ ] Support `a?.b += 1` and `a?.b++` for `FieldRef` targets.
- [x] Fix post-inc return value for object slots stored in scope frames. - [x] Fix post-inc return value for object slots stored in scope frames.

View File

@ -1949,9 +1949,12 @@ class Compiler(
is CastRef -> containsUnsupportedRef(ref.castValueRef()) || containsUnsupportedRef(ref.castTypeRef()) is CastRef -> containsUnsupportedRef(ref.castValueRef()) || containsUnsupportedRef(ref.castTypeRef())
is net.sergeych.lyng.obj.TypeDeclRef -> false is net.sergeych.lyng.obj.TypeDeclRef -> false
is AssignRef -> { is AssignRef -> {
if (ref.target is ListLiteralRef) return true
val target = ref.target as? LocalSlotRef 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 AssignOpRef -> containsUnsupportedRef(ref.target) || containsUnsupportedRef(ref.value)
is AssignIfNullRef -> containsUnsupportedRef(ref.target) || containsUnsupportedRef(ref.value) is AssignIfNullRef -> containsUnsupportedRef(ref.target) || containsUnsupportedRef(ref.value)

View File

@ -1700,6 +1700,15 @@ class BytecodeCompiler(
updateNameObjClassFromSlot(nameTarget, slot) updateNameObjClassFromSlot(nameTarget, slot)
return value 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 value = compileRef(assignValue(ref)) ?: return null
val target = ref.target val target = ref.target
if (target is ClassScopeMemberRef) { if (target is ClassScopeMemberRef) {
@ -6252,7 +6261,11 @@ class BytecodeCompiler(
is CastRef -> containsValueFnRef(ref.castValueRef()) || containsValueFnRef(ref.castTypeRef()) is CastRef -> containsValueFnRef(ref.castValueRef()) || containsValueFnRef(ref.castTypeRef())
is AssignRef -> { is AssignRef -> {
val target = assignTarget(ref) 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 AssignOpRef -> containsValueFnRef(ref.target) || containsValueFnRef(ref.value)
is AssignIfNullRef -> containsValueFnRef(ref.target) || containsValueFnRef(ref.value) is AssignIfNullRef -> containsValueFnRef(ref.target) || containsValueFnRef(ref.value)

View File

@ -61,6 +61,10 @@ sealed class BytecodeConst {
val isTransient: Boolean, val isTransient: Boolean,
val pos: Pos, val pos: Pos,
) : BytecodeConst() ) : BytecodeConst()
data class DestructureAssign(
val pattern: ListLiteralRef,
val pos: Pos,
) : BytecodeConst()
data class CallArgsPlan(val tailBlock: Boolean, val specs: List<CallArgSpec>) : BytecodeConst() data class CallArgsPlan(val tailBlock: Boolean, val specs: List<CallArgSpec>) : BytecodeConst()
data class CallArgSpec(val name: String?, val isSplat: Boolean) data class CallArgSpec(val name: String?, val isSplat: Boolean)
} }

View File

@ -147,7 +147,8 @@ class CmdBuilder {
listOf(OperandKind.CONST) listOf(OperandKind.CONST)
Opcode.PUSH_TRY -> Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) 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) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, 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, 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_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(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.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_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_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]) Opcode.CALL_DYNAMIC_MEMBER -> CmdCallDynamicMember(operands[0], operands[1], operands[2], operands[3], operands[4])

View File

@ -193,6 +193,7 @@ object CmdDisassembler {
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY 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 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 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 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) 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) listOf(OperandKind.CONST)
Opcode.PUSH_TRY -> Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) 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) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, 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, Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,

View File

@ -1272,7 +1272,26 @@ class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cm
scope.updateSlotFor(name, immutableRec) scope.updateSlotFor(name, immutableRec)
} }
} }
if (slot >= frame.fn.scopeSlotCount) {
frame.storeObjResult(slot, ObjVoid) 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 return
} }
} }

View File

@ -132,6 +132,7 @@ enum class Opcode(val code: Int) {
CLEAR_PENDING_THROWABLE(0x8F), CLEAR_PENDING_THROWABLE(0x8F),
CALL_DIRECT(0x90), CALL_DIRECT(0x90),
ASSIGN_DESTRUCTURE(0x91),
CALL_MEMBER_SLOT(0x92), CALL_MEMBER_SLOT(0x92),
CALL_SLOT(0x93), CALL_SLOT(0x93),