Step 7: bytecode class-scope member refs

This commit is contained in:
Sergey Chernov 2026-02-09 01:53:12 +03:00
parent 3a46e59ec8
commit 5fd322a43e
7 changed files with 85 additions and 3 deletions

View File

@ -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.

View File

@ -1983,7 +1983,7 @@ class Compiler(
is ImplicitThisMethodCallRef -> false
is QualifiedThisMethodSlotCallRef -> false
is QualifiedThisFieldSlotRef -> false
is ClassScopeMemberRef -> true
is ClassScopeMemberRef -> false
else -> false
}
}

View File

@ -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

View File

@ -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()

View File

@ -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)
}
}
}

View File

@ -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,

View File

@ -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),