Fix bytecode object refs and post-inc

This commit is contained in:
Sergey Chernov 2026-02-09 03:20:14 +03:00
parent 5b1a8af4e3
commit 8a7582891a
8 changed files with 359 additions and 35 deletions

View File

@ -35,10 +35,12 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
- [ ] Step 10: Bytecode for declaration statements in module scripts. - [ ] Step 10: Bytecode for declaration statements in module scripts.
- [ ] Support `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement` in bytecode compilation. - [ ] Support `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement` in bytecode compilation.
- [ ] Decide whether to compile declarations into module bytecode or keep a mixed execution path. - [ ] Decide whether to compile declarations into module bytecode or keep a mixed execution path.
- [x] Ensure module object member refs compile as instance access (not class-scope).
- [ ] Step 11: Destructuring assignment bytecode. - [ ] Step 11: Destructuring assignment bytecode.
- [ ] Handle `[a, b] = expr` (AssignRef target `ListLiteralRef`) without interpreter fallback. - [ ] 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.
## Notes ## Notes

View File

@ -28,4 +28,9 @@ class ClassDeclStatement(
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope) return delegate.execute(scope)
} }
override suspend fun callOn(scope: Scope): Obj {
val target = scope.parent ?: scope
return delegate.execute(target)
}
} }

View File

@ -165,6 +165,7 @@ class Compiler(
private val classScopeCallableMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf() private val classScopeCallableMembersByClassName: MutableMap<String, MutableSet<String>> = mutableMapOf()
private val encodedPayloadTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf() private val encodedPayloadTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf() private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
private val objectDeclNames: MutableSet<String> = mutableSetOf()
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) { private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
val plan = moduleSlotPlan() ?: return val plan = moduleSlotPlan() ?: return
@ -1536,7 +1537,12 @@ class Compiler(
allowedScopeNames = modulePlan.keys, allowedScopeNames = modulePlan.keys,
moduleScopeId = moduleSlotPlan()?.id, moduleScopeId = moduleSlotPlan()?.id,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownClassMapForBytecode() knownNameObjClass = knownClassMapForBytecode(),
knownObjectNames = objectDeclNames,
classFieldTypesByName = classFieldTypesByName,
enumEntriesByName = enumEntriesByName,
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
callableReturnTypeByName = callableReturnTypeByName
) as BytecodeStatement ) as BytecodeStatement
unwrapped to bytecodeStmt.bytecodeFunction() unwrapped to bytecodeStmt.bytecodeFunction()
} else { } else {
@ -1830,7 +1836,12 @@ class Compiler(
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
moduleScopeId = moduleSlotPlan()?.id, moduleScopeId = moduleSlotPlan()?.id,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownClassMapForBytecode() knownNameObjClass = knownClassMapForBytecode(),
knownObjectNames = objectDeclNames,
classFieldTypesByName = classFieldTypesByName,
enumEntriesByName = enumEntriesByName,
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
callableReturnTypeByName = callableReturnTypeByName
) )
} }
@ -1861,7 +1872,12 @@ class Compiler(
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
moduleScopeId = moduleSlotPlan()?.id, moduleScopeId = moduleSlotPlan()?.id,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownNames knownNameObjClass = knownNames,
knownObjectNames = objectDeclNames,
classFieldTypesByName = classFieldTypesByName,
enumEntriesByName = enumEntriesByName,
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
callableReturnTypeByName = callableReturnTypeByName
) )
} }
@ -1899,6 +1915,9 @@ class Compiler(
is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false
is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr) is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr)
is ExtensionPropertyDeclStatement -> false is ExtensionPropertyDeclStatement -> false
is ClassDeclStatement -> false
is FunctionDeclStatement -> false
is EnumDeclStatement -> false
is TryStatement -> { is TryStatement -> {
containsUnsupportedForBytecode(target.body) || containsUnsupportedForBytecode(target.body) ||
target.catches.any { containsUnsupportedForBytecode(it.block) } || target.catches.any { containsUnsupportedForBytecode(it.block) } ||
@ -5660,6 +5679,9 @@ class Compiler(
if (declaredName != null) { if (declaredName != null) {
resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken!!.pos) resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken!!.pos)
declareLocalName(declaredName, isMutable = false) declareLocalName(declaredName, isMutable = false)
if (codeContexts.lastOrNull() is CodeContext.Module) {
objectDeclNames.add(declaredName)
}
if (outerClassName != null) { if (outerClassName != null) {
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
outerCtx?.classScopeMembers?.add(declaredName) outerCtx?.classScopeMembers?.add(declaredName)
@ -6612,11 +6634,17 @@ class Compiler(
val declKind = if (parentContext is CodeContext.ClassBody) SymbolKind.MEMBER else SymbolKind.FUNCTION val declKind = if (parentContext is CodeContext.ClassBody) SymbolKind.MEMBER else SymbolKind.FUNCTION
resolutionSink?.declareSymbol(name, declKind, isMutable = false, pos = nameStartPos, isOverride = isOverride) resolutionSink?.declareSymbol(name, declKind, isMutable = false, pos = nameStartPos, isOverride = isOverride)
if (parentContext is CodeContext.ClassBody && extTypeName == null) { if (parentContext is CodeContext.ClassBody && extTypeName == null) {
parentContext.declaredMembers.add(name) if (isStatic) {
if (!parentContext.memberMethodIds.containsKey(name)) { parentContext.classScopeMembers.add(name)
parentContext.memberMethodIds[name] = parentContext.nextMethodId++ registerClassScopeMember(parentContext.name, name)
registerClassScopeCallableMember(parentContext.name, name)
} else {
parentContext.declaredMembers.add(name)
if (!parentContext.memberMethodIds.containsKey(name)) {
parentContext.memberMethodIds[name] = parentContext.nextMethodId++
}
memberMethodId = parentContext.memberMethodIds[name]
} }
memberMethodId = parentContext.memberMethodIds[name]
} }
if (declKind != SymbolKind.MEMBER) { if (declKind != SymbolKind.MEMBER) {
declareLocalName(name, isMutable = false) declareLocalName(name, isMutable = false)
@ -7752,7 +7780,10 @@ class Compiler(
} }
if (declaringClassNameCaptured != null && extTypeName == null) { if (declaringClassNameCaptured != null && extTypeName == null) {
if (isDelegate || isProperty) { if (isStatic) {
classCtx?.classScopeMembers?.add(name)
registerClassScopeMember(declaringClassNameCaptured, name)
} else if (isDelegate || isProperty) {
if (classCtx != null) { if (classCtx != null) {
if (!classCtx.memberMethodIds.containsKey(name)) { if (!classCtx.memberMethodIds.containsKey(name)) {
classCtx.memberMethodIds[name] = classCtx.nextMethodId++ classCtx.memberMethodIds[name] = classCtx.nextMethodId++

View File

@ -27,4 +27,9 @@ class EnumDeclStatement(
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope) return delegate.execute(scope)
} }
override suspend fun callOn(scope: Scope): Obj {
val target = scope.parent ?: scope
return delegate.execute(target)
}
} }

View File

@ -27,4 +27,9 @@ class FunctionDeclStatement(
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope) return delegate.execute(scope)
} }
override suspend fun callOn(scope: Scope): Obj {
val target = scope.parent ?: scope
return delegate.execute(target)
}
} }

View File

@ -28,6 +28,11 @@ class BytecodeCompiler(
private val moduleScopeId: Int? = null, private val moduleScopeId: Int? = null,
private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(), private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
private val knownNameObjClass: Map<String, ObjClass> = emptyMap(), private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
private val knownObjectNames: Set<String> = emptySet(),
private val classFieldTypesByName: Map<String, Map<String, ObjClass>> = emptyMap(),
private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
) { ) {
private var builder = CmdBuilder() private var builder = CmdBuilder()
private var nextSlot = 0 private var nextSlot = 0
@ -54,7 +59,8 @@ class BytecodeCompiler(
private val localRangeRefs = LinkedHashMap<ScopeSlotKey, RangeRef>() private val localRangeRefs = LinkedHashMap<ScopeSlotKey, RangeRef>()
private val slotTypes = mutableMapOf<Int, SlotType>() private val slotTypes = mutableMapOf<Int, SlotType>()
private val slotObjClass = mutableMapOf<Int, ObjClass>() private val slotObjClass = mutableMapOf<Int, ObjClass>()
private val nameObjClass = mutableMapOf<String, ObjClass>() private val nameObjClass = knownNameObjClass.toMutableMap()
private val knownClassNames = knownNameObjClass.keys.toSet()
private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>() private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>()
private val intLoopVarNames = LinkedHashSet<String>() private val intLoopVarNames = LinkedHashSet<String>()
private val loopStack = ArrayDeque<LoopContext>() private val loopStack = ArrayDeque<LoopContext>()
@ -148,6 +154,54 @@ class BytecodeCompiler(
localSlotMutables localSlotMutables
) )
} }
is net.sergeych.lyng.ClassDeclStatement -> {
val value = emitStatementCall(stmt)
builder.emit(Opcode.RET, value.slot)
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
builder.build(
name,
localCount,
addrCount = nextAddrSlot,
returnLabels = returnLabels,
scopeSlotIndices,
scopeSlotNames,
scopeSlotIsModule,
localSlotNames,
localSlotMutables
)
}
is net.sergeych.lyng.FunctionDeclStatement -> {
val value = emitStatementCall(stmt)
builder.emit(Opcode.RET, value.slot)
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
builder.build(
name,
localCount,
addrCount = nextAddrSlot,
returnLabels = returnLabels,
scopeSlotIndices,
scopeSlotNames,
scopeSlotIsModule,
localSlotNames,
localSlotMutables
)
}
is net.sergeych.lyng.EnumDeclStatement -> {
val value = emitStatementCall(stmt)
builder.emit(Opcode.RET, value.slot)
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
builder.build(
name,
localCount,
addrCount = nextAddrSlot,
returnLabels = returnLabels,
scopeSlotIndices,
scopeSlotNames,
scopeSlotIsModule,
localSlotNames,
localSlotMutables
)
}
else -> null else -> null
} }
} }
@ -1689,6 +1743,25 @@ class BytecodeCompiler(
} }
return value return value
} }
if (isKnownClassReceiver(target.target)) {
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
if (!target.isOptional) {
builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, value.slot)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
)
builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, value.slot)
builder.mark(endLabel)
}
return value
}
val fieldId = receiverClass.instanceFieldIdMap()[target.name] val fieldId = receiverClass.instanceFieldIdMap()[target.name]
val methodId = if (fieldId == null) { val methodId = if (fieldId == null) {
receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
@ -1887,28 +1960,105 @@ class BytecodeCompiler(
} ?: return compileEvalRef(ref) } ?: return compileEvalRef(ref)
val fieldTarget = ref.target as? FieldRef val fieldTarget = ref.target as? FieldRef
if (fieldTarget != null) { if (fieldTarget != null) {
if (fieldTarget.isOptional) return compileEvalRef(ref)
val receiverClass = resolveReceiverClass(fieldTarget.target) val receiverClass = resolveReceiverClass(fieldTarget.target)
?: throw BytecodeCompileException( ?: throw BytecodeCompileException(
"Member assignment requires compile-time receiver type: ${fieldTarget.name}", "Member assignment requires compile-time receiver type: ${fieldTarget.name}",
Pos.builtIn Pos.builtIn
) )
val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
val current = allocSlot()
val result = allocSlot()
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
if (receiverClass == ObjDynamic.type) { if (receiverClass == ObjDynamic.type) {
val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
val current = allocSlot()
val result = allocSlot()
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name)) val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
if (!fieldTarget.isOptional) {
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
updateSlotType(result, SlotType.OBJ)
return CompiledValue(result, SlotType.OBJ)
}
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current) builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result) builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result) builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, current)
builder.emit(objOp, current, rhs.slot, result)
builder.mark(endLabel)
updateSlotType(result, SlotType.OBJ) updateSlotType(result, SlotType.OBJ)
return CompiledValue(result, SlotType.OBJ) return CompiledValue(result, SlotType.OBJ)
} }
throw BytecodeCompileException( val fieldId = receiverClass.instanceFieldIdMap()[fieldTarget.name]
"Member assignment requires compile-time receiver type: ${fieldTarget.name}", val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name]
Pos.builtIn if (fieldId == null && methodId == null && isKnownClassReceiver(fieldTarget.target)) {
) val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
if (!fieldTarget.isOptional) {
builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, result)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_CLASS_SCOPE, receiver.slot, nameId, result)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, current)
builder.emit(objOp, current, rhs.slot, result)
builder.mark(endLabel)
}
updateSlotType(result, SlotType.OBJ)
return CompiledValue(result, SlotType.OBJ)
}
if (fieldId != null || methodId != null) {
if (!fieldTarget.isOptional) {
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, result)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, result)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, current)
builder.emit(objOp, current, rhs.slot, result)
builder.mark(endLabel)
}
updateSlotType(result, SlotType.OBJ)
return CompiledValue(result, SlotType.OBJ)
}
return compileEvalRef(ref)
} }
val implicitTarget = ref.target as? ImplicitThisMemberRef val implicitTarget = ref.target as? ImplicitThisMemberRef
if (implicitTarget != null) { if (implicitTarget != null) {
@ -2190,6 +2340,30 @@ class BytecodeCompiler(
val encodedMethodId = encodeMemberId(receiverClass, methodId) val encodedMethodId = encodeMemberId(receiverClass, methodId)
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
val dst = allocSlot() val dst = allocSlot()
if (fieldId == null && methodId == null && isKnownClassReceiver(ref.target)) {
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
if (!ref.isOptional) {
builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, dst)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst)
builder.mark(endLabel)
}
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
if (fieldId != null || methodId != null) { if (fieldId != null || methodId != null) {
if (!ref.isOptional) { if (!ref.isOptional) {
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId ?: -1, encodedMethodId ?: -1, dst) builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, encodedFieldId ?: -1, encodedMethodId ?: -1, dst)
@ -2507,13 +2681,15 @@ class BytecodeCompiler(
val boxed = allocSlot() val boxed = allocSlot()
builder.emit(Opcode.BOX_OBJ, current, boxed) builder.emit(Opcode.BOX_OBJ, current, boxed)
if (wantResult && ref.isPost) { if (wantResult && ref.isPost) {
val old = allocSlot()
builder.emit(Opcode.MOVE_OBJ, boxed, old)
val result = allocSlot() val result = allocSlot()
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
builder.emit(op, boxed, oneSlot, result) builder.emit(op, boxed, oneSlot, result)
builder.emit(Opcode.MOVE_OBJ, result, boxed) builder.emit(Opcode.MOVE_OBJ, result, boxed)
emitStoreToAddr(boxed, addrSlot, SlotType.OBJ) emitStoreToAddr(boxed, addrSlot, SlotType.OBJ)
updateSlotType(slot, SlotType.OBJ) updateSlotType(slot, SlotType.OBJ)
CompiledValue(boxed, SlotType.OBJ) CompiledValue(old, SlotType.OBJ)
} else { } else {
val result = allocSlot() val result = allocSlot()
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
@ -3092,6 +3268,34 @@ class BytecodeCompiler(
builder.mark(endLabel) builder.mark(endLabel)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
if (isKnownClassReceiver(ref.receiver)) {
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
val memberSlot = allocSlot()
if (!ref.isOptional) {
builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, memberSlot)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_CLASS_SCOPE, receiver.slot, nameId, memberSlot)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, memberSlot)
builder.mark(endLabel)
}
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_SLOT, memberSlot, args.base, encodedCount, dst)
return CompiledValue(dst, SlotType.OBJ)
}
val extSlot = resolveExtensionCallableSlot(receiverClass, ref.name) val extSlot = resolveExtensionCallableSlot(receiverClass, ref.name)
?: throw BytecodeCompileException( ?: throw BytecodeCompileException(
"Unknown member ${ref.name} on ${receiverClass.className}", "Unknown member ${ref.name} on ${receiverClass.className}",
@ -3476,6 +3680,17 @@ class BytecodeCompiler(
throw BytecodeCompileException("Unsupported statement in bytecode: $stmtName", stmt.pos) throw BytecodeCompileException("Unsupported statement in bytecode: $stmtName", stmt.pos)
} }
private fun emitStatementCall(stmt: Statement): CompiledValue {
val constId = builder.addConst(BytecodeConst.ObjRef(stmt))
val calleeSlot = allocSlot()
builder.emit(Opcode.CONST_OBJ, constId, calleeSlot)
updateSlotType(calleeSlot, SlotType.OBJ)
val dst = allocSlot()
builder.emit(Opcode.CALL_SLOT, calleeSlot, 0, 0, dst)
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? { private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
val target = if (stmt is BytecodeStatement) stmt.original else stmt val target = if (stmt is BytecodeStatement) stmt.original else stmt
setPos(target.pos) setPos(target.pos)
@ -3509,9 +3724,9 @@ class BytecodeCompiler(
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target) is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target) is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
is net.sergeych.lyng.ClassDeclStatement -> emitStatementEval(target) is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target)
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementEval(target) is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target)
is net.sergeych.lyng.EnumDeclStatement -> emitStatementEval(target) is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target)
is net.sergeych.lyng.TryStatement -> emitTry(target, true) is net.sergeych.lyng.TryStatement -> emitTry(target, true)
is net.sergeych.lyng.WhenStatement -> compileWhen(target, true) is net.sergeych.lyng.WhenStatement -> compileWhen(target, true)
is net.sergeych.lyng.BreakStatement -> compileBreak(target) is net.sergeych.lyng.BreakStatement -> compileBreak(target)
@ -3536,6 +3751,9 @@ class BytecodeCompiler(
is VarDeclStatement -> emitVarDecl(target) is VarDeclStatement -> emitVarDecl(target)
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
is IfStatement -> compileIfStatement(target) is IfStatement -> compileIfStatement(target)
is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target)
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target)
is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target)
is net.sergeych.lyng.ForInStatement -> { is net.sergeych.lyng.ForInStatement -> {
val resultSlot = emitForIn(target, false) ?: return null val resultSlot = emitForIn(target, false) ?: return null
CompiledValue(resultSlot, SlotType.OBJ) CompiledValue(resultSlot, SlotType.OBJ)
@ -4986,6 +5204,9 @@ class BytecodeCompiler(
private fun resolveReceiverClass(ref: ObjRef): ObjClass? { private fun resolveReceiverClass(ref: ObjRef): ObjClass? {
return when (ref) { return when (ref) {
is LocalSlotRef -> { is LocalSlotRef -> {
if (knownObjectNames.contains(ref.name)) {
return nameObjClass[ref.name] ?: ObjDynamic.type
}
val slot = resolveSlot(ref) val slot = resolveSlot(ref)
val fromSlot = slot?.let { slotObjClass[it] } val fromSlot = slot?.let { slotObjClass[it] }
fromSlot fromSlot
@ -5002,6 +5223,9 @@ class BytecodeCompiler(
} }
} }
is LocalVarRef -> { is LocalVarRef -> {
if (knownObjectNames.contains(ref.name)) {
return nameObjClass[ref.name] ?: ObjDynamic.type
}
val fromSlot = resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] } val fromSlot = resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] }
if (fromSlot != null) return fromSlot if (fromSlot != null) return fromSlot
val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key val key = localSlotInfoMap.entries.firstOrNull { it.value.name == ref.name }?.key
@ -5046,6 +5270,14 @@ class BytecodeCompiler(
} }
} }
private fun isKnownClassReceiver(ref: ObjRef): Boolean {
return when (ref) {
is LocalVarRef -> knownClassNames.contains(ref.name) && !knownObjectNames.contains(ref.name)
is LocalSlotRef -> knownClassNames.contains(ref.name) && !knownObjectNames.contains(ref.name)
else -> false
}
}
private fun isAllowedObjectMember(memberName: String): Boolean { private fun isAllowedObjectMember(memberName: String): Boolean {
return when (memberName) { return when (memberName) {
"toString", "toString",
@ -5149,8 +5381,12 @@ class BytecodeCompiler(
private fun inferCallReturnClass(ref: CallRef): ObjClass? { private fun inferCallReturnClass(ref: CallRef): ObjClass? {
return when (val target = ref.target) { return when (val target = ref.target) {
is LocalSlotRef -> nameObjClass[target.name] ?: resolveTypeNameClass(target.name) is LocalSlotRef -> callableReturnTypeByScopeId[target.scopeId]?.get(target.slot)
is LocalVarRef -> nameObjClass[target.name] ?: resolveTypeNameClass(target.name) ?: nameObjClass[target.name]
?: resolveTypeNameClass(target.name)
is LocalVarRef -> callableReturnTypeByName[target.name]
?: nameObjClass[target.name]
?: resolveTypeNameClass(target.name)
is ConstRef -> target.constValue as? ObjClass is ConstRef -> target.constValue as? ObjClass
else -> null else -> null
} }
@ -5214,6 +5450,16 @@ class BytecodeCompiler(
private fun inferFieldReturnClass(targetClass: ObjClass?, name: String): ObjClass? { private fun inferFieldReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
if (targetClass == null) return null if (targetClass == null) return null
if (targetClass == ObjDynamic.type) return ObjDynamic.type if (targetClass == ObjDynamic.type) return ObjDynamic.type
classFieldTypesByName[targetClass.className]?.get(name)?.let { return it }
enumEntriesByName[targetClass.className]?.let { entries ->
return when {
name == "entries" -> ObjList.type
name == "name" -> ObjString.type
name == "ordinal" -> ObjInt.type
entries.contains(name) -> targetClass
else -> null
}
}
if (targetClass == ObjInstant.type && (name == "distantFuture" || name == "distantPast")) { if (targetClass == ObjInstant.type && (name == "distantFuture" || name == "distantPast")) {
return ObjInstant.type return ObjInstant.type
} }

View File

@ -45,6 +45,11 @@ class BytecodeStatement private constructor(
moduleScopeId: Int? = null, moduleScopeId: Int? = null,
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(), slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
knownNameObjClass: Map<String, ObjClass> = emptyMap(), knownNameObjClass: Map<String, ObjClass> = emptyMap(),
knownObjectNames: Set<String> = emptySet(),
classFieldTypesByName: Map<String, Map<String, ObjClass>> = emptyMap(),
enumEntriesByName: Map<String, List<String>> = emptyMap(),
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
): Statement { ): Statement {
if (statement is BytecodeStatement) return statement if (statement is BytecodeStatement) return statement
val hasUnsupported = containsUnsupportedStatement(statement) val hasUnsupported = containsUnsupportedStatement(statement)
@ -63,7 +68,12 @@ class BytecodeStatement private constructor(
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
moduleScopeId = moduleScopeId, moduleScopeId = moduleScopeId,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownNameObjClass knownNameObjClass = knownNameObjClass,
knownObjectNames = knownObjectNames,
classFieldTypesByName = classFieldTypesByName,
enumEntriesByName = enumEntriesByName,
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
callableReturnTypeByName = callableReturnTypeByName
) )
val compiled = compiler.compileStatement(nameHint, statement) val compiled = compiler.compileStatement(nameHint, statement)
val fn = compiled ?: throw BytecodeCompileException( val fn = compiled ?: throw BytecodeCompileException(

View File

@ -240,7 +240,9 @@ class CmdAssertIs(internal val objSlot: Int, internal val typeSlot: Int) : Cmd()
"${typeObj.inspect(frame.ensureScope())} is not the class instance" "${typeObj.inspect(frame.ensureScope())} is not the class instance"
) )
if (!obj.isInstanceOf(clazz)) { if (!obj.isInstanceOf(clazz)) {
frame.ensureScope().raiseClassCastError("expected ${clazz.className}, got ${obj.objClass.className}") frame.ensureScope().raiseClassCastError(
"Cannot cast ${obj.objClass.className} to ${clazz.className}"
)
} }
return return
} }
@ -1296,6 +1298,14 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
type = ObjRecord.Type.Property type = ObjRecord.Type.Property
) )
) )
val getterName = extensionPropertyGetterName(decl.extTypeName, decl.property.name)
val getterWrapper = ObjExtensionPropertyGetterCallable(decl.property.name, decl.property)
frame.ensureScope().addItem(getterName, false, getterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun)
if (decl.property.setter != null) {
val setterName = extensionPropertySetterName(decl.extTypeName, decl.property.name)
val setterWrapper = ObjExtensionPropertySetterCallable(decl.property.name, decl.property)
frame.ensureScope().addItem(setterName, false, setterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun)
}
frame.setObj(slot, decl.property) frame.setObj(slot, decl.property)
return return
} }
@ -1449,7 +1459,12 @@ class CmdGetMemberSlot(
} }
} else null } else null
} ?: frame.ensureScope().raiseSymbolNotFound("member") } ?: frame.ensureScope().raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>" val rawName = rec.memberName ?: "<member>"
val name = if (receiver is ObjInstance && rawName.contains("::")) {
rawName.substringAfterLast("::")
} else {
rawName
}
suspend fun autoCallIfMethod(resolved: ObjRecord, recv: Obj): Obj { suspend fun autoCallIfMethod(resolved: ObjRecord, recv: Obj): Obj {
return if (resolved.type == ObjRecord.Type.Fun && !resolved.isAbstract) { return if (resolved.type == ObjRecord.Type.Fun && !resolved.isAbstract) {
resolved.value.invoke(frame.ensureScope(), resolved.receiver ?: recv, Arguments.EMPTY, resolved.declaringClass) resolved.value.invoke(frame.ensureScope(), resolved.receiver ?: recv, Arguments.EMPTY, resolved.declaringClass)
@ -1498,12 +1513,17 @@ class CmdSetMemberSlot(
} }
} else null } else null
} ?: frame.ensureScope().raiseSymbolNotFound("member") } ?: frame.ensureScope().raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>" val rawName = rec.memberName ?: "<member>"
val name = if (receiver is ObjInstance && rawName.contains("::")) {
rawName.substringAfterLast("::")
} else {
rawName
}
if (receiver is ObjQualifiedView) { if (receiver is ObjQualifiedView) {
receiver.writeField(frame.ensureScope(), name, frame.slotToObj(valueSlot)) receiver.writeField(frame.ensureScope(), name, frame.slotToObj(valueSlot))
return return
} }
frame.ensureScope().assign(rec, name, frame.slotToObj(valueSlot)) receiver.writeField(frame.ensureScope(), name, frame.slotToObj(valueSlot))
return return
} }
} }
@ -2383,10 +2403,10 @@ class CmdFrame(
val direct = record.value val direct = record.value
if (direct is FrameSlotRef) return direct.read() if (direct is FrameSlotRef) return direct.read()
val name = fn.scopeSlotNames[slot] val name = fn.scopeSlotNames[slot]
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
return target.resolve(record, name)
}
if (direct !== ObjUnset) { if (direct !== ObjUnset) {
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
return target.resolve(record, name)
}
return direct return direct
} }
if (name == null) return record.value if (name == null) return record.value
@ -2405,10 +2425,10 @@ class CmdFrame(
if (direct is FrameSlotRef) return direct.read() if (direct is FrameSlotRef) return direct.read()
val slotId = addrScopeSlots[addrSlot] val slotId = addrScopeSlots[addrSlot]
val name = fn.scopeSlotNames.getOrNull(slotId) val name = fn.scopeSlotNames.getOrNull(slotId)
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
return target.resolve(record, name)
}
if (direct !== ObjUnset) { if (direct !== ObjUnset) {
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
return target.resolve(record, name)
}
return direct return direct
} }
if (name == null) return record.value if (name == null) return record.value