Enforce compile-time only member access

This commit is contained in:
Sergey Chernov 2026-02-04 03:30:56 +03:00
parent 862486e0e8
commit 47654aee38
18 changed files with 333 additions and 714 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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