diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index ad1a93f..623e215 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -25,8 +25,8 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [x] Keep JVM tests green before commit. - [x] Step 6: Map literal spread in bytecode. - [x] Replace `MapLiteralEntry.Spread` bytecode exception with runtime `putAll`/merge logic. -- [ ] Step 7: Class-scope member refs in bytecode. - - [ ] Support `ClassScopeMemberRef` without scope-map fallback. +- [x] Step 7: Class-scope member refs in bytecode. + - [x] Support `ClassScopeMemberRef` without scope-map fallback. - [ ] Step 8: ObjDynamic member access in bytecode. - [ ] Allow dynamic receiver field/method lookup without falling back to interpreter. - [ ] Step 9: Module-level bytecode execution. diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 9c8857a..2c66a14 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -1983,7 +1983,7 @@ class Compiler( is ImplicitThisMethodCallRef -> false is QualifiedThisMethodSlotCallRef -> false is QualifiedThisFieldSlotRef -> false - is ClassScopeMemberRef -> true + is ClassScopeMemberRef -> false else -> false } } 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 939e515..50e8932 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -310,6 +310,7 @@ class BytecodeCompiler( is CallRef -> compileCall(ref) is MethodCallRef -> compileMethodCall(ref) is FieldRef -> compileFieldRef(ref) + is ClassScopeMemberRef -> compileClassScopeMemberRef(ref) is ThisFieldSlotRef -> compileThisFieldSlotRef(ref) is QualifiedThisFieldSlotRef -> compileQualifiedThisFieldSlotRef(ref) is ImplicitThisMemberRef -> { @@ -1647,6 +1648,21 @@ class BytecodeCompiler( } val value = compileRef(assignValue(ref)) ?: return null val target = ref.target + if (target is ClassScopeMemberRef) { + val className = target.ownerClassName() + val classSlot = compileRef(LocalVarRef(className, Pos.builtIn)) ?: run { + val cls = resolveTypeNameClass(className) ?: return null + val id = builder.addConst(BytecodeConst.ObjRef(cls)) + val slot = allocSlot() + builder.emit(Opcode.CONST_OBJ, id, slot) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(slot, SlotType.OBJ) + } + val classObj = ensureObjSlot(classSlot) + val nameId = builder.addConst(BytecodeConst.StringVal(target.name)) + builder.emit(Opcode.SET_CLASS_SCOPE, classObj.slot, nameId, value.slot) + return value + } if (target is FieldRef) { val receiverClass = resolveReceiverClass(target.target) ?: throw BytecodeCompileException( @@ -2144,6 +2160,24 @@ class BytecodeCompiler( return CompiledValue(dst, SlotType.OBJ) } + private fun compileClassScopeMemberRef(ref: ClassScopeMemberRef): CompiledValue? { + val className = ref.ownerClassName() + val classSlot = compileRef(LocalVarRef(className, Pos.builtIn)) ?: run { + val cls = resolveTypeNameClass(className) ?: return null + val id = builder.addConst(BytecodeConst.ObjRef(cls)) + val slot = allocSlot() + builder.emit(Opcode.CONST_OBJ, id, slot) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(slot, SlotType.OBJ) + } + val classObj = ensureObjSlot(classSlot) + val nameId = builder.addConst(BytecodeConst.StringVal(ref.name)) + val dst = allocSlot() + builder.emit(Opcode.GET_CLASS_SCOPE, classObj.slot, nameId, dst) + updateSlotType(dst, SlotType.OBJ) + return CompiledValue(dst, SlotType.OBJ) + } + private fun compileThisFieldSlotRef(ref: ThisFieldSlotRef): CompiledValue? { val receiver = compileThisRef() val fieldId = ref.fieldId() ?: -1 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 e0c29f5..6cb7fc2 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -193,6 +193,10 @@ class CmdBuilder { listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT) Opcode.SET_MEMBER_SLOT -> listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT) + Opcode.GET_CLASS_SCOPE -> + listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) + Opcode.SET_CLASS_SCOPE -> + listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) Opcode.ITER_PUSH -> listOf(OperandKind.SLOT) Opcode.ITER_POP, Opcode.ITER_CANCEL -> @@ -395,6 +399,8 @@ class CmdBuilder { Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3]) Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3]) + Opcode.GET_CLASS_SCOPE -> CmdGetClassScope(operands[0], operands[1], operands[2]) + Opcode.SET_CLASS_SCOPE -> CmdSetClassScope(operands[0], operands[1], operands[2]) Opcode.ITER_PUSH -> CmdIterPush(operands[0]) Opcode.ITER_POP -> CmdIterPop() Opcode.ITER_CANCEL -> CmdIterCancel() 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 b5af516..32c016a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -200,6 +200,8 @@ object CmdDisassembler { is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst) is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst) is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot) + is CmdGetClassScope -> Opcode.GET_CLASS_SCOPE to intArrayOf(cmd.classSlot, cmd.nameId, cmd.dst) + is CmdSetClassScope -> Opcode.SET_CLASS_SCOPE to intArrayOf(cmd.classSlot, cmd.nameId, cmd.valueSlot) is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot) is CmdIterPop -> Opcode.ITER_POP to intArrayOf() is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf() @@ -293,6 +295,10 @@ object CmdDisassembler { listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT) Opcode.SET_MEMBER_SLOT -> listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT) + Opcode.GET_CLASS_SCOPE -> + listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) + Opcode.SET_CLASS_SCOPE -> + listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) } } } 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 c27bdae..a867a39 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -1493,6 +1493,40 @@ class CmdSetMemberSlot( } } +class CmdGetClassScope( + internal val classSlot: 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("GET_CLASS_SCOPE expects StringVal at $nameId") + val scope = frame.ensureScope() + val cls = frame.slotToObj(classSlot) as? ObjClass + ?: scope.raiseSymbolNotFound(nameConst.value) + val rec = cls.readField(scope, nameConst.value) + val value = scope.resolve(rec, nameConst.value) + frame.storeObjResult(dst, value) + return + } +} + +class CmdSetClassScope( + internal val classSlot: 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("SET_CLASS_SCOPE expects StringVal at $nameId") + val scope = frame.ensureScope() + val cls = frame.slotToObj(classSlot) as? ObjClass + ?: scope.raiseSymbolNotFound(nameConst.value) + cls.writeField(scope, nameConst.value, frame.slotToObj(valueSlot)) + return + } +} + class CmdCallMemberSlot( internal val recvSlot: Int, internal val methodId: Int, 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 9279dab..635c8e0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -140,6 +140,8 @@ enum class Opcode(val code: Int) { LIST_LITERAL(0xA5), GET_MEMBER_SLOT(0xA8), SET_MEMBER_SLOT(0xA9), + GET_CLASS_SCOPE(0xAA), + SET_CLASS_SCOPE(0xAB), RESOLVE_SCOPE_SLOT(0xB1), LOAD_OBJ_ADDR(0xB2),