Remove interpreter fallbacks and enforce bytecode execution

This commit is contained in:
Sergey Chernov 2026-02-14 00:41:33 +03:00
parent 26564438e2
commit e73fe0d3c5
16 changed files with 700 additions and 701 deletions

View File

@ -29,7 +29,6 @@ import com.github.ajalt.clikt.parameters.options.option
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.Compiler import net.sergeych.lyng.Compiler
import net.sergeych.lyng.LyngVersion import net.sergeych.lyng.LyngVersion
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.Source import net.sergeych.lyng.Source
@ -158,7 +157,6 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
val version by option("-v", "--version", help = "Print version and exit").flag() val version by option("-v", "--version", help = "Print version and exit").flag()
val benchmark by option("--benchmark", help = "Run JVM microbenchmarks and exit").flag() val benchmark by option("--benchmark", help = "Run JVM microbenchmarks and exit").flag()
val bytecodeFallbacks by option("--bytecode-fallbacks", help = "Report lambdas that fall back to interpreter").flag()
val script by argument(help = "one or more scripts to execute").optional() val script by argument(help = "one or more scripts to execute").optional()
val execute: String? by option( val execute: String? by option(
"-x", "--execute", help = """ "-x", "--execute", help = """
@ -170,7 +168,7 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
override fun help(context: Context): String = override fun help(context: Context): String =
""" """
The Lyng script language interpreter, language version is $LyngVersion. The Lyng script language runtime, language version is $LyngVersion.
Please refer form more information to the project site: Please refer form more information to the project site:
https://gitea.sergeych.net/SergeychWorks/lyng https://gitea.sergeych.net/SergeychWorks/lyng
@ -201,15 +199,12 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
launcher { launcher {
// there is no script name, it is a first argument instead: // there is no script name, it is a first argument instead:
processErrors { processErrors {
val reporter = bytecodeFallbackReporter(bytecodeFallbacks)
val script = Compiler.compileWithResolution( val script = Compiler.compileWithResolution(
Source("<eval>", execute!!), Source("<eval>", execute!!),
baseScope.currentImportProvider, baseScope.currentImportProvider,
seedScope = baseScope, seedScope = baseScope
bytecodeFallbackReporter = reporter
) )
script.execute(baseScope) script.execute(baseScope)
flushBytecodeFallbacks(reporter)
} }
} }
} }
@ -220,7 +215,7 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
echoFormattedHelp() echoFormattedHelp()
} else { } else {
baseScope.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList())) baseScope.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
launcher { executeFile(script!!, bytecodeFallbacks) } launcher { executeFile(script!!) }
} }
} }
} }
@ -228,14 +223,14 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
} }
} }
fun executeFileWithArgs(fileName: String, args: List<String>, reportBytecodeFallbacks: Boolean = false) { fun executeFileWithArgs(fileName: String, args: List<String>) {
runBlocking { runBlocking {
baseScopeDefer.await().addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList())) baseScopeDefer.await().addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
executeFile(fileName, reportBytecodeFallbacks) executeFile(fileName)
} }
} }
suspend fun executeFile(fileName: String, reportBytecodeFallbacks: Boolean = false) { suspend fun executeFile(fileName: String) {
var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource -> var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource ->
fileSource.buffer().use { bs -> fileSource.buffer().use { bs ->
bs.readUtf8() bs.readUtf8()
@ -248,15 +243,12 @@ suspend fun executeFile(fileName: String, reportBytecodeFallbacks: Boolean = fal
} }
processErrors { processErrors {
val scope = baseScopeDefer.await() val scope = baseScopeDefer.await()
val reporter = bytecodeFallbackReporter(reportBytecodeFallbacks)
val script = Compiler.compileWithResolution( val script = Compiler.compileWithResolution(
Source(fileName, text), Source(fileName, text),
scope.currentImportProvider, scope.currentImportProvider,
seedScope = scope, seedScope = scope
bytecodeFallbackReporter = reporter
) )
script.execute(scope) script.execute(scope)
flushBytecodeFallbacks(reporter)
} }
} }
@ -268,22 +260,3 @@ suspend fun processErrors(block: suspend () -> Unit) {
println("\nError executing the script:\n$e\n") println("\nError executing the script:\n$e\n")
} }
} }
private fun bytecodeFallbackReporter(enabled: Boolean): ((Pos, String) -> Unit)? {
if (!enabled) return null
val reports = ArrayList<String>()
val reporter: (Pos, String) -> Unit = { pos, msg ->
reports.add("$pos: $msg")
}
return object : (Pos, String) -> Unit by reporter {
override fun invoke(pos: Pos, msg: String) = reporter(pos, msg)
override fun toString(): String = reports.joinToString("\n")
}
}
private fun flushBytecodeFallbacks(reporter: ((Pos, String) -> Unit)?) {
val text = reporter?.toString().orEmpty()
if (text.isBlank()) return
println("Bytecode lambda fallbacks:")
println(text)
}

View File

@ -23,7 +23,6 @@ import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.statement
import net.sergeych.lyngio.process.* import net.sergeych.lyngio.process.*
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
import net.sergeych.lyngio.process.security.ProcessAccessPolicy import net.sergeych.lyngio.process.security.ProcessAccessPolicy
@ -216,7 +215,7 @@ private suspend inline fun Scope.processGuard(crossinline block: suspend () -> O
} }
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow { private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
val producer = statement { val producer = ObjNativeCallable {
val builder = (this as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder val builder = (this as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
?: this.thisObj as? ObjFlowBuilder ?: this.thisObj as? ObjFlowBuilder

View File

@ -112,6 +112,9 @@ class Compiler(
if (!added) { if (!added) {
currentShadowedLocalNames?.add(name) currentShadowedLocalNames?.add(name)
} }
if (added) {
scopeSeedNames.remove(name)
}
if (added && localDeclCountStack.isNotEmpty()) { if (added && localDeclCountStack.isNotEmpty()) {
localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1 localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1
} }
@ -136,6 +139,9 @@ class Compiler(
if (plan.slots.containsKey(name)) return if (plan.slots.containsKey(name)) return
plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated) plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated)
plan.nextIndex += 1 plan.nextIndex += 1
if (!seedingSlotPlan && plan == moduleSlotPlan()) {
moduleDeclaredNames.add(name)
}
} }
private fun declareSlotNameAt( private fun declareSlotNameAt(
@ -155,6 +161,7 @@ class Compiler(
private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull() private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull()
private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf() private val slotTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf() private val nameObjClass: MutableMap<String, ObjClass> = mutableMapOf()
private val scopeSeedNames: MutableSet<String> = mutableSetOf()
private val slotTypeDeclByScopeId: MutableMap<Int, MutableMap<Int, TypeDecl>> = mutableMapOf() private val slotTypeDeclByScopeId: MutableMap<Int, MutableMap<Int, TypeDecl>> = mutableMapOf()
private val nameTypeDecl: MutableMap<String, TypeDecl> = mutableMapOf() private val nameTypeDecl: MutableMap<String, TypeDecl> = mutableMapOf()
private data class TypeAliasDecl( private data class TypeAliasDecl(
@ -177,69 +184,81 @@ class Compiler(
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf() private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
private val objectDeclNames: MutableSet<String> = mutableSetOf() private val objectDeclNames: MutableSet<String> = mutableSetOf()
private val externCallableNames: MutableSet<String> = mutableSetOf() private val externCallableNames: MutableSet<String> = mutableSetOf()
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
private var seedingSlotPlan: Boolean = false
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) { private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
val plan = moduleSlotPlan() ?: return val plan = moduleSlotPlan() ?: return
var current: Scope? = scope seedingSlotPlan = true
while (current != null) { try {
for ((name, record) in current.objects) { var current: Scope? = scope
if (!record.visibility.isPublic) continue while (current != null) {
if (plan.slots.containsKey(name)) continue for ((name, record) in current.objects) {
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
val instance = record.value as? ObjInstance
if (instance != null && nameObjClass[name] == null) {
nameObjClass[name] = instance.objClass
}
}
for ((cls, map) in current.extensions) {
for ((name, record) in map) {
if (!record.visibility.isPublic) continue if (!record.visibility.isPublic) continue
when (record.type) { if (plan.slots.containsKey(name)) continue
ObjRecord.Type.Property -> { declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
val getterName = extensionPropertyGetterName(cls.className, name) scopeSeedNames.add(name)
if (!plan.slots.containsKey(getterName)) { val instance = record.value as? ObjInstance
declareSlotNameIn( if (instance != null && nameObjClass[name] == null) {
plan, nameObjClass[name] = instance.objClass
getterName, }
isMutable = false, }
isDelegated = false for ((cls, map) in current.extensions) {
) for ((name, record) in map) {
} if (!record.visibility.isPublic) continue
val prop = record.value as? ObjProperty when (record.type) {
if (prop?.setter != null) { ObjRecord.Type.Property -> {
val setterName = extensionPropertySetterName(cls.className, name) val getterName = extensionPropertyGetterName(cls.className, name)
if (!plan.slots.containsKey(setterName)) { if (!plan.slots.containsKey(getterName)) {
declareSlotNameIn( declareSlotNameIn(
plan, plan,
setterName, getterName,
isMutable = false, isMutable = false,
isDelegated = false isDelegated = false
) )
scopeSeedNames.add(getterName)
}
val prop = record.value as? ObjProperty
if (prop?.setter != null) {
val setterName = extensionPropertySetterName(cls.className, name)
if (!plan.slots.containsKey(setterName)) {
declareSlotNameIn(
plan,
setterName,
isMutable = false,
isDelegated = false
)
scopeSeedNames.add(setterName)
}
} }
} }
} else -> {
else -> { val callableName = extensionCallableName(cls.className, name)
val callableName = extensionCallableName(cls.className, name) if (!plan.slots.containsKey(callableName)) {
if (!plan.slots.containsKey(callableName)) { declareSlotNameIn(
declareSlotNameIn( plan,
plan, callableName,
callableName, isMutable = false,
isMutable = false, isDelegated = false
isDelegated = false )
) scopeSeedNames.add(callableName)
}
} }
} }
} }
} }
for ((name, slotIndex) in current.slotNameToIndexSnapshot()) {
val record = current.getSlotRecord(slotIndex)
if (!record.visibility.isPublic) continue
if (plan.slots.containsKey(name)) continue
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
scopeSeedNames.add(name)
}
if (!includeParents) return
current = current.parent
} }
for ((name, slotIndex) in current.slotNameToIndexSnapshot()) { } finally {
val record = current.getSlotRecord(slotIndex) seedingSlotPlan = false
if (!record.visibility.isPublic) continue
if (plan.slots.containsKey(name)) continue
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
}
if (!includeParents) return
current = current.parent
} }
} }
@ -254,6 +273,7 @@ class Compiler(
record.isMutable, record.isMutable,
record.type == ObjRecord.Type.Delegated record.type == ObjRecord.Type.Delegated
) )
scopeSeedNames.add(name)
} }
} }
@ -322,6 +342,7 @@ class Compiler(
val nameToken = nextNonWs() val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) { if (nameToken.type == Token.Type.ID) {
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false) declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
scopeSeedNames.add(nameToken.value)
} }
} }
"enum" -> { "enum" -> {
@ -329,6 +350,7 @@ class Compiler(
val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next
if (nameToken.type == Token.Type.ID) { if (nameToken.type == Token.Type.ID) {
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false) declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
scopeSeedNames.add(nameToken.value)
} }
} }
} }
@ -1557,6 +1579,7 @@ class Compiler(
"<script>", "<script>",
allowLocalSlots = true, allowLocalSlots = true,
allowedScopeNames = modulePlan.keys, allowedScopeNames = modulePlan.keys,
scopeSlotNameSet = scopeSeedNames,
moduleScopeId = moduleSlotPlan()?.id, moduleScopeId = moduleSlotPlan()?.id,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownClassMapForBytecode(), knownNameObjClass = knownClassMapForBytecode(),
@ -1765,8 +1788,11 @@ class Compiler(
} ?: -1 } ?: -1
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId } val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
val moduleId = moduleSlotPlan()?.id if (scopeSeedNames.contains(name)) return null
if (moduleId != null && slotLoc.scopeId == moduleId) return null val modulePlan = moduleSlotPlan()
if (modulePlan != null && slotLoc.scopeId == modulePlan.id) {
return null
}
recordCaptureSlot(name, slotLoc) recordCaptureSlot(name, slotLoc)
val plan = capturePlanStack.lastOrNull() ?: return null val plan = capturePlanStack.lastOrNull() ?: return null
val entry = plan.slotPlan.slots[name] ?: return null val entry = plan.slotPlan.slots[name] ?: return null
@ -1846,6 +1872,7 @@ class Compiler(
returnLabels = returnLabels, returnLabels = returnLabels,
rangeLocalNames = currentRangeParamNames, rangeLocalNames = currentRangeParamNames,
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
scopeSlotNameSet = scopeSeedNames,
moduleScopeId = moduleSlotPlan()?.id, moduleScopeId = moduleSlotPlan()?.id,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownClassMapForBytecode(), knownNameObjClass = knownClassMapForBytecode(),
@ -1870,6 +1897,7 @@ class Compiler(
returnLabels = returnLabels, returnLabels = returnLabels,
rangeLocalNames = currentRangeParamNames, rangeLocalNames = currentRangeParamNames,
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
scopeSlotNameSet = scopeSeedNames,
moduleScopeId = moduleSlotPlan()?.id, moduleScopeId = moduleSlotPlan()?.id,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownClassMapForBytecode(), knownNameObjClass = knownClassMapForBytecode(),
@ -1913,6 +1941,7 @@ class Compiler(
returnLabels = returnLabels, returnLabels = returnLabels,
rangeLocalNames = currentRangeParamNames, rangeLocalNames = currentRangeParamNames,
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
scopeSlotNameSet = scopeSeedNames,
moduleScopeId = moduleSlotPlan()?.id, moduleScopeId = moduleSlotPlan()?.id,
forcedLocalSlots = forcedLocalSlots, forcedLocalSlots = forcedLocalSlots,
forcedLocalScopeId = forcedLocalScopeId, forcedLocalScopeId = forcedLocalScopeId,
@ -2390,8 +2419,14 @@ class Compiler(
val args = parsed.first val args = parsed.first
val tailBlock = parsed.second val tailBlock = parsed.second
isCall = true isCall = true
val field = FieldRef(left, next.value, isOptional) val receiverClass = resolveReceiverClassForMember(left)
operand = CallRef(field, args, tailBlock, isOptional) val nestedClass = receiverClass?.let { resolveClassByName("${it.className}.${next.value}") }
if (nestedClass != null) {
val field = FieldRef(left, next.value, isOptional)
operand = CallRef(field, args, tailBlock, isOptional)
} else {
operand = MethodCallRef(left, next.value, args, tailBlock, isOptional)
}
} else { } else {
// instance method call // instance method call
val receiverType = if (next.value == "apply" || next.value == "run") { val receiverType = if (next.value == "apply" || next.value == "run") {
@ -2967,17 +3002,11 @@ class Compiler(
lambdaReturnTypeByRef[ref] = returnClass lambdaReturnTypeByRef[ref] = returnClass
} }
if (captureSlots.isNotEmpty()) { if (captureSlots.isNotEmpty()) {
val moduleScopeId = moduleSlotPlan()?.id
val captureEntries = captureSlots.map { capture -> val captureEntries = captureSlots.map { capture ->
val owner = capturePlan.captureOwners[capture.name] val owner = capturePlan.captureOwners[capture.name]
?: error("Missing capture owner for ${capture.name}") ?: error("Missing capture owner for ${capture.name}")
val kind = if (moduleScopeId != null && owner.scopeId == moduleScopeId) {
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.MODULE
} else {
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL
}
net.sergeych.lyng.bytecode.LambdaCaptureEntry( net.sergeych.lyng.bytecode.LambdaCaptureEntry(
ownerKind = kind, ownerKind = net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL,
ownerScopeId = owner.scopeId, ownerScopeId = owner.scopeId,
ownerSlotId = owner.slot, ownerSlotId = owner.slot,
ownerName = capture.name, ownerName = capture.name,
@ -5725,6 +5754,9 @@ class Compiler(
val outerClassName = currentEnclosingClassName() val outerClassName = currentEnclosingClassName()
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
resolutionSink?.declareSymbol(declaredName, SymbolKind.ENUM, isMutable = false, pos = nameToken.pos) resolutionSink?.declareSymbol(declaredName, SymbolKind.ENUM, isMutable = false, pos = nameToken.pos)
if (codeContexts.lastOrNull() is CodeContext.Module) {
scopeSeedNames.add(declaredName)
}
if (outerClassName != null) { if (outerClassName != null) {
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
outerCtx?.classScopeMembers?.add(declaredName) outerCtx?.classScopeMembers?.add(declaredName)
@ -6024,6 +6056,9 @@ class Compiler(
val outerClassName = currentEnclosingClassName() val outerClassName = currentEnclosingClassName()
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos) resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
if (codeContexts.lastOrNull() is CodeContext.Module) {
scopeSeedNames.add(declaredName)
}
if (outerClassName != null) { if (outerClassName != null) {
val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
outerCtx?.classScopeMembers?.add(declaredName) outerCtx?.classScopeMembers?.add(declaredName)
@ -7909,29 +7944,7 @@ class Compiler(
pendingDeclDoc = null pendingDeclDoc = null
} }
if (declaringClassNameCaptured == null && fun buildVarDecl(initialExpression: Statement?): VarDeclStatement {
extTypeName == null &&
!isStatic &&
!isProperty &&
!actualExtern &&
!isAbstract
) {
if (isDelegate) {
val initExpr = initialExpression ?: throw ScriptError(start, "Delegate must be initialized")
val slotPlan = slotPlanStack.lastOrNull()
val slotIndex = slotPlan?.slots?.get(name)?.index
val scopeId = slotPlan?.id
return DelegatedVarDeclStatement(
name,
isMutable,
visibility,
initExpr,
isTransient,
slotIndex,
scopeId,
start
)
}
val slotPlan = slotPlanStack.lastOrNull() val slotPlan = slotPlanStack.lastOrNull()
val slotIndex = slotPlan?.slots?.get(name)?.index val slotIndex = slotPlan?.slots?.get(name)?.index
val scopeId = slotPlan?.id val scopeId = slotPlan?.id
@ -7983,6 +7996,30 @@ class Compiler(
) )
} }
if (declaringClassNameCaptured == null &&
extTypeName == null &&
!isStatic &&
!isProperty
) {
if (isDelegate) {
val initExpr = initialExpression ?: throw ScriptError(start, "Delegate must be initialized")
val slotPlan = slotPlanStack.lastOrNull()
val slotIndex = slotPlan?.slots?.get(name)?.index
val scopeId = slotPlan?.id
return DelegatedVarDeclStatement(
name,
isMutable,
visibility,
initExpr,
isTransient,
slotIndex,
scopeId,
start
)
}
return buildVarDecl(initialExpression)
}
if (isStatic) { if (isStatic) {
if (declaringClassNameCaptured != null) { if (declaringClassNameCaptured != null) {
val directRef = unwrapDirectRef(initialExpression) val directRef = unwrapDirectRef(initialExpression)
@ -8288,56 +8325,7 @@ class Compiler(
) )
} }
return object : Statement() { return buildVarDecl(initialExpression)
override val pos: Pos = start
override suspend fun execute(context: Scope): Obj {
if (context.containsLocal(name)) {
throw ScriptError(start, "Variable $name is already defined")
}
if (isDelegate) {
val initValue = initialExpression!!.execute(context)
val accessTypeStr = if (isMutable) "Var" else "Val"
val accessType = ObjString(accessTypeStr)
val finalDelegate = try {
initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, ObjNull))
} catch (_: Exception) {
initValue
}
val rec = context.addItem(
name, isMutable, ObjUnset, visibility, setterVisibility,
recordType = ObjRecord.Type.Delegated,
isAbstract = isAbstract,
isClosed = isClosed,
isOverride = isOverride,
isTransient = isTransient
)
rec.delegate = finalDelegate
return finalDelegate
}
if (getter != null || setter != null) {
val prop = ObjProperty(name, getter, setter)
context.addItem(
name, isMutable, prop, visibility, setterVisibility,
recordType = ObjRecord.Type.Property,
isAbstract = isAbstract,
isClosed = isClosed,
isOverride = isOverride,
isTransient = isTransient
)
return prop
}
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
context.addItem(
name, isMutable, initValue, visibility,
recordType = ObjRecord.Type.Other,
isTransient = isTransient
)
return initValue
}
}
} finally { } finally {
if (implicitTypeParams.isNotEmpty()) pendingTypeParamStack.removeLast() if (implicitTypeParams.isNotEmpty()) pendingTypeParamStack.removeLast()
} }

View File

@ -174,54 +174,49 @@ internal suspend fun executeFunctionDecl(
spec.extTypeName?.let { typeName -> spec.extTypeName?.let { typeName ->
val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found") val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found")
if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance") if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance")
val callable = net.sergeych.lyng.obj.ObjNativeCallable { scope.addExtension(
val result = (thisObj as? ObjInstance)?.let { i -> type,
val execScope = applyClosureForBytecode(i.instanceScope).also { spec.name,
it.args = args ObjRecord(
} compiledFnBody,
compiledFnBody.execute(execScope) isMutable = false,
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this)) visibility = spec.visibility,
result declaringClass = null,
} type = ObjRecord.Type.Fun
scope.addExtension(type, spec.name, ObjRecord(callable, isMutable = false, visibility = spec.visibility, declaringClass = null)) )
)
val wrapperName = spec.extensionWrapperName ?: extensionCallableName(typeName, spec.name) val wrapperName = spec.extensionWrapperName ?: extensionCallableName(typeName, spec.name)
val wrapper = ObjExtensionMethodCallable(spec.name, callable) val wrapper = ObjExtensionMethodCallable(spec.name, compiledFnBody)
scope.addItem(wrapperName, false, wrapper, spec.visibility, recordType = ObjRecord.Type.Fun) scope.addItem(wrapperName, false, wrapper, spec.visibility, recordType = ObjRecord.Type.Fun)
} ?: run { } ?: run {
val th = scope.thisObj val th = scope.thisObj
if (!spec.isStatic && th is ObjClass) { if (!spec.isStatic && th is ObjClass) {
val cls: ObjClass = th val cls: ObjClass = th
cls.addFn( cls.createField(
spec.name, spec.name,
compiledFnBody,
isMutable = true, isMutable = true,
visibility = spec.visibility, visibility = spec.visibility,
pos = spec.startPos,
declaringClass = cls,
isAbstract = spec.isAbstract, isAbstract = spec.isAbstract,
isClosed = spec.isClosed, isClosed = spec.isClosed,
isOverride = spec.isOverride, isOverride = spec.isOverride,
pos = spec.startPos, type = ObjRecord.Type.Fun,
methodId = spec.memberMethodId methodId = spec.memberMethodId
) { )
val savedCtx = this.currentClassCtx
this.currentClassCtx = cls
try {
(thisObj as? ObjInstance)?.let { i ->
val execScope = i.instanceScope.createChildScope(
pos = this.pos,
args = this.args,
newThisObj = i
)
execScope.currentClassCtx = cls
compiledFnBody.execute(execScope)
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this))
} finally {
this.currentClassCtx = savedCtx
}
}
val memberValue = cls.members[spec.name]?.value ?: compiledFnBody val memberValue = cls.members[spec.name]?.value ?: compiledFnBody
scope.addItem(spec.name, false, memberValue, spec.visibility, callSignature = spec.externCallSignature) scope.addItem(spec.name, false, memberValue, spec.visibility, callSignature = spec.externCallSignature)
compiledFnBody compiledFnBody
} else { } else {
scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature) scope.addItem(
spec.name,
false,
compiledFnBody,
spec.visibility,
recordType = ObjRecord.Type.Fun,
callSignature = spec.externCallSignature
)
} }
} }
return annotatedFnBody return annotatedFnBody

View File

@ -78,8 +78,17 @@ open class Scope(
thisObj = primary thisObj = primary
val extrasSnapshot = when { val extrasSnapshot = when {
extras.isEmpty() -> emptyList() extras.isEmpty() -> emptyList()
extras is MutableList<*> -> synchronized(extras) { extras.toList() } else -> {
else -> extras.toList() try {
if (extras is MutableList<*>) {
synchronized(extras) { extras.toList() }
} else {
extras.toList()
}
} catch (_: Exception) {
emptyList()
}
}
} }
synchronized(thisVariants) { synchronized(thisVariants) {
thisVariants.clear() thisVariants.clear()
@ -818,34 +827,27 @@ open class Scope(
} }
/** /**
* Resolve and evaluate a qualified identifier exactly as compiled code would. * Resolve and evaluate a qualified identifier (e.g. `A.B.C`) using the same name/field
* For input like `A.B.C`, it builds the same ObjRef chain the compiler emits: * resolution rules as compiled code, without invoking the compiler or ObjRef evaluation.
* `LocalVarRef("A", Pos.builtIn)` followed by `FieldRef` for each segment, then evaluates it.
* This mirrors `eval("A.B.C")` resolution semantics without invoking the compiler.
*/ */
suspend fun resolveQualifiedIdentifier(qualifiedName: String): Obj { suspend fun resolveQualifiedIdentifier(qualifiedName: String): Obj {
val trimmed = qualifiedName.trim() val trimmed = qualifiedName.trim()
if (trimmed.isEmpty()) raiseSymbolNotFound("empty identifier") if (trimmed.isEmpty()) raiseSymbolNotFound("empty identifier")
val parts = trimmed.split('.') val parts = trimmed.split('.')
val first = parts[0] val first = parts[0]
val ref: ObjRef = if (first == "this") { val base: Obj = if (first == "this") {
ConstRef(thisObj.asReadonly) thisObj
} else { } else {
var s: Scope? = this val rec = get(first) ?: raiseSymbolNotFound(first)
var slot: Int? = null resolve(rec, first)
var guard = 0
while (s != null && guard++ < 1024 && slot == null) {
slot = s.getSlotIndexOf(first)
s = s.parent
}
if (slot == null) raiseSymbolNotFound(first)
LocalSlotRef(first, slot, 0, isMutable = false, isDelegated = false, Pos.builtIn, strict = true)
} }
var ref0: ObjRef = ref var current = base
for (i in 1 until parts.size) { for (i in 1 until parts.size) {
ref0 = FieldRef(ref0, parts[i], false) val name = parts[i]
val rec = current.readField(this, name)
current = resolve(rec, name)
} }
return ref0.evalValue(this) return current
} }
suspend fun resolve(rec: ObjRecord, name: String): Obj { suspend fun resolve(rec: ObjRecord, name: String): Obj {

View File

@ -85,7 +85,9 @@ class Script(
seedModuleSlots(moduleTarget) seedModuleSlots(moduleTarget)
} }
moduleBytecode?.let { fn -> moduleBytecode?.let { fn ->
return CmdVm().execute(fn, scope, scope.args) return CmdVm().execute(fn, scope, scope.args) { frame, _ ->
seedModuleLocals(frame, moduleTarget)
}
} }
if (statements.isNotEmpty()) { if (statements.isNotEmpty()) {
scope.raiseIllegalState("interpreter execution is not supported; missing module bytecode") scope.raiseIllegalState("interpreter execution is not supported; missing module bytecode")
@ -98,6 +100,31 @@ class Script(
seedImportBindings(scope) seedImportBindings(scope)
} }
private suspend fun seedModuleLocals(frame: net.sergeych.lyng.bytecode.CmdFrame, scope: Scope) {
if (importBindings.isEmpty()) return
val localNames = frame.fn.localSlotNames
if (localNames.isEmpty()) return
val values = HashMap<String, Obj>(importBindings.size)
for (name in importBindings.keys) {
val record = scope.getLocalRecordDirect(name) ?: continue
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
scope.resolve(record, name)
} else {
record.value
}
if (value !== ObjUnset) {
values[name] = value
}
}
if (values.isEmpty()) return
val base = frame.fn.scopeSlotCount
for (i in localNames.indices) {
val name = localNames[i] ?: continue
val value = values[name] ?: continue
frame.setObjUnchecked(base + i, value)
}
}
private suspend fun seedImportBindings(scope: Scope) { private suspend fun seedImportBindings(scope: Scope) {
val provider = scope.currentImportProvider val provider = scope.currentImportProvider
val importedModules = LinkedHashSet<ModuleScope>() val importedModules = LinkedHashSet<ModuleScope>()

View File

@ -19,6 +19,10 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
sealed class WhenCondition(open val expr: Statement, open val pos: Pos) { sealed class WhenCondition(open val expr: Statement, open val pos: Pos) {
protected fun interpreterDisabled(scope: Scope): Nothing {
return scope.raiseIllegalState("interpreter execution is not supported; when condition requires bytecode")
}
abstract suspend fun matches(scope: Scope, value: Obj): Boolean abstract suspend fun matches(scope: Scope, value: Obj): Boolean
} }
@ -27,7 +31,7 @@ class WhenEqualsCondition(
override val pos: Pos, override val pos: Pos,
) : WhenCondition(expr, pos) { ) : WhenCondition(expr, pos) {
override suspend fun matches(scope: Scope, value: Obj): Boolean { override suspend fun matches(scope: Scope, value: Obj): Boolean {
return expr.execute(scope).compareTo(scope, value) == 0 return interpreterDisabled(scope)
} }
} }
@ -37,8 +41,7 @@ class WhenInCondition(
override val pos: Pos, override val pos: Pos,
) : WhenCondition(expr, pos) { ) : WhenCondition(expr, pos) {
override suspend fun matches(scope: Scope, value: Obj): Boolean { override suspend fun matches(scope: Scope, value: Obj): Boolean {
val result = expr.execute(scope).contains(scope, value) return interpreterDisabled(scope)
return if (negated) !result else result
} }
} }
@ -48,12 +51,7 @@ class WhenIsCondition(
override val pos: Pos, override val pos: Pos,
) : WhenCondition(expr, pos) { ) : WhenCondition(expr, pos) {
override suspend fun matches(scope: Scope, value: Obj): Boolean { override suspend fun matches(scope: Scope, value: Obj): Boolean {
val typeExpr = expr.execute(scope) return interpreterDisabled(scope)
val result = when (typeExpr) {
is net.sergeych.lyng.obj.ObjTypeExpr -> net.sergeych.lyng.obj.matchesTypeDecl(scope, value, typeExpr.typeDecl)
else -> value.isInstanceOf(typeExpr)
}
return if (negated) !result else result
} }
} }

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 scopeSlotNameSet: Set<String>? = null,
private val moduleScopeId: Int? = null, private val moduleScopeId: Int? = null,
private val forcedLocalSlots: Map<String, Int> = emptyMap(), private val forcedLocalSlots: Map<String, Int> = emptyMap(),
private val forcedLocalScopeId: Int? = null, private val forcedLocalScopeId: Int? = null,
@ -498,13 +499,6 @@ class BytecodeCompiler(
return CompiledValue(local, SlotType.OBJ) return CompiledValue(local, SlotType.OBJ)
} }
if (ref.name.isEmpty()) return null if (ref.name.isEmpty()) return null
if (ref.captureOwnerScopeId == null && refScopeId(ref) == 0) {
val byName = scopeSlotIndexByName[ref.name]
if (byName != null) {
val resolved = slotTypes[byName] ?: SlotType.UNKNOWN
return CompiledValue(byName, resolved)
}
}
val mapped = resolveSlot(ref) ?: return null val mapped = resolveSlot(ref) ?: return null
var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) { if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
@ -518,6 +512,13 @@ class BytecodeCompiler(
updateSlotType(local, resolved) updateSlotType(local, resolved)
return CompiledValue(local, resolved) return CompiledValue(local, resolved)
} }
if (mapped < scopeSlotCount && resolved == SlotType.UNKNOWN) {
val addrSlot = ensureScopeAddr(mapped)
val local = allocSlot()
emitLoadFromAddr(addrSlot, local, SlotType.OBJ)
updateSlotType(local, SlotType.OBJ)
return CompiledValue(local, SlotType.OBJ)
}
CompiledValue(mapped, resolved) CompiledValue(mapped, resolved)
} }
is LocalVarRef -> { is LocalVarRef -> {
@ -529,12 +530,6 @@ class BytecodeCompiler(
return CompiledValue(slot, resolved) return CompiledValue(slot, resolved)
} }
if (allowLocalSlots) { if (allowLocalSlots) {
val localIndex = localSlotIndexByName[ref.name]
if (localIndex != null) {
val slot = scopeSlotCount + localIndex
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
scopeSlotIndexByName[ref.name]?.let { slot -> scopeSlotIndexByName[ref.name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved) return CompiledValue(slot, resolved)
@ -1292,6 +1287,23 @@ class BytecodeCompiler(
val rightRef = binaryRight(ref) val rightRef = binaryRight(ref)
var a = compileRefWithFallback(leftRef, null, refPos(ref)) ?: return null var a = compileRefWithFallback(leftRef, null, refPos(ref)) ?: return null
var b = compileRefWithFallback(rightRef, null, refPos(ref)) ?: return null var b = compileRefWithFallback(rightRef, null, refPos(ref)) ?: return null
if (op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)) {
val leftNeedsObj = a.type == SlotType.INT && b.type == SlotType.REAL
val rightNeedsObj = b.type == SlotType.INT && a.type == SlotType.REAL
if (leftNeedsObj || rightNeedsObj) {
val leftObj = if (leftNeedsObj) {
compileScopeSlotObj(leftRef) ?: a
} else {
a
}
val rightObj = if (rightNeedsObj) {
compileScopeSlotObj(rightRef) ?: b
} else {
b
}
return compileObjBinaryOp(leftRef, leftObj, rightObj, op, refPos(ref))
}
}
val intOps = setOf( val intOps = setOf(
BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT, BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
@ -1917,34 +1929,15 @@ class BytecodeCompiler(
builder.emit(Opcode.THROW, posId, msgSlot) builder.emit(Opcode.THROW, posId, msgSlot)
return value return value
} }
val resolvedSlot = resolveSlot(localTarget) val slot = resolveSlot(localTarget)
?: resolveAssignableSlotByName(localTarget.name)?.first ?: resolveAssignableSlotByName(localTarget.name)?.first
?: return null ?: return null
val slot = if (resolvedSlot < scopeSlotCount && localTarget.captureOwnerScopeId == null) {
localSlotIndexByName[localTarget.name]?.let { scopeSlotCount + it } ?: resolvedSlot
} else {
resolvedSlot
}
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) { if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
val addrSlot = ensureScopeAddr(slot) val addrSlot = ensureScopeAddr(slot)
emitStoreToAddr(value.slot, addrSlot, value.type) emitStoreToAddr(value.slot, addrSlot, value.type)
if (localTarget.captureOwnerScopeId == null) {
localSlotIndexByName[localTarget.name]?.let { mirror ->
val mirrorSlot = scopeSlotCount + mirror
emitMove(value, mirrorSlot)
updateSlotType(mirrorSlot, value.type)
}
}
} else if (slot < scopeSlotCount) { } else if (slot < scopeSlotCount) {
val addrSlot = ensureScopeAddr(slot) val addrSlot = ensureScopeAddr(slot)
emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ) emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
if (localTarget.captureOwnerScopeId == null) {
localSlotIndexByName[localTarget.name]?.let { mirror ->
val mirrorSlot = scopeSlotCount + mirror
emitMove(value, mirrorSlot)
updateSlotType(mirrorSlot, value.type)
}
}
} else { } else {
when (value.type) { when (value.type) {
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot) SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
@ -1980,19 +1973,9 @@ class BytecodeCompiler(
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) { if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
val addrSlot = ensureScopeAddr(slot) val addrSlot = ensureScopeAddr(slot)
emitStoreToAddr(value.slot, addrSlot, value.type) emitStoreToAddr(value.slot, addrSlot, value.type)
localSlotIndexByName[nameTarget]?.let { mirror ->
val mirrorSlot = scopeSlotCount + mirror
emitMove(value, mirrorSlot)
updateSlotType(mirrorSlot, value.type)
}
} else if (slot < scopeSlotCount) { } else if (slot < scopeSlotCount) {
val addrSlot = ensureScopeAddr(slot) val addrSlot = ensureScopeAddr(slot)
emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ) emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
localSlotIndexByName[nameTarget]?.let { mirror ->
val mirrorSlot = scopeSlotCount + mirror
emitMove(value, mirrorSlot)
updateSlotType(mirrorSlot, value.type)
}
} else { } else {
when (value.type) { when (value.type) {
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot) SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
@ -2337,8 +2320,65 @@ class BytecodeCompiler(
updateSlotType(result, SlotType.OBJ) updateSlotType(result, SlotType.OBJ)
return CompiledValue(result, SlotType.OBJ) return CompiledValue(result, SlotType.OBJ)
} }
val fieldId = receiverClass.instanceFieldIdMap()[fieldTarget.name] if (receiverClass is ObjInstanceClass && !isThisReceiver(fieldTarget.target)) {
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name] val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
if (!fieldTarget.isOptional) {
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, result)
builder.mark(endLabel)
}
updateSlotType(result, SlotType.OBJ)
return CompiledValue(result, SlotType.OBJ)
}
val resolvedMember = receiverClass.resolveInstanceMember(fieldTarget.name)
if (resolvedMember?.declaringClass?.className == "Obj") {
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
if (!fieldTarget.isOptional) {
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, result)
builder.mark(endLabel)
}
updateSlotType(result, SlotType.OBJ)
return CompiledValue(result, SlotType.OBJ)
}
val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[fieldTarget.name] else null
val methodId = if (resolvedMember != null) receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name] else null
if (fieldId == null && methodId == null && isKnownClassReceiver(fieldTarget.target)) { if (fieldId == null && methodId == null && isKnownClassReceiver(fieldTarget.target)) {
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name)) val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
if (!fieldTarget.isOptional) { if (!fieldTarget.isOptional) {
@ -2608,12 +2648,29 @@ class BytecodeCompiler(
builder.mark(endLabel) builder.mark(endLabel)
updateSlotType(resultSlot, SlotType.OBJ) updateSlotType(resultSlot, SlotType.OBJ)
return CompiledValue(resultSlot, SlotType.OBJ) return CompiledValue(resultSlot, SlotType.OBJ)
} else if (receiverClass is ObjInstanceClass && !isThisReceiver(target.target)) {
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
if (!target.isOptional) {
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
} else {
val recvNull = allocSlot()
builder.emit(Opcode.CONST_NULL, recvNull)
val recvCmp = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
val skipLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
builder.mark(skipLabel)
}
} else { } else {
val fieldId = receiverClass.instanceFieldIdMap()[target.name] val resolvedMember = receiverClass.resolveInstanceMember(target.name)
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] if (resolvedMember?.declaringClass?.className == "Obj") {
if (fieldId != null || methodId != null) { val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
if (!target.isOptional) { if (!target.isOptional) {
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot) builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
} else { } else {
val recvNull = allocSlot() val recvNull = allocSlot()
builder.emit(Opcode.CONST_NULL, recvNull) builder.emit(Opcode.CONST_NULL, recvNull)
@ -2624,39 +2681,59 @@ class BytecodeCompiler(
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel)) listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
) )
builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot) builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
builder.mark(skipLabel) builder.mark(skipLabel)
} }
} else { } else {
val extSlot = resolveExtensionSetterSlot(receiverClass, target.name) val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[target.name] else null
?: throw BytecodeCompileException( val methodId = if (resolvedMember != null) receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] else null
"Unknown member ${target.name} on ${receiverClass.className}", if (fieldId != null || methodId != null) {
Pos.builtIn if (!target.isOptional) {
) builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
val callee = ensureObjSlot(extSlot) } else {
val receiverObj = ensureObjSlot(receiver) val recvNull = allocSlot()
val valueObj = ensureObjSlot(newValue) builder.emit(Opcode.CONST_NULL, recvNull)
val argSlots = intArrayOf(allocSlot(), allocSlot()) val recvCmp = allocSlot()
builder.emit(Opcode.MOVE_OBJ, receiverObj.slot, argSlots[0]) builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
builder.emit(Opcode.MOVE_OBJ, valueObj.slot, argSlots[1]) val skipLabel = builder.label()
updateSlotType(argSlots[0], SlotType.OBJ) builder.emit(
updateSlotType(argSlots[1], SlotType.OBJ) Opcode.JMP_IF_TRUE,
val callArgs = CallArgs(base = argSlots[0], count = argSlots.size, planId = null) listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
val encodedCount = encodeCallArgCount(callArgs) ?: return null )
if (!target.isOptional) { builder.emit(Opcode.SET_MEMBER_SLOT, receiver.slot, fieldId ?: -1, methodId ?: -1, newValue.slot)
builder.emit(Opcode.CALL_SLOT, callee.slot, callArgs.base, encodedCount, resultSlot) builder.mark(skipLabel)
}
} else { } else {
val recvNull = allocSlot() val extSlot = resolveExtensionSetterSlot(receiverClass, target.name)
builder.emit(Opcode.CONST_NULL, recvNull) ?: throw BytecodeCompileException(
val recvCmp = allocSlot() "Unknown member ${target.name} on ${receiverClass.className}",
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp) Pos.builtIn
val skipLabel = builder.label() )
builder.emit( val callee = ensureObjSlot(extSlot)
Opcode.JMP_IF_TRUE, val receiverObj = ensureObjSlot(receiver)
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel)) val valueObj = ensureObjSlot(newValue)
) val argSlots = intArrayOf(allocSlot(), allocSlot())
builder.emit(Opcode.CALL_SLOT, callee.slot, callArgs.base, encodedCount, resultSlot) builder.emit(Opcode.MOVE_OBJ, receiverObj.slot, argSlots[0])
builder.mark(skipLabel) builder.emit(Opcode.MOVE_OBJ, valueObj.slot, argSlots[1])
updateSlotType(argSlots[0], SlotType.OBJ)
updateSlotType(argSlots[1], SlotType.OBJ)
val callArgs = CallArgs(base = argSlots[0], count = argSlots.size, planId = null)
val encodedCount = encodeCallArgCount(callArgs) ?: return null
if (!target.isOptional) {
builder.emit(Opcode.CALL_SLOT, callee.slot, callArgs.base, encodedCount, resultSlot)
} else {
val recvNull = allocSlot()
builder.emit(Opcode.CONST_NULL, recvNull)
val recvCmp = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
val skipLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
)
builder.emit(Opcode.CALL_SLOT, callee.slot, callArgs.base, encodedCount, resultSlot)
builder.mark(skipLabel)
}
} }
} }
} }
@ -2718,8 +2795,61 @@ class BytecodeCompiler(
updateSlotType(dst, SlotType.OBJ) updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
val fieldId = receiverClass.instanceFieldIdMap()[ref.name] if (receiverClass is ObjInstanceClass && !isThisReceiver(ref.target)) {
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
val dst = allocSlot()
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
if (!ref.isOptional) {
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst)
builder.mark(endLabel)
}
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
val resolvedMember = receiverClass.resolveInstanceMember(ref.name)
if (resolvedMember?.declaringClass?.className == "Obj") {
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
val dst = allocSlot()
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
if (!ref.isOptional) {
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst)
builder.mark(endLabel)
}
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[ref.name] else null
val methodId = if (resolvedMember != null) receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] else null
val encodedFieldId = encodeMemberId(receiverClass, fieldId) val encodedFieldId = encodeMemberId(receiverClass, fieldId)
val encodedMethodId = encodeMemberId(receiverClass, methodId) val encodedMethodId = encodeMemberId(receiverClass, methodId)
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
@ -3688,6 +3818,44 @@ class BytecodeCompiler(
private fun compileCall(ref: CallRef): CompiledValue? { private fun compileCall(ref: CallRef): CompiledValue? {
val callPos = callSitePos() val callPos = callSitePos()
val fieldTarget = ref.target as? FieldRef
if (fieldTarget != null && isKnownClassReceiver(fieldTarget.target)) {
val receiverClass = resolveReceiverClass(fieldTarget.target)
val methodId = receiverClass?.instanceMethodIdMap(includeAbstract = true)?.get(fieldTarget.name)
if (methodId != null) {
val receiver = compileRefWithFallback(fieldTarget.target, null, refPosOrCurrent(fieldTarget.target))
?: return null
val dst = allocSlot()
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
if (!ref.isOptionalInvoke) {
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null
setPos(callPos)
builder.emit(Opcode.CALL_MEMBER_SLOT, receiver.slot, encodedMethodId, args.base, encodedCount, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst)
builder.mark(endLabel)
}
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
}
val localTarget = ref.target as? LocalVarRef val localTarget = ref.target as? LocalVarRef
val isExternCall = localTarget != null && externCallableNames.contains(localTarget.name) val isExternCall = localTarget != null && externCallableNames.contains(localTarget.name)
if (localTarget != null) { if (localTarget != null) {
@ -3791,7 +3959,7 @@ class BytecodeCompiler(
val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
val dst = allocSlot() val dst = allocSlot()
if (receiverClass == ObjDynamic.type) { fun emitDynamicCall(): CompiledValue? {
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
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name)) val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
@ -3818,8 +3986,25 @@ class BytecodeCompiler(
builder.mark(endLabel) builder.mark(endLabel)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
if (receiverClass == ObjDynamic.type) {
return emitDynamicCall()
}
if (receiverClass is ObjInstanceClass && !isThisReceiver(ref.receiver)) {
return emitDynamicCall()
}
val resolvedMember = receiverClass.resolveInstanceMember(ref.name)
if (resolvedMember?.declaringClass?.className == "Obj") {
return emitDynamicCall()
}
val abstractRecord = receiverClass.members[ref.name] ?: receiverClass.classScope?.objects?.get(ref.name)
if (abstractRecord?.isAbstract == true) {
return emitDynamicCall()
}
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
if (methodId != null) { if (methodId != null && resolvedMember == null) {
return emitDynamicCall()
}
if (methodId != null && resolvedMember?.declaringClass?.className != "Obj") {
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId
if (!ref.isOptional) { if (!ref.isOptional) {
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
@ -3848,7 +4033,7 @@ class BytecodeCompiler(
builder.mark(endLabel) builder.mark(endLabel)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
val fieldId = receiverClass.instanceFieldIdMap()[ref.name] val fieldId = if (resolvedMember != null) receiverClass.instanceFieldIdMap()[ref.name] else null
if (fieldId != null) { if (fieldId != null) {
val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: fieldId val encodedFieldId = encodeMemberId(receiverClass, fieldId) ?: fieldId
val calleeSlot = allocSlot() val calleeSlot = allocSlot()
@ -4045,7 +4230,12 @@ class BytecodeCompiler(
private fun resolveExtensionCallableSlot(receiverClass: ObjClass, memberName: String): CompiledValue? { private fun resolveExtensionCallableSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
for (cls in receiverClass.mro) { for (cls in receiverClass.mro) {
val candidate = extensionCallableName(cls.className, memberName) val candidate = extensionCallableName(cls.className, memberName)
if (allowedScopeNames != null && !allowedScopeNames.contains(candidate)) continue if (allowedScopeNames != null &&
!allowedScopeNames.contains(candidate) &&
!localSlotIndexByName.containsKey(candidate)
) {
continue
}
resolveDirectNameSlot(candidate)?.let { return it } resolveDirectNameSlot(candidate)?.let { return it }
} }
return null return null
@ -4054,7 +4244,12 @@ class BytecodeCompiler(
private fun resolveExtensionGetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? { private fun resolveExtensionGetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
for (cls in receiverClass.mro) { for (cls in receiverClass.mro) {
val candidate = extensionPropertyGetterName(cls.className, memberName) val candidate = extensionPropertyGetterName(cls.className, memberName)
if (allowedScopeNames != null && !allowedScopeNames.contains(candidate)) continue if (allowedScopeNames != null &&
!allowedScopeNames.contains(candidate) &&
!localSlotIndexByName.containsKey(candidate)
) {
continue
}
resolveDirectNameSlot(candidate)?.let { return it } resolveDirectNameSlot(candidate)?.let { return it }
} }
return null return null
@ -4063,7 +4258,12 @@ class BytecodeCompiler(
private fun resolveExtensionSetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? { private fun resolveExtensionSetterSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {
for (cls in receiverClass.mro) { for (cls in receiverClass.mro) {
val candidate = extensionPropertySetterName(cls.className, memberName) val candidate = extensionPropertySetterName(cls.className, memberName)
if (allowedScopeNames != null && !allowedScopeNames.contains(candidate)) continue if (allowedScopeNames != null &&
!allowedScopeNames.contains(candidate) &&
!localSlotIndexByName.containsKey(candidate)
) {
continue
}
resolveDirectNameSlot(candidate)?.let { return it } resolveDirectNameSlot(candidate)?.let { return it }
} }
return null return null
@ -4339,9 +4539,12 @@ class BytecodeCompiler(
private fun emitDeclClass(stmt: net.sergeych.lyng.ClassDeclStatement): CompiledValue { private fun emitDeclClass(stmt: net.sergeych.lyng.ClassDeclStatement): CompiledValue {
val constId = builder.addConst(BytecodeConst.ClassDecl(stmt.spec)) val constId = builder.addConst(BytecodeConst.ClassDecl(stmt.spec))
val dst = allocSlot() val dst = stmt.spec.declaredName?.let { name ->
resolveDirectNameSlot(name)?.slot
} ?: allocSlot()
builder.emit(Opcode.DECL_CLASS, constId, dst) builder.emit(Opcode.DECL_CLASS, constId, dst)
updateSlotType(dst, SlotType.OBJ) updateSlotType(dst, SlotType.OBJ)
stmt.spec.declaredName?.let { updateNameObjClassFromSlot(it, dst) }
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
@ -4658,9 +4861,6 @@ class BytecodeCompiler(
if (shouldInlineBlock(stmt)) { if (shouldInlineBlock(stmt)) {
return emitInlineStatements(stmt.statements(), needResult) return emitInlineStatements(stmt.statements(), needResult)
} }
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan, emptyList()))
builder.emit(Opcode.PUSH_SCOPE, planId)
resetAddrCache()
val statements = stmt.statements() val statements = stmt.statements()
var lastValue: CompiledValue? = null var lastValue: CompiledValue? = null
for ((index, statement) in statements.withIndex()) { for ((index, statement) in statements.withIndex()) {
@ -4675,24 +4875,18 @@ class BytecodeCompiler(
"Bytecode compile error: failed to compile block statement ($name)", "Bytecode compile error: failed to compile block statement ($name)",
statement.pos statement.pos
) )
} }
if (wantResult) { if (wantResult) {
lastValue = value lastValue = value
} }
} }
val result = if (needResult) { return if (needResult) {
var value = lastValue ?: run { lastValue ?: run {
val slot = allocSlot() val slot = allocSlot()
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
builder.emit(Opcode.CONST_OBJ, voidId, slot) builder.emit(Opcode.CONST_OBJ, voidId, slot)
CompiledValue(slot, SlotType.OBJ) CompiledValue(slot, SlotType.OBJ)
} }
if (value.slot < scopeSlotCount) {
val captured = allocSlot()
emitMove(value, captured)
value = CompiledValue(captured, value.type)
}
value
} else { } else {
lastValue ?: run { lastValue ?: run {
val slot = allocSlot() val slot = allocSlot()
@ -4701,9 +4895,6 @@ class BytecodeCompiler(
CompiledValue(slot, SlotType.OBJ) CompiledValue(slot, SlotType.OBJ)
} }
} }
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
return result
} }
private fun emitTry(stmt: net.sergeych.lyng.TryStatement, needResult: Boolean): CompiledValue? { private fun emitTry(stmt: net.sergeych.lyng.TryStatement, needResult: Boolean): CompiledValue? {
@ -4801,15 +4992,8 @@ class BytecodeCompiler(
): CompiledValue? { ): CompiledValue? {
val stmt = block as? BlockStatement val stmt = block as? BlockStatement
if (stmt == null) { if (stmt == null) {
val declId = builder.addConst(BytecodeConst.LocalDecl(catchVarName, false, Visibility.Public, false))
builder.emit(Opcode.DECL_LOCAL, declId, exceptionSlot)
return compileStatementValueOrFallback(block, needResult) return compileStatementValueOrFallback(block, needResult)
} }
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan, emptyList()))
builder.emit(Opcode.PUSH_SCOPE, planId)
resetAddrCache()
val declId = builder.addConst(BytecodeConst.LocalDecl(catchVarName, false, Visibility.Public, false))
builder.emit(Opcode.DECL_LOCAL, declId, exceptionSlot)
val catchSlotIndex = stmt.slotPlan[catchVarName] val catchSlotIndex = stmt.slotPlan[catchVarName]
if (catchSlotIndex != null) { if (catchSlotIndex != null) {
val key = ScopeSlotKey(stmt.scopeId, catchSlotIndex) val key = ScopeSlotKey(stmt.scopeId, catchSlotIndex)
@ -4847,8 +5031,6 @@ class BytecodeCompiler(
CompiledValue(slot, SlotType.OBJ) CompiledValue(slot, SlotType.OBJ)
} }
} }
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
return result return result
} }
@ -4931,40 +5113,9 @@ 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 {
if (isModuleSlot) {
scopeSlotIndexByName[stmt.name]
} else {
null
}
}
if (isModuleSlot && scopeSlot != null) {
val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
val unsetId = builder.addConst(BytecodeConst.ObjRef(ObjUnset))
builder.emit(Opcode.CONST_OBJ, unsetId, scopeSlot)
updateSlotType(scopeSlot, SlotType.OBJ)
CompiledValue(scopeSlot, SlotType.OBJ)
}
if (value.slot != scopeSlot) {
emitMove(value, scopeSlot)
}
updateSlotType(scopeSlot, value.type)
updateNameObjClassFromSlot(stmt.name, scopeSlot)
updateSlotObjClass(scopeSlot, stmt.initializer, stmt.initializerObjClass)
val declId = builder.addConst(
BytecodeConst.LocalDecl(
stmt.name,
stmt.isMutable,
stmt.visibility,
stmt.isTransient
)
)
builder.emit(Opcode.DECL_LOCAL, declId, scopeSlot)
return CompiledValue(scopeSlot, value.type)
} }
val localSlot = if (allowLocalSlots && stmt.slotIndex != null) { val localSlot = if (allowLocalSlots && stmt.slotIndex != null) {
val key = ScopeSlotKey(scopeId, stmt.slotIndex) val key = ScopeSlotKey(scopeId, stmt.slotIndex)
@ -5182,7 +5333,6 @@ class BytecodeCompiler(
} }
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 loopSlotPlan = stmt.loopSlotPlan val loopSlotPlan = stmt.loopSlotPlan
var useLoopScope = loopSlotPlan.isNotEmpty()
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName] val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
val loopKey = loopSlotIndex?.let { ScopeSlotKey(stmt.loopScopeId, it) } val loopKey = loopSlotIndex?.let { ScopeSlotKey(stmt.loopScopeId, it) }
val loopLocalIndex = loopKey?.let { localSlotIndexByKey[it] } ?: localSlotIndexByName[stmt.loopVarName] val loopLocalIndex = loopKey?.let { localSlotIndexByKey[it] } ?: localSlotIndexByName[stmt.loopVarName]
@ -5203,47 +5353,13 @@ class BytecodeCompiler(
usedOverride = true usedOverride = true
slot slot
} }
var emitDeclLocal = usedOverride // Loop scopes are intentionally disabled; loop vars live in frame slots only.
if (useLoopScope) {
val loopVarOnly = loopSlotPlan.size == 1 && loopSlotPlan.containsKey(stmt.loopVarName)
val loopVarIsLocal = loopSlotId >= scopeSlotCount
if (loopVarOnly && loopVarIsLocal) {
useLoopScope = false
}
}
if (useLoopScope && allowLocalSlots) {
val needsScope = allowedScopeNames?.let { names ->
loopSlotPlan.keys.any { names.contains(it) }
} == true
if (!needsScope) {
useLoopScope = false
}
}
emitDeclLocal = emitDeclLocal && useLoopScope
if (loopSlotId < scopeSlotCount) { if (loopSlotId < scopeSlotCount) {
val localSlot = allocSlot() val localSlot = allocSlot()
loopSlotOverrides[stmt.loopVarName] = localSlot loopSlotOverrides[stmt.loopVarName] = localSlot
usedOverride = true usedOverride = true
emitDeclLocal = false
loopSlotId = localSlot loopSlotId = localSlot
} }
val planId = if (useLoopScope) {
builder.addConst(BytecodeConst.SlotPlan(loopSlotPlan, emptyList()))
} else {
-1
}
val loopDeclId = if (emitDeclLocal) {
builder.addConst(
BytecodeConst.LocalDecl(
stmt.loopVarName,
true,
Visibility.Public,
isTransient = false
)
)
} else {
-1
}
try { try {
if (range == null && rangeRef == null && typedRangeLocal == null) { if (range == null && rangeRef == null && typedRangeLocal == null) {
@ -5284,10 +5400,6 @@ class BytecodeCompiler(
val loopLabel = builder.label() val loopLabel = builder.label()
val continueLabel = builder.label() val continueLabel = builder.label()
val endLabel = builder.label() val endLabel = builder.label()
if (useLoopScope) {
builder.emit(Opcode.PUSH_SCOPE, planId)
resetAddrCache()
}
builder.mark(loopLabel) builder.mark(loopLabel)
val hasNextSlot = allocSlot() val hasNextSlot = allocSlot()
@ -5305,9 +5417,6 @@ class BytecodeCompiler(
emitMove(CompiledValue(nextObj.slot, SlotType.OBJ), loopSlotId) emitMove(CompiledValue(nextObj.slot, SlotType.OBJ), loopSlotId)
updateSlotType(loopSlotId, SlotType.OBJ) updateSlotType(loopSlotId, SlotType.OBJ)
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ) updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
if (emitDeclLocal) {
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
}
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(
@ -5349,10 +5458,6 @@ class BytecodeCompiler(
} }
builder.mark(afterElse) builder.mark(afterElse)
} }
if (useLoopScope) {
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
}
return resultSlot return resultSlot
} }
@ -5398,10 +5503,6 @@ class BytecodeCompiler(
val continueLabel = builder.label() val continueLabel = builder.label()
val endLabel = builder.label() val endLabel = builder.label()
val doneLabel = builder.label() val doneLabel = builder.label()
if (useLoopScope) {
builder.emit(Opcode.PUSH_SCOPE, planId)
resetAddrCache()
}
builder.mark(loopLabel) builder.mark(loopLabel)
val cmpSlot = allocSlot() val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot) builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
@ -5412,9 +5513,6 @@ class BytecodeCompiler(
emitMove(CompiledValue(iSlot, SlotType.INT), loopSlotId) emitMove(CompiledValue(iSlot, SlotType.INT), loopSlotId)
updateSlotType(loopSlotId, SlotType.INT) updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
if (emitDeclLocal) {
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
}
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(
stmt.label, stmt.label,
@ -5449,10 +5547,6 @@ class BytecodeCompiler(
} }
builder.mark(afterElse) builder.mark(afterElse)
} }
if (useLoopScope) {
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
}
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(doneLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(doneLabel)))
builder.mark(badRangeLabel) builder.mark(badRangeLabel)
val msgId = builder.addConst(BytecodeConst.StringVal("expected Int range")) val msgId = builder.addConst(BytecodeConst.StringVal("expected Int range"))
@ -5475,10 +5569,6 @@ class BytecodeCompiler(
val loopLabel = builder.label() val loopLabel = builder.label()
val continueLabel = builder.label() val continueLabel = builder.label()
val endLabel = builder.label() val endLabel = builder.label()
if (useLoopScope) {
builder.emit(Opcode.PUSH_SCOPE, planId)
resetAddrCache()
}
builder.mark(loopLabel) builder.mark(loopLabel)
val cmpSlot = allocSlot() val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot) builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
@ -5489,9 +5579,6 @@ 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 (emitDeclLocal) {
builder.emit(Opcode.DECL_LOCAL, loopDeclId, loopSlotId)
}
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(
stmt.label, stmt.label,
@ -5526,10 +5613,6 @@ class BytecodeCompiler(
} }
builder.mark(afterElse) builder.mark(afterElse)
} }
if (useLoopScope) {
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
}
return resultSlot return resultSlot
} finally { } finally {
if (usedOverride) { if (usedOverride) {
@ -5550,13 +5633,8 @@ class BytecodeCompiler(
val loopLabel = builder.label() val loopLabel = builder.label()
val continueLabel = builder.label() val continueLabel = builder.label()
val endLabel = builder.label() val endLabel = builder.label()
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(continueLabel)))
builder.mark(loopLabel) builder.mark(loopLabel)
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
if (condition.type != SlotType.BOOL) return null
builder.emit(
Opcode.JMP_IF_FALSE,
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(endLabel))
)
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(
stmt.label, stmt.label,
@ -5574,7 +5652,12 @@ class BytecodeCompiler(
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot) builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
} }
builder.mark(continueLabel) builder.mark(continueLabel)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel))) val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
if (condition.type != SlotType.BOOL) return null
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
)
builder.mark(endLabel) builder.mark(endLabel)
if (stmt.elseStatement != null) { if (stmt.elseStatement != null) {
@ -5605,18 +5688,8 @@ class BytecodeCompiler(
val loopLabel = builder.label() val loopLabel = builder.label()
val continueLabel = builder.label() val continueLabel = builder.label()
val endLabel = builder.label() val endLabel = builder.label()
val useLoopScope = stmt.loopSlotPlan.isNotEmpty() val breakLabel = endLabel
val breakLabel = if (useLoopScope) builder.label() else endLabel
val planId = if (useLoopScope) {
builder.addConst(BytecodeConst.SlotPlan(stmt.loopSlotPlan, emptyList()))
} else {
-1
}
builder.mark(loopLabel) builder.mark(loopLabel)
if (useLoopScope) {
builder.emit(Opcode.PUSH_SCOPE, planId)
resetAddrCache()
}
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(
stmt.label, stmt.label,
@ -5627,12 +5700,7 @@ class BytecodeCompiler(
hasIterator = false hasIterator = false
) )
) )
val bodyTarget = if (stmt.body is BytecodeStatement) stmt.body.original else stmt.body val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null
val bodyValue = if (useLoopScope && bodyTarget is BlockStatement) {
emitInlineBlock(bodyTarget, wantResult)
} else {
compileStatementValueOrFallback(stmt.body, wantResult)
} ?: return null
loopStack.removeLast() loopStack.removeLast()
if (wantResult) { if (wantResult) {
val bodyObj = ensureObjSlot(bodyValue) val bodyObj = ensureObjSlot(bodyValue)
@ -5641,20 +5709,10 @@ class BytecodeCompiler(
builder.mark(continueLabel) builder.mark(continueLabel)
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
if (condition.type != SlotType.BOOL) return null if (condition.type != SlotType.BOOL) return null
if (useLoopScope) {
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
}
builder.emit( builder.emit(
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel)) listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
) )
if (useLoopScope) {
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(breakLabel)
builder.emit(Opcode.POP_SCOPE)
resetAddrCache()
}
builder.mark(endLabel) builder.mark(endLabel)
if (stmt.elseStatement != null) { if (stmt.elseStatement != null) {
@ -6094,6 +6152,21 @@ class BytecodeCompiler(
) )
} }
private fun compileScopeSlotObj(ref: ObjRef): CompiledValue? {
val slot = when (ref) {
is LocalSlotRef -> resolveSlot(ref)
is LocalVarRef -> scopeSlotIndexByName[ref.name]
is FastLocalVarRef -> scopeSlotIndexByName[ref.name]
else -> null
} ?: return null
if (slot >= scopeSlotCount) return null
val addrSlot = ensureScopeAddr(slot)
val local = allocSlot()
emitLoadFromAddr(addrSlot, local, SlotType.OBJ)
updateSlotType(local, SlotType.OBJ)
return CompiledValue(local, SlotType.OBJ)
}
private fun resolveReceiverClass(ref: ObjRef): ObjClass? { private fun resolveReceiverClass(ref: ObjRef): ObjClass? {
return when (ref) { return when (ref) {
is LocalSlotRef -> { is LocalSlotRef -> {
@ -6189,6 +6262,14 @@ class BytecodeCompiler(
} }
} }
private fun isThisReceiver(ref: ObjRef): Boolean {
return when (ref) {
is LocalSlotRef -> ref.name == "this"
is QualifiedThisRef -> true
else -> false
}
}
private fun isAllowedObjectMember(memberName: String): Boolean { private fun isAllowedObjectMember(memberName: String): Boolean {
return when (memberName) { return when (memberName) {
"toString", "toString",
@ -6484,6 +6565,8 @@ class BytecodeCompiler(
val scopeId = refScopeId(ref) val scopeId = refScopeId(ref)
if (!ref.isDelegated && isModuleSlot(scopeId, ref.name)) { if (!ref.isDelegated && isModuleSlot(scopeId, ref.name)) {
val key = ScopeSlotKey(scopeId, refSlot(ref)) val key = ScopeSlotKey(scopeId, refSlot(ref))
val localIndex = localSlotIndexByKey[key]
if (localIndex != null) return scopeSlotCount + localIndex
scopeSlotMap[key]?.let { return it } scopeSlotMap[key]?.let { return it }
scopeSlotIndexByName[ref.name]?.let { return it } scopeSlotIndexByName[ref.name]?.let { return it }
} }
@ -6495,29 +6578,16 @@ class BytecodeCompiler(
return scopeSlotCount + localIndex return scopeSlotCount + localIndex
} }
} }
val nameLocal = localSlotIndexByName[ref.name]
if (nameLocal != null && localSlotCaptures.getOrNull(nameLocal) == true) {
return scopeSlotCount + nameLocal
}
for (idx in localSlotNames.indices) {
if (localSlotNames[idx] != ref.name) continue
if (localSlotCaptures.getOrNull(idx) != true) continue
return scopeSlotCount + idx
}
return scopeSlotMap[scopeKey] return scopeSlotMap[scopeKey]
} }
if (ref.isDelegated) { if (ref.isDelegated) {
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
val nameIndex = localSlotIndexByName[ref.name]
if (nameIndex != null) return scopeSlotCount + nameIndex
} }
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
val nameIndex = localSlotIndexByName[ref.name]
if (nameIndex != null) return scopeSlotCount + nameIndex
val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
return scopeSlotMap[scopeKey] return scopeSlotMap[scopeKey]
} }
@ -6705,6 +6775,7 @@ class BytecodeCompiler(
is VarDeclStatement -> { is VarDeclStatement -> {
val slotIndex = stmt.slotIndex val slotIndex = stmt.slotIndex
val scopeId = stmt.scopeId ?: 0 val scopeId = stmt.scopeId ?: 0
val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
val cls = stmt.initializerObjClass ?: objClassForInitializer(stmt.initializer) val cls = stmt.initializerObjClass ?: objClassForInitializer(stmt.initializer)
if (cls != null) { if (cls != null) {
nameObjClass[stmt.name] = cls nameObjClass[stmt.name] = cls
@ -6712,8 +6783,7 @@ class BytecodeCompiler(
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
} }
} }
val isModuleSlot = isModuleSlot(scopeId, stmt.name) if (allowLocalSlots && slotIndex != null && !isModuleDecl) {
if (allowLocalSlots && 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)) {
@ -6740,7 +6810,8 @@ class BytecodeCompiler(
val scopeId = stmt.spec.scopeId ?: 0 val scopeId = stmt.spec.scopeId ?: 0
if (slotIndex != null) { if (slotIndex != null) {
val key = ScopeSlotKey(scopeId, slotIndex) val key = ScopeSlotKey(scopeId, slotIndex)
if (allowLocalSlots) { val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
if (allowLocalSlots && !isModuleDecl) {
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false) localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
} }
@ -6757,7 +6828,8 @@ class BytecodeCompiler(
is DelegatedVarDeclStatement -> { is DelegatedVarDeclStatement -> {
val slotIndex = stmt.slotIndex val slotIndex = stmt.slotIndex
val scopeId = stmt.scopeId ?: 0 val scopeId = stmt.scopeId ?: 0
if (allowLocalSlots && slotIndex != null) { val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
if (allowLocalSlots && slotIndex != null && !isModuleDecl) {
val key = ScopeSlotKey(scopeId, slotIndex) val key = ScopeSlotKey(scopeId, slotIndex)
declaredLocalKeys.add(key) declaredLocalKeys.add(key)
if (!localSlotInfoMap.containsKey(key)) { if (!localSlotInfoMap.containsKey(key)) {
@ -6895,12 +6967,11 @@ class BytecodeCompiler(
private fun isModuleSlot(scopeId: Int, name: String?): Boolean { private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
val moduleId = moduleScopeId val moduleId = moduleScopeId
if (moduleId == null) { if (moduleId == null) {
if (allowedScopeNames == null || name == null) return false val scopeNames = scopeSlotNameSet ?: allowedScopeNames
return allowedScopeNames.contains(name) if (scopeNames == null || name == null) return false
return scopeNames.contains(name)
} }
if (scopeId != moduleId) return false return scopeId == moduleId
if (allowedScopeNames == null || name == null) return true
return allowedScopeNames.contains(name)
} }
private fun collectLoopVarNames(stmt: Statement) { private fun collectLoopVarNames(stmt: Statement) {

View File

@ -43,6 +43,7 @@ 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,
scopeSlotNameSet: Set<String>? = null,
moduleScopeId: Int? = null, moduleScopeId: Int? = null,
forcedLocalSlots: Map<String, Int> = emptyMap(), forcedLocalSlots: Map<String, Int> = emptyMap(),
forcedLocalScopeId: Int? = null, forcedLocalScopeId: Int? = null,
@ -71,6 +72,7 @@ class BytecodeStatement private constructor(
returnLabels = returnLabels, returnLabels = returnLabels,
rangeLocalNames = rangeLocalNames, rangeLocalNames = rangeLocalNames,
allowedScopeNames = allowedScopeNames, allowedScopeNames = allowedScopeNames,
scopeSlotNameSet = scopeSlotNameSet,
moduleScopeId = moduleScopeId, moduleScopeId = moduleScopeId,
forcedLocalSlots = forcedLocalSlots, forcedLocalSlots = forcedLocalSlots,
forcedLocalScopeId = forcedLocalScopeId, forcedLocalScopeId = forcedLocalScopeId,

View File

@ -2219,8 +2219,30 @@ class CmdGetClassScope(
val scope = frame.ensureScope() val scope = frame.ensureScope()
val cls = frame.slotToObj(classSlot) as? ObjClass val cls = frame.slotToObj(classSlot) as? ObjClass
?: scope.raiseSymbolNotFound(nameConst.value) ?: scope.raiseSymbolNotFound(nameConst.value)
val rec = cls.readField(scope, nameConst.value) val name = nameConst.value
val value = scope.resolve(rec, nameConst.value) var rec: ObjRecord? = null
var decl: ObjClass? = null
for (c in cls.mro) {
if (c.className == "Obj") break
val candidate = c.classScope?.objects?.get(name) ?: c.members[name]
if (candidate == null || candidate.isAbstract) continue
val declared = candidate.declaringClass ?: c
if (!canAccessMember(candidate.visibility, declared, scope.currentClassCtx, name)) {
scope.raiseError(
ObjIllegalAccessException(
scope,
"can't access field ${name}: not visible (declared in ${declared.className}, caller ${scope.currentClassCtx?.className ?: "?"})"
)
)
}
rec = candidate
decl = declared
break
}
val resolved = rec ?: scope.raiseSymbolNotFound(name)
val declClass = decl ?: cls
val resolvedRec = cls.resolveRecord(scope, resolved, name, declClass)
val value = resolvedRec.value
frame.storeObjResult(dst, value) frame.storeObjResult(dst, value)
return return
} }
@ -3187,7 +3209,11 @@ class CmdFrame(
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
SlotType.OBJ.code -> { SlotType.OBJ.code -> {
val obj = frame.getObj(local) val obj = frame.getObj(local)
if (obj is FrameSlotRef) obj.read() else obj when (obj) {
is FrameSlotRef -> obj.read()
is RecordSlotRef -> obj.read()
else -> obj
}
} }
else -> ObjVoid else -> ObjVoid
} }
@ -3347,6 +3373,7 @@ class CmdFrame(
val record = target.getSlotRecord(index) val record = target.getSlotRecord(index)
val direct = record.value val direct = record.value
if (direct is FrameSlotRef) return direct.read() if (direct is FrameSlotRef) return direct.read()
if (direct is RecordSlotRef) return direct.read()
val name = fn.scopeSlotNames[slot] val name = fn.scopeSlotNames[slot]
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) { if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
return target.resolve(record, name) return target.resolve(record, name)
@ -3368,6 +3395,7 @@ class CmdFrame(
val record = target.getSlotRecord(index) val record = target.getSlotRecord(index)
val direct = record.value val direct = record.value
if (direct is FrameSlotRef) return direct.read() if (direct is FrameSlotRef) return direct.read()
if (direct is RecordSlotRef) return direct.read()
val slotId = addrScopeSlots[addrSlot] val slotId = addrScopeSlots[addrSlot]
val name = fn.scopeSlotNames.getOrNull(slotId) val name = fn.scopeSlotNames.getOrNull(slotId)
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) { if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {

View File

@ -265,13 +265,35 @@ open class Obj {
open val objClass: ObjClass get() = rootObjectType open val objClass: ObjClass get() = rootObjectType
open suspend fun plus(scope: Scope, other: Obj): Obj { open suspend fun plus(scope: Scope, other: Obj): Obj {
return invokeInstanceMethod(scope, "plus", Arguments(other)) { val otherValue = when (other) {
is FrameSlotRef -> other.read()
is RecordSlotRef -> other.read()
else -> other
}
val self = when (this) {
is FrameSlotRef -> this.read()
is RecordSlotRef -> this.read()
else -> this
}
if (self !== this) return self.plus(scope, otherValue)
return invokeInstanceMethod(scope, "plus", Arguments(otherValue)) {
scope.raiseNotImplemented("plus for ${objClass.className}") scope.raiseNotImplemented("plus for ${objClass.className}")
} }
} }
open suspend fun minus(scope: Scope, other: Obj): Obj { open suspend fun minus(scope: Scope, other: Obj): Obj {
return invokeInstanceMethod(scope, "minus", Arguments(other)) { val otherValue = when (other) {
is FrameSlotRef -> other.read()
is RecordSlotRef -> other.read()
else -> other
}
val self = when (this) {
is FrameSlotRef -> this.read()
is RecordSlotRef -> this.read()
else -> this
}
if (self !== this) return self.minus(scope, otherValue)
return invokeInstanceMethod(scope, "minus", Arguments(otherValue)) {
scope.raiseNotImplemented("minus for ${objClass.className}") scope.raiseNotImplemented("minus for ${objClass.className}")
} }
} }
@ -283,19 +305,52 @@ open class Obj {
} }
open suspend fun mul(scope: Scope, other: Obj): Obj { open suspend fun mul(scope: Scope, other: Obj): Obj {
return invokeInstanceMethod(scope, "mul", Arguments(other)) { val otherValue = when (other) {
is FrameSlotRef -> other.read()
is RecordSlotRef -> other.read()
else -> other
}
val self = when (this) {
is FrameSlotRef -> this.read()
is RecordSlotRef -> this.read()
else -> this
}
if (self !== this) return self.mul(scope, otherValue)
return invokeInstanceMethod(scope, "mul", Arguments(otherValue)) {
scope.raiseNotImplemented("mul for ${objClass.className}") scope.raiseNotImplemented("mul for ${objClass.className}")
} }
} }
open suspend fun div(scope: Scope, other: Obj): Obj { open suspend fun div(scope: Scope, other: Obj): Obj {
return invokeInstanceMethod(scope, "div", Arguments(other)) { val otherValue = when (other) {
is FrameSlotRef -> other.read()
is RecordSlotRef -> other.read()
else -> other
}
val self = when (this) {
is FrameSlotRef -> this.read()
is RecordSlotRef -> this.read()
else -> this
}
if (self !== this) return self.div(scope, otherValue)
return invokeInstanceMethod(scope, "div", Arguments(otherValue)) {
scope.raiseNotImplemented("div for ${objClass.className}") scope.raiseNotImplemented("div for ${objClass.className}")
} }
} }
open suspend fun mod(scope: Scope, other: Obj): Obj { open suspend fun mod(scope: Scope, other: Obj): Obj {
return invokeInstanceMethod(scope, "mod", Arguments(other)) { val otherValue = when (other) {
is FrameSlotRef -> other.read()
is RecordSlotRef -> other.read()
else -> other
}
val self = when (this) {
is FrameSlotRef -> this.read()
is RecordSlotRef -> this.read()
else -> this
}
if (self !== this) return self.mod(scope, otherValue)
return invokeInstanceMethod(scope, "mod", Arguments(otherValue)) {
scope.raiseNotImplemented("mod for ${objClass.className}") scope.raiseNotImplemented("mod for ${objClass.className}")
} }
} }
@ -755,6 +810,14 @@ open class Obj {
thisObj.putAt(this, requiredArg<Obj>(0), newValue) thisObj.putAt(this, requiredArg<Obj>(0), newValue)
newValue newValue
} }
addFnDoc(
name = "toJson",
doc = "Encodes this object to JSON.",
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) {
thisObj.toJson(this).toString().toObj()
}
addFnDoc( addFnDoc(
name = "toJsonString", name = "toJsonString",
doc = "Encodes this object to a JSON string.", doc = "Encodes this object to a JSON string.",

View File

@ -24,7 +24,6 @@ import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addConstDoc import net.sergeych.lyng.miniast.addConstDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type import net.sergeych.lyng.miniast.type
import net.sergeych.lyng.statement
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType

View File

@ -35,22 +35,7 @@ sealed interface ObjRef {
* Default implementation calls [get] and returns its value. Nodes can override to avoid record traffic. * Default implementation calls [get] and returns its value. Nodes can override to avoid record traffic.
*/ */
suspend fun evalValue(scope: Scope): Obj { suspend fun evalValue(scope: Scope): Obj {
val rec = get(scope) scope.raiseIllegalState("interpreter execution is not supported; ObjRef evaluation requires bytecode")
if (rec.type == ObjRecord.Type.Delegated) {
val receiver = rec.receiver ?: scope.thisObj
// Use resolve to handle delegated property logic
return scope.resolve(rec, "unknown")
}
// Template record: must map to instance storage
if (rec.receiver != null && rec.declaringClass != null) {
return rec.receiver!!.resolveRecord(scope, rec, "unknown", rec.declaringClass).value
}
val value = rec.value
return when (value) {
is FrameSlotRef -> value.read()
is RecordSlotRef -> value.read()
else -> value
}
} }
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
throw ScriptError(pos, "can't assign value") throw ScriptError(pos, "can't assign value")

View File

@ -19,17 +19,6 @@ package net.sergeych.lyng
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.ObjException
import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjIterable
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjRange
import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjString
import net.sergeych.lyng.obj.ObjVoid
import net.sergeych.lyng.obj.toBool
import net.sergeych.lyng.obj.toInt
import net.sergeych.lyng.obj.toLong
fun String.toSource(name: String = "eval"): Source = Source(name, this) fun String.toSource(name: String = "eval"): Source = Source(name, this)
@ -105,117 +94,6 @@ class ForInStatement(
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "for-in statement") return interpreterDisabled(scope, "for-in statement")
} }
private suspend fun loopIntRange(
forScope: Scope,
start: Long,
end: Long,
loopVar: ObjRecord,
loopSlotIndex: Int,
body: Statement,
elseStatement: Statement?,
label: String?,
catchBreak: Boolean,
): Obj {
var result: Obj = ObjVoid
val cacheLow = ObjInt.CACHE_LOW
val cacheHigh = ObjInt.CACHE_HIGH
val useCache = start >= cacheLow && end <= cacheHigh + 1
val cache = if (useCache) ObjInt.cacheArray() else null
val useSlot = loopSlotIndex >= 0
if (catchBreak) {
if (useCache && cache != null) {
var i = start
while (i < end) {
val v = cache[(i - cacheLow).toInt()]
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
try {
result = body.execute(forScope)
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
if (lbe.doContinue) {
i++
continue
}
return lbe.result
}
throw lbe
}
i++
}
} else {
for (i in start..<end) {
val v = ObjInt.of(i)
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
try {
result = body.execute(forScope)
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
if (lbe.doContinue) continue
return lbe.result
}
throw lbe
}
}
}
} else {
if (useCache && cache != null) {
var i = start
while (i < end) {
val v = cache[(i - cacheLow).toInt()]
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
result = body.execute(forScope)
i++
}
} else {
for (i in start..<end) {
val v = ObjInt.of(i)
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
result = body.execute(forScope)
}
}
}
return elseStatement?.execute(forScope) ?: result
}
private suspend fun loopIterable(
forScope: Scope,
sourceObj: Obj,
loopVar: ObjRecord,
body: Statement,
elseStatement: Statement?,
label: String?,
catchBreak: Boolean,
): Obj {
var result: Obj = ObjVoid
var breakCaught = false
sourceObj.enumerate(forScope) { item ->
loopVar.value = item
if (catchBreak) {
try {
result = body.execute(forScope)
true
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
breakCaught = true
if (lbe.doContinue) true else {
result = lbe.result
false
}
} else {
throw lbe
}
}
} else {
result = body.execute(forScope)
true
}
}
if (!breakCaught && elseStatement != null) {
result = elseStatement.execute(forScope)
}
return result
}
} }
class WhileStatement( class WhileStatement(
@ -299,18 +177,6 @@ fun Statement.require(cond: Boolean, message: () -> String) {
if (!cond) raise(message()) if (!cond) raise(message())
} }
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Scope) -> Obj): Statement =
object : Statement(isStaticConst, isConst) {
override val pos: Pos = pos
override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "statement bridge")
}
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Scope.() -> Obj): Statement =
object : Statement(isStaticConst, isConst) {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "statement bridge")
}
object NopStatement: Statement(true, true, ObjType.Void) { object NopStatement: Statement(true, true, ObjType.Void) {
override val pos: Pos = Pos.builtIn override val pos: Pos = Pos.builtIn

View File

@ -397,4 +397,5 @@ class BytecodeRecentOpsTest {
val result = script.execute(Script.defaultImportManager.newStdScope()) val result = script.execute(Script.defaultImportManager.newStdScope())
assertEquals(2, result.toInt()) assertEquals(2, result.toInt())
} }
} }

View File

@ -656,56 +656,58 @@ class ScriptTest {
@Test @Test
fun whileTest() = runTest { fun whileTest() = runTest {
assertEquals( withTimeout(2.seconds) {
5.0, assertEquals(
eval( 5.0,
""" eval(
var acc = 0 """
while( acc < 5 ) acc = acc + 0.5 var acc = 0
acc while( acc < 5 ) acc = acc + 0.5
"""
).toDouble()
)
assertEquals(
5.0,
eval(
"""
var acc = 0
// return from while
while( acc < 5 ) {
acc = acc + 0.5
acc acc
} """
""" ).toDouble()
).toDouble() )
) assertEquals(
assertEquals( 5.0,
3.0, eval(
eval( """
""" var acc = 0
var acc = 0 // return from while
while( acc < 5 ) { while( acc < 5 ) {
acc = acc + 0.5 acc = acc + 0.5
if( acc >= 3 ) break acc
} }
"""
).toDouble()
)
assertEquals(
3.0,
eval(
"""
var acc = 0
while( acc < 5 ) {
acc = acc + 0.5
if( acc >= 3 ) break
}
acc acc
""" """
).toDouble() ).toDouble()
) )
assertEquals( assertEquals(
17.0, 17.0,
eval( eval(
""" """
var acc = 0 var acc = 0
while( acc < 5 ) { while( acc < 5 ) {
acc = acc + 0.5 acc = acc + 0.5
if( acc >= 3 ) break 17 if( acc >= 3 ) break 17
} }
""" """
).toDouble() ).toDouble()
) )
}
} }
@Test @Test