Enforce compile-time only member access
This commit is contained in:
parent
862486e0e8
commit
47654aee38
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
|
||||||
|
|
||||||
import java.util.IdentityHashMap
|
|
||||||
|
|
||||||
internal actual object CmdCallSiteCache {
|
|
||||||
private val cache = ThreadLocal.withInitial {
|
|
||||||
IdentityHashMap<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
|
||||||
val map = cache.get()
|
|
||||||
return map.getOrPut(fn) { mutableMapOf() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1079,7 +1079,9 @@ class Compiler(
|
|||||||
block,
|
block,
|
||||||
"<script>",
|
"<script>",
|
||||||
allowLocalSlots = true,
|
allowLocalSlots = true,
|
||||||
allowedScopeNames = modulePlan.keys
|
allowedScopeNames = modulePlan.keys,
|
||||||
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
|
knownNameObjClass = knownClassMapForBytecode()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -1284,6 +1286,27 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun knownClassMapForBytecode(): Map<String, ObjClass> {
|
||||||
|
val result = LinkedHashMap<String, ObjClass>()
|
||||||
|
fun addScope(scope: Scope?) {
|
||||||
|
if (scope == null) return
|
||||||
|
for ((name, rec) in scope.objects) {
|
||||||
|
val cls = rec.value as? ObjClass ?: continue
|
||||||
|
result.putIfAbsent(name, cls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addScope(seedScope)
|
||||||
|
addScope(importManager.rootScope)
|
||||||
|
for (scope in importedScopes) {
|
||||||
|
addScope(scope)
|
||||||
|
}
|
||||||
|
for (name in compileClassInfos.keys) {
|
||||||
|
val cls = resolveClassByName(name) ?: continue
|
||||||
|
result.putIfAbsent(name, cls)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
private fun wrapBytecode(stmt: Statement): Statement {
|
private fun wrapBytecode(stmt: Statement): Statement {
|
||||||
if (!useBytecodeStatements) return stmt
|
if (!useBytecodeStatements) return stmt
|
||||||
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
||||||
@ -1322,7 +1345,9 @@ class Compiler(
|
|||||||
allowLocalSlots = allowLocals,
|
allowLocalSlots = allowLocals,
|
||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = currentRangeParamNames,
|
rangeLocalNames = currentRangeParamNames,
|
||||||
allowedScopeNames = allowedScopeNames
|
allowedScopeNames = allowedScopeNames,
|
||||||
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
|
knownNameObjClass = knownClassMapForBytecode()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1338,7 +1363,9 @@ class Compiler(
|
|||||||
allowLocalSlots = true,
|
allowLocalSlots = true,
|
||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = currentRangeParamNames,
|
rangeLocalNames = currentRangeParamNames,
|
||||||
allowedScopeNames = allowedScopeNames
|
allowedScopeNames = allowedScopeNames,
|
||||||
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
|
knownNameObjClass = knownClassMapForBytecode()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3250,8 +3277,7 @@ class Compiler(
|
|||||||
if (left is LocalSlotRef && left.name == "scope") return
|
if (left is LocalSlotRef && left.name == "scope") return
|
||||||
val receiverClass = resolveReceiverClassForMember(left)
|
val receiverClass = resolveReceiverClassForMember(left)
|
||||||
if (receiverClass == null) {
|
if (receiverClass == null) {
|
||||||
val allowed = memberName == "toString" || memberName == "toInspectString"
|
if (isAllowedObjectMember(memberName)) return
|
||||||
if (allowed) return
|
|
||||||
throw ScriptError(pos, "member access requires compile-time receiver type: $memberName")
|
throw ScriptError(pos, "member access requires compile-time receiver type: $memberName")
|
||||||
}
|
}
|
||||||
if (receiverClass == Obj.rootObjectType) {
|
if (receiverClass == Obj.rootObjectType) {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ package net.sergeych.lyng.bytecode
|
|||||||
|
|
||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
|
|
||||||
class BytecodeFallbackException(
|
class BytecodeCompileException(
|
||||||
message: String,
|
message: String,
|
||||||
val pos: Pos? = null,
|
val pos: Pos? = null,
|
||||||
) : RuntimeException(message) {
|
) : RuntimeException(message) {
|
||||||
@ -42,6 +42,8 @@ class BytecodeCompiler(
|
|||||||
private val returnLabels: Set<String> = emptySet(),
|
private val returnLabels: Set<String> = emptySet(),
|
||||||
private val rangeLocalNames: Set<String> = emptySet(),
|
private val rangeLocalNames: Set<String> = emptySet(),
|
||||||
private val allowedScopeNames: Set<String>? = null,
|
private val allowedScopeNames: Set<String>? = null,
|
||||||
|
private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
|
private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
||||||
) {
|
) {
|
||||||
private var builder = CmdBuilder()
|
private var builder = CmdBuilder()
|
||||||
private var nextSlot = 0
|
private var nextSlot = 0
|
||||||
@ -281,7 +283,7 @@ class BytecodeCompiler(
|
|||||||
is ThisMethodSlotCallRef -> compileThisMethodSlotCall(ref)
|
is ThisMethodSlotCallRef -> compileThisMethodSlotCall(ref)
|
||||||
is StatementRef -> {
|
is StatementRef -> {
|
||||||
val compiled = compileStatementValueOrFallback(ref.statement)
|
val compiled = compileStatementValueOrFallback(ref.statement)
|
||||||
compiled ?: throw BytecodeFallbackException(
|
compiled ?: throw BytecodeCompileException(
|
||||||
"Unsupported StatementRef(${ref.statement::class.simpleName})",
|
"Unsupported StatementRef(${ref.statement::class.simpleName})",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -309,9 +311,9 @@ class BytecodeCompiler(
|
|||||||
val methodId = ref.methodId ?: -1
|
val methodId = ref.methodId ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
val typeName = ref.preferredThisTypeName()
|
val typeName = ref.preferredThisTypeName()
|
||||||
?: throw BytecodeFallbackException("Missing member id for ${ref.name}", Pos.builtIn)
|
?: throw BytecodeCompileException("Missing member id for ${ref.name}", Pos.builtIn)
|
||||||
val wrapperName = extensionPropertyGetterName(typeName, ref.name)
|
val wrapperName = extensionPropertyGetterName(typeName, ref.name)
|
||||||
val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeFallbackException(
|
val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeCompileException(
|
||||||
"Missing extension wrapper for ${typeName}.${ref.name}",
|
"Missing extension wrapper for ${typeName}.${ref.name}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -367,12 +369,12 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
val typeName = ref.preferredThisTypeName() ?: throw BytecodeFallbackException(
|
val typeName = ref.preferredThisTypeName() ?: throw BytecodeCompileException(
|
||||||
"Missing member id for ${ref.methodName()}",
|
"Missing member id for ${ref.methodName()}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
val wrapperName = extensionCallableName(typeName, ref.methodName())
|
val wrapperName = extensionCallableName(typeName, ref.methodName())
|
||||||
val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeFallbackException(
|
val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeCompileException(
|
||||||
"Missing extension wrapper for ${typeName}.${ref.methodName()}",
|
"Missing extension wrapper for ${typeName}.${ref.methodName()}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -468,7 +470,7 @@ class BytecodeCompiler(
|
|||||||
is UnaryOpRef -> "UnaryOpRef(${ref.op})"
|
is UnaryOpRef -> "UnaryOpRef(${ref.op})"
|
||||||
else -> ref::class.simpleName ?: "UnknownRef"
|
else -> ref::class.simpleName ?: "UnknownRef"
|
||||||
}
|
}
|
||||||
throw BytecodeFallbackException("Unsupported expression ($refInfo)", Pos.builtIn)
|
throw BytecodeCompileException("Unsupported expression ($refInfo)", Pos.builtIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileListLiteral(ref: ListLiteralRef): CompiledValue? {
|
private fun compileListLiteral(ref: ListLiteralRef): CompiledValue? {
|
||||||
@ -513,7 +515,7 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.SET_INDEX, dst, keySlot, value.slot)
|
builder.emit(Opcode.SET_INDEX, dst, keySlot, value.slot)
|
||||||
}
|
}
|
||||||
is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> {
|
is net.sergeych.lyng.obj.MapLiteralEntry.Spread -> {
|
||||||
throw BytecodeFallbackException("Map spread is not supported in bytecode", Pos.builtIn)
|
throw BytecodeCompileException("Map spread is not supported in bytecode", Pos.builtIn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -563,16 +565,7 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.NEG_REAL, a.slot, out)
|
builder.emit(Opcode.NEG_REAL, a.slot, out)
|
||||||
CompiledValue(out, SlotType.REAL)
|
CompiledValue(out, SlotType.REAL)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> compileObjUnaryOp(unaryOperand(ref), a, "negate", Pos.builtIn)
|
||||||
val zeroId = builder.addConst(BytecodeConst.IntVal(0))
|
|
||||||
val zeroSlot = allocSlot()
|
|
||||||
builder.emit(Opcode.CONST_INT, zeroId, zeroSlot)
|
|
||||||
updateSlotType(zeroSlot, SlotType.INT)
|
|
||||||
val obj = ensureObjSlot(a)
|
|
||||||
builder.emit(Opcode.SUB_OBJ, zeroSlot, obj.slot, out)
|
|
||||||
updateSlotType(out, SlotType.OBJ)
|
|
||||||
CompiledValue(out, SlotType.OBJ)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
UnaryOp.NOT -> {
|
UnaryOp.NOT -> {
|
||||||
when (a.type) {
|
when (a.type) {
|
||||||
@ -598,11 +591,154 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.INV_INT, a.slot, out)
|
builder.emit(Opcode.INV_INT, a.slot, out)
|
||||||
return CompiledValue(out, SlotType.INT)
|
return CompiledValue(out, SlotType.INT)
|
||||||
}
|
}
|
||||||
return compileEvalRef(ref)
|
return compileObjUnaryOp(unaryOperand(ref), a, "bitNot", Pos.builtIn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun compileObjUnaryOp(
|
||||||
|
ref: ObjRef,
|
||||||
|
value: CompiledValue,
|
||||||
|
memberName: String,
|
||||||
|
pos: Pos
|
||||||
|
): CompiledValue? {
|
||||||
|
val receiverClass = resolveReceiverClass(ref)
|
||||||
|
val methodId = receiverClass?.instanceMethodIdMap(includeAbstract = true)?.get(memberName)
|
||||||
|
if (methodId != null) {
|
||||||
|
val receiverObj = ensureObjSlot(value)
|
||||||
|
val dst = allocSlot()
|
||||||
|
builder.emit(Opcode.CALL_MEMBER_SLOT, receiverObj.slot, methodId, 0, 0, dst)
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
if (receiverClass == null && memberName == "negate") {
|
||||||
|
val zeroId = builder.addConst(BytecodeConst.IntVal(0))
|
||||||
|
val zeroSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_INT, zeroId, zeroSlot)
|
||||||
|
updateSlotType(zeroSlot, SlotType.INT)
|
||||||
|
val obj = ensureObjSlot(value)
|
||||||
|
val dst = allocSlot()
|
||||||
|
builder.emit(Opcode.SUB_OBJ, zeroSlot, obj.slot, dst)
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
if (memberName == "negate" && receiverClass in setOf(ObjInt.type, ObjReal.type)) {
|
||||||
|
val zeroId = builder.addConst(BytecodeConst.IntVal(0))
|
||||||
|
val zeroSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_INT, zeroId, zeroSlot)
|
||||||
|
updateSlotType(zeroSlot, SlotType.INT)
|
||||||
|
val obj = ensureObjSlot(value)
|
||||||
|
val dst = allocSlot()
|
||||||
|
builder.emit(Opcode.SUB_OBJ, zeroSlot, obj.slot, dst)
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
throw BytecodeCompileException(
|
||||||
|
"Unknown member $memberName on ${receiverClass?.className ?: "unknown"}",
|
||||||
|
pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun operatorMemberName(op: BinOp): String? = when (op) {
|
||||||
|
BinOp.PLUS -> "plus"
|
||||||
|
BinOp.MINUS -> "minus"
|
||||||
|
BinOp.STAR -> "mul"
|
||||||
|
BinOp.SLASH -> "div"
|
||||||
|
BinOp.PERCENT -> "mod"
|
||||||
|
BinOp.BAND -> "bitAnd"
|
||||||
|
BinOp.BOR -> "bitOr"
|
||||||
|
BinOp.BXOR -> "bitXor"
|
||||||
|
BinOp.SHL -> "shl"
|
||||||
|
BinOp.SHR -> "shr"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun allowKotlinOperatorFallback(receiverClass: ObjClass, op: BinOp): Boolean = when (op) {
|
||||||
|
BinOp.PLUS -> receiverClass in setOf(
|
||||||
|
ObjString.type,
|
||||||
|
ObjInt.type,
|
||||||
|
ObjReal.type,
|
||||||
|
ObjList.type,
|
||||||
|
ObjSet.type,
|
||||||
|
ObjMap.type,
|
||||||
|
ObjBuffer.type,
|
||||||
|
ObjInstant.type,
|
||||||
|
ObjDateTime.type
|
||||||
|
)
|
||||||
|
BinOp.MINUS -> receiverClass in setOf(
|
||||||
|
ObjInt.type,
|
||||||
|
ObjReal.type,
|
||||||
|
ObjSet.type,
|
||||||
|
ObjInstant.type,
|
||||||
|
ObjDateTime.type
|
||||||
|
)
|
||||||
|
BinOp.STAR -> receiverClass in setOf(ObjInt.type, ObjReal.type, ObjString.type)
|
||||||
|
BinOp.SLASH, BinOp.PERCENT -> receiverClass in setOf(ObjInt.type, ObjReal.type)
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileObjBinaryOp(
|
||||||
|
leftRef: ObjRef,
|
||||||
|
leftValue: CompiledValue,
|
||||||
|
rightValue: CompiledValue,
|
||||||
|
op: BinOp,
|
||||||
|
pos: Pos
|
||||||
|
): CompiledValue? {
|
||||||
|
val memberName = operatorMemberName(op) ?: return null
|
||||||
|
val receiverClass = resolveReceiverClass(leftRef)
|
||||||
|
if (receiverClass == null) {
|
||||||
|
val objOpcode = when (op) {
|
||||||
|
BinOp.PLUS -> Opcode.ADD_OBJ
|
||||||
|
BinOp.MINUS -> Opcode.SUB_OBJ
|
||||||
|
BinOp.STAR -> Opcode.MUL_OBJ
|
||||||
|
BinOp.SLASH -> Opcode.DIV_OBJ
|
||||||
|
BinOp.PERCENT -> Opcode.MOD_OBJ
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (objOpcode != null) {
|
||||||
|
val receiverObj = ensureObjSlot(leftValue)
|
||||||
|
val argObj = ensureObjSlot(rightValue)
|
||||||
|
val dst = allocSlot()
|
||||||
|
builder.emit(objOpcode, receiverObj.slot, argObj.slot, dst)
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
throw BytecodeCompileException(
|
||||||
|
"Operator requires compile-time receiver type: $memberName",
|
||||||
|
pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[memberName]
|
||||||
|
if (methodId != null) {
|
||||||
|
val receiverObj = ensureObjSlot(leftValue)
|
||||||
|
val argObj = ensureObjSlot(rightValue)
|
||||||
|
val dst = allocSlot()
|
||||||
|
builder.emit(Opcode.CALL_MEMBER_SLOT, receiverObj.slot, methodId, argObj.slot, 1, dst)
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
val objOpcode = when (op) {
|
||||||
|
BinOp.PLUS -> Opcode.ADD_OBJ
|
||||||
|
BinOp.MINUS -> Opcode.SUB_OBJ
|
||||||
|
BinOp.STAR -> Opcode.MUL_OBJ
|
||||||
|
BinOp.SLASH -> Opcode.DIV_OBJ
|
||||||
|
BinOp.PERCENT -> Opcode.MOD_OBJ
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (objOpcode != null && allowKotlinOperatorFallback(receiverClass, op)) {
|
||||||
|
val receiverObj = ensureObjSlot(leftValue)
|
||||||
|
val argObj = ensureObjSlot(rightValue)
|
||||||
|
val dst = allocSlot()
|
||||||
|
builder.emit(objOpcode, receiverObj.slot, argObj.slot, dst)
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
throw BytecodeCompileException(
|
||||||
|
"Unknown member $memberName on ${receiverClass.className}",
|
||||||
|
pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun compileBinary(ref: BinaryOpRef): CompiledValue? {
|
private fun compileBinary(ref: BinaryOpRef): CompiledValue? {
|
||||||
val op = binaryOp(ref)
|
val op = binaryOp(ref)
|
||||||
if (op == BinOp.AND || op == BinOp.OR) {
|
if (op == BinOp.AND || op == BinOp.OR) {
|
||||||
@ -712,12 +848,12 @@ class BytecodeCompiler(
|
|||||||
if (op == BinOp.MATCH || op == BinOp.NOTMATCH) {
|
if (op == BinOp.MATCH || op == BinOp.NOTMATCH) {
|
||||||
val leftRef = binaryLeft(ref)
|
val leftRef = binaryLeft(ref)
|
||||||
val rightRef = binaryRight(ref)
|
val rightRef = binaryRight(ref)
|
||||||
val receiverClass = resolveReceiverClass(leftRef) ?: throw BytecodeFallbackException(
|
val receiverClass = resolveReceiverClass(leftRef) ?: throw BytecodeCompileException(
|
||||||
"Match operator requires compile-time receiver type",
|
"Match operator requires compile-time receiver type",
|
||||||
refPos(ref)
|
refPos(ref)
|
||||||
)
|
)
|
||||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)["operatorMatch"]
|
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)["operatorMatch"]
|
||||||
?: throw BytecodeFallbackException(
|
?: throw BytecodeCompileException(
|
||||||
"Unknown member operatorMatch on ${receiverClass.className}",
|
"Unknown member operatorMatch on ${receiverClass.className}",
|
||||||
refPos(ref)
|
refPos(ref)
|
||||||
)
|
)
|
||||||
@ -782,36 +918,12 @@ class BytecodeCompiler(
|
|||||||
val typesMismatch = a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN
|
val typesMismatch = a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN
|
||||||
val allowMixedNumeric = op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH)
|
val allowMixedNumeric = op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH)
|
||||||
if (typesMismatch && op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)) {
|
if (typesMismatch && op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)) {
|
||||||
val leftObj = ensureObjSlot(a)
|
return compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
val rightObj = ensureObjSlot(b)
|
|
||||||
val out = allocSlot()
|
|
||||||
val objOpcode = when (op) {
|
|
||||||
BinOp.PLUS -> Opcode.ADD_OBJ
|
|
||||||
BinOp.MINUS -> Opcode.SUB_OBJ
|
|
||||||
BinOp.STAR -> Opcode.MUL_OBJ
|
|
||||||
BinOp.SLASH -> Opcode.DIV_OBJ
|
|
||||||
BinOp.PERCENT -> Opcode.MOD_OBJ
|
|
||||||
else -> null
|
|
||||||
} ?: return null
|
|
||||||
builder.emit(objOpcode, leftObj.slot, rightObj.slot, out)
|
|
||||||
return CompiledValue(out, SlotType.OBJ)
|
|
||||||
}
|
}
|
||||||
if ((a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) &&
|
if ((a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) &&
|
||||||
op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)
|
op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)
|
||||||
) {
|
) {
|
||||||
val leftObj = ensureObjSlot(a)
|
return compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
val rightObj = ensureObjSlot(b)
|
|
||||||
val out = allocSlot()
|
|
||||||
val objOpcode = when (op) {
|
|
||||||
BinOp.PLUS -> Opcode.ADD_OBJ
|
|
||||||
BinOp.MINUS -> Opcode.SUB_OBJ
|
|
||||||
BinOp.STAR -> Opcode.MUL_OBJ
|
|
||||||
BinOp.SLASH -> Opcode.DIV_OBJ
|
|
||||||
BinOp.PERCENT -> Opcode.MOD_OBJ
|
|
||||||
else -> null
|
|
||||||
} ?: return null
|
|
||||||
builder.emit(objOpcode, leftObj.slot, rightObj.slot, out)
|
|
||||||
return CompiledValue(out, SlotType.OBJ)
|
|
||||||
}
|
}
|
||||||
if (typesMismatch && !allowMixedNumeric &&
|
if (typesMismatch && !allowMixedNumeric &&
|
||||||
op !in setOf(BinOp.EQ, BinOp.NEQ, BinOp.LT, BinOp.LTE, BinOp.GT, BinOp.GTE)
|
op !in setOf(BinOp.EQ, BinOp.NEQ, BinOp.LT, BinOp.LTE, BinOp.GT, BinOp.GTE)
|
||||||
@ -844,9 +956,8 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SlotType.OBJ -> {
|
SlotType.OBJ -> {
|
||||||
if (b.type != SlotType.OBJ) return null
|
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||||
builder.emit(Opcode.ADD_OBJ, a.slot, b.slot, out)
|
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
CompiledValue(out, SlotType.OBJ)
|
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@ -874,9 +985,8 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SlotType.OBJ -> {
|
SlotType.OBJ -> {
|
||||||
if (b.type != SlotType.OBJ) return null
|
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||||
builder.emit(Opcode.SUB_OBJ, a.slot, b.slot, out)
|
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
CompiledValue(out, SlotType.OBJ)
|
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@ -904,9 +1014,8 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SlotType.OBJ -> {
|
SlotType.OBJ -> {
|
||||||
if (b.type != SlotType.OBJ) return null
|
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||||
builder.emit(Opcode.MUL_OBJ, a.slot, b.slot, out)
|
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
CompiledValue(out, SlotType.OBJ)
|
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@ -934,9 +1043,8 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SlotType.OBJ -> {
|
SlotType.OBJ -> {
|
||||||
if (b.type != SlotType.OBJ) return null
|
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||||
builder.emit(Opcode.DIV_OBJ, a.slot, b.slot, out)
|
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
CompiledValue(out, SlotType.OBJ)
|
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@ -948,9 +1056,8 @@ class BytecodeCompiler(
|
|||||||
CompiledValue(out, SlotType.INT)
|
CompiledValue(out, SlotType.INT)
|
||||||
}
|
}
|
||||||
SlotType.OBJ -> {
|
SlotType.OBJ -> {
|
||||||
if (b.type != SlotType.OBJ) return null
|
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||||
builder.emit(Opcode.MOD_OBJ, a.slot, b.slot, out)
|
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
CompiledValue(out, SlotType.OBJ)
|
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@ -994,30 +1101,55 @@ class BytecodeCompiler(
|
|||||||
CompiledValue(out, SlotType.BOOL)
|
CompiledValue(out, SlotType.BOOL)
|
||||||
}
|
}
|
||||||
BinOp.BAND -> {
|
BinOp.BAND -> {
|
||||||
if (a.type != SlotType.INT) return null
|
when (a.type) {
|
||||||
|
SlotType.INT -> {
|
||||||
builder.emit(Opcode.AND_INT, a.slot, b.slot, out)
|
builder.emit(Opcode.AND_INT, a.slot, b.slot, out)
|
||||||
CompiledValue(out, SlotType.INT)
|
CompiledValue(out, SlotType.INT)
|
||||||
}
|
}
|
||||||
|
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
BinOp.BOR -> {
|
BinOp.BOR -> {
|
||||||
if (a.type != SlotType.INT) return null
|
when (a.type) {
|
||||||
|
SlotType.INT -> {
|
||||||
builder.emit(Opcode.OR_INT, a.slot, b.slot, out)
|
builder.emit(Opcode.OR_INT, a.slot, b.slot, out)
|
||||||
CompiledValue(out, SlotType.INT)
|
CompiledValue(out, SlotType.INT)
|
||||||
}
|
}
|
||||||
|
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
BinOp.BXOR -> {
|
BinOp.BXOR -> {
|
||||||
if (a.type != SlotType.INT) return null
|
when (a.type) {
|
||||||
|
SlotType.INT -> {
|
||||||
builder.emit(Opcode.XOR_INT, a.slot, b.slot, out)
|
builder.emit(Opcode.XOR_INT, a.slot, b.slot, out)
|
||||||
CompiledValue(out, SlotType.INT)
|
CompiledValue(out, SlotType.INT)
|
||||||
}
|
}
|
||||||
|
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
BinOp.SHL -> {
|
BinOp.SHL -> {
|
||||||
if (a.type != SlotType.INT) return null
|
when (a.type) {
|
||||||
|
SlotType.INT -> {
|
||||||
builder.emit(Opcode.SHL_INT, a.slot, b.slot, out)
|
builder.emit(Opcode.SHL_INT, a.slot, b.slot, out)
|
||||||
CompiledValue(out, SlotType.INT)
|
CompiledValue(out, SlotType.INT)
|
||||||
}
|
}
|
||||||
|
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
BinOp.SHR -> {
|
BinOp.SHR -> {
|
||||||
if (a.type != SlotType.INT) return null
|
when (a.type) {
|
||||||
|
SlotType.INT -> {
|
||||||
builder.emit(Opcode.SHR_INT, a.slot, b.slot, out)
|
builder.emit(Opcode.SHR_INT, a.slot, b.slot, out)
|
||||||
CompiledValue(out, SlotType.INT)
|
CompiledValue(out, SlotType.INT)
|
||||||
}
|
}
|
||||||
|
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1472,7 +1604,7 @@ class BytecodeCompiler(
|
|||||||
val target = ref.target
|
val target = ref.target
|
||||||
if (target is FieldRef) {
|
if (target is FieldRef) {
|
||||||
val receiverClass = resolveReceiverClass(target.target)
|
val receiverClass = resolveReceiverClass(target.target)
|
||||||
?: throw BytecodeFallbackException(
|
?: throw BytecodeCompileException(
|
||||||
"Member assignment requires compile-time receiver type: ${target.name}",
|
"Member assignment requires compile-time receiver type: ${target.name}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -1485,7 +1617,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
||||||
?: throw BytecodeFallbackException(
|
?: throw BytecodeCompileException(
|
||||||
"Unknown member ${target.name} on ${receiverClass.className}",
|
"Unknown member ${target.name} on ${receiverClass.className}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -1542,9 +1674,9 @@ class BytecodeCompiler(
|
|||||||
val methodId = target.methodId ?: -1
|
val methodId = target.methodId ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
val typeName = target.preferredThisTypeName()
|
val typeName = target.preferredThisTypeName()
|
||||||
?: throw BytecodeFallbackException("Missing member id for ${target.name}", Pos.builtIn)
|
?: throw BytecodeCompileException("Missing member id for ${target.name}", Pos.builtIn)
|
||||||
val wrapperName = extensionPropertySetterName(typeName, target.name)
|
val wrapperName = extensionPropertySetterName(typeName, target.name)
|
||||||
val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeFallbackException(
|
val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeCompileException(
|
||||||
"Missing extension wrapper for ${typeName}.${target.name}",
|
"Missing extension wrapper for ${typeName}.${target.name}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -1570,7 +1702,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = target.fieldId() ?: -1
|
val fieldId = target.fieldId() ?: -1
|
||||||
val methodId = target.methodId() ?: -1
|
val methodId = target.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${target.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${target.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot)
|
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot)
|
||||||
return value
|
return value
|
||||||
@ -1580,7 +1712,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = target.fieldId() ?: -1
|
val fieldId = target.fieldId() ?: -1
|
||||||
val methodId = target.methodId() ?: -1
|
val methodId = target.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${target.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${target.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot)
|
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId, methodId, value.slot)
|
||||||
return value
|
return value
|
||||||
@ -1673,7 +1805,7 @@ 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) {
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Member assignment requires compile-time receiver type: ${fieldTarget.name}",
|
"Member assignment requires compile-time receiver type: ${fieldTarget.name}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -1684,7 +1816,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = implicitTarget.fieldId ?: -1
|
val fieldId = implicitTarget.fieldId ?: -1
|
||||||
val methodId = implicitTarget.methodId ?: -1
|
val methodId = implicitTarget.methodId ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${implicitTarget.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${implicitTarget.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
val current = allocSlot()
|
val current = allocSlot()
|
||||||
val result = allocSlot()
|
val result = allocSlot()
|
||||||
@ -1701,7 +1833,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = thisFieldTarget.fieldId() ?: -1
|
val fieldId = thisFieldTarget.fieldId() ?: -1
|
||||||
val methodId = thisFieldTarget.methodId() ?: -1
|
val methodId = thisFieldTarget.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${thisFieldTarget.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${thisFieldTarget.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
val current = allocSlot()
|
val current = allocSlot()
|
||||||
val result = allocSlot()
|
val result = allocSlot()
|
||||||
@ -1718,7 +1850,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = qualifiedTarget.fieldId() ?: -1
|
val fieldId = qualifiedTarget.fieldId() ?: -1
|
||||||
val methodId = qualifiedTarget.methodId() ?: -1
|
val methodId = qualifiedTarget.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${qualifiedTarget.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${qualifiedTarget.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
val current = allocSlot()
|
val current = allocSlot()
|
||||||
val result = allocSlot()
|
val result = allocSlot()
|
||||||
@ -1808,7 +1940,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
is FieldRef -> {
|
is FieldRef -> {
|
||||||
val receiverClass = resolveReceiverClass(target.target)
|
val receiverClass = resolveReceiverClass(target.target)
|
||||||
?: throw BytecodeFallbackException(
|
?: throw BytecodeCompileException(
|
||||||
"Member assignment requires compile-time receiver type: ${target.name}",
|
"Member assignment requires compile-time receiver type: ${target.name}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -1833,7 +1965,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
||||||
?: throw BytecodeFallbackException(
|
?: throw BytecodeCompileException(
|
||||||
"Unknown member ${target.name} on ${receiverClass.className}",
|
"Unknown member ${target.name} on ${receiverClass.className}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -1895,7 +2027,7 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun compileFieldRef(ref: FieldRef): CompiledValue? {
|
private fun compileFieldRef(ref: FieldRef): CompiledValue? {
|
||||||
val receiverClass = resolveReceiverClass(ref.target)
|
val receiverClass = resolveReceiverClass(ref.target)
|
||||||
?: throw BytecodeFallbackException(
|
?: throw BytecodeCompileException(
|
||||||
"Member access requires compile-time receiver type: ${ref.name}",
|
"Member access requires compile-time receiver type: ${ref.name}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -1927,7 +2059,7 @@ class BytecodeCompiler(
|
|||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
val extSlot = resolveExtensionGetterSlot(receiverClass, ref.name)
|
val extSlot = resolveExtensionGetterSlot(receiverClass, ref.name)
|
||||||
?: throw BytecodeFallbackException(
|
?: throw BytecodeCompileException(
|
||||||
"Unknown member ${ref.name} on ${receiverClass.className}",
|
"Unknown member ${ref.name} on ${receiverClass.className}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -1964,7 +2096,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = ref.fieldId() ?: -1
|
val fieldId = ref.fieldId() ?: -1
|
||||||
val methodId = ref.methodId() ?: -1
|
val methodId = ref.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${ref.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${ref.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
val dst = allocSlot()
|
val dst = allocSlot()
|
||||||
if (!ref.optional()) {
|
if (!ref.optional()) {
|
||||||
@ -1995,7 +2127,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = ref.fieldId() ?: -1
|
val fieldId = ref.fieldId() ?: -1
|
||||||
val methodId = ref.methodId() ?: -1
|
val methodId = ref.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${ref.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${ref.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
val dst = allocSlot()
|
val dst = allocSlot()
|
||||||
if (!ref.optional()) {
|
if (!ref.optional()) {
|
||||||
@ -2296,7 +2428,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = thisFieldTarget.fieldId() ?: -1
|
val fieldId = thisFieldTarget.fieldId() ?: -1
|
||||||
val methodId = thisFieldTarget.methodId() ?: -1
|
val methodId = thisFieldTarget.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${thisFieldTarget.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${thisFieldTarget.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
val current = allocSlot()
|
val current = allocSlot()
|
||||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
|
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
|
||||||
@ -2325,7 +2457,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = implicitTarget.fieldId ?: -1
|
val fieldId = implicitTarget.fieldId ?: -1
|
||||||
val methodId = implicitTarget.methodId ?: -1
|
val methodId = implicitTarget.methodId ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${implicitTarget.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${implicitTarget.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
val current = allocSlot()
|
val current = allocSlot()
|
||||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
|
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
|
||||||
@ -2354,7 +2486,7 @@ class BytecodeCompiler(
|
|||||||
val fieldId = qualifiedTarget.fieldId() ?: -1
|
val fieldId = qualifiedTarget.fieldId() ?: -1
|
||||||
val methodId = qualifiedTarget.methodId() ?: -1
|
val methodId = qualifiedTarget.methodId() ?: -1
|
||||||
if (fieldId < 0 && methodId < 0) {
|
if (fieldId < 0 && methodId < 0) {
|
||||||
throw BytecodeFallbackException("Missing member id for ${qualifiedTarget.name}", Pos.builtIn)
|
throw BytecodeCompileException("Missing member id for ${qualifiedTarget.name}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
val current = allocSlot()
|
val current = allocSlot()
|
||||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
|
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
|
||||||
@ -2381,7 +2513,7 @@ class BytecodeCompiler(
|
|||||||
if (fieldTarget != null) {
|
if (fieldTarget != null) {
|
||||||
if (fieldTarget.isOptional) return null
|
if (fieldTarget.isOptional) return null
|
||||||
val receiverClass = resolveReceiverClass(fieldTarget.target)
|
val receiverClass = resolveReceiverClass(fieldTarget.target)
|
||||||
?: throw BytecodeFallbackException(
|
?: throw BytecodeCompileException(
|
||||||
"Member access requires compile-time receiver type: ${fieldTarget.name}",
|
"Member access requires compile-time receiver type: ${fieldTarget.name}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -2591,7 +2723,7 @@ class BytecodeCompiler(
|
|||||||
if (direct == null) {
|
if (direct == null) {
|
||||||
val thisSlot = resolveDirectNameSlot("this")
|
val thisSlot = resolveDirectNameSlot("this")
|
||||||
if (thisSlot != null) {
|
if (thisSlot != null) {
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Unresolved member call '${localTarget.name}': missing compile-time member id",
|
"Unresolved member call '${localTarget.name}': missing compile-time member id",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -2600,7 +2732,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
val fieldTarget = ref.target as? FieldRef
|
val fieldTarget = ref.target as? FieldRef
|
||||||
if (fieldTarget != null) {
|
if (fieldTarget != null) {
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Member call requires compile-time receiver type: ${fieldTarget.name}",
|
"Member call requires compile-time receiver type: ${fieldTarget.name}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -2684,10 +2816,17 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
|
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
|
||||||
val receiverClass = resolveReceiverClass(ref.receiver)
|
val receiverClass = resolveReceiverClass(ref.receiver)
|
||||||
|
?: if (isAllowedObjectMember(ref.name)) {
|
||||||
|
Obj.rootObjectType
|
||||||
|
} else {
|
||||||
|
throw BytecodeCompileException(
|
||||||
|
"Member call requires compile-time receiver type: ${ref.name}",
|
||||||
|
Pos.builtIn
|
||||||
|
)
|
||||||
|
}
|
||||||
val receiver = compileRefWithFallback(ref.receiver, null, Pos.builtIn) ?: return null
|
val receiver = compileRefWithFallback(ref.receiver, null, Pos.builtIn) ?: return null
|
||||||
val dst = allocSlot()
|
val dst = allocSlot()
|
||||||
val methodId = receiverClass?.instanceMethodIdMap(includeAbstract = true)?.get(ref.name)
|
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||||
?: Obj.rootObjectType.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
|
||||||
if (methodId != null) {
|
if (methodId != null) {
|
||||||
if (!ref.isOptional) {
|
if (!ref.isOptional) {
|
||||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||||
@ -2714,14 +2853,8 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
if (receiverClass == null) {
|
|
||||||
throw BytecodeFallbackException(
|
|
||||||
"Member call requires compile-time receiver type: ${ref.name}",
|
|
||||||
Pos.builtIn
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val extSlot = resolveExtensionCallableSlot(receiverClass, ref.name)
|
val extSlot = resolveExtensionCallableSlot(receiverClass, ref.name)
|
||||||
?: throw BytecodeFallbackException(
|
?: throw BytecodeCompileException(
|
||||||
"Unknown member ${ref.name} on ${receiverClass.className}",
|
"Unknown member ${ref.name} on ${receiverClass.className}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -2754,7 +2887,7 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? {
|
private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? {
|
||||||
val receiver = compileThisRef()
|
val receiver = compileThisRef()
|
||||||
val methodId = ref.methodId() ?: throw BytecodeFallbackException(
|
val methodId = ref.methodId() ?: throw BytecodeCompileException(
|
||||||
"Missing member id for ${ref.methodName()}",
|
"Missing member id for ${ref.methodName()}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -2810,7 +2943,7 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun compileQualifiedThisMethodSlotCall(ref: QualifiedThisMethodSlotCallRef): CompiledValue? {
|
private fun compileQualifiedThisMethodSlotCall(ref: QualifiedThisMethodSlotCallRef): CompiledValue? {
|
||||||
val receiver = compileThisVariantRef(ref.receiverTypeName()) ?: return null
|
val receiver = compileThisVariantRef(ref.receiverTypeName()) ?: return null
|
||||||
val methodId = ref.methodId() ?: throw BytecodeFallbackException(
|
val methodId = ref.methodId() ?: throw BytecodeCompileException(
|
||||||
"Missing member id for ${ref.methodName()}",
|
"Missing member id for ${ref.methodName()}",
|
||||||
Pos.builtIn
|
Pos.builtIn
|
||||||
)
|
)
|
||||||
@ -2925,8 +3058,8 @@ class BytecodeCompiler(
|
|||||||
return when (stmt) {
|
return when (stmt) {
|
||||||
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
||||||
else -> {
|
else -> {
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Bytecode fallback: unsupported argument expression",
|
"Bytecode compile error: unsupported argument expression",
|
||||||
stmt.pos
|
stmt.pos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3081,15 +3214,15 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun emitFallbackStatement(stmt: Statement): CompiledValue {
|
private fun emitFallbackStatement(stmt: Statement): CompiledValue {
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Bytecode fallback: unsupported statement",
|
"Bytecode compile error: unsupported statement",
|
||||||
stmt.pos
|
stmt.pos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitStatementEval(stmt: Statement): CompiledValue {
|
private fun emitStatementEval(stmt: Statement): CompiledValue {
|
||||||
val stmtName = stmt::class.simpleName ?: "UnknownStatement"
|
val stmtName = stmt::class.simpleName ?: "UnknownStatement"
|
||||||
throw BytecodeFallbackException("Unsupported statement in bytecode: $stmtName", stmt.pos)
|
throw BytecodeCompileException("Unsupported statement in bytecode: $stmtName", stmt.pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
|
private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
|
||||||
@ -3198,8 +3331,8 @@ class BytecodeCompiler(
|
|||||||
val original = (statement as? BytecodeStatement)?.original
|
val original = (statement as? BytecodeStatement)?.original
|
||||||
val name = original?.let { "${statement::class.simpleName}(${it::class.simpleName})" }
|
val name = original?.let { "${statement::class.simpleName}(${it::class.simpleName})" }
|
||||||
?: statement::class.simpleName
|
?: statement::class.simpleName
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Bytecode fallback: failed to compile block statement ($name)",
|
"Bytecode compile error: failed to compile block statement ($name)",
|
||||||
statement.pos
|
statement.pos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -3531,16 +3664,16 @@ class BytecodeCompiler(
|
|||||||
val iterableMethods = ObjIterable.instanceMethodIdMap(includeAbstract = true)
|
val iterableMethods = ObjIterable.instanceMethodIdMap(includeAbstract = true)
|
||||||
val iteratorMethodId = iterableMethods["iterator"]
|
val iteratorMethodId = iterableMethods["iterator"]
|
||||||
if (iteratorMethodId == null) {
|
if (iteratorMethodId == null) {
|
||||||
throw BytecodeFallbackException("Missing member id for Iterable.iterator", stmt.pos)
|
throw BytecodeCompileException("Missing member id for Iterable.iterator", stmt.pos)
|
||||||
}
|
}
|
||||||
val iteratorMethods = ObjIterator.instanceMethodIdMap(includeAbstract = true)
|
val iteratorMethods = ObjIterator.instanceMethodIdMap(includeAbstract = true)
|
||||||
val hasNextMethodId = iteratorMethods["hasNext"]
|
val hasNextMethodId = iteratorMethods["hasNext"]
|
||||||
if (hasNextMethodId == null) {
|
if (hasNextMethodId == null) {
|
||||||
throw BytecodeFallbackException("Missing member id for Iterator.hasNext", stmt.pos)
|
throw BytecodeCompileException("Missing member id for Iterator.hasNext", stmt.pos)
|
||||||
}
|
}
|
||||||
val nextMethodId = iteratorMethods["next"]
|
val nextMethodId = iteratorMethods["next"]
|
||||||
if (nextMethodId == null) {
|
if (nextMethodId == null) {
|
||||||
throw BytecodeFallbackException("Missing member id for Iterator.next", stmt.pos)
|
throw BytecodeCompileException("Missing member id for Iterator.next", stmt.pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
val iterSlot = allocSlot()
|
val iterSlot = allocSlot()
|
||||||
@ -4020,8 +4153,8 @@ class BytecodeCompiler(
|
|||||||
return when (target) {
|
return when (target) {
|
||||||
is ExpressionStatement -> compileRefWithFallback(target.ref, SlotType.BOOL, target.pos)
|
is ExpressionStatement -> compileRefWithFallback(target.ref, SlotType.BOOL, target.pos)
|
||||||
else -> {
|
else -> {
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Bytecode fallback: unsupported condition",
|
"Bytecode compile error: unsupported condition",
|
||||||
pos
|
pos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -4135,8 +4268,8 @@ class BytecodeCompiler(
|
|||||||
val stack = loopStack.toList()
|
val stack = loopStack.toList()
|
||||||
val targetIndex = findLoopContextIndex(stmt.label) ?: run {
|
val targetIndex = findLoopContextIndex(stmt.label) ?: run {
|
||||||
val labels = stack.joinToString(prefix = "[", postfix = "]") { it.label ?: "<unlabeled>" }
|
val labels = stack.joinToString(prefix = "[", postfix = "]") { it.label ?: "<unlabeled>" }
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Bytecode fallback: break label '${stmt.label}' not found in $labels",
|
"Bytecode compile error: break label '${stmt.label}' not found in $labels",
|
||||||
stmt.pos
|
stmt.pos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -4329,7 +4462,7 @@ class BytecodeCompiler(
|
|||||||
val localKeys = localSlotIndexByName.keys.sorted().joinToString(prefix = "[", postfix = "]")
|
val localKeys = localSlotIndexByName.keys.sorted().joinToString(prefix = "[", postfix = "]")
|
||||||
val scopeKeys = scopeSlotIndexByName.keys.sorted().joinToString(prefix = "[", postfix = "]")
|
val scopeKeys = scopeSlotIndexByName.keys.sorted().joinToString(prefix = "[", postfix = "]")
|
||||||
val info = " ref=$refKind loopSlots=$loopKeys localSlots=$localKeys scopeSlots=$scopeKeys forceScopeSlots=$forceScopeSlots"
|
val info = " ref=$refKind loopSlots=$loopKeys localSlots=$localKeys scopeSlots=$scopeKeys forceScopeSlots=$forceScopeSlots"
|
||||||
throw BytecodeFallbackException("Unresolved name '$name'.$info", pos)
|
throw BytecodeCompileException("Unresolved name '$name'.$info", pos)
|
||||||
}
|
}
|
||||||
val refInfo = when (ref) {
|
val refInfo = when (ref) {
|
||||||
is FieldRef -> "FieldRef(${ref.name})"
|
is FieldRef -> "FieldRef(${ref.name})"
|
||||||
@ -4341,8 +4474,8 @@ class BytecodeCompiler(
|
|||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Bytecode fallback: unsupported expression ($refInfo)$extra",
|
"Bytecode compile error: unsupported expression ($refInfo)$extra",
|
||||||
pos
|
pos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -4354,6 +4487,7 @@ class BytecodeCompiler(
|
|||||||
val fromSlot = slot?.let { slotObjClass[it] }
|
val fromSlot = slot?.let { slotObjClass[it] }
|
||||||
fromSlot
|
fromSlot
|
||||||
?: nameObjClass[ref.name]
|
?: nameObjClass[ref.name]
|
||||||
|
?: resolveTypeNameClass(ref.name)
|
||||||
?: slotInitClassByKey[ScopeSlotKey(refScopeId(ref), refSlot(ref))]
|
?: slotInitClassByKey[ScopeSlotKey(refScopeId(ref), refSlot(ref))]
|
||||||
?: run {
|
?: run {
|
||||||
val match = slotInitClassByKey.entries.firstOrNull { (key, _) ->
|
val match = slotInitClassByKey.entries.firstOrNull { (key, _) ->
|
||||||
@ -4365,6 +4499,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
is LocalVarRef -> resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] }
|
is LocalVarRef -> resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] }
|
||||||
?: nameObjClass[ref.name]
|
?: nameObjClass[ref.name]
|
||||||
|
?: resolveTypeNameClass(ref.name)
|
||||||
is ListLiteralRef -> ObjList.type
|
is ListLiteralRef -> ObjList.type
|
||||||
is MapLiteralRef -> ObjMap.type
|
is MapLiteralRef -> ObjMap.type
|
||||||
is RangeRef -> ObjRange.type
|
is RangeRef -> ObjRange.type
|
||||||
@ -4426,6 +4561,18 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isAllowedObjectMember(memberName: String): Boolean {
|
||||||
|
return when (memberName) {
|
||||||
|
"toString",
|
||||||
|
"toInspectString",
|
||||||
|
"let",
|
||||||
|
"also",
|
||||||
|
"apply",
|
||||||
|
"run" -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
||||||
private fun refScopeId(ref: LocalSlotRef): Int = ref.scopeId
|
private fun refScopeId(ref: LocalSlotRef): Int = ref.scopeId
|
||||||
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
||||||
@ -4434,8 +4581,8 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun resolveReceiverClassForScopeCollection(ref: ObjRef): ObjClass? {
|
private fun resolveReceiverClassForScopeCollection(ref: ObjRef): ObjClass? {
|
||||||
return when (ref) {
|
return when (ref) {
|
||||||
is LocalSlotRef -> nameObjClass[ref.name]
|
is LocalSlotRef -> nameObjClass[ref.name] ?: resolveTypeNameClass(ref.name)
|
||||||
is LocalVarRef -> nameObjClass[ref.name]
|
is LocalVarRef -> nameObjClass[ref.name] ?: resolveTypeNameClass(ref.name)
|
||||||
is ListLiteralRef -> ObjList.type
|
is ListLiteralRef -> ObjList.type
|
||||||
is MapLiteralRef -> ObjMap.type
|
is MapLiteralRef -> ObjMap.type
|
||||||
is RangeRef -> ObjRange.type
|
is RangeRef -> ObjRange.type
|
||||||
@ -4605,6 +4752,9 @@ class BytecodeCompiler(
|
|||||||
slotTypes.clear()
|
slotTypes.clear()
|
||||||
slotObjClass.clear()
|
slotObjClass.clear()
|
||||||
nameObjClass.clear()
|
nameObjClass.clear()
|
||||||
|
if (knownNameObjClass.isNotEmpty()) {
|
||||||
|
nameObjClass.putAll(knownNameObjClass)
|
||||||
|
}
|
||||||
slotInitClassByKey.clear()
|
slotInitClassByKey.clear()
|
||||||
scopeSlotMap.clear()
|
scopeSlotMap.clear()
|
||||||
scopeSlotNameMap.clear()
|
scopeSlotNameMap.clear()
|
||||||
@ -4623,6 +4773,13 @@ class BytecodeCompiler(
|
|||||||
addrSlotByScopeSlot.clear()
|
addrSlotByScopeSlot.clear()
|
||||||
loopStack.clear()
|
loopStack.clear()
|
||||||
forceScopeSlots = allowLocalSlots && containsValueFnRef(stmt)
|
forceScopeSlots = allowLocalSlots && containsValueFnRef(stmt)
|
||||||
|
if (slotTypeByScopeId.isNotEmpty()) {
|
||||||
|
for ((scopeId, slots) in slotTypeByScopeId) {
|
||||||
|
for ((slotIndex, cls) in slots) {
|
||||||
|
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (allowLocalSlots) {
|
if (allowLocalSlots) {
|
||||||
collectLoopVarNames(stmt)
|
collectLoopVarNames(stmt)
|
||||||
}
|
}
|
||||||
@ -5144,7 +5301,6 @@ class BytecodeCompiler(
|
|||||||
val receiverClass = resolveReceiverClassForScopeCollection(ref.receiver)
|
val receiverClass = resolveReceiverClassForScopeCollection(ref.receiver)
|
||||||
if (receiverClass != null) {
|
if (receiverClass != null) {
|
||||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||||
?: Obj.rootObjectType.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
|
||||||
if (methodId == null) {
|
if (methodId == null) {
|
||||||
queueExtensionCallableNames(receiverClass, ref.name)
|
queueExtensionCallableNames(receiverClass, ref.name)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import net.sergeych.lyng.WhenInCondition
|
|||||||
import net.sergeych.lyng.WhenIsCondition
|
import net.sergeych.lyng.WhenIsCondition
|
||||||
import net.sergeych.lyng.WhenStatement
|
import net.sergeych.lyng.WhenStatement
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
import net.sergeych.lyng.obj.RangeRef
|
import net.sergeych.lyng.obj.RangeRef
|
||||||
|
|
||||||
class BytecodeStatement private constructor(
|
class BytecodeStatement private constructor(
|
||||||
@ -50,13 +51,15 @@ class BytecodeStatement private constructor(
|
|||||||
returnLabels: Set<String> = emptySet(),
|
returnLabels: Set<String> = emptySet(),
|
||||||
rangeLocalNames: Set<String> = emptySet(),
|
rangeLocalNames: Set<String> = emptySet(),
|
||||||
allowedScopeNames: Set<String>? = null,
|
allowedScopeNames: Set<String>? = null,
|
||||||
|
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
|
knownNameObjClass: 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)
|
||||||
if (hasUnsupported) {
|
if (hasUnsupported) {
|
||||||
val statementName = statement::class.qualifiedName ?: statement::class.simpleName ?: "UnknownStatement"
|
val statementName = statement::class.qualifiedName ?: statement::class.simpleName ?: "UnknownStatement"
|
||||||
throw BytecodeFallbackException(
|
throw BytecodeCompileException(
|
||||||
"Bytecode fallback: unsupported statement $statementName in '$nameHint'",
|
"Bytecode compile error: unsupported statement $statementName in '$nameHint'",
|
||||||
statement.pos
|
statement.pos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -65,11 +68,13 @@ class BytecodeStatement private constructor(
|
|||||||
allowLocalSlots = safeLocals,
|
allowLocalSlots = safeLocals,
|
||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = rangeLocalNames,
|
rangeLocalNames = rangeLocalNames,
|
||||||
allowedScopeNames = allowedScopeNames
|
allowedScopeNames = allowedScopeNames,
|
||||||
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
|
knownNameObjClass = knownNameObjClass
|
||||||
)
|
)
|
||||||
val compiled = compiler.compileStatement(nameHint, statement)
|
val compiled = compiler.compileStatement(nameHint, statement)
|
||||||
val fn = compiled ?: throw BytecodeFallbackException(
|
val fn = compiled ?: throw BytecodeCompileException(
|
||||||
"Bytecode fallback: failed to compile '$nameHint'",
|
"Bytecode compile error: failed to compile '$nameHint'",
|
||||||
statement.pos
|
statement.pos
|
||||||
)
|
)
|
||||||
return BytecodeStatement(statement, fn)
|
return BytecodeStatement(statement, fn)
|
||||||
|
|||||||
@ -30,7 +30,6 @@ class CmdBuilder {
|
|||||||
private val constPool = mutableListOf<BytecodeConst>()
|
private val constPool = mutableListOf<BytecodeConst>()
|
||||||
private val labelPositions = mutableMapOf<Label, Int>()
|
private val labelPositions = mutableMapOf<Label, Int>()
|
||||||
private var nextLabelId = 0
|
private var nextLabelId = 0
|
||||||
private val fallbackStatements = mutableListOf<net.sergeych.lyng.Statement>()
|
|
||||||
|
|
||||||
fun addConst(c: BytecodeConst): Int {
|
fun addConst(c: BytecodeConst): Int {
|
||||||
constPool += c
|
constPool += c
|
||||||
@ -51,11 +50,6 @@ class CmdBuilder {
|
|||||||
labelPositions[label] = instructions.size
|
labelPositions[label] = instructions.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFallback(stmt: net.sergeych.lyng.Statement): Int {
|
|
||||||
fallbackStatements += stmt
|
|
||||||
return fallbackStatements.lastIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
fun build(
|
fun build(
|
||||||
name: String,
|
name: String,
|
||||||
localCount: Int,
|
localCount: Int,
|
||||||
@ -109,7 +103,6 @@ class CmdBuilder {
|
|||||||
localSlotNames = localSlotNames,
|
localSlotNames = localSlotNames,
|
||||||
localSlotMutables = localSlotMutables,
|
localSlotMutables = localSlotMutables,
|
||||||
constants = constPool.toList(),
|
constants = constPool.toList(),
|
||||||
fallbackStatements = fallbackStatements.toList(),
|
|
||||||
cmds = cmds.toTypedArray()
|
cmds = cmds.toTypedArray()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -175,12 +168,6 @@ class CmdBuilder {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_SLOT ->
|
Opcode.CALL_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_VIRTUAL ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
|
||||||
Opcode.GET_FIELD ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
|
||||||
Opcode.SET_FIELD ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
|
||||||
Opcode.GET_INDEX ->
|
Opcode.GET_INDEX ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.SET_INDEX ->
|
Opcode.SET_INDEX ->
|
||||||
@ -382,10 +369,7 @@ class CmdBuilder {
|
|||||||
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.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_VIRTUAL -> CmdCallVirtual(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.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2])
|
|
||||||
Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2])
|
|
||||||
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
|
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
|
||||||
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
|
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
|
||||||
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
|
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
|||||||
@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
|
||||||
|
|
||||||
internal expect object CmdCallSiteCache {
|
|
||||||
fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite>
|
|
||||||
}
|
|
||||||
@ -180,11 +180,8 @@ object CmdDisassembler {
|
|||||||
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclLocal -> Opcode.DECL_LOCAL 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 CmdCallVirtual -> Opcode.CALL_VIRTUAL 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 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 CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst)
|
|
||||||
is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot)
|
|
||||||
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
|
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
|
||||||
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
|
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
|
||||||
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
|
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
|
||||||
@ -266,14 +263,8 @@ object CmdDisassembler {
|
|||||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_SLOT ->
|
Opcode.CALL_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_VIRTUAL ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
|
||||||
Opcode.CALL_MEMBER_SLOT ->
|
Opcode.CALL_MEMBER_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.GET_FIELD ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
|
||||||
Opcode.SET_FIELD ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
|
||||||
Opcode.GET_INDEX ->
|
Opcode.GET_INDEX ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.SET_INDEX ->
|
Opcode.SET_INDEX ->
|
||||||
|
|||||||
@ -28,7 +28,6 @@ data class CmdFunction(
|
|||||||
val localSlotNames: Array<String?>,
|
val localSlotNames: Array<String?>,
|
||||||
val localSlotMutables: BooleanArray,
|
val localSlotMutables: BooleanArray,
|
||||||
val constants: List<BytecodeConst>,
|
val constants: List<BytecodeConst>,
|
||||||
val fallbackStatements: List<net.sergeych.lyng.Statement>,
|
|
||||||
val cmds: Array<Cmd>,
|
val cmds: Array<Cmd>,
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
|
|||||||
@ -17,10 +17,8 @@
|
|||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
import net.sergeych.lyng.Arguments
|
||||||
import net.sergeych.lyng.ExecutionError
|
|
||||||
import net.sergeych.lyng.ModuleScope
|
import net.sergeych.lyng.ModuleScope
|
||||||
import net.sergeych.lyng.PerfFlags
|
import net.sergeych.lyng.PerfFlags
|
||||||
import net.sergeych.lyng.PerfStats
|
|
||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.ReturnException
|
import net.sergeych.lyng.ReturnException
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
@ -1160,44 +1158,6 @@ class CmdCallDirect(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CmdCallVirtual(
|
|
||||||
internal val recvSlot: Int,
|
|
||||||
internal val methodId: Int,
|
|
||||||
internal val argBase: Int,
|
|
||||||
internal val argCount: Int,
|
|
||||||
internal val dst: Int,
|
|
||||||
) : Cmd() {
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
|
||||||
frame.scope.raiseError("CALL_VIRTUAL is not allowed: compile-time member resolution is required")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CmdCallFallback(
|
|
||||||
internal val id: Int,
|
|
||||||
internal val argBase: Int,
|
|
||||||
internal val argCount: Int,
|
|
||||||
internal val dst: Int,
|
|
||||||
) : Cmd() {
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
|
||||||
frame.syncFrameToScope()
|
|
||||||
}
|
|
||||||
val stmt = frame.fn.fallbackStatements.getOrNull(id)
|
|
||||||
?: error("Fallback statement not found: $id")
|
|
||||||
val args = frame.buildArguments(argBase, argCount)
|
|
||||||
val result = if (PerfFlags.SCOPE_POOL) {
|
|
||||||
frame.scope.withChildFrame(args) { child -> stmt.execute(child) }
|
|
||||||
} else {
|
|
||||||
stmt.execute(frame.scope.createChildScope(frame.scope.pos, args = args))
|
|
||||||
}
|
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
|
||||||
frame.syncScopeToFrame()
|
|
||||||
}
|
|
||||||
frame.storeObjResult(dst, result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CmdCallSlot(
|
class CmdCallSlot(
|
||||||
internal val calleeSlot: Int,
|
internal val calleeSlot: Int,
|
||||||
internal val argBase: Int,
|
internal val argBase: Int,
|
||||||
@ -1236,63 +1196,6 @@ class CmdCallSlot(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CmdGetField(
|
|
||||||
internal val recvSlot: Int,
|
|
||||||
internal val fieldId: Int,
|
|
||||||
internal val dst: Int,
|
|
||||||
) : Cmd() {
|
|
||||||
private var rKey: Long = 0L
|
|
||||||
private var rVer: Int = -1
|
|
||||||
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
|
||||||
val receiver = frame.slotToObj(recvSlot)
|
|
||||||
val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal
|
|
||||||
?: error("GET_FIELD expects StringVal at $fieldId")
|
|
||||||
if (PerfFlags.FIELD_PIC) {
|
|
||||||
val (key, ver) = when (receiver) {
|
|
||||||
is ObjInstance -> receiver.objClass.classId to receiver.objClass.layoutVersion
|
|
||||||
is ObjClass -> receiver.classId to receiver.layoutVersion
|
|
||||||
else -> 0L to -1
|
|
||||||
}
|
|
||||||
if (key != 0L) {
|
|
||||||
if (key == rKey && ver == rVer) {
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicHit++
|
|
||||||
} else {
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicMiss++
|
|
||||||
rKey = key
|
|
||||||
rVer = ver
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val result = receiver.readField(frame.scope, nameConst.value).value
|
|
||||||
frame.storeObjResult(dst, result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CmdGetName(
|
|
||||||
internal val nameId: Int,
|
|
||||||
internal val dst: Int,
|
|
||||||
) : Cmd() {
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
|
||||||
frame.syncFrameToScope()
|
|
||||||
}
|
|
||||||
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
|
|
||||||
?: error("GET_NAME expects StringVal at $nameId")
|
|
||||||
val name = nameConst.value
|
|
||||||
val result = frame.scope.get(name)?.value ?: run {
|
|
||||||
try {
|
|
||||||
frame.scope.thisObj.readField(frame.scope, name).value
|
|
||||||
} catch (e: ExecutionError) {
|
|
||||||
if ((e.message ?: "").contains("no such field: $name")) ObjUnset else throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frame.storeObjResult(dst, result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CmdListLiteral(
|
class CmdListLiteral(
|
||||||
internal val planId: Int,
|
internal val planId: Int,
|
||||||
internal val baseSlot: Int,
|
internal val baseSlot: Int,
|
||||||
@ -1407,39 +1310,6 @@ class CmdCallMemberSlot(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CmdSetField(
|
|
||||||
internal val recvSlot: Int,
|
|
||||||
internal val fieldId: Int,
|
|
||||||
internal val valueSlot: Int,
|
|
||||||
) : Cmd() {
|
|
||||||
private var wKey: Long = 0L
|
|
||||||
private var wVer: Int = -1
|
|
||||||
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
|
||||||
val receiver = frame.slotToObj(recvSlot)
|
|
||||||
val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal
|
|
||||||
?: error("SET_FIELD expects StringVal at $fieldId")
|
|
||||||
if (PerfFlags.FIELD_PIC) {
|
|
||||||
val (key, ver) = when (receiver) {
|
|
||||||
is ObjInstance -> receiver.objClass.classId to receiver.objClass.layoutVersion
|
|
||||||
is ObjClass -> receiver.classId to receiver.layoutVersion
|
|
||||||
else -> 0L to -1
|
|
||||||
}
|
|
||||||
if (key != 0L) {
|
|
||||||
if (key == wKey && ver == wVer) {
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicSetHit++
|
|
||||||
} else {
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicSetMiss++
|
|
||||||
wKey = key
|
|
||||||
wVer = ver
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
receiver.writeField(frame.scope, nameConst.value, frame.slotToObj(valueSlot))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CmdGetIndex(
|
class CmdGetIndex(
|
||||||
internal val targetSlot: Int,
|
internal val targetSlot: Int,
|
||||||
internal val indexSlot: Int,
|
internal val indexSlot: Int,
|
||||||
@ -1463,18 +1333,6 @@ class CmdSetIndex(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CmdEvalFallback(internal val id: Int, internal val dst: Int) : Cmd() {
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
|
||||||
val stmt = frame.fn.fallbackStatements.getOrNull(id)
|
|
||||||
?: error("Fallback statement not found: $id")
|
|
||||||
frame.syncFrameToScope()
|
|
||||||
val result = stmt.execute(frame.scope)
|
|
||||||
frame.syncScopeToFrame()
|
|
||||||
frame.storeObjResult(dst, result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CmdEvalRef(internal val id: Int, internal val dst: Int) : Cmd() {
|
class CmdEvalRef(internal val id: Int, internal val dst: Int) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||||
@ -1558,7 +1416,6 @@ class CmdFrame(
|
|||||||
var ip: Int = 0
|
var ip: Int = 0
|
||||||
var scope: Scope = scope0
|
var scope: Scope = scope0
|
||||||
private val moduleScope: Scope = resolveModuleScope(scope0)
|
private val moduleScope: Scope = resolveModuleScope(scope0)
|
||||||
val methodCallSites: MutableMap<Int, MethodCallSite> = CmdCallSiteCache.methodCallSites(fn)
|
|
||||||
|
|
||||||
internal val scopeStack = ArrayDeque<Scope>()
|
internal val scopeStack = ArrayDeque<Scope>()
|
||||||
internal val scopeVirtualStack = ArrayDeque<Boolean>()
|
internal val scopeVirtualStack = ArrayDeque<Boolean>()
|
||||||
|
|||||||
@ -1,241 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
|
||||||
import net.sergeych.lyng.ExecutionError
|
|
||||||
import net.sergeych.lyng.PerfFlags
|
|
||||||
import net.sergeych.lyng.PerfStats
|
|
||||||
import net.sergeych.lyng.Scope
|
|
||||||
import net.sergeych.lyng.Visibility
|
|
||||||
import net.sergeych.lyng.canAccessMember
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
|
||||||
import net.sergeych.lyng.obj.ObjIllegalAccessException
|
|
||||||
import net.sergeych.lyng.obj.ObjInstance
|
|
||||||
import net.sergeych.lyng.obj.ObjProperty
|
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
|
||||||
|
|
||||||
class MethodCallSite(private val name: String) {
|
|
||||||
private var mKey1: Long = 0L; private var mVer1: Int = -1
|
|
||||||
private var mInvoker1: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
|
||||||
private var mKey2: Long = 0L; private var mVer2: Int = -1
|
|
||||||
private var mInvoker2: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
|
||||||
private var mKey3: Long = 0L; private var mVer3: Int = -1
|
|
||||||
private var mInvoker3: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
|
||||||
private var mKey4: Long = 0L; private var mVer4: Int = -1
|
|
||||||
private var mInvoker4: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
|
||||||
|
|
||||||
private var mAccesses: Int = 0; private var mMisses: Int = 0; private var mPromotedTo4: Boolean = false
|
|
||||||
private var mFreezeWindowsLeft: Int = 0
|
|
||||||
private var mWindowAccesses: Int = 0
|
|
||||||
private var mWindowMisses: Int = 0
|
|
||||||
|
|
||||||
private inline fun size4MethodsEnabled(): Boolean =
|
|
||||||
PerfFlags.METHOD_PIC_SIZE_4 ||
|
|
||||||
((PerfFlags.PIC_ADAPTIVE_2_TO_4 || PerfFlags.PIC_ADAPTIVE_METHODS_ONLY) && mPromotedTo4 && mFreezeWindowsLeft == 0)
|
|
||||||
|
|
||||||
private fun noteMethodHit() {
|
|
||||||
if (!(PerfFlags.PIC_ADAPTIVE_2_TO_4 || PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
|
|
||||||
val a = (mAccesses + 1).coerceAtMost(1_000_000)
|
|
||||||
mAccesses = a
|
|
||||||
if (PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
|
|
||||||
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
|
|
||||||
if (mWindowAccesses >= 256) endHeuristicWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun noteMethodMiss() {
|
|
||||||
if (!(PerfFlags.PIC_ADAPTIVE_2_TO_4 || PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
|
|
||||||
val a = (mAccesses + 1).coerceAtMost(1_000_000)
|
|
||||||
mAccesses = a
|
|
||||||
mMisses = (mMisses + 1).coerceAtMost(1_000_000)
|
|
||||||
if (!mPromotedTo4 && mFreezeWindowsLeft == 0 && a >= 256) {
|
|
||||||
if (mMisses * 100 / a > 20) mPromotedTo4 = true
|
|
||||||
mAccesses = 0; mMisses = 0
|
|
||||||
}
|
|
||||||
if (PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
|
|
||||||
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
|
|
||||||
mWindowMisses = (mWindowMisses + 1).coerceAtMost(1_000_000)
|
|
||||||
if (mWindowAccesses >= 256) endHeuristicWindow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun endHeuristicWindow() {
|
|
||||||
val accesses = mWindowAccesses
|
|
||||||
val misses = mWindowMisses
|
|
||||||
mWindowAccesses = 0
|
|
||||||
mWindowMisses = 0
|
|
||||||
if (mFreezeWindowsLeft > 0) {
|
|
||||||
mFreezeWindowsLeft = (mFreezeWindowsLeft - 1).coerceAtLeast(0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (mPromotedTo4 && accesses >= 256) {
|
|
||||||
val rate = misses * 100 / accesses
|
|
||||||
if (rate >= 25) {
|
|
||||||
mPromotedTo4 = false
|
|
||||||
mFreezeWindowsLeft = 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun invoke(scope: Scope, base: Obj, callArgs: Arguments): Obj {
|
|
||||||
if (PerfFlags.METHOD_PIC) {
|
|
||||||
val (key, ver) = when (base) {
|
|
||||||
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
|
||||||
is ObjClass -> base.classId to base.layoutVersion
|
|
||||||
else -> 0L to -1
|
|
||||||
}
|
|
||||||
if (key != 0L) {
|
|
||||||
mInvoker1?.let { inv ->
|
|
||||||
if (key == mKey1 && ver == mVer1) {
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicHit++
|
|
||||||
noteMethodHit()
|
|
||||||
return inv(base, scope, callArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mInvoker2?.let { inv ->
|
|
||||||
if (key == mKey2 && ver == mVer2) {
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicHit++
|
|
||||||
noteMethodHit()
|
|
||||||
val tK = mKey2; val tV = mVer2; val tI = mInvoker2
|
|
||||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
|
||||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
|
||||||
return inv(base, scope, callArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (size4MethodsEnabled()) mInvoker3?.let { inv ->
|
|
||||||
if (key == mKey3 && ver == mVer3) {
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicHit++
|
|
||||||
noteMethodHit()
|
|
||||||
val tK = mKey3; val tV = mVer3; val tI = mInvoker3
|
|
||||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
|
||||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
|
||||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
|
||||||
return inv(base, scope, callArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (size4MethodsEnabled()) mInvoker4?.let { inv ->
|
|
||||||
if (key == mKey4 && ver == mVer4) {
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicHit++
|
|
||||||
noteMethodHit()
|
|
||||||
val tK = mKey4; val tV = mVer4; val tI = mInvoker4
|
|
||||||
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
|
|
||||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
|
||||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
|
||||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
|
||||||
return inv(base, scope, callArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.methodPicMiss++
|
|
||||||
noteMethodMiss()
|
|
||||||
val result = try {
|
|
||||||
base.invokeInstanceMethod(scope, name, callArgs)
|
|
||||||
} catch (e: ExecutionError) {
|
|
||||||
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
|
|
||||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
|
||||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
|
||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { _, sc, _ ->
|
|
||||||
sc.raiseError(e.message ?: "method not found: $name")
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
if (size4MethodsEnabled()) {
|
|
||||||
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
|
|
||||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
|
||||||
}
|
|
||||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
|
||||||
when (base) {
|
|
||||||
is ObjInstance -> {
|
|
||||||
val cls0 = base.objClass
|
|
||||||
val keyInScope = cls0.publicMemberResolution[name]
|
|
||||||
val methodSlot = if (keyInScope != null) cls0.methodSlotForKey(keyInScope) else null
|
|
||||||
val fastRec = if (methodSlot != null) {
|
|
||||||
val idx = methodSlot.slot
|
|
||||||
if (idx >= 0 && idx < base.methodSlots.size) base.methodSlots[idx] else null
|
|
||||||
} else if (keyInScope != null) {
|
|
||||||
base.methodRecordForKey(keyInScope) ?: base.instanceScope.objects[keyInScope]
|
|
||||||
} else null
|
|
||||||
val resolved = if (fastRec != null) null else cls0.resolveInstanceMember(name)
|
|
||||||
val targetRec = when {
|
|
||||||
fastRec != null && fastRec.type == ObjRecord.Type.Fun -> fastRec
|
|
||||||
resolved != null && resolved.record.type == ObjRecord.Type.Fun && !resolved.record.isAbstract -> resolved.record
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
if (targetRec != null) {
|
|
||||||
val visibility = targetRec.visibility
|
|
||||||
val decl = targetRec.declaringClass ?: (resolved?.declaringClass ?: cls0)
|
|
||||||
if (methodSlot != null && targetRec.type == ObjRecord.Type.Fun) {
|
|
||||||
val slotIndex = methodSlot.slot
|
|
||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
|
||||||
val inst = obj as ObjInstance
|
|
||||||
if (inst.objClass === cls0) {
|
|
||||||
val rec = if (slotIndex >= 0 && slotIndex < inst.methodSlots.size) inst.methodSlots[slotIndex] else null
|
|
||||||
if (rec != null && rec.type == ObjRecord.Type.Fun && !rec.isAbstract) {
|
|
||||||
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) {
|
|
||||||
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
|
|
||||||
}
|
|
||||||
rec.value.invoke(inst.instanceScope, inst, a, decl)
|
|
||||||
} else {
|
|
||||||
obj.invokeInstanceMethod(sc, name, a)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
obj.invokeInstanceMethod(sc, name, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val callable = targetRec.value
|
|
||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
|
||||||
val inst = obj as ObjInstance
|
|
||||||
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) {
|
|
||||||
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
|
|
||||||
}
|
|
||||||
callable.invoke(inst.instanceScope, inst, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
|
||||||
obj.invokeInstanceMethod(sc, name, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ObjClass -> {
|
|
||||||
val clsScope = base.classScope
|
|
||||||
val rec = clsScope?.get(name)
|
|
||||||
if (rec != null) {
|
|
||||||
val callable = rec.value
|
|
||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
|
||||||
callable.invoke(sc, obj, a)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
|
||||||
obj.invokeInstanceMethod(sc, name, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
|
||||||
obj.invokeInstanceMethod(sc, name, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return base.invokeInstanceMethod(scope, name, callArgs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -126,12 +126,9 @@ enum class Opcode(val code: Int) {
|
|||||||
DECL_EXT_PROPERTY(0x8A),
|
DECL_EXT_PROPERTY(0x8A),
|
||||||
|
|
||||||
CALL_DIRECT(0x90),
|
CALL_DIRECT(0x90),
|
||||||
CALL_VIRTUAL(0x91),
|
|
||||||
CALL_MEMBER_SLOT(0x92),
|
CALL_MEMBER_SLOT(0x92),
|
||||||
CALL_SLOT(0x93),
|
CALL_SLOT(0x93),
|
||||||
|
|
||||||
GET_FIELD(0xA0),
|
|
||||||
SET_FIELD(0xA1),
|
|
||||||
GET_INDEX(0xA2),
|
GET_INDEX(0xA2),
|
||||||
SET_INDEX(0xA3),
|
SET_INDEX(0xA3),
|
||||||
LIST_LITERAL(0xA5),
|
LIST_LITERAL(0xA5),
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
|
||||||
|
|
||||||
internal actual object CmdCallSiteCache {
|
|
||||||
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
|
||||||
|
|
||||||
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
|
||||||
return cache.getOrPut(fn) { mutableMapOf() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
|
||||||
|
|
||||||
import java.util.IdentityHashMap
|
|
||||||
|
|
||||||
internal actual object CmdCallSiteCache {
|
|
||||||
private val cache = ThreadLocal.withInitial {
|
|
||||||
IdentityHashMap<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
|
||||||
val map = cache.get()
|
|
||||||
return map.getOrPut(fn) { mutableMapOf() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
|
||||||
|
|
||||||
@kotlin.native.concurrent.ThreadLocal
|
|
||||||
internal actual object CmdCallSiteCache {
|
|
||||||
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
|
||||||
|
|
||||||
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
|
||||||
return cache.getOrPut(fn) { mutableMapOf() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
|
||||||
|
|
||||||
internal actual object CmdCallSiteCache {
|
|
||||||
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
|
||||||
|
|
||||||
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
|
||||||
return cache.getOrPut(fn) { mutableMapOf() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -291,7 +291,7 @@ override fun List<T>.toString() {
|
|||||||
var result = "["
|
var result = "["
|
||||||
for (item in this) {
|
for (item in this) {
|
||||||
if (!first) result += ","
|
if (!first) result += ","
|
||||||
result += item.toString()
|
result += (item as Object).toString()
|
||||||
first = false
|
first = false
|
||||||
}
|
}
|
||||||
result + "]"
|
result + "]"
|
||||||
@ -310,8 +310,9 @@ fun List<T>.sort(): Void {
|
|||||||
/* Print this exception and its stack trace to standard output. */
|
/* Print this exception and its stack trace to standard output. */
|
||||||
fun Exception.printStackTrace(): Void {
|
fun Exception.printStackTrace(): Void {
|
||||||
println(this)
|
println(this)
|
||||||
for( entry in stackTrace )
|
for( entry in stackTrace ) {
|
||||||
println("\tat "+entry.toString())
|
println("\tat "+(entry as Object).toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compile this string into a regular expression. */
|
/* Compile this string into a regular expression. */
|
||||||
@ -371,7 +372,7 @@ class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,T
|
|||||||
private var value = Unset
|
private var value = Unset
|
||||||
|
|
||||||
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
|
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
|
||||||
if (access.toString() != "DelegateAccess.Val") throw "lazy delegate can only be used with 'val'"
|
if (access != DelegateAccess.Val) throw "lazy delegate can only be used with 'val'"
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -242,6 +242,7 @@ This metadata drives:
|
|||||||
## Migration Notes
|
## Migration Notes
|
||||||
- Keep reflection APIs separate to audit usage.
|
- Keep reflection APIs separate to audit usage.
|
||||||
- Add warnings for member shadowing to surface risky code.
|
- Add warnings for member shadowing to surface risky code.
|
||||||
|
- Runtime fallback opcodes are removed (CALL_VIRTUAL/GET_FIELD/SET_FIELD); unresolved names or members are compile-time errors.
|
||||||
|
|
||||||
## Compatibility Notes (Kotlin interop)
|
## Compatibility Notes (Kotlin interop)
|
||||||
- Provide minimal Kotlin-facing APIs that mirror compile-time-visible names.
|
- Provide minimal Kotlin-facing APIs that mirror compile-time-visible names.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user