optimization: Scope access is now a Kotlin interface, compiler uses direct slot access also for closures

This commit is contained in:
Sergey Chernov 2026-02-05 13:55:24 +03:00
parent 43a6a7aaf4
commit 6220e982a0
14 changed files with 438 additions and 165 deletions

View File

@ -12,3 +12,9 @@
- `void` is a singleton of class `Void` (syntax sugar for return type). - `void` is a singleton of class `Void` (syntax sugar for return type).
- Object members are always allowed even on unknown types; non-Object members require explicit casts. Remove `inspect` from Object and use `toInspectString()` instead. - Object members are always allowed even on unknown types; non-Object members require explicit casts. Remove `inspect` from Object and use `toInspectString()` instead.
- Do not reintroduce bytecode fallback opcodes (e.g., `GET_NAME`, `EVAL_*`, `CALL_FALLBACK`) or runtime name-resolution fallbacks; all symbol resolution must stay compile-time only. - Do not reintroduce bytecode fallback opcodes (e.g., `GET_NAME`, `EVAL_*`, `CALL_FALLBACK`) or runtime name-resolution fallbacks; all symbol resolution must stay compile-time only.
## Bytecode frame-first migration plan
- Treat frame slots as the only storage for locals/temps by default; avoid pre-creating scope slot mappings for compiled functions.
- Create closure references only when a capture is detected; use a direct frame+slot reference (foreign slot ref) instead of scope slots.
- Keep Scope as a lazy reflection facade: resolve name -> slot only on demand for Kotlin interop (no eager name mapping on every call).
- Avoid PUSH_SCOPE/POP_SCOPE in bytecode for loops/functions unless dynamic name access or Kotlin reflection is requested.

View File

@ -4,15 +4,21 @@
test the Lyng way. It is not meant to be effective. test the Lyng way. It is not meant to be effective.
*/ */
fun naiveCountHappyNumbers() { fun naiveCountHappyNumbers(): Int {
var count = 0 var count = 0
for( n1 in 0..9 ) for( n1 in 0..9 ) {
for( n2 in 0..9 ) for( n2 in 0..9 ) {
for( n3 in 0..9 ) for( n3 in 0..9 ) {
for( n4 in 0..9 ) for( n4 in 0..9 ) {
for( n5 in 0..9 ) for( n5 in 0..9 ) {
for( n6 in 0..9 ) for( n6 in 0..9 ) {
if( n1 + n2 + n3 == n4 + n5 + n6 ) count++ if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
}
}
}
}
}
}
count count
} }
@ -28,4 +34,3 @@ for( r in 1..900 ) {
assert( found == 55252 ) assert( found == 55252 )
delay(0.05) delay(0.05)
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -56,7 +56,7 @@ class FsIntegrationJvmTest {
""" """
import lyng.io.fs import lyng.io.fs
// list current folder files // list current folder files
println( Path(".").list().toList() ) println( Path(".").list() )
""".trimIndent() """.trimIndent()
) )
} }

View File

@ -1131,6 +1131,7 @@ class Compiler(
"<script>", "<script>",
allowLocalSlots = true, allowLocalSlots = true,
allowedScopeNames = modulePlan.keys, allowedScopeNames = modulePlan.keys,
moduleScopeId = moduleSlotPlan()?.id,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownClassMapForBytecode() knownNameObjClass = knownClassMapForBytecode()
) )
@ -1357,7 +1358,7 @@ class Compiler(
if (scope == null) return if (scope == null) return
for ((name, rec) in scope.objects) { for ((name, rec) in scope.objects) {
val cls = rec.value as? ObjClass ?: continue val cls = rec.value as? ObjClass ?: continue
result.putIfAbsent(name, cls) if (!result.containsKey(name)) result[name] = cls
} }
} }
addScope(seedScope) addScope(seedScope)
@ -1367,7 +1368,7 @@ class Compiler(
} }
for (name in compileClassInfos.keys) { for (name in compileClassInfos.keys) {
val cls = resolveClassByName(name) ?: continue val cls = resolveClassByName(name) ?: continue
result.putIfAbsent(name, cls) if (!result.containsKey(name)) result[name] = cls
} }
return result return result
} }
@ -1411,6 +1412,7 @@ class Compiler(
returnLabels = returnLabels, returnLabels = returnLabels,
rangeLocalNames = currentRangeParamNames, rangeLocalNames = currentRangeParamNames,
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
moduleScopeId = moduleSlotPlan()?.id,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownClassMapForBytecode() knownNameObjClass = knownClassMapForBytecode()
) )
@ -1441,6 +1443,7 @@ class Compiler(
returnLabels = returnLabels, returnLabels = returnLabels,
rangeLocalNames = currentRangeParamNames, rangeLocalNames = currentRangeParamNames,
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
moduleScopeId = moduleSlotPlan()?.id,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownNames knownNameObjClass = knownNames
) )

View File

@ -0,0 +1,57 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* 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
import net.sergeych.lyng.bytecode.SlotType
import net.sergeych.lyng.obj.*
interface FrameAccess {
fun getSlotTypeCode(slot: Int): Byte
fun getObj(slot: Int): Obj
fun getInt(slot: Int): Long
fun getReal(slot: Int): Double
fun getBool(slot: Int): Boolean
fun setObj(slot: Int, value: Obj)
fun setInt(slot: Int, value: Long)
fun setReal(slot: Int, value: Double)
fun setBool(slot: Int, value: Boolean)
}
class FrameSlotRef(
private val frame: FrameAccess,
private val slot: Int,
) : net.sergeych.lyng.obj.Obj() {
fun read(): Obj {
return when (frame.getSlotTypeCode(slot)) {
SlotType.INT.code -> ObjInt.of(frame.getInt(slot))
SlotType.REAL.code -> ObjReal.of(frame.getReal(slot))
SlotType.BOOL.code -> if (frame.getBool(slot)) ObjTrue else ObjFalse
SlotType.OBJ.code -> frame.getObj(slot)
else -> ObjNull
}
}
fun write(value: Obj) {
when (value) {
is ObjInt -> frame.setInt(slot, value.value)
is ObjReal -> frame.setReal(slot, value.value)
is ObjBool -> frame.setBool(slot, value.value)
else -> frame.setObj(slot, value)
}
}
}

View File

@ -17,9 +17,9 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.bytecode.CmdDisassembler
import net.sergeych.lyng.bytecode.BytecodeStatement import net.sergeych.lyng.bytecode.BytecodeStatement
import net.sergeych.lyng.bytecode.CmdDisassembler
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.pacman.ImportProvider import net.sergeych.lyng.pacman.ImportProvider
@ -414,7 +414,13 @@ open class Scope(
// Slot fast-path API // Slot fast-path API
fun getSlotRecord(index: Int): ObjRecord = slots[index] fun getSlotRecord(index: Int): ObjRecord = slots[index]
fun setSlotValue(index: Int, newValue: Obj) { fun setSlotValue(index: Int, newValue: Obj) {
slots[index].value = newValue val record = slots[index]
val value = record.value
if (value is FrameSlotRef) {
value.write(newValue)
return
}
record.value = newValue
} }
val slotCount: Int val slotCount: Int
get() = slots.size get() = slots.size
@ -839,11 +845,21 @@ open class Scope(
} }
suspend fun resolve(rec: ObjRecord, name: String): Obj { suspend fun resolve(rec: ObjRecord, name: String): Obj {
val value = rec.value
if (value is FrameSlotRef) {
return value.read()
}
val receiver = rec.receiver ?: thisObj val receiver = rec.receiver ?: thisObj
return receiver.resolveRecord(this, rec, name, rec.declaringClass).value return receiver.resolveRecord(this, rec, name, rec.declaringClass).value
} }
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) { suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) {
val value = rec.value
if (value is FrameSlotRef) {
if (!rec.isMutable && value.read() !== ObjUnset) raiseIllegalAssignment("can't reassign val $name")
value.write(newValue)
return
}
if (rec.type == ObjRecord.Type.Delegated) { if (rec.type == ObjRecord.Type.Delegated) {
val receiver = rec.receiver ?: thisObj val receiver = rec.receiver ?: thisObj
val del = rec.delegate ?: run { val del = rec.delegate ?: run {

View File

@ -25,6 +25,7 @@ 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 moduleScopeId: Int? = null,
private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(), private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
private val knownNameObjClass: Map<String, ObjClass> = emptyMap(), private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
) { ) {
@ -58,6 +59,7 @@ class BytecodeCompiler(
private val intLoopVarNames = LinkedHashSet<String>() private val intLoopVarNames = LinkedHashSet<String>()
private val loopStack = ArrayDeque<LoopContext>() private val loopStack = ArrayDeque<LoopContext>()
private var forceScopeSlots = false private var forceScopeSlots = false
private var currentPos: Pos? = null
private data class LoopContext( private data class LoopContext(
val label: String?, val label: String?,
@ -70,6 +72,7 @@ class BytecodeCompiler(
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? { fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? {
prepareCompilation(stmt) prepareCompilation(stmt)
setPos(stmt.pos)
return when (stmt) { return when (stmt) {
is ExpressionStatement -> compileExpression(name, stmt) is ExpressionStatement -> compileExpression(name, stmt)
is net.sergeych.lyng.IfStatement -> compileIf(name, stmt) is net.sergeych.lyng.IfStatement -> compileIf(name, stmt)
@ -321,6 +324,7 @@ class BytecodeCompiler(
} }
private fun compileImplicitThisMethodCall(ref: ImplicitThisMethodCallRef): CompiledValue? { private fun compileImplicitThisMethodCall(ref: ImplicitThisMethodCallRef): CompiledValue? {
val callPos = ref.pos()
val receiver = ref.preferredThisTypeName()?.let { typeName -> val receiver = ref.preferredThisTypeName()?.let { typeName ->
compileThisVariantRef(typeName) ?: return null compileThisVariantRef(typeName) ?: return null
} ?: compileThisRef() } ?: compileThisRef()
@ -330,6 +334,7 @@ class BytecodeCompiler(
if (!ref.optionalInvoke()) { if (!ref.optionalInvoke()) {
val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
@ -345,6 +350,7 @@ class BytecodeCompiler(
) )
val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel) builder.mark(nullLabel)
@ -365,6 +371,7 @@ class BytecodeCompiler(
if (!ref.optionalInvoke()) { if (!ref.optionalInvoke()) {
val args = compileCallArgsWithReceiver(receiver, ref.arguments(), ref.hasTailBlock()) ?: return null val args = compileCallArgsWithReceiver(receiver, ref.arguments(), ref.hasTailBlock()) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_SLOT, calleeObj.slot, args.base, encodedCount, dst) builder.emit(Opcode.CALL_SLOT, calleeObj.slot, args.base, encodedCount, dst)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
@ -380,6 +387,7 @@ class BytecodeCompiler(
) )
val args = compileCallArgsWithReceiver(receiver, ref.arguments(), ref.hasTailBlock()) ?: return null val args = compileCallArgsWithReceiver(receiver, ref.arguments(), ref.hasTailBlock()) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_SLOT, calleeObj.slot, args.base, encodedCount, dst) builder.emit(Opcode.CALL_SLOT, calleeObj.slot, args.base, encodedCount, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel) builder.mark(nullLabel)
@ -2707,6 +2715,7 @@ class BytecodeCompiler(
} }
private fun compileCall(ref: CallRef): CompiledValue? { private fun compileCall(ref: CallRef): CompiledValue? {
val callPos = callSitePos()
val localTarget = ref.target as? LocalVarRef val localTarget = ref.target as? LocalVarRef
if (localTarget != null) { if (localTarget != null) {
val direct = resolveDirectNameSlot(localTarget.name) val direct = resolveDirectNameSlot(localTarget.name)
@ -2732,11 +2741,12 @@ class BytecodeCompiler(
"Map" -> ObjMap.type "Map" -> ObjMap.type
else -> null else -> null
} }
val callee = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null val callee = compileRefWithFallback(ref.target, null, refPosOrCurrent(ref.target)) ?: return null
val dst = allocSlot() val dst = allocSlot()
if (!ref.isOptionalInvoke) { if (!ref.isOptionalInvoke) {
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst) builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst)
if (initClass != null) { if (initClass != null) {
slotObjClass[dst] = initClass slotObjClass[dst] = initClass
@ -2755,6 +2765,7 @@ class BytecodeCompiler(
) )
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst) builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst)
if (initClass != null) { if (initClass != null) {
slotObjClass[dst] = initClass slotObjClass[dst] = initClass
@ -2805,6 +2816,7 @@ class BytecodeCompiler(
} }
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? { private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
val callPos = callSitePos()
val receiverClass = resolveReceiverClass(ref.receiver) val receiverClass = resolveReceiverClass(ref.receiver)
?: if (isAllowedObjectMember(ref.name)) { ?: if (isAllowedObjectMember(ref.name)) {
Obj.rootObjectType Obj.rootObjectType
@ -2814,13 +2826,14 @@ class BytecodeCompiler(
Pos.builtIn Pos.builtIn
) )
} }
val receiver = compileRefWithFallback(ref.receiver, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
val dst = allocSlot() val dst = allocSlot()
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] val methodId = receiverClass.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
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
@ -2836,6 +2849,7 @@ class BytecodeCompiler(
) )
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel) builder.mark(nullLabel)
@ -2852,6 +2866,7 @@ class BytecodeCompiler(
if (!ref.isOptional) { if (!ref.isOptional) {
val args = compileCallArgsWithReceiver(receiver, ref.args, ref.tailBlock) ?: return null val args = compileCallArgsWithReceiver(receiver, ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst) builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
@ -2867,6 +2882,7 @@ class BytecodeCompiler(
) )
val args = compileCallArgsWithReceiver(receiver, ref.args, ref.tailBlock) ?: return null val args = compileCallArgsWithReceiver(receiver, ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst) builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel) builder.mark(nullLabel)
@ -2876,6 +2892,7 @@ class BytecodeCompiler(
} }
private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? { private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? {
val callPos = callSitePos()
val receiver = compileThisRef() val receiver = compileThisRef()
val methodId = ref.methodId() ?: throw BytecodeCompileException( val methodId = ref.methodId() ?: throw BytecodeCompileException(
"Missing member id for ${ref.methodName()}", "Missing member id for ${ref.methodName()}",
@ -2885,6 +2902,7 @@ class BytecodeCompiler(
if (!ref.optionalInvoke()) { if (!ref.optionalInvoke()) {
val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
@ -2900,6 +2918,7 @@ class BytecodeCompiler(
) )
val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel) builder.mark(nullLabel)
@ -2932,6 +2951,7 @@ class BytecodeCompiler(
} }
private fun compileQualifiedThisMethodSlotCall(ref: QualifiedThisMethodSlotCallRef): CompiledValue? { private fun compileQualifiedThisMethodSlotCall(ref: QualifiedThisMethodSlotCallRef): CompiledValue? {
val callPos = callSitePos()
val receiver = compileThisVariantRef(ref.receiverTypeName()) ?: return null val receiver = compileThisVariantRef(ref.receiverTypeName()) ?: return null
val methodId = ref.methodId() ?: throw BytecodeCompileException( val methodId = ref.methodId() ?: throw BytecodeCompileException(
"Missing member id for ${ref.methodName()}", "Missing member id for ${ref.methodName()}",
@ -2941,6 +2961,7 @@ class BytecodeCompiler(
if (!ref.optionalInvoke()) { if (!ref.optionalInvoke()) {
val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
@ -2956,6 +2977,7 @@ class BytecodeCompiler(
) )
val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null val args = compileCallArgs(ref.arguments(), ref.hasTailBlock()) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst) builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, methodId, args.base, encodedCount, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel) builder.mark(nullLabel)
@ -3163,7 +3185,11 @@ class BytecodeCompiler(
} }
private fun compileBlock(name: String, stmt: BlockStatement): CmdFunction? { private fun compileBlock(name: String, stmt: BlockStatement): CmdFunction? {
val result = emitBlock(stmt, true) ?: return null val result = if (shouldInlineBlock(stmt)) {
emitInlineStatements(stmt.statements(), true)
} else {
emitBlock(stmt, true)
} ?: return null
builder.emit(Opcode.RET, result.slot) builder.emit(Opcode.RET, result.slot)
val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
return builder.build( return builder.build(
@ -3217,6 +3243,7 @@ class BytecodeCompiler(
private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? { private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
val target = if (stmt is BytecodeStatement) stmt.original else stmt val target = if (stmt is BytecodeStatement) stmt.original else stmt
setPos(target.pos)
return if (needResult) { return if (needResult) {
when (target) { when (target) {
is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos) is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos)
@ -3307,6 +3334,9 @@ class BytecodeCompiler(
} }
private fun emitBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? { private fun emitBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? {
if (shouldInlineBlock(stmt)) {
return emitInlineStatements(stmt.statements(), needResult)
}
val captureNames = if (stmt.captureSlots.isEmpty()) emptyList() else stmt.captureSlots.map { it.name } val captureNames = if (stmt.captureSlots.isEmpty()) emptyList() else stmt.captureSlots.map { it.name }
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan, captureNames)) val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan, captureNames))
builder.emit(Opcode.PUSH_SCOPE, planId) builder.emit(Opcode.PUSH_SCOPE, planId)
@ -3386,6 +3416,10 @@ class BytecodeCompiler(
private fun emitInlineBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? = private fun emitInlineBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? =
emitInlineStatements(stmt.statements(), needResult) emitInlineStatements(stmt.statements(), needResult)
private fun shouldInlineBlock(stmt: BlockStatement): Boolean {
return allowLocalSlots && !forceScopeSlots
}
private fun compileInlineBlock(name: String, stmt: net.sergeych.lyng.InlineBlockStatement): CmdFunction? { private fun compileInlineBlock(name: String, stmt: net.sergeych.lyng.InlineBlockStatement): CmdFunction? {
val result = emitInlineStatements(stmt.statements(), true) ?: return null val result = emitInlineStatements(stmt.statements(), true) ?: return null
builder.emit(Opcode.RET, result.slot) builder.emit(Opcode.RET, result.slot)
@ -3406,7 +3440,7 @@ class BytecodeCompiler(
private fun compileLoopBody(stmt: Statement, needResult: Boolean): CompiledValue? { private fun compileLoopBody(stmt: Statement, needResult: Boolean): CompiledValue? {
val target = if (stmt is BytecodeStatement) stmt.original else stmt val target = if (stmt is BytecodeStatement) stmt.original else stmt
if (target is BlockStatement) { if (target is BlockStatement) {
val useInline = target.slotPlan.isEmpty() && target.captureSlots.isEmpty() val useInline = !forceScopeSlots && target.slotPlan.isEmpty() && target.captureSlots.isEmpty()
return if (useInline) emitInlineBlock(target, needResult) else emitBlock(target, needResult) return if (useInline) emitInlineBlock(target, needResult) else emitBlock(target, needResult)
} }
return compileStatementValueOrFallback(target, needResult) return compileStatementValueOrFallback(target, needResult)
@ -3415,17 +3449,18 @@ class BytecodeCompiler(
private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? { private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? {
updateNameObjClass(stmt.name, stmt.initializer, stmt.initializerObjClass) updateNameObjClass(stmt.name, stmt.initializer, stmt.initializerObjClass)
val scopeId = stmt.scopeId ?: 0 val scopeId = stmt.scopeId ?: 0
val isModuleSlot = isModuleSlot(scopeId, stmt.name)
val scopeSlot = stmt.slotIndex?.let { slotIndex -> val scopeSlot = stmt.slotIndex?.let { slotIndex ->
val key = ScopeSlotKey(scopeId, slotIndex) val key = ScopeSlotKey(scopeId, slotIndex)
scopeSlotMap[key] scopeSlotMap[key]
} ?: run { } ?: run {
if (scopeId == 0) { if (isModuleSlot) {
scopeSlotIndexByName[stmt.name] scopeSlotIndexByName[stmt.name]
} else { } else {
null null
} }
} }
if (scopeId == 0 && scopeSlot != null) { if (isModuleSlot && scopeSlot != null) {
val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run { val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
val unsetId = builder.addConst(BytecodeConst.ObjRef(ObjUnset)) val unsetId = builder.addConst(BytecodeConst.ObjRef(ObjUnset))
builder.emit(Opcode.CONST_OBJ, unsetId, scopeSlot) builder.emit(Opcode.CONST_OBJ, unsetId, scopeSlot)
@ -3469,15 +3504,18 @@ class BytecodeCompiler(
updateSlotType(localSlot, value.type) updateSlotType(localSlot, value.type)
updateSlotObjClass(localSlot, stmt.initializer, stmt.initializerObjClass) updateSlotObjClass(localSlot, stmt.initializer, stmt.initializerObjClass)
updateNameObjClassFromSlot(stmt.name, localSlot) updateNameObjClassFromSlot(stmt.name, localSlot)
val declId = builder.addConst( val shadowedScopeSlot = scopeSlotIndexByName.containsKey(stmt.name)
BytecodeConst.LocalDecl( if (forceScopeSlots || !shadowedScopeSlot) {
stmt.name, val declId = builder.addConst(
stmt.isMutable, BytecodeConst.LocalDecl(
stmt.visibility, stmt.name,
stmt.isTransient stmt.isMutable,
stmt.visibility,
stmt.isTransient
)
) )
) builder.emit(Opcode.DECL_LOCAL, declId, localSlot)
builder.emit(Opcode.DECL_LOCAL, declId, localSlot) }
return CompiledValue(localSlot, value.type) return CompiledValue(localSlot, value.type)
} }
if (scopeSlot != null) { if (scopeSlot != null) {
@ -3605,15 +3643,11 @@ class BytecodeCompiler(
rangeRef = extractRangeFromLocal(stmt.source) rangeRef = extractRangeFromLocal(stmt.source)
} }
val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
val useLoopScope = stmt.loopSlotPlan.isNotEmpty() val loopSlotPlan = stmt.loopSlotPlan
val planId = if (useLoopScope) { var useLoopScope = loopSlotPlan.isNotEmpty()
builder.addConst(BytecodeConst.SlotPlan(stmt.loopSlotPlan, emptyList()))
} else {
-1
}
val loopLocalIndex = localSlotIndexByName[stmt.loopVarName] val loopLocalIndex = localSlotIndexByName[stmt.loopVarName]
var usedOverride = false var usedOverride = false
val loopSlotId = when { var loopSlotId = when {
loopLocalIndex != null -> scopeSlotCount + loopLocalIndex loopLocalIndex != null -> scopeSlotCount + loopLocalIndex
else -> { else -> {
val localKey = localSlotInfoMap.entries.firstOrNull { it.value.name == stmt.loopVarName }?.key val localKey = localSlotInfoMap.entries.firstOrNull { it.value.name == stmt.loopVarName }?.key
@ -3629,7 +3663,36 @@ class BytecodeCompiler(
usedOverride = true usedOverride = true
slot slot
} }
val loopDeclId = if (usedOverride) { var emitDeclLocal = usedOverride
if (useLoopScope && !forceScopeSlots) {
val loopVarOnly = loopSlotPlan.size == 1 && loopSlotPlan.containsKey(stmt.loopVarName)
val loopVarIsLocal = loopSlotId >= scopeSlotCount
if (loopVarOnly && loopVarIsLocal) {
useLoopScope = false
}
}
if (useLoopScope && allowLocalSlots && !forceScopeSlots) {
val needsScope = allowedScopeNames?.let { names ->
loopSlotPlan.keys.any { names.contains(it) }
} == true
if (!needsScope) {
useLoopScope = false
}
}
emitDeclLocal = emitDeclLocal && useLoopScope
if (!forceScopeSlots && loopSlotId < scopeSlotCount) {
val localSlot = allocSlot()
loopSlotOverrides[stmt.loopVarName] = localSlot
usedOverride = true
emitDeclLocal = false
loopSlotId = localSlot
}
val planId = if (useLoopScope) {
builder.addConst(BytecodeConst.SlotPlan(loopSlotPlan, emptyList()))
} else {
-1
}
val loopDeclId = if (emitDeclLocal) {
builder.addConst( builder.addConst(
BytecodeConst.LocalDecl( BytecodeConst.LocalDecl(
stmt.loopVarName, stmt.loopVarName,
@ -3702,7 +3765,7 @@ class BytecodeCompiler(
builder.emit(Opcode.MOVE_OBJ, nextObj.slot, loopSlotId) builder.emit(Opcode.MOVE_OBJ, nextObj.slot, loopSlotId)
updateSlotType(loopSlotId, SlotType.OBJ) updateSlotType(loopSlotId, SlotType.OBJ)
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ) updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
if (usedOverride) { if (emitDeclLocal) {
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId) builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
} }
@ -3809,7 +3872,7 @@ class BytecodeCompiler(
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId) builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
updateSlotType(loopSlotId, SlotType.INT) updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
if (usedOverride) { if (emitDeclLocal) {
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId) builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
} }
loopStack.addLast( loopStack.addLast(
@ -3886,7 +3949,7 @@ class BytecodeCompiler(
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId) builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
updateSlotType(loopSlotId, SlotType.INT) updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
if (usedOverride) { if (emitDeclLocal) {
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId) builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
} }
loopStack.addLast( loopStack.addLast(
@ -4414,7 +4477,27 @@ class BytecodeCompiler(
} }
} }
private fun setPos(pos: Pos?) {
currentPos = pos
builder.setPos(pos)
}
private fun callSitePos(): Pos = currentPos ?: Pos.builtIn
private fun refPosOrCurrent(ref: ObjRef): Pos {
val refPos = when (ref) {
is LocalVarRef -> ref.pos()
is LocalSlotRef -> ref.pos()
is QualifiedThisRef -> ref.pos()
is ImplicitThisMethodCallRef -> ref.pos()
is StatementRef -> ref.statement.pos
else -> null
}
return refPos ?: callSitePos()
}
private fun compileRefWithFallback(ref: ObjRef, forceType: SlotType?, pos: Pos): CompiledValue? { private fun compileRefWithFallback(ref: ObjRef, forceType: SlotType?, pos: Pos): CompiledValue? {
setPos(pos)
var compiled = compileRef(ref) var compiled = compileRef(ref)
if (compiled != null) { if (compiled != null) {
if (forceType == null) return compiled if (forceType == null) return compiled
@ -4706,8 +4789,9 @@ class BytecodeCompiler(
private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn
private fun resolveSlot(ref: LocalSlotRef): Int? { private fun resolveSlot(ref: LocalSlotRef): Int? {
loopSlotOverrides[ref.name]?.let { return it }
val scopeId = refScopeId(ref) val scopeId = refScopeId(ref)
if (scopeId == 0) { if (isModuleSlot(scopeId, ref.name)) {
val key = ScopeSlotKey(scopeId, refSlot(ref)) val key = ScopeSlotKey(scopeId, refSlot(ref))
scopeSlotMap[key]?.let { return it } scopeSlotMap[key]?.let { return it }
scopeSlotIndexByName[ref.name]?.let { return it } scopeSlotIndexByName[ref.name]?.let { return it }
@ -4718,6 +4802,10 @@ class BytecodeCompiler(
if (ownerLocal != null) { if (ownerLocal != null) {
return scopeSlotCount + ownerLocal return scopeSlotCount + ownerLocal
} }
val nameLocal = localSlotIndexByName[ref.name]
if (nameLocal != null) {
return scopeSlotCount + nameLocal
}
val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
return scopeSlotMap[scopeKey] return scopeSlotMap[scopeKey]
} }
@ -4725,7 +4813,6 @@ class BytecodeCompiler(
val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
return scopeSlotMap[scopeKey] return scopeSlotMap[scopeKey]
} }
loopSlotOverrides[ref.name]?.let { return it }
val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
val localIndex = localSlotIndexByKey[localKey] val localIndex = localSlotIndexByKey[localKey]
if (localIndex != null) return scopeSlotCount + localIndex if (localIndex != null) return scopeSlotCount + localIndex
@ -4805,7 +4892,7 @@ class BytecodeCompiler(
val name = scopeSlotNameMap[key] val name = scopeSlotNameMap[key]
scopeSlotIndices[index] = key.slot scopeSlotIndices[index] = key.slot
scopeSlotNames[index] = name scopeSlotNames[index] = name
scopeSlotIsModule[index] = key.scopeId == 0 scopeSlotIsModule[index] = key.scopeId == (moduleScopeId ?: 0)
scopeSlotMutableMap[key]?.let { scopeSlotMutables[index] = it } scopeSlotMutableMap[key]?.let { scopeSlotMutables[index] = it }
} }
if (allowLocalSlots && localSlotInfoMap.isNotEmpty()) { if (allowLocalSlots && localSlotInfoMap.isNotEmpty()) {
@ -4878,7 +4965,8 @@ class BytecodeCompiler(
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
} }
} }
if (allowLocalSlots && !forceScopeSlots && slotIndex != null && scopeId != 0) { val isModuleSlot = isModuleSlot(scopeId, stmt.name)
if (allowLocalSlots && !forceScopeSlots && slotIndex != null && !isModuleSlot) {
val key = ScopeSlotKey(scopeId, slotIndex) val key = ScopeSlotKey(scopeId, slotIndex)
declaredLocalKeys.add(key) declaredLocalKeys.add(key)
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
@ -5052,6 +5140,13 @@ class BytecodeCompiler(
} }
} }
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
val moduleId = moduleScopeId ?: 0
if (scopeId != moduleId) return false
if (allowedScopeNames == null || name == null) return true
return allowedScopeNames.contains(name)
}
private fun collectLoopVarNames(stmt: Statement) { private fun collectLoopVarNames(stmt: Statement) {
if (stmt is BytecodeStatement) { if (stmt is BytecodeStatement) {
collectLoopVarNames(stmt.original) collectLoopVarNames(stmt.original)
@ -5154,7 +5249,7 @@ class BytecodeCompiler(
return return
} }
val shouldLocalize = !forceScopeSlots || intLoopVarNames.contains(ref.name) val shouldLocalize = !forceScopeSlots || intLoopVarNames.contains(ref.name)
val isModuleSlot = scopeId == 0 val isModuleSlot = isModuleSlot(scopeId, ref.name)
if (allowLocalSlots && !ref.isDelegated && shouldLocalize && !isModuleSlot) { if (allowLocalSlots && !ref.isDelegated && shouldLocalize && !isModuleSlot) {
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable) localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable)
@ -5203,7 +5298,7 @@ class BytecodeCompiler(
} }
} else { } else {
val shouldLocalize = !forceScopeSlots || intLoopVarNames.contains(target.name) val shouldLocalize = !forceScopeSlots || intLoopVarNames.contains(target.name)
val isModuleSlot = scopeId == 0 val isModuleSlot = isModuleSlot(scopeId, target.name)
if (allowLocalSlots && !target.isDelegated && shouldLocalize && !isModuleSlot) { if (allowLocalSlots && !target.isDelegated && shouldLocalize && !isModuleSlot) {
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable) localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable)

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2026 Sergey S. Chernov * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,17 +12,19 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
import net.sergeych.lyng.FrameAccess
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjNull import net.sergeych.lyng.obj.ObjNull
class BytecodeFrame( class BytecodeFrame(
val localCount: Int, val localCount: Int,
val argCount: Int, val argCount: Int,
) { ) : FrameAccess {
val slotCount: Int = localCount + argCount val slotCount: Int = localCount + argCount
val argBase: Int = localCount val argBase: Int = localCount
@ -33,31 +35,31 @@ class BytecodeFrame(
private val boolSlots: BooleanArray = BooleanArray(slotCount) private val boolSlots: BooleanArray = BooleanArray(slotCount)
fun getSlotType(slot: Int): SlotType = SlotType.values().first { it.code == slotTypes[slot] } fun getSlotType(slot: Int): SlotType = SlotType.values().first { it.code == slotTypes[slot] }
fun getSlotTypeCode(slot: Int): Byte = slotTypes[slot] override fun getSlotTypeCode(slot: Int): Byte = slotTypes[slot]
fun setSlotType(slot: Int, type: SlotType) { fun setSlotType(slot: Int, type: SlotType) {
slotTypes[slot] = type.code slotTypes[slot] = type.code
} }
fun getObj(slot: Int): Obj = objSlots[slot] ?: ObjNull override fun getObj(slot: Int): Obj = objSlots[slot] ?: ObjNull
fun setObj(slot: Int, value: Obj) { override fun setObj(slot: Int, value: Obj) {
objSlots[slot] = value objSlots[slot] = value
slotTypes[slot] = SlotType.OBJ.code slotTypes[slot] = SlotType.OBJ.code
} }
fun getInt(slot: Int): Long = intSlots[slot] override fun getInt(slot: Int): Long = intSlots[slot]
fun setInt(slot: Int, value: Long) { override fun setInt(slot: Int, value: Long) {
intSlots[slot] = value intSlots[slot] = value
slotTypes[slot] = SlotType.INT.code slotTypes[slot] = SlotType.INT.code
} }
fun getReal(slot: Int): Double = realSlots[slot] override fun getReal(slot: Int): Double = realSlots[slot]
fun setReal(slot: Int, value: Double) { override fun setReal(slot: Int, value: Double) {
realSlots[slot] = value realSlots[slot] = value
slotTypes[slot] = SlotType.REAL.code slotTypes[slot] = SlotType.REAL.code
} }
fun getBool(slot: Int): Boolean = boolSlots[slot] override fun getBool(slot: Int): Boolean = boolSlots[slot]
fun setBool(slot: Int, value: Boolean) { override fun setBool(slot: Int, value: Boolean) {
boolSlots[slot] = value boolSlots[slot] = value
slotTypes[slot] = SlotType.BOOL.code slotTypes[slot] = SlotType.BOOL.code
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2026 Sergey S. Chernov * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,23 +12,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Pos import net.sergeych.lyng.*
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.DestructuringVarDeclStatement
import net.sergeych.lyng.WhenCase
import net.sergeych.lyng.WhenCondition
import net.sergeych.lyng.WhenEqualsCondition
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.Obj
import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.RangeRef
class BytecodeStatement private constructor( class BytecodeStatement private constructor(
val original: Statement, val original: Statement,
@ -51,13 +42,14 @@ 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,
moduleScopeId: Int? = null,
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(), slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
knownNameObjClass: Map<String, ObjClass> = emptyMap(), knownNameObjClass: Map<String, ObjClass> = emptyMap(),
): 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.toString()
throw BytecodeCompileException( throw BytecodeCompileException(
"Bytecode compile error: unsupported statement $statementName in '$nameHint'", "Bytecode compile error: unsupported statement $statementName in '$nameHint'",
statement.pos statement.pos
@ -69,6 +61,7 @@ class BytecodeStatement private constructor(
returnLabels = returnLabels, returnLabels = returnLabels,
rangeLocalNames = rangeLocalNames, rangeLocalNames = rangeLocalNames,
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
moduleScopeId = moduleScopeId,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownNameObjClass knownNameObjClass = knownNameObjClass
) )

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2026 Sergey S. Chernov * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
@ -27,9 +28,11 @@ class CmdBuilder {
data class Instr(val op: Opcode, val operands: List<Operand>) data class Instr(val op: Opcode, val operands: List<Operand>)
private val instructions = mutableListOf<Instr>() private val instructions = mutableListOf<Instr>()
private val posByInstr = mutableListOf<net.sergeych.lyng.Pos?>()
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 var currentPos: net.sergeych.lyng.Pos? = null
fun addConst(c: BytecodeConst): Int { fun addConst(c: BytecodeConst): Int {
constPool += c constPool += c
@ -38,10 +41,16 @@ class CmdBuilder {
fun emit(op: Opcode, vararg operands: Int) { fun emit(op: Opcode, vararg operands: Int) {
instructions += Instr(op, operands.map { Operand.IntVal(it) }) instructions += Instr(op, operands.map { Operand.IntVal(it) })
posByInstr += currentPos
} }
fun emit(op: Opcode, operands: List<Operand>) { fun emit(op: Opcode, operands: List<Operand>) {
instructions += Instr(op, operands) instructions += Instr(op, operands)
posByInstr += currentPos
}
fun setPos(pos: net.sergeych.lyng.Pos?) {
currentPos = pos
} }
fun label(): Label = Label(nextLabelId++) fun label(): Label = Label(nextLabelId++)
@ -103,7 +112,8 @@ class CmdBuilder {
localSlotNames = localSlotNames, localSlotNames = localSlotNames,
localSlotMutables = localSlotMutables, localSlotMutables = localSlotMutables,
constants = constPool.toList(), constants = constPool.toList(),
cmds = cmds.toTypedArray() cmds = cmds.toTypedArray(),
posByIp = posByInstr.toTypedArray()
) )
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2026 Sergey S. Chernov * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
@ -29,6 +30,7 @@ data class CmdFunction(
val localSlotMutables: BooleanArray, val localSlotMutables: BooleanArray,
val constants: List<BytecodeConst>, val constants: List<BytecodeConst>,
val cmds: Array<Cmd>, val cmds: Array<Cmd>,
val posByIp: Array<net.sergeych.lyng.Pos?>,
) { ) {
init { init {
require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" } require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" }
@ -37,5 +39,8 @@ data class CmdFunction(
require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" } require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" }
require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" } require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" }
require(addrCount >= 0) { "addrCount must be non-negative" } require(addrCount >= 0) { "addrCount must be non-negative" }
if (posByIp.isNotEmpty()) {
require(posByIp.size == cmds.size) { "posByIp size mismatch" }
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2026 Sergey S. Chernov * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -12,17 +12,12 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Arguments import net.sergeych.lyng.*
import net.sergeych.lyng.ModuleScope
import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Pos
import net.sergeych.lyng.ReturnException
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
class CmdVm { class CmdVm {
@ -152,7 +147,7 @@ class CmdConstBool(internal val constId: Int, internal val dst: Int) : Cmd() {
class CmdLoadThis(internal val dst: Int) : Cmd() { class CmdLoadThis(internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.scope.thisObj) frame.setObj(dst, frame.ensureScope().thisObj)
return return
} }
} }
@ -165,8 +160,8 @@ class CmdLoadThisVariant(
val typeConst = frame.fn.constants.getOrNull(typeId) as? BytecodeConst.StringVal val typeConst = frame.fn.constants.getOrNull(typeId) as? BytecodeConst.StringVal
?: error("LOAD_THIS_VARIANT expects StringVal at $typeId") ?: error("LOAD_THIS_VARIANT expects StringVal at $typeId")
val typeName = typeConst.value val typeName = typeConst.value
val receiver = frame.scope.thisVariants.firstOrNull { it.isInstanceOf(typeName) } val receiver = frame.ensureScope().thisVariants.firstOrNull { it.isInstanceOf(typeName) }
?: frame.scope.raiseClassCastError("Cannot cast ${frame.scope.thisObj.objClass.className} to $typeName") ?: frame.ensureScope().raiseClassCastError("Cannot cast ${frame.ensureScope().thisObj.objClass.className} to $typeName")
frame.setObj(dst, receiver) frame.setObj(dst, receiver)
return return
} }
@ -222,11 +217,11 @@ class CmdAssertIs(internal val objSlot: Int, internal val typeSlot: Int) : Cmd()
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val obj = frame.slotToObj(objSlot) val obj = frame.slotToObj(objSlot)
val typeObj = frame.slotToObj(typeSlot) val typeObj = frame.slotToObj(typeSlot)
val clazz = typeObj as? ObjClass ?: frame.scope.raiseClassCastError( val clazz = typeObj as? ObjClass ?: frame.ensureScope().raiseClassCastError(
"${typeObj.inspect(frame.scope)} is not the class instance" "${typeObj.inspect(frame.ensureScope())} is not the class instance"
) )
if (!obj.isInstanceOf(clazz)) { if (!obj.isInstanceOf(clazz)) {
frame.scope.raiseClassCastError("expected ${clazz.className}, got ${obj.objClass.className}") frame.ensureScope().raiseClassCastError("expected ${clazz.className}, got ${obj.objClass.className}")
} }
return return
} }
@ -240,8 +235,8 @@ class CmdMakeQualifiedView(
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val obj0 = frame.slotToObj(objSlot) val obj0 = frame.slotToObj(objSlot)
val typeObj = frame.slotToObj(typeSlot) val typeObj = frame.slotToObj(typeSlot)
val clazz = typeObj as? ObjClass ?: frame.scope.raiseClassCastError( val clazz = typeObj as? ObjClass ?: frame.ensureScope().raiseClassCastError(
"${typeObj.inspect(frame.scope)} is not the class instance" "${typeObj.inspect(frame.ensureScope())} is not the class instance"
) )
val base = when (obj0) { val base = when (obj0) {
is ObjQualifiedView -> obj0.instance is ObjQualifiedView -> obj0.instance
@ -787,7 +782,7 @@ class CmdCmpEqObj(internal val a: Int, internal val b: Int, internal val dst: In
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val left = frame.slotToObj(a) val left = frame.slotToObj(a)
val right = frame.slotToObj(b) val right = frame.slotToObj(b)
frame.setBool(dst, left.equals(frame.scope, right)) frame.setBool(dst, left.equals(frame.ensureScope(), right))
return return
} }
} }
@ -796,7 +791,7 @@ class CmdCmpNeqObj(internal val a: Int, internal val b: Int, internal val dst: I
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val left = frame.slotToObj(a) val left = frame.slotToObj(a)
val right = frame.slotToObj(b) val right = frame.slotToObj(b)
frame.setBool(dst, !left.equals(frame.scope, right)) frame.setBool(dst, !left.equals(frame.ensureScope(), right))
return return
} }
} }
@ -838,28 +833,28 @@ class CmdOrBool(internal val a: Int, internal val b: Int, internal val dst: Int)
class CmdCmpLtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpLtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) < 0) frame.setBool(dst, frame.slotToObj(a).compareTo(frame.ensureScope(), frame.slotToObj(b)) < 0)
return return
} }
} }
class CmdCmpLteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpLteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) <= 0) frame.setBool(dst, frame.slotToObj(a).compareTo(frame.ensureScope(), frame.slotToObj(b)) <= 0)
return return
} }
} }
class CmdCmpGtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpGtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) > 0) frame.setBool(dst, frame.slotToObj(a).compareTo(frame.ensureScope(), frame.slotToObj(b)) > 0)
return return
} }
} }
class CmdCmpGteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { class CmdCmpGteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) >= 0) frame.setBool(dst, frame.slotToObj(a).compareTo(frame.ensureScope(), frame.slotToObj(b)) >= 0)
return return
} }
} }
@ -883,7 +878,7 @@ class CmdAddObj(internal val a: Int, internal val b: Int, internal val dst: Int)
return return
} }
} }
frame.setObj(dst, frame.slotToObj(a).plus(frame.scope, frame.slotToObj(b))) frame.setObj(dst, frame.slotToObj(a).plus(frame.ensureScope(), frame.slotToObj(b)))
return return
} }
} }
@ -907,7 +902,7 @@ class CmdSubObj(internal val a: Int, internal val b: Int, internal val dst: Int)
return return
} }
} }
frame.setObj(dst, frame.slotToObj(a).minus(frame.scope, frame.slotToObj(b))) frame.setObj(dst, frame.slotToObj(a).minus(frame.ensureScope(), frame.slotToObj(b)))
return return
} }
} }
@ -931,7 +926,7 @@ class CmdMulObj(internal val a: Int, internal val b: Int, internal val dst: Int)
return return
} }
} }
frame.setObj(dst, frame.slotToObj(a).mul(frame.scope, frame.slotToObj(b))) frame.setObj(dst, frame.slotToObj(a).mul(frame.ensureScope(), frame.slotToObj(b)))
return return
} }
} }
@ -955,7 +950,7 @@ class CmdDivObj(internal val a: Int, internal val b: Int, internal val dst: Int)
return return
} }
} }
frame.setObj(dst, frame.slotToObj(a).div(frame.scope, frame.slotToObj(b))) frame.setObj(dst, frame.slotToObj(a).div(frame.ensureScope(), frame.slotToObj(b)))
return return
} }
} }
@ -979,14 +974,14 @@ class CmdModObj(internal val a: Int, internal val b: Int, internal val dst: Int)
return return
} }
} }
frame.setObj(dst, frame.slotToObj(a).mod(frame.scope, frame.slotToObj(b))) frame.setObj(dst, frame.slotToObj(a).mod(frame.ensureScope(), frame.slotToObj(b)))
return return
} }
} }
class CmdContainsObj(internal val target: Int, internal val value: Int, internal val dst: Int) : Cmd() { class CmdContainsObj(internal val target: Int, internal val value: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.slotToObj(target).contains(frame.scope, frame.slotToObj(value))) frame.setBool(dst, frame.slotToObj(target).contains(frame.ensureScope(), frame.slotToObj(value)))
return return
} }
} }
@ -1002,17 +997,17 @@ class CmdAssignOpObj(
val target = frame.slotToObj(targetSlot) val target = frame.slotToObj(targetSlot)
val value = frame.slotToObj(valueSlot) val value = frame.slotToObj(valueSlot)
val result = when (BinOp.values().getOrNull(opId)) { val result = when (BinOp.values().getOrNull(opId)) {
BinOp.PLUS -> target.plusAssign(frame.scope, value) BinOp.PLUS -> target.plusAssign(frame.ensureScope(), value)
BinOp.MINUS -> target.minusAssign(frame.scope, value) BinOp.MINUS -> target.minusAssign(frame.ensureScope(), value)
BinOp.STAR -> target.mulAssign(frame.scope, value) BinOp.STAR -> target.mulAssign(frame.ensureScope(), value)
BinOp.SLASH -> target.divAssign(frame.scope, value) BinOp.SLASH -> target.divAssign(frame.ensureScope(), value)
BinOp.PERCENT -> target.modAssign(frame.scope, value) BinOp.PERCENT -> target.modAssign(frame.ensureScope(), value)
else -> null else -> null
} }
if (result == null) { if (result == null) {
val name = (frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal)?.value val name = (frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal)?.value
if (name != null) frame.scope.raiseIllegalAssignment("symbol is readonly: $name") if (name != null) frame.ensureScope().raiseIllegalAssignment("symbol is readonly: $name")
frame.scope.raiseIllegalAssignment("symbol is readonly") frame.ensureScope().raiseIllegalAssignment("symbol is readonly")
} }
frame.storeObjResult(dst, result) frame.storeObjResult(dst, result)
return return
@ -1118,7 +1113,7 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl
?: error("DECL_LOCAL expects LocalDecl at $constId") ?: error("DECL_LOCAL expects LocalDecl at $constId")
val value = frame.slotToObj(slot).byValueCopy() val value = frame.slotToObj(slot).byValueCopy()
frame.scope.addItem( frame.ensureScope().addItem(
decl.name, decl.name,
decl.isMutable, decl.isMutable,
value, value,
@ -1134,12 +1129,12 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl
?: error("DECL_EXT_PROPERTY expects ExtensionPropertyDecl at $constId") ?: error("DECL_EXT_PROPERTY expects ExtensionPropertyDecl at $constId")
val type = frame.scope[decl.extTypeName]?.value val type = frame.ensureScope()[decl.extTypeName]?.value
?: frame.scope.raiseSymbolNotFound("class ${decl.extTypeName} not found") ?: frame.ensureScope().raiseSymbolNotFound("class ${decl.extTypeName} not found")
if (type !is ObjClass) { if (type !is ObjClass) {
frame.scope.raiseClassCastError("${decl.extTypeName} is not the class instance") frame.ensureScope().raiseClassCastError("${decl.extTypeName} is not the class instance")
} }
frame.scope.addExtension( frame.ensureScope().addExtension(
type, type,
decl.property.name, decl.property.name,
ObjRecord( ObjRecord(
@ -1164,16 +1159,16 @@ class CmdCallDirect(
) : Cmd() { ) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope() frame.syncFrameToScope(useRefs = true)
} }
val ref = frame.fn.constants.getOrNull(id) as? BytecodeConst.ObjRef val ref = frame.fn.constants.getOrNull(id) as? BytecodeConst.ObjRef
?: error("CALL_DIRECT expects ObjRef at $id") ?: error("CALL_DIRECT expects ObjRef at $id")
val callee = ref.value val callee = ref.value
val args = frame.buildArguments(argBase, argCount) val args = frame.buildArguments(argBase, argCount)
val result = if (PerfFlags.SCOPE_POOL) { val result = if (PerfFlags.SCOPE_POOL) {
frame.scope.withChildFrame(args) { child -> callee.callOn(child) } frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
} else { } else {
callee.callOn(frame.scope.createChildScope(frame.scope.pos, args = args)) callee.callOn(frame.ensureScope().createChildScope(frame.ensureScope().pos, args = args))
} }
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
@ -1191,7 +1186,7 @@ class CmdCallSlot(
) : Cmd() { ) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope() frame.syncFrameToScope(useRefs = true)
} }
val callee = frame.slotToObj(calleeSlot) val callee = frame.slotToObj(calleeSlot)
if (callee === ObjUnset) { if (callee === ObjUnset) {
@ -1203,15 +1198,15 @@ class CmdCallSlot(
} }
val message = name?.let { "property '$it' is unset (not initialized)" } val message = name?.let { "property '$it' is unset (not initialized)" }
?: "property is unset (not initialized) in ${frame.fn.name} at slot $calleeSlot" ?: "property is unset (not initialized) in ${frame.fn.name} at slot $calleeSlot"
frame.scope.raiseUnset(message) frame.ensureScope().raiseUnset(message)
} }
val args = frame.buildArguments(argBase, argCount) val args = frame.buildArguments(argBase, argCount)
val canPool = PerfFlags.SCOPE_POOL && callee !is Statement val canPool = PerfFlags.SCOPE_POOL && callee !is Statement
val result = if (canPool) { val result = if (canPool) {
frame.scope.withChildFrame(args) { child -> callee.callOn(child) } frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
} else { } else {
// Pooling for Statement-based callables (lambdas) can still alter closure semantics; keep safe path for now. // Pooling for Statement-based callables (lambdas) can still alter closure semantics; keep safe path for now.
callee.callOn(frame.scope.createChildScope(frame.scope.pos, args = args)) callee.callOn(frame.ensureScope().createChildScope(frame.ensureScope().pos, args = args))
} }
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
@ -1239,7 +1234,7 @@ class CmdListLiteral(
list.ensureCapacity(list.size + value.list.size) list.ensureCapacity(list.size + value.list.size)
list.addAll(value.list) list.addAll(value.list)
} }
else -> frame.scope.raiseError("Spread element must be list") else -> frame.ensureScope().raiseError("Spread element must be list")
} }
} else { } else {
list.add(value) list.add(value)
@ -1266,14 +1261,14 @@ class CmdGetMemberSlot(
if (methodId >= 0) { if (methodId >= 0) {
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId) inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
} else null } else null
} ?: frame.scope.raiseSymbolNotFound("member") } ?: frame.ensureScope().raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>" val name = rec.memberName ?: "<member>"
if (receiver is ObjQualifiedView) { if (receiver is ObjQualifiedView) {
val resolved = receiver.readField(frame.scope, name) val resolved = receiver.readField(frame.ensureScope(), name)
frame.storeObjResult(dst, resolved.value) frame.storeObjResult(dst, resolved.value)
return return
} }
val resolved = receiver.resolveRecord(frame.scope, rec, name, rec.declaringClass) val resolved = receiver.resolveRecord(frame.ensureScope(), rec, name, rec.declaringClass)
frame.storeObjResult(dst, resolved.value) frame.storeObjResult(dst, resolved.value)
return return
} }
@ -1295,13 +1290,13 @@ class CmdSetMemberSlot(
if (methodId >= 0) { if (methodId >= 0) {
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId) inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
} else null } else null
} ?: frame.scope.raiseSymbolNotFound("member") } ?: frame.ensureScope().raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>" val name = rec.memberName ?: "<member>"
if (receiver is ObjQualifiedView) { if (receiver is ObjQualifiedView) {
receiver.writeField(frame.scope, name, frame.slotToObj(valueSlot)) receiver.writeField(frame.ensureScope(), name, frame.slotToObj(valueSlot))
return return
} }
frame.scope.assign(rec, name, frame.slotToObj(valueSlot)) frame.ensureScope().assign(rec, name, frame.slotToObj(valueSlot))
return return
} }
} }
@ -1315,17 +1310,17 @@ class CmdCallMemberSlot(
) : Cmd() { ) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope() frame.syncFrameToScope(useRefs = true)
} }
val receiver = frame.slotToObj(recvSlot) val receiver = frame.slotToObj(recvSlot)
val inst = receiver as? ObjInstance val inst = receiver as? ObjInstance
val rec = inst?.methodRecordForId(methodId) val rec = inst?.methodRecordForId(methodId)
?: receiver.objClass.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
?: frame.scope.raiseError("member id $methodId not found on ${receiver.objClass.className}") ?: frame.ensureScope().raiseError("member id $methodId not found on ${receiver.objClass.className}")
val callArgs = frame.buildArguments(argBase, argCount) val callArgs = frame.buildArguments(argBase, argCount)
val name = rec.memberName ?: "<member>" val name = rec.memberName ?: "<member>"
if (receiver is ObjQualifiedView) { if (receiver is ObjQualifiedView) {
val result = receiver.invokeInstanceMethod(frame.scope, name, callArgs) val result = receiver.invokeInstanceMethod(frame.ensureScope(), name, callArgs)
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
} }
@ -1335,14 +1330,14 @@ class CmdCallMemberSlot(
val decl = rec.declaringClass ?: receiver.objClass val decl = rec.declaringClass ?: receiver.objClass
val result = when (rec.type) { val result = when (rec.type) {
ObjRecord.Type.Property -> { ObjRecord.Type.Property -> {
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.scope, receiver, decl) if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.ensureScope(), receiver, decl)
else frame.scope.raiseError("property $name cannot be called with arguments") else frame.ensureScope().raiseError("property $name cannot be called with arguments")
} }
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> { ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> {
val callScope = inst?.instanceScope ?: frame.scope val callScope = inst?.instanceScope ?: frame.ensureScope()
rec.value.invoke(callScope, receiver, callArgs, decl) rec.value.invoke(callScope, receiver, callArgs, decl)
} }
else -> frame.scope.raiseError("member $name is not callable") else -> frame.ensureScope().raiseError("member $name is not callable")
} }
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
@ -1358,7 +1353,7 @@ class CmdGetIndex(
internal val dst: Int, internal val dst: Int,
) : Cmd() { ) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val result = frame.slotToObj(targetSlot).getAt(frame.scope, frame.slotToObj(indexSlot)) val result = frame.slotToObj(targetSlot).getAt(frame.ensureScope(), frame.slotToObj(indexSlot))
frame.storeObjResult(dst, result) frame.storeObjResult(dst, result)
return return
} }
@ -1370,7 +1365,7 @@ class CmdSetIndex(
internal val valueSlot: Int, internal val valueSlot: Int,
) : Cmd() { ) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.slotToObj(targetSlot).putAt(frame.scope, frame.slotToObj(indexSlot), frame.slotToObj(valueSlot)) frame.slotToObj(targetSlot).putAt(frame.ensureScope(), frame.slotToObj(indexSlot), frame.slotToObj(valueSlot))
return return
} }
} }
@ -1378,11 +1373,11 @@ class CmdSetIndex(
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()) {
frame.syncFrameToScope() frame.syncFrameToScope(useRefs = true)
} }
val ref = frame.fn.constants[id] as? BytecodeConst.Ref val ref = frame.fn.constants[id] as? BytecodeConst.Ref
?: error("EVAL_REF expects Ref at $id") ?: error("EVAL_REF expects Ref at $id")
val result = ref.value.evalValue(frame.scope) val result = ref.value.evalValue(frame.ensureScope())
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
} }
@ -1394,11 +1389,11 @@ class CmdEvalRef(internal val id: Int, internal val dst: Int) : Cmd() {
class CmdEvalStmt(internal val id: Int, internal val dst: Int) : Cmd() { class CmdEvalStmt(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()) {
frame.syncFrameToScope() frame.syncFrameToScope(useRefs = true)
} }
val stmt = frame.fn.constants.getOrNull(id) as? BytecodeConst.StatementVal val stmt = frame.fn.constants.getOrNull(id) as? BytecodeConst.StatementVal
?: error("EVAL_STMT expects StatementVal at $id") ?: error("EVAL_STMT expects StatementVal at $id")
val result = stmt.statement.execute(frame.scope) val result = stmt.statement.execute(frame.ensureScope())
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
} }
@ -1410,11 +1405,11 @@ class CmdEvalStmt(internal val id: Int, internal val dst: Int) : Cmd() {
class CmdMakeValueFn(internal val id: Int, internal val dst: Int) : Cmd() { class CmdMakeValueFn(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()) {
frame.syncFrameToScope() frame.syncFrameToScope(useRefs = true)
} }
val valueFn = frame.fn.constants.getOrNull(id) as? BytecodeConst.ValueFn val valueFn = frame.fn.constants.getOrNull(id) as? BytecodeConst.ValueFn
?: error("MAKE_VALUE_FN expects ValueFn at $id") ?: error("MAKE_VALUE_FN expects ValueFn at $id")
val result = valueFn.fn(frame.scope).value val result = valueFn.fn(frame.ensureScope()).value
if (frame.fn.localSlotNames.isNotEmpty()) { if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
} }
@ -1458,6 +1453,8 @@ 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)
private val scopeSlotNames: Set<String> = fn.scopeSlotNames.filterNotNull().toSet()
private var lastScopePosIp = -1
internal val scopeStack = ArrayDeque<Scope>() internal val scopeStack = ArrayDeque<Scope>()
internal val scopeVirtualStack = ArrayDeque<Boolean>() internal val scopeVirtualStack = ArrayDeque<Boolean>()
@ -1504,10 +1501,24 @@ class CmdFrame(
return last return last
} }
fun ensureScope(): Scope {
val pos = posForIp(ip - 1)
if (pos != null && lastScopePosIp != ip) {
scope.pos = pos
lastScopePosIp = ip
}
return scope
}
private fun posForIp(ip: Int): Pos? {
if (ip < 0) return null
return fn.posByIp.getOrNull(ip)
}
fun pushScope(plan: Map<String, Int>, captures: List<String>) { fun pushScope(plan: Map<String, Int>, captures: List<String>) {
val parentScope = scope val parentScope = scope
if (captures.isNotEmpty()) { if (captures.isNotEmpty()) {
syncFrameToScope() syncFrameToScope(useRefs = true)
} }
val captureRecords = if (captures.isNotEmpty()) { val captureRecords = if (captures.isNotEmpty()) {
captures.map { name -> captures.map { name ->
@ -1555,7 +1566,7 @@ class CmdFrame(
val captures = captureStack.removeLastOrNull() ?: emptyList() val captures = captureStack.removeLastOrNull() ?: emptyList()
scopeDepth -= 1 scopeDepth -= 1
if (captures.isNotEmpty()) { if (captures.isNotEmpty()) {
syncFrameToScope() syncFrameToScope(useRefs = true)
} }
} }
@ -1569,13 +1580,13 @@ class CmdFrame(
suspend fun cancelTopIterator() { suspend fun cancelTopIterator() {
val iter = iterStack.removeLastOrNull() ?: return val iter = iterStack.removeLastOrNull() ?: return
iter.invokeInstanceMethod(scope, "cancelIteration") { ObjVoid } iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid }
} }
suspend fun cancelIterators() { suspend fun cancelIterators() {
while (iterStack.isNotEmpty()) { while (iterStack.isNotEmpty()) {
val iter = iterStack.removeLast() val iter = iterStack.removeLast()
iter.invokeInstanceMethod(scope, "cancelIteration") { ObjVoid } iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid }
} }
} }
@ -1781,7 +1792,7 @@ class CmdFrame(
suspend fun throwObj(pos: Pos, value: Obj) { suspend fun throwObj(pos: Pos, value: Obj) {
var errorObject = value var errorObject = value
val throwScope = scope.createChildScope(pos = pos) val throwScope = ensureScope().createChildScope(pos = pos)
if (errorObject is ObjString) { if (errorObject is ObjString) {
errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() } errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() }
} }
@ -1803,18 +1814,21 @@ class CmdFrame(
} }
} }
fun syncFrameToScope() { fun syncFrameToScope(useRefs: Boolean = false) {
val names = fn.localSlotNames val names = fn.localSlotNames
if (names.isEmpty()) return if (names.isEmpty()) return
for (i in names.indices) { for (i in names.indices) {
val name = names[i] ?: continue val name = names[i] ?: continue
if (scopeSlotNames.contains(name)) continue
val target = resolveLocalScope(i) ?: continue val target = resolveLocalScope(i) ?: continue
val value = localSlotToObj(i) val value = if (useRefs) FrameSlotRef(frame, i) else localSlotToObj(i)
val rec = target.getLocalRecordDirect(name) val rec = target.getLocalRecordDirect(name)
if (rec == null) { if (rec == null) {
val isMutable = fn.localSlotMutables.getOrElse(i) { true } val isMutable = fn.localSlotMutables.getOrElse(i) { true }
target.addItem(name, isMutable, value) target.addItem(name, isMutable, value)
} else { } else {
val existing = rec.value
if (existing is FrameSlotRef && !useRefs) continue
rec.value = value rec.value = value
} }
} }
@ -1828,6 +1842,16 @@ class CmdFrame(
val target = resolveLocalScope(i) ?: continue val target = resolveLocalScope(i) ?: continue
val rec = target.getLocalRecordDirect(name) ?: continue val rec = target.getLocalRecordDirect(name) ?: continue
val value = rec.value val value = rec.value
if (value is FrameSlotRef) {
val resolved = value.read()
when (resolved) {
is ObjInt -> frame.setInt(i, resolved.value)
is ObjReal -> frame.setReal(i, resolved.value)
is ObjBool -> frame.setBool(i, resolved.value)
else -> frame.setObj(i, resolved)
}
continue
}
when (value) { when (value) {
is ObjInt -> frame.setInt(i, value.value) is ObjInt -> frame.setInt(i, value.value)
is ObjReal -> frame.setReal(i, value.value) is ObjReal -> frame.setReal(i, value.value)
@ -1856,6 +1880,7 @@ class CmdFrame(
argBase: Int, argBase: Int,
plan: BytecodeConst.CallArgsPlan, plan: BytecodeConst.CallArgsPlan,
): Arguments { ): Arguments {
val scope = ensureScope()
val positional = ArrayList<Obj>(plan.specs.size) val positional = ArrayList<Obj>(plan.specs.size)
var named: LinkedHashMap<String, Obj>? = null var named: LinkedHashMap<String, Obj>? = null
var namedSeen = false var namedSeen = false
@ -1931,7 +1956,9 @@ class CmdFrame(
val target = scopeTarget(slot) val target = scopeTarget(slot)
val index = ensureScopeSlot(target, slot) val index = ensureScopeSlot(target, slot)
val record = target.getSlotRecord(index) val record = target.getSlotRecord(index)
if (record.value !== ObjUnset) return record.value val direct = record.value
if (direct is FrameSlotRef) return direct.read()
if (direct !== ObjUnset) return direct
val name = fn.scopeSlotNames[slot] ?: return record.value val name = fn.scopeSlotNames[slot] ?: return record.value
val resolved = target.get(name) ?: return record.value val resolved = target.get(name) ?: return record.value
if (resolved.value !== ObjUnset) { if (resolved.value !== ObjUnset) {
@ -1944,7 +1971,9 @@ class CmdFrame(
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved") val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
val index = addrIndices[addrSlot] val index = addrIndices[addrSlot]
val record = target.getSlotRecord(index) val record = target.getSlotRecord(index)
if (record.value !== ObjUnset) return record.value val direct = record.value
if (direct is FrameSlotRef) return direct.read()
if (direct !== ObjUnset) return direct
val slotId = addrScopeSlots[addrSlot] val slotId = addrScopeSlots[addrSlot]
val name = fn.scopeSlotNames[slotId] ?: return record.value val name = fn.scopeSlotNames[slotId] ?: return record.value
val resolved = target.get(name) ?: return record.value val resolved = target.get(name) ?: return record.value

View File

@ -463,6 +463,7 @@ class CastRef(
/** Qualified `this@Type`: resolves to a view of current `this` starting dispatch from the ancestor Type. */ /** Qualified `this@Type`: resolves to a view of current `this` starting dispatch from the ancestor Type. */
class QualifiedThisRef(val typeName: String, private val atPos: Pos) : ObjRef { class QualifiedThisRef(val typeName: String, private val atPos: Pos) : ObjRef {
internal fun pos(): Pos = atPos
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
val t = scope[typeName]?.value as? ObjClass val t = scope[typeName]?.value as? ObjClass
?: scope.raiseError("unknown type $typeName") ?: scope.raiseError("unknown type $typeName")
@ -2107,6 +2108,7 @@ class ImplicitThisMethodCallRef(
private val atPos: Pos, private val atPos: Pos,
private val preferredThisTypeName: String? = null private val preferredThisTypeName: String? = null
) : ObjRef { ) : ObjRef {
internal fun pos(): Pos = atPos
internal fun methodName(): String = name internal fun methodName(): String = name
internal fun arguments(): List<ParsedArgument> = args internal fun arguments(): List<ParsedArgument> = args
internal fun hasTailBlock(): Boolean = tailBlock internal fun hasTailBlock(): Boolean = tailBlock
@ -2184,13 +2186,38 @@ class LocalSlotRef(
return null return null
} }
private fun resolveOwnerAndSlot(scope: Scope): Pair<Scope, Int>? {
var s: Scope? = scope
var guard = 0
while (s != null && guard++ < 1024) {
val idx = s.getSlotIndexOf(name)
if (idx != null) {
if (idx == slot) return s to slot
if (!strict || captureOwnerSlot != null) return s to idx
}
s = s.parent
}
return null
}
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
scope.pos = atPos scope.pos = atPos
val owner = resolveOwner(scope) ?: scope.raiseError("slot owner not found for $name") val resolved = resolveOwnerAndSlot(scope)
if (slot < 0 || slot >= owner.slotCount()) { if (resolved == null) {
val rec = scope.get(name) ?: scope.raiseError("slot owner not found for $name")
if (rec.declaringClass != null &&
!canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)
) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
}
return rec
}
val owner = resolved.first
val slotIndex = resolved.second
if (slotIndex < 0 || slotIndex >= owner.slotCount()) {
scope.raiseError("slot index out of range for $name") scope.raiseError("slot index out of range for $name")
} }
val rec = owner.getSlotRecord(slot) val rec = owner.getSlotRecord(slotIndex)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access")) scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
} }
@ -2199,11 +2226,22 @@ class LocalSlotRef(
override suspend fun evalValue(scope: Scope): Obj { override suspend fun evalValue(scope: Scope): Obj {
scope.pos = atPos scope.pos = atPos
val owner = resolveOwner(scope) ?: scope.raiseError("slot owner not found for $name") val resolved = resolveOwnerAndSlot(scope)
if (slot < 0 || slot >= owner.slotCount()) { if (resolved == null) {
val rec = scope.get(name) ?: scope.raiseError("slot owner not found for $name")
if (rec.declaringClass != null &&
!canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)
) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
}
return scope.resolve(rec, name)
}
val owner = resolved.first
val slotIndex = resolved.second
if (slotIndex < 0 || slotIndex >= owner.slotCount()) {
scope.raiseError("slot index out of range for $name") scope.raiseError("slot index out of range for $name")
} }
val rec = owner.getSlotRecord(slot) val rec = owner.getSlotRecord(slotIndex)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access")) scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
} }
@ -2212,11 +2250,23 @@ class LocalSlotRef(
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
scope.pos = atPos scope.pos = atPos
val owner = resolveOwner(scope) ?: scope.raiseError("slot owner not found for $name") val resolved = resolveOwnerAndSlot(scope)
if (slot < 0 || slot >= owner.slotCount()) { if (resolved == null) {
val rec = scope.get(name) ?: scope.raiseError("slot owner not found for $name")
if (rec.declaringClass != null &&
!canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)
) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
}
scope.assign(rec, name, newValue)
return
}
val owner = resolved.first
val slotIndex = resolved.second
if (slotIndex < 0 || slotIndex >= owner.slotCount()) {
scope.raiseError("slot index out of range for $name") scope.raiseError("slot index out of range for $name")
} }
val rec = owner.getSlotRecord(slot) val rec = owner.getSlotRecord(slotIndex)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access")) scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
} }

View File

@ -7,12 +7,16 @@ Current focus
- Enforce compile-time name/member resolution only; no runtime scope lookup or fallback. - Enforce compile-time name/member resolution only; no runtime scope lookup or fallback.
- Bytecode uses memberId-based ops (CALL_MEMBER_SLOT/GET_MEMBER_SLOT/SET_MEMBER_SLOT). - Bytecode uses memberId-based ops (CALL_MEMBER_SLOT/GET_MEMBER_SLOT/SET_MEMBER_SLOT).
- Runtime lookup opcodes (CALL_VIRTUAL/GET_FIELD/SET_FIELD) and fallback callsites are removed. - Runtime lookup opcodes (CALL_VIRTUAL/GET_FIELD/SET_FIELD) and fallback callsites are removed.
- Migrate bytecode to frame-first locals with lazy scope reflection; avoid eager scope slot plans in compiled functions.
- Use FrameSlotRef for captures and only materialize Scope for Kotlin interop; use frame.ip -> pos mapping for diagnostics.
Key recent changes Key recent changes
- Removed method callsite PICs and fallback opcodes; bytecode now relies on compile-time member ids only. - Removed method callsite PICs and fallback opcodes; bytecode now relies on compile-time member ids only.
- Operator dispatch emits memberId calls when known; falls back to Obj opcodes for allowed built-ins without name lookup. - Operator dispatch emits memberId calls when known; falls back to Obj opcodes for allowed built-ins without name lookup.
- Object members are allowed on unknown types; other members still require a statically known receiver type. - Object members are allowed on unknown types; other members still require a statically known receiver type.
- Renamed BytecodeFallbackException to BytecodeCompileException. - Added frame.ip -> pos mapping; call-site ops restore pos after args to keep stack traces accurate.
- Loop var overrides now take precedence in slot resolution to keep loop locals in frame slots.
- LocalSlotRef now falls back to name lookup when slot plans are missing (closure safety).
Known failing tests Known failing tests
- None (jvmTest passing). - None (jvmTest passing).
@ -20,14 +24,12 @@ Known failing tests
Files touched recently Files touched recently
- notes/type_system_spec.md (spec updated) - notes/type_system_spec.md (spec updated)
- AGENTS.md (type inference reminders) - AGENTS.md (type inference reminders)
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/VarDeclStatement.kt
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt - lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt - lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt
- lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt - lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt
- various bytecode runtime/disassembler files (memberId ops)
Last test run Last test run
- ./gradlew :lynglib:jvmTest --tests ScriptTest.testForInIterableUnknownTypeDisasm - ./gradlew :lynglib:jvmTest
Spec decisions (notes/type_system_spec.md) Spec decisions (notes/type_system_spec.md)
- Nullability: Kotlin-style, T non-null, T? nullable, !! asserts non-null. - Nullability: Kotlin-style, T non-null, T? nullable, !! asserts non-null.