Step 15: class-scope ?= in bytecode
This commit is contained in:
parent
541738646f
commit
0b94b46d40
@ -50,9 +50,9 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
|
|||||||
- [x] Support `FastLocalVarRef` reads with the same slot resolution as `LocalVarRef`.
|
- [x] Support `FastLocalVarRef` reads with the same slot resolution as `LocalVarRef`.
|
||||||
- [x] If `BoundLocalVarRef` is still emitted, map it to a direct slot read instead of failing.
|
- [x] If `BoundLocalVarRef` is still emitted, map it to a direct slot read instead of failing.
|
||||||
- [x] Add a JVM test that exercises fast-local reads in a bytecode-compiled function.
|
- [x] Add a JVM test that exercises fast-local reads in a bytecode-compiled function.
|
||||||
- [ ] Step 15: Class-scope `?=` in bytecode.
|
- [x] Step 15: Class-scope `?=` in bytecode.
|
||||||
- [ ] Handle `C.x ?= v` and `C?.x ?= v` for class-scope members without falling back.
|
- [x] Handle `C.x ?= v` and `C?.x ?= v` for class-scope members without falling back.
|
||||||
- [ ] Add a JVM test for class-scope `?=` on static vars.
|
- [x] Add a JVM test for class-scope `?=` on static vars.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@ -2220,6 +2220,20 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
val newValue = compileRefWithFallback(ref.value, null, Pos.builtIn) ?: return null
|
val newValue = compileRefWithFallback(ref.value, null, Pos.builtIn) ?: return null
|
||||||
when (target) {
|
when (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, newValue.slot)
|
||||||
|
}
|
||||||
is LocalSlotRef -> {
|
is LocalSlotRef -> {
|
||||||
if (!allowLocalSlots || !target.isMutable || target.isDelegated) return null
|
if (!allowLocalSlots || !target.isMutable || target.isDelegated) return null
|
||||||
val slot = resolveSlot(target) ?: return null
|
val slot = resolveSlot(target) ?: return null
|
||||||
@ -2244,7 +2258,24 @@ class BytecodeCompiler(
|
|||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
|
val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
|
||||||
if (receiverClass == ObjDynamic.type) {
|
if (isKnownClassReceiver(target.target)) {
|
||||||
|
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
|
||||||
|
if (!target.isOptional) {
|
||||||
|
builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, newValue.slot)
|
||||||
|
} else {
|
||||||
|
val recvNull = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_NULL, recvNull)
|
||||||
|
val recvCmp = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
|
||||||
|
val skipLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, newValue.slot)
|
||||||
|
builder.mark(skipLabel)
|
||||||
|
}
|
||||||
|
} else if (receiverClass == ObjDynamic.type) {
|
||||||
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
|
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
|
||||||
if (!target.isOptional) {
|
if (!target.isOptional) {
|
||||||
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
|
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
|
||||||
@ -2266,7 +2297,7 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
updateSlotType(resultSlot, SlotType.OBJ)
|
updateSlotType(resultSlot, SlotType.OBJ)
|
||||||
return CompiledValue(resultSlot, SlotType.OBJ)
|
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
}
|
} else {
|
||||||
val fieldId = receiverClass.instanceFieldIdMap()[target.name]
|
val fieldId = receiverClass.instanceFieldIdMap()[target.name]
|
||||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
|
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
|
||||||
if (fieldId != null || methodId != null) {
|
if (fieldId != null || methodId != null) {
|
||||||
@ -2318,6 +2349,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
is IndexRef -> {
|
is IndexRef -> {
|
||||||
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
|
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
|
||||||
if (!target.optionalRef) {
|
if (!target.optionalRef) {
|
||||||
|
|||||||
@ -146,6 +146,19 @@ class BytecodeRecentOpsTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun classScopeIfNullAssign() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class C { static var x: Object? = null }
|
||||||
|
C.x ?= 7
|
||||||
|
assertEquals(7, C.x)
|
||||||
|
C.x ?= 9
|
||||||
|
assertEquals(7, C.x)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun qualifiedThisValueRef() = runTest {
|
fun qualifiedThisValueRef() = runTest {
|
||||||
eval(
|
eval(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user