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,
|
||||
"<script>",
|
||||
allowLocalSlots = true,
|
||||
allowedScopeNames = modulePlan.keys
|
||||
allowedScopeNames = modulePlan.keys,
|
||||
slotTypeByScopeId = slotTypeByScopeId,
|
||||
knownNameObjClass = knownClassMapForBytecode()
|
||||
)
|
||||
)
|
||||
} 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 {
|
||||
if (!useBytecodeStatements) return stmt
|
||||
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
||||
@ -1322,7 +1345,9 @@ class Compiler(
|
||||
allowLocalSlots = allowLocals,
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = currentRangeParamNames,
|
||||
allowedScopeNames = allowedScopeNames
|
||||
allowedScopeNames = allowedScopeNames,
|
||||
slotTypeByScopeId = slotTypeByScopeId,
|
||||
knownNameObjClass = knownClassMapForBytecode()
|
||||
)
|
||||
}
|
||||
|
||||
@ -1338,7 +1363,9 @@ class Compiler(
|
||||
allowLocalSlots = true,
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = currentRangeParamNames,
|
||||
allowedScopeNames = allowedScopeNames
|
||||
allowedScopeNames = allowedScopeNames,
|
||||
slotTypeByScopeId = slotTypeByScopeId,
|
||||
knownNameObjClass = knownClassMapForBytecode()
|
||||
)
|
||||
}
|
||||
|
||||
@ -3250,8 +3277,7 @@ class Compiler(
|
||||
if (left is LocalSlotRef && left.name == "scope") return
|
||||
val receiverClass = resolveReceiverClassForMember(left)
|
||||
if (receiverClass == null) {
|
||||
val allowed = memberName == "toString" || memberName == "toInspectString"
|
||||
if (allowed) return
|
||||
if (isAllowedObjectMember(memberName)) return
|
||||
throw ScriptError(pos, "member access requires compile-time receiver type: $memberName")
|
||||
}
|
||||
if (receiverClass == Obj.rootObjectType) {
|
||||
|
||||
@ -19,7 +19,7 @@ package net.sergeych.lyng.bytecode
|
||||
|
||||
import net.sergeych.lyng.Pos
|
||||
|
||||
class BytecodeFallbackException(
|
||||
class BytecodeCompileException(
|
||||
message: String,
|
||||
val pos: Pos? = null,
|
||||
) : RuntimeException(message) {
|
||||
@ -42,6 +42,8 @@ class BytecodeCompiler(
|
||||
private val returnLabels: Set<String> = emptySet(),
|
||||
private val rangeLocalNames: Set<String> = emptySet(),
|
||||
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 nextSlot = 0
|
||||
@ -281,7 +283,7 @@ class BytecodeCompiler(
|
||||
is ThisMethodSlotCallRef -> compileThisMethodSlotCall(ref)
|
||||
is StatementRef -> {
|
||||
val compiled = compileStatementValueOrFallback(ref.statement)
|
||||
compiled ?: throw BytecodeFallbackException(
|
||||
compiled ?: throw BytecodeCompileException(
|
||||
"Unsupported StatementRef(${ref.statement::class.simpleName})",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -309,9 +311,9 @@ class BytecodeCompiler(
|
||||
val methodId = ref.methodId ?: -1
|
||||
if (fieldId < 0 && methodId < 0) {
|
||||
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 callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeFallbackException(
|
||||
val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeCompileException(
|
||||
"Missing extension wrapper for ${typeName}.${ref.name}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -367,12 +369,12 @@ class BytecodeCompiler(
|
||||
builder.mark(endLabel)
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
val typeName = ref.preferredThisTypeName() ?: throw BytecodeFallbackException(
|
||||
val typeName = ref.preferredThisTypeName() ?: throw BytecodeCompileException(
|
||||
"Missing member id for ${ref.methodName()}",
|
||||
Pos.builtIn
|
||||
)
|
||||
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()}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -468,7 +470,7 @@ class BytecodeCompiler(
|
||||
is UnaryOpRef -> "UnaryOpRef(${ref.op})"
|
||||
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? {
|
||||
@ -513,7 +515,7 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.SET_INDEX, dst, keySlot, value.slot)
|
||||
}
|
||||
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)
|
||||
CompiledValue(out, SlotType.REAL)
|
||||
}
|
||||
else -> {
|
||||
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)
|
||||
}
|
||||
else -> compileObjUnaryOp(unaryOperand(ref), a, "negate", Pos.builtIn)
|
||||
}
|
||||
UnaryOp.NOT -> {
|
||||
when (a.type) {
|
||||
@ -598,11 +591,154 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.INV_INT, a.slot, out)
|
||||
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? {
|
||||
val op = binaryOp(ref)
|
||||
if (op == BinOp.AND || op == BinOp.OR) {
|
||||
@ -712,12 +848,12 @@ class BytecodeCompiler(
|
||||
if (op == BinOp.MATCH || op == BinOp.NOTMATCH) {
|
||||
val leftRef = binaryLeft(ref)
|
||||
val rightRef = binaryRight(ref)
|
||||
val receiverClass = resolveReceiverClass(leftRef) ?: throw BytecodeFallbackException(
|
||||
val receiverClass = resolveReceiverClass(leftRef) ?: throw BytecodeCompileException(
|
||||
"Match operator requires compile-time receiver type",
|
||||
refPos(ref)
|
||||
)
|
||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)["operatorMatch"]
|
||||
?: throw BytecodeFallbackException(
|
||||
?: throw BytecodeCompileException(
|
||||
"Unknown member operatorMatch on ${receiverClass.className}",
|
||||
refPos(ref)
|
||||
)
|
||||
@ -782,36 +918,12 @@ class BytecodeCompiler(
|
||||
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)
|
||||
if (typesMismatch && op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)) {
|
||||
val leftObj = ensureObjSlot(a)
|
||||
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)
|
||||
return compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
}
|
||||
if ((a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) &&
|
||||
op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)
|
||||
) {
|
||||
val leftObj = ensureObjSlot(a)
|
||||
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)
|
||||
return compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
}
|
||||
if (typesMismatch && !allowMixedNumeric &&
|
||||
op !in setOf(BinOp.EQ, BinOp.NEQ, BinOp.LT, BinOp.LTE, BinOp.GT, BinOp.GTE)
|
||||
@ -844,9 +956,8 @@ class BytecodeCompiler(
|
||||
}
|
||||
}
|
||||
SlotType.OBJ -> {
|
||||
if (b.type != SlotType.OBJ) return null
|
||||
builder.emit(Opcode.ADD_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.OBJ)
|
||||
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
@ -874,9 +985,8 @@ class BytecodeCompiler(
|
||||
}
|
||||
}
|
||||
SlotType.OBJ -> {
|
||||
if (b.type != SlotType.OBJ) return null
|
||||
builder.emit(Opcode.SUB_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.OBJ)
|
||||
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
@ -904,9 +1014,8 @@ class BytecodeCompiler(
|
||||
}
|
||||
}
|
||||
SlotType.OBJ -> {
|
||||
if (b.type != SlotType.OBJ) return null
|
||||
builder.emit(Opcode.MUL_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.OBJ)
|
||||
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
@ -934,9 +1043,8 @@ class BytecodeCompiler(
|
||||
}
|
||||
}
|
||||
SlotType.OBJ -> {
|
||||
if (b.type != SlotType.OBJ) return null
|
||||
builder.emit(Opcode.DIV_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.OBJ)
|
||||
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
@ -948,9 +1056,8 @@ class BytecodeCompiler(
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.OBJ -> {
|
||||
if (b.type != SlotType.OBJ) return null
|
||||
builder.emit(Opcode.MOD_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.OBJ)
|
||||
if (b.type != SlotType.OBJ && b.type != SlotType.UNKNOWN) return null
|
||||
compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
@ -994,30 +1101,55 @@ class BytecodeCompiler(
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
BinOp.BAND -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
when (a.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.AND_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
BinOp.BOR -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
when (a.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.OR_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
BinOp.BXOR -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
when (a.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.XOR_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
BinOp.SHL -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
when (a.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.SHL_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
BinOp.SHR -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
when (a.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.SHR_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.OBJ, SlotType.UNKNOWN -> compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -1472,7 +1604,7 @@ class BytecodeCompiler(
|
||||
val target = ref.target
|
||||
if (target is FieldRef) {
|
||||
val receiverClass = resolveReceiverClass(target.target)
|
||||
?: throw BytecodeFallbackException(
|
||||
?: throw BytecodeCompileException(
|
||||
"Member assignment requires compile-time receiver type: ${target.name}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -1485,7 +1617,7 @@ class BytecodeCompiler(
|
||||
}
|
||||
if (fieldId < 0 && methodId < 0) {
|
||||
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
||||
?: throw BytecodeFallbackException(
|
||||
?: throw BytecodeCompileException(
|
||||
"Unknown member ${target.name} on ${receiverClass.className}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -1542,9 +1674,9 @@ class BytecodeCompiler(
|
||||
val methodId = target.methodId ?: -1
|
||||
if (fieldId < 0 && methodId < 0) {
|
||||
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 callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeFallbackException(
|
||||
val callee = resolveDirectNameSlot(wrapperName) ?: throw BytecodeCompileException(
|
||||
"Missing extension wrapper for ${typeName}.${target.name}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -1570,7 +1702,7 @@ class BytecodeCompiler(
|
||||
val fieldId = target.fieldId() ?: -1
|
||||
val methodId = target.methodId() ?: -1
|
||||
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)
|
||||
return value
|
||||
@ -1580,7 +1712,7 @@ class BytecodeCompiler(
|
||||
val fieldId = target.fieldId() ?: -1
|
||||
val methodId = target.methodId() ?: -1
|
||||
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)
|
||||
return value
|
||||
@ -1673,7 +1805,7 @@ class BytecodeCompiler(
|
||||
} ?: return compileEvalRef(ref)
|
||||
val fieldTarget = ref.target as? FieldRef
|
||||
if (fieldTarget != null) {
|
||||
throw BytecodeFallbackException(
|
||||
throw BytecodeCompileException(
|
||||
"Member assignment requires compile-time receiver type: ${fieldTarget.name}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -1684,7 +1816,7 @@ class BytecodeCompiler(
|
||||
val fieldId = implicitTarget.fieldId ?: -1
|
||||
val methodId = implicitTarget.methodId ?: -1
|
||||
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 result = allocSlot()
|
||||
@ -1701,7 +1833,7 @@ class BytecodeCompiler(
|
||||
val fieldId = thisFieldTarget.fieldId() ?: -1
|
||||
val methodId = thisFieldTarget.methodId() ?: -1
|
||||
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 result = allocSlot()
|
||||
@ -1718,7 +1850,7 @@ class BytecodeCompiler(
|
||||
val fieldId = qualifiedTarget.fieldId() ?: -1
|
||||
val methodId = qualifiedTarget.methodId() ?: -1
|
||||
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 result = allocSlot()
|
||||
@ -1808,7 +1940,7 @@ class BytecodeCompiler(
|
||||
}
|
||||
is FieldRef -> {
|
||||
val receiverClass = resolveReceiverClass(target.target)
|
||||
?: throw BytecodeFallbackException(
|
||||
?: throw BytecodeCompileException(
|
||||
"Member assignment requires compile-time receiver type: ${target.name}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -1833,7 +1965,7 @@ class BytecodeCompiler(
|
||||
}
|
||||
} else {
|
||||
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
|
||||
?: throw BytecodeFallbackException(
|
||||
?: throw BytecodeCompileException(
|
||||
"Unknown member ${target.name} on ${receiverClass.className}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -1895,7 +2027,7 @@ class BytecodeCompiler(
|
||||
|
||||
private fun compileFieldRef(ref: FieldRef): CompiledValue? {
|
||||
val receiverClass = resolveReceiverClass(ref.target)
|
||||
?: throw BytecodeFallbackException(
|
||||
?: throw BytecodeCompileException(
|
||||
"Member access requires compile-time receiver type: ${ref.name}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -1927,7 +2059,7 @@ class BytecodeCompiler(
|
||||
return CompiledValue(dst, SlotType.OBJ)
|
||||
}
|
||||
val extSlot = resolveExtensionGetterSlot(receiverClass, ref.name)
|
||||
?: throw BytecodeFallbackException(
|
||||
?: throw BytecodeCompileException(
|
||||
"Unknown member ${ref.name} on ${receiverClass.className}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -1964,7 +2096,7 @@ class BytecodeCompiler(
|
||||
val fieldId = ref.fieldId() ?: -1
|
||||
val methodId = ref.methodId() ?: -1
|
||||
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()
|
||||
if (!ref.optional()) {
|
||||
@ -1995,7 +2127,7 @@ class BytecodeCompiler(
|
||||
val fieldId = ref.fieldId() ?: -1
|
||||
val methodId = ref.methodId() ?: -1
|
||||
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()
|
||||
if (!ref.optional()) {
|
||||
@ -2296,7 +2428,7 @@ class BytecodeCompiler(
|
||||
val fieldId = thisFieldTarget.fieldId() ?: -1
|
||||
val methodId = thisFieldTarget.methodId() ?: -1
|
||||
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()
|
||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
|
||||
@ -2325,7 +2457,7 @@ class BytecodeCompiler(
|
||||
val fieldId = implicitTarget.fieldId ?: -1
|
||||
val methodId = implicitTarget.methodId ?: -1
|
||||
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()
|
||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
|
||||
@ -2354,7 +2486,7 @@ class BytecodeCompiler(
|
||||
val fieldId = qualifiedTarget.fieldId() ?: -1
|
||||
val methodId = qualifiedTarget.methodId() ?: -1
|
||||
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()
|
||||
builder.emit(Opcode.GET_MEMBER_SLOT, receiver.slot, fieldId, methodId, current)
|
||||
@ -2381,7 +2513,7 @@ class BytecodeCompiler(
|
||||
if (fieldTarget != null) {
|
||||
if (fieldTarget.isOptional) return null
|
||||
val receiverClass = resolveReceiverClass(fieldTarget.target)
|
||||
?: throw BytecodeFallbackException(
|
||||
?: throw BytecodeCompileException(
|
||||
"Member access requires compile-time receiver type: ${fieldTarget.name}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -2591,7 +2723,7 @@ class BytecodeCompiler(
|
||||
if (direct == null) {
|
||||
val thisSlot = resolveDirectNameSlot("this")
|
||||
if (thisSlot != null) {
|
||||
throw BytecodeFallbackException(
|
||||
throw BytecodeCompileException(
|
||||
"Unresolved member call '${localTarget.name}': missing compile-time member id",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -2600,7 +2732,7 @@ class BytecodeCompiler(
|
||||
}
|
||||
val fieldTarget = ref.target as? FieldRef
|
||||
if (fieldTarget != null) {
|
||||
throw BytecodeFallbackException(
|
||||
throw BytecodeCompileException(
|
||||
"Member call requires compile-time receiver type: ${fieldTarget.name}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -2684,10 +2816,17 @@ class BytecodeCompiler(
|
||||
|
||||
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
|
||||
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 dst = allocSlot()
|
||||
val methodId = receiverClass?.instanceMethodIdMap(includeAbstract = true)?.get(ref.name)
|
||||
?: Obj.rootObjectType.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||
if (methodId != null) {
|
||||
if (!ref.isOptional) {
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||
@ -2714,14 +2853,8 @@ class BytecodeCompiler(
|
||||
builder.mark(endLabel)
|
||||
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)
|
||||
?: throw BytecodeFallbackException(
|
||||
?: throw BytecodeCompileException(
|
||||
"Unknown member ${ref.name} on ${receiverClass.className}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -2754,7 +2887,7 @@ class BytecodeCompiler(
|
||||
|
||||
private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? {
|
||||
val receiver = compileThisRef()
|
||||
val methodId = ref.methodId() ?: throw BytecodeFallbackException(
|
||||
val methodId = ref.methodId() ?: throw BytecodeCompileException(
|
||||
"Missing member id for ${ref.methodName()}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -2810,7 +2943,7 @@ class BytecodeCompiler(
|
||||
|
||||
private fun compileQualifiedThisMethodSlotCall(ref: QualifiedThisMethodSlotCallRef): CompiledValue? {
|
||||
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()}",
|
||||
Pos.builtIn
|
||||
)
|
||||
@ -2925,8 +3058,8 @@ class BytecodeCompiler(
|
||||
return when (stmt) {
|
||||
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
||||
else -> {
|
||||
throw BytecodeFallbackException(
|
||||
"Bytecode fallback: unsupported argument expression",
|
||||
throw BytecodeCompileException(
|
||||
"Bytecode compile error: unsupported argument expression",
|
||||
stmt.pos
|
||||
)
|
||||
}
|
||||
@ -3081,15 +3214,15 @@ class BytecodeCompiler(
|
||||
}
|
||||
|
||||
private fun emitFallbackStatement(stmt: Statement): CompiledValue {
|
||||
throw BytecodeFallbackException(
|
||||
"Bytecode fallback: unsupported statement",
|
||||
throw BytecodeCompileException(
|
||||
"Bytecode compile error: unsupported statement",
|
||||
stmt.pos
|
||||
)
|
||||
}
|
||||
|
||||
private fun emitStatementEval(stmt: Statement): CompiledValue {
|
||||
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? {
|
||||
@ -3198,8 +3331,8 @@ class BytecodeCompiler(
|
||||
val original = (statement as? BytecodeStatement)?.original
|
||||
val name = original?.let { "${statement::class.simpleName}(${it::class.simpleName})" }
|
||||
?: statement::class.simpleName
|
||||
throw BytecodeFallbackException(
|
||||
"Bytecode fallback: failed to compile block statement ($name)",
|
||||
throw BytecodeCompileException(
|
||||
"Bytecode compile error: failed to compile block statement ($name)",
|
||||
statement.pos
|
||||
)
|
||||
}
|
||||
@ -3531,16 +3664,16 @@ class BytecodeCompiler(
|
||||
val iterableMethods = ObjIterable.instanceMethodIdMap(includeAbstract = true)
|
||||
val iteratorMethodId = iterableMethods["iterator"]
|
||||
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 hasNextMethodId = iteratorMethods["hasNext"]
|
||||
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"]
|
||||
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()
|
||||
@ -4020,8 +4153,8 @@ class BytecodeCompiler(
|
||||
return when (target) {
|
||||
is ExpressionStatement -> compileRefWithFallback(target.ref, SlotType.BOOL, target.pos)
|
||||
else -> {
|
||||
throw BytecodeFallbackException(
|
||||
"Bytecode fallback: unsupported condition",
|
||||
throw BytecodeCompileException(
|
||||
"Bytecode compile error: unsupported condition",
|
||||
pos
|
||||
)
|
||||
}
|
||||
@ -4135,8 +4268,8 @@ class BytecodeCompiler(
|
||||
val stack = loopStack.toList()
|
||||
val targetIndex = findLoopContextIndex(stmt.label) ?: run {
|
||||
val labels = stack.joinToString(prefix = "[", postfix = "]") { it.label ?: "<unlabeled>" }
|
||||
throw BytecodeFallbackException(
|
||||
"Bytecode fallback: break label '${stmt.label}' not found in $labels",
|
||||
throw BytecodeCompileException(
|
||||
"Bytecode compile error: break label '${stmt.label}' not found in $labels",
|
||||
stmt.pos
|
||||
)
|
||||
}
|
||||
@ -4329,7 +4462,7 @@ class BytecodeCompiler(
|
||||
val localKeys = localSlotIndexByName.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"
|
||||
throw BytecodeFallbackException("Unresolved name '$name'.$info", pos)
|
||||
throw BytecodeCompileException("Unresolved name '$name'.$info", pos)
|
||||
}
|
||||
val refInfo = when (ref) {
|
||||
is FieldRef -> "FieldRef(${ref.name})"
|
||||
@ -4341,8 +4474,8 @@ class BytecodeCompiler(
|
||||
} else {
|
||||
""
|
||||
}
|
||||
throw BytecodeFallbackException(
|
||||
"Bytecode fallback: unsupported expression ($refInfo)$extra",
|
||||
throw BytecodeCompileException(
|
||||
"Bytecode compile error: unsupported expression ($refInfo)$extra",
|
||||
pos
|
||||
)
|
||||
}
|
||||
@ -4354,6 +4487,7 @@ class BytecodeCompiler(
|
||||
val fromSlot = slot?.let { slotObjClass[it] }
|
||||
fromSlot
|
||||
?: nameObjClass[ref.name]
|
||||
?: resolveTypeNameClass(ref.name)
|
||||
?: slotInitClassByKey[ScopeSlotKey(refScopeId(ref), refSlot(ref))]
|
||||
?: run {
|
||||
val match = slotInitClassByKey.entries.firstOrNull { (key, _) ->
|
||||
@ -4365,6 +4499,7 @@ class BytecodeCompiler(
|
||||
}
|
||||
is LocalVarRef -> resolveDirectNameSlot(ref.name)?.let { slotObjClass[it.slot] }
|
||||
?: nameObjClass[ref.name]
|
||||
?: resolveTypeNameClass(ref.name)
|
||||
is ListLiteralRef -> ObjList.type
|
||||
is MapLiteralRef -> ObjMap.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 refScopeId(ref: LocalSlotRef): Int = ref.scopeId
|
||||
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
||||
@ -4434,8 +4581,8 @@ class BytecodeCompiler(
|
||||
|
||||
private fun resolveReceiverClassForScopeCollection(ref: ObjRef): ObjClass? {
|
||||
return when (ref) {
|
||||
is LocalSlotRef -> nameObjClass[ref.name]
|
||||
is LocalVarRef -> nameObjClass[ref.name]
|
||||
is LocalSlotRef -> nameObjClass[ref.name] ?: resolveTypeNameClass(ref.name)
|
||||
is LocalVarRef -> nameObjClass[ref.name] ?: resolveTypeNameClass(ref.name)
|
||||
is ListLiteralRef -> ObjList.type
|
||||
is MapLiteralRef -> ObjMap.type
|
||||
is RangeRef -> ObjRange.type
|
||||
@ -4605,6 +4752,9 @@ class BytecodeCompiler(
|
||||
slotTypes.clear()
|
||||
slotObjClass.clear()
|
||||
nameObjClass.clear()
|
||||
if (knownNameObjClass.isNotEmpty()) {
|
||||
nameObjClass.putAll(knownNameObjClass)
|
||||
}
|
||||
slotInitClassByKey.clear()
|
||||
scopeSlotMap.clear()
|
||||
scopeSlotNameMap.clear()
|
||||
@ -4623,6 +4773,13 @@ class BytecodeCompiler(
|
||||
addrSlotByScopeSlot.clear()
|
||||
loopStack.clear()
|
||||
forceScopeSlots = allowLocalSlots && containsValueFnRef(stmt)
|
||||
if (slotTypeByScopeId.isNotEmpty()) {
|
||||
for ((scopeId, slots) in slotTypeByScopeId) {
|
||||
for ((slotIndex, cls) in slots) {
|
||||
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allowLocalSlots) {
|
||||
collectLoopVarNames(stmt)
|
||||
}
|
||||
@ -5144,7 +5301,6 @@ class BytecodeCompiler(
|
||||
val receiverClass = resolveReceiverClassForScopeCollection(ref.receiver)
|
||||
if (receiverClass != null) {
|
||||
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||
?: Obj.rootObjectType.instanceMethodIdMap(includeAbstract = true)[ref.name]
|
||||
if (methodId == null) {
|
||||
queueExtensionCallableNames(receiverClass, ref.name)
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ import net.sergeych.lyng.WhenInCondition
|
||||
import net.sergeych.lyng.WhenIsCondition
|
||||
import net.sergeych.lyng.WhenStatement
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.RangeRef
|
||||
|
||||
class BytecodeStatement private constructor(
|
||||
@ -50,13 +51,15 @@ class BytecodeStatement private constructor(
|
||||
returnLabels: Set<String> = emptySet(),
|
||||
rangeLocalNames: Set<String> = emptySet(),
|
||||
allowedScopeNames: Set<String>? = null,
|
||||
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||
knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
||||
): Statement {
|
||||
if (statement is BytecodeStatement) return statement
|
||||
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||
if (hasUnsupported) {
|
||||
val statementName = statement::class.qualifiedName ?: statement::class.simpleName ?: "UnknownStatement"
|
||||
throw BytecodeFallbackException(
|
||||
"Bytecode fallback: unsupported statement $statementName in '$nameHint'",
|
||||
throw BytecodeCompileException(
|
||||
"Bytecode compile error: unsupported statement $statementName in '$nameHint'",
|
||||
statement.pos
|
||||
)
|
||||
}
|
||||
@ -65,11 +68,13 @@ class BytecodeStatement private constructor(
|
||||
allowLocalSlots = safeLocals,
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = rangeLocalNames,
|
||||
allowedScopeNames = allowedScopeNames
|
||||
allowedScopeNames = allowedScopeNames,
|
||||
slotTypeByScopeId = slotTypeByScopeId,
|
||||
knownNameObjClass = knownNameObjClass
|
||||
)
|
||||
val compiled = compiler.compileStatement(nameHint, statement)
|
||||
val fn = compiled ?: throw BytecodeFallbackException(
|
||||
"Bytecode fallback: failed to compile '$nameHint'",
|
||||
val fn = compiled ?: throw BytecodeCompileException(
|
||||
"Bytecode compile error: failed to compile '$nameHint'",
|
||||
statement.pos
|
||||
)
|
||||
return BytecodeStatement(statement, fn)
|
||||
|
||||
@ -30,7 +30,6 @@ class CmdBuilder {
|
||||
private val constPool = mutableListOf<BytecodeConst>()
|
||||
private val labelPositions = mutableMapOf<Label, Int>()
|
||||
private var nextLabelId = 0
|
||||
private val fallbackStatements = mutableListOf<net.sergeych.lyng.Statement>()
|
||||
|
||||
fun addConst(c: BytecodeConst): Int {
|
||||
constPool += c
|
||||
@ -51,11 +50,6 @@ class CmdBuilder {
|
||||
labelPositions[label] = instructions.size
|
||||
}
|
||||
|
||||
fun addFallback(stmt: net.sergeych.lyng.Statement): Int {
|
||||
fallbackStatements += stmt
|
||||
return fallbackStatements.lastIndex
|
||||
}
|
||||
|
||||
fun build(
|
||||
name: String,
|
||||
localCount: Int,
|
||||
@ -109,7 +103,6 @@ class CmdBuilder {
|
||||
localSlotNames = localSlotNames,
|
||||
localSlotMutables = localSlotMutables,
|
||||
constants = constPool.toList(),
|
||||
fallbackStatements = fallbackStatements.toList(),
|
||||
cmds = cmds.toTypedArray()
|
||||
)
|
||||
}
|
||||
@ -175,12 +168,6 @@ class CmdBuilder {
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_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 ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.SET_INDEX ->
|
||||
@ -382,10 +369,7 @@ class CmdBuilder {
|
||||
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
||||
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_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.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.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
|
||||
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 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 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 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 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)
|
||||
@ -266,14 +263,8 @@ object CmdDisassembler {
|
||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_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 ->
|
||||
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 ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.SET_INDEX ->
|
||||
|
||||
@ -28,7 +28,6 @@ data class CmdFunction(
|
||||
val localSlotNames: Array<String?>,
|
||||
val localSlotMutables: BooleanArray,
|
||||
val constants: List<BytecodeConst>,
|
||||
val fallbackStatements: List<net.sergeych.lyng.Statement>,
|
||||
val cmds: Array<Cmd>,
|
||||
) {
|
||||
init {
|
||||
|
||||
@ -17,10 +17,8 @@
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.ExecutionError
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.PerfStats
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.ReturnException
|
||||
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(
|
||||
internal val calleeSlot: 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(
|
||||
internal val planId: 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(
|
||||
internal val targetSlot: 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() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.fn.localSlotNames.isNotEmpty()) {
|
||||
@ -1558,7 +1416,6 @@ class CmdFrame(
|
||||
var ip: Int = 0
|
||||
var scope: Scope = scope0
|
||||
private val moduleScope: Scope = resolveModuleScope(scope0)
|
||||
val methodCallSites: MutableMap<Int, MethodCallSite> = CmdCallSiteCache.methodCallSites(fn)
|
||||
|
||||
internal val scopeStack = ArrayDeque<Scope>()
|
||||
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),
|
||||
|
||||
CALL_DIRECT(0x90),
|
||||
CALL_VIRTUAL(0x91),
|
||||
CALL_MEMBER_SLOT(0x92),
|
||||
CALL_SLOT(0x93),
|
||||
|
||||
GET_FIELD(0xA0),
|
||||
SET_FIELD(0xA1),
|
||||
GET_INDEX(0xA2),
|
||||
SET_INDEX(0xA3),
|
||||
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 = "["
|
||||
for (item in this) {
|
||||
if (!first) result += ","
|
||||
result += item.toString()
|
||||
result += (item as Object).toString()
|
||||
first = false
|
||||
}
|
||||
result + "]"
|
||||
@ -310,8 +310,9 @@ fun List<T>.sort(): Void {
|
||||
/* Print this exception and its stack trace to standard output. */
|
||||
fun Exception.printStackTrace(): Void {
|
||||
println(this)
|
||||
for( entry in stackTrace )
|
||||
println("\tat "+entry.toString())
|
||||
for( entry in stackTrace ) {
|
||||
println("\tat "+(entry as Object).toString())
|
||||
}
|
||||
}
|
||||
|
||||
/* 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -242,6 +242,7 @@ This metadata drives:
|
||||
## Migration Notes
|
||||
- Keep reflection APIs separate to audit usage.
|
||||
- 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)
|
||||
- Provide minimal Kotlin-facing APIs that mirror compile-time-visible names.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user