Optimize member access via slots

This commit is contained in:
Sergey Chernov 2026-01-25 03:11:40 +03:00
parent 9b580bafb6
commit 74d73540c6
5 changed files with 1820 additions and 598 deletions

View File

@ -44,6 +44,10 @@ class Compiler(
private val currentLocalNames: MutableSet<String>? private val currentLocalNames: MutableSet<String>?
get() = localNamesStack.lastOrNull() get() = localNamesStack.lastOrNull()
private data class SlotPlan(val slots: MutableMap<String, Int>, var nextIndex: Int)
private data class SlotLocation(val slot: Int, val depth: Int)
private val slotPlanStack = mutableListOf<SlotPlan>()
// Track declared local variables count per function for precise capacity hints // Track declared local variables count per function for precise capacity hints
private val localDeclCountStack = mutableListOf<Int>() private val localDeclCountStack = mutableListOf<Int>()
private val currentLocalDeclCount: Int private val currentLocalDeclCount: Int
@ -64,6 +68,35 @@ class Compiler(
if (added && localDeclCountStack.isNotEmpty()) { if (added && localDeclCountStack.isNotEmpty()) {
localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1 localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1
} }
declareSlotName(name)
}
private fun declareSlotName(name: String) {
val plan = slotPlanStack.lastOrNull() ?: return
if (plan.slots.containsKey(name)) return
plan.slots[name] = plan.nextIndex
plan.nextIndex += 1
}
private fun buildParamSlotPlan(names: List<String>): SlotPlan {
val map = mutableMapOf<String, Int>()
var idx = 0
for (name in names) {
if (!map.containsKey(name)) {
map[name] = idx
idx++
}
}
return SlotPlan(map, idx)
}
private fun lookupSlotLocation(name: String): SlotLocation? {
for (i in slotPlanStack.indices.reversed()) {
val slot = slotPlanStack[i].slots[name] ?: continue
val depth = slotPlanStack.size - 1 - i
return SlotLocation(slot, depth)
}
return null
} }
var packageName: String? = null var packageName: String? = null
@ -243,9 +276,12 @@ class Compiler(
} }
} }
val module = importManager.prepareImport(pos, name, null) val module = importManager.prepareImport(pos, name, null)
statements += statement { statements += object : Statement() {
module.importInto(this, null) override val pos: Pos = pos
ObjVoid override suspend fun execute(scope: Scope): Obj {
module.importInto(scope, null)
return ObjVoid
}
} }
continue continue
} }
@ -376,7 +412,13 @@ class Compiler(
private suspend fun parseExpression(): Statement? { private suspend fun parseExpression(): Statement? {
val pos = cc.currentPos() val pos = cc.currentPos()
return parseExpressionLevel()?.let { a -> statement(pos) { a.evalValue(it) } } return parseExpressionLevel()?.let { ref ->
val stmtPos = pos
object : Statement() {
override val pos: Pos = stmtPos
override suspend fun execute(scope: Scope): Obj = ref.evalValue(scope)
}
}
} }
private suspend fun parseExpressionLevel(level: Int = 0): ObjRef? { private suspend fun parseExpressionLevel(level: Int = 0): ObjRef? {
@ -414,6 +456,10 @@ class Compiler(
val name = when (target) { val name = when (target) {
is LocalVarRef -> target.name is LocalVarRef -> target.name
is FastLocalVarRef -> target.name is FastLocalVarRef -> target.name
is LocalSlotRef -> target.name
is ImplicitThisMemberRef -> target.name
is ThisFieldSlotRef -> target.name
is QualifiedThisFieldSlotRef -> target.name
is FieldRef -> if (target.target is LocalVarRef && target.target.name == "this") target.name else null is FieldRef -> if (target.target is LocalVarRef && target.target.name == "this") target.name else null
else -> null else -> null
} }
@ -487,7 +533,16 @@ class Compiler(
val args = parsed.first val args = parsed.first
val tailBlock = parsed.second val tailBlock = parsed.second
isCall = true isCall = true
operand = MethodCallRef(left, next.value, args, tailBlock, isOptional) operand = when (left) {
is LocalVarRef -> if (left.name == "this") {
ThisMethodSlotCallRef(next.value, args, tailBlock, isOptional)
} else {
MethodCallRef(left, next.value, args, tailBlock, isOptional)
}
is QualifiedThisRef ->
QualifiedThisMethodSlotCallRef(left.typeName, next.value, args, tailBlock, isOptional)
else -> MethodCallRef(left, next.value, args, tailBlock, isOptional)
}
} }
@ -496,16 +551,37 @@ class Compiler(
cc.next() cc.next()
isCall = true isCall = true
val lambda = parseLambdaExpression() val lambda = parseLambdaExpression()
val argStmt = statement { lambda.get(this).value } val argPos = next.pos
val argStmt = object : Statement() {
override val pos: Pos = argPos
override suspend fun execute(scope: Scope): Obj = lambda.get(scope).value
}
val args = listOf(ParsedArgument(argStmt, next.pos)) val args = listOf(ParsedArgument(argStmt, next.pos))
operand = MethodCallRef(left, next.value, args, true, isOptional) operand = when (left) {
is LocalVarRef -> if (left.name == "this") {
ThisMethodSlotCallRef(next.value, args, true, isOptional)
} else {
MethodCallRef(left, next.value, args, true, isOptional)
}
is QualifiedThisRef ->
QualifiedThisMethodSlotCallRef(left.typeName, next.value, args, true, isOptional)
else -> MethodCallRef(left, next.value, args, true, isOptional)
}
} }
else -> {} else -> {}
} }
} }
if (!isCall) { if (!isCall) {
operand = FieldRef(left, next.value, isOptional) operand = when (left) {
is LocalVarRef -> if (left.name == "this") {
ThisFieldSlotRef(next.value, isOptional)
} else {
FieldRef(left, next.value, isOptional)
}
is QualifiedThisRef -> QualifiedThisFieldSlotRef(left.typeName, next.value, isOptional)
else -> FieldRef(left, next.value, isOptional)
}
} }
} }
@ -597,7 +673,15 @@ class Compiler(
// selector: <lvalue>, '.' , <id> // selector: <lvalue>, '.' , <id>
// we replace operand with selector code, that // we replace operand with selector code, that
// is RW: // is RW:
operand = FieldRef(left, t.value, false) operand = when (left) {
is LocalVarRef -> if (left.name == "this") {
ThisFieldSlotRef(t.value, false)
} else {
FieldRef(left, t.value, false)
}
is QualifiedThisRef -> QualifiedThisFieldSlotRef(left.typeName, t.value, false)
else -> FieldRef(left, t.value, false)
}
} ?: run { } ?: run {
// variable to read or like // variable to read or like
cc.previous() cc.previous()
@ -696,19 +780,31 @@ class Compiler(
) )
val paramNames = argsDeclaration?.params?.map { it.name } ?: emptyList() val paramNames = argsDeclaration?.params?.map { it.name } ?: emptyList()
val hasImplicitIt = argsDeclaration == null
val slotParamNames = if (hasImplicitIt) paramNames + "it" else paramNames
val paramSlotPlan = buildParamSlotPlan(slotParamNames)
label?.let { cc.labels.add(it) } label?.let { cc.labels.add(it) }
val body = inCodeContext(CodeContext.Function("<lambda>")) { slotPlanStack.add(paramSlotPlan)
withLocalNames(paramNames.toSet()) { val body = try {
inCodeContext(CodeContext.Function("<lambda>")) {
withLocalNames(slotParamNames.toSet()) {
parseBlock(skipLeadingBrace = true) parseBlock(skipLeadingBrace = true)
} }
} }
} finally {
slotPlanStack.removeLast()
}
label?.let { cc.labels.remove(it) } label?.let { cc.labels.remove(it) }
val paramSlotPlanSnapshot = if (paramSlotPlan.slots.isEmpty()) emptyMap() else paramSlotPlan.slots.toMap()
return ValueFnRef { closureScope -> return ValueFnRef { closureScope ->
statement(body.pos) { scope -> val stmt = object : Statement() {
override val pos: Pos = body.pos
override suspend fun execute(scope: Scope): Obj {
// and the source closure of the lambda which might have other thisObj. // and the source closure of the lambda which might have other thisObj.
val context = scope.applyClosure(closureScope) val context = scope.applyClosure(closureScope)
if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot)
// Execute lambda body in a closure-aware context. Blocks inside the lambda // Execute lambda body in a closure-aware context. Blocks inside the lambda
// will create child scopes as usual, so re-declarations inside loops work. // will create child scopes as usual, so re-declarations inside loops work.
if (argsDeclaration == null) { if (argsDeclaration == null) {
@ -727,13 +823,15 @@ class Compiler(
// assign vars as declared the standard way // assign vars as declared the standard way
argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val) argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val)
} }
try { return try {
body.execute(context) body.execute(context)
} catch (e: ReturnException) { } catch (e: ReturnException) {
if (e.label == null || e.label == label) e.result if (e.label == null || e.label == label) e.result
else throw e else throw e
} }
}.asReadonly }
}
stmt.asReadonly
} }
} }
@ -1122,7 +1220,12 @@ class Compiler(
val next = cc.peekNextNonWhitespace() val next = cc.peekNextNonWhitespace()
if (next.type == Token.Type.COMMA || next.type == Token.Type.RPAREN) { if (next.type == Token.Type.COMMA || next.type == Token.Type.RPAREN) {
val localVar = LocalVarRef(name, t1.pos) val localVar = LocalVarRef(name, t1.pos)
return ParsedArgument(statement(t1.pos) { localVar.evalValue(it) }, t1.pos, isSplat = false, name = name) val argPos = t1.pos
val argStmt = object : Statement() {
override val pos: Pos = argPos
override suspend fun execute(scope: Scope): Obj = localVar.evalValue(scope)
}
return ParsedArgument(argStmt, t1.pos, isSplat = false, name = name)
} }
val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'") val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'")
return ParsedArgument(rhs, t1.pos, isSplat = false, name = name) return ParsedArgument(rhs, t1.pos, isSplat = false, name = name)
@ -1166,8 +1269,9 @@ class Compiler(
val callableAccessor = parseLambdaExpression() val callableAccessor = parseLambdaExpression()
args += ParsedArgument( args += ParsedArgument(
// transform ObjRef to the callable value // transform ObjRef to the callable value
statement { object : Statement() {
callableAccessor.get(this).value override val pos: Pos = end.pos
override suspend fun execute(scope: Scope): Obj = callableAccessor.get(scope).value
}, },
end.pos end.pos
) )
@ -1194,7 +1298,12 @@ class Compiler(
val next = cc.peekNextNonWhitespace() val next = cc.peekNextNonWhitespace()
if (next.type == Token.Type.COMMA || next.type == Token.Type.RPAREN) { if (next.type == Token.Type.COMMA || next.type == Token.Type.RPAREN) {
val localVar = LocalVarRef(name, t1.pos) val localVar = LocalVarRef(name, t1.pos)
return ParsedArgument(statement(t1.pos) { localVar.evalValue(it) }, t1.pos, isSplat = false, name = name) val argPos = t1.pos
val argStmt = object : Statement() {
override val pos: Pos = argPos
override suspend fun execute(scope: Scope): Obj = localVar.evalValue(scope)
}
return ParsedArgument(argStmt, t1.pos, isSplat = false, name = name)
} }
val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'") val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'")
return ParsedArgument(rhs, t1.pos, isSplat = false, name = name) return ParsedArgument(rhs, t1.pos, isSplat = false, name = name)
@ -1246,14 +1355,21 @@ class Compiler(
// into the lambda body. This ensures expected order: // into the lambda body. This ensures expected order:
// foo { ... }.bar() == (foo { ... }).bar() // foo { ... }.bar() == (foo { ... }).bar()
val callableAccessor = parseLambdaExpression() val callableAccessor = parseLambdaExpression()
val argStmt = statement { callableAccessor.get(this).value } val argStmt = object : Statement() {
override val pos: Pos = cc.currentPos()
override suspend fun execute(scope: Scope): Obj = callableAccessor.get(scope).value
}
listOf(ParsedArgument(argStmt, cc.currentPos())) listOf(ParsedArgument(argStmt, cc.currentPos()))
} else { } else {
val r = parseArgs() val r = parseArgs()
detectedBlockArgument = r.second detectedBlockArgument = r.second
r.first r.first
} }
return CallRef(left, args, detectedBlockArgument, isOptional) return when (left) {
is ImplicitThisMemberRef ->
ImplicitThisMethodCallRef(left.name, args, detectedBlockArgument, isOptional, left.atPos)
else -> CallRef(left, args, detectedBlockArgument, isOptional)
}
} }
private suspend fun parseAccessor(): ObjRef? { private suspend fun parseAccessor(): ObjRef? {
@ -1301,9 +1417,17 @@ class Compiler(
"null" -> ConstRef(ObjNull.asReadonly) "null" -> ConstRef(ObjNull.asReadonly)
"true" -> ConstRef(ObjTrue.asReadonly) "true" -> ConstRef(ObjTrue.asReadonly)
"false" -> ConstRef(ObjFalse.asReadonly) "false" -> ConstRef(ObjFalse.asReadonly)
else -> if (PerfFlags.EMIT_FAST_LOCAL_REFS && (currentLocalNames?.contains(t.value) == true)) else -> {
val slotLoc = lookupSlotLocation(t.value)
val inClassCtx = codeContexts.any { it is CodeContext.ClassBody }
when {
slotLoc != null -> LocalSlotRef(t.value, slotLoc.slot, slotLoc.depth, t.pos)
PerfFlags.EMIT_FAST_LOCAL_REFS && (currentLocalNames?.contains(t.value) == true) ->
FastLocalVarRef(t.value, t.pos) FastLocalVarRef(t.value, t.pos)
else LocalVarRef(t.value, t.pos) inClassCtx -> ImplicitThisMemberRef(t.value, t.pos)
else -> LocalVarRef(t.value, t.pos)
}
}
} }
} }
@ -1526,20 +1650,27 @@ class Compiler(
lastParsedBlockRange?.let { range -> lastParsedBlockRange?.let { range ->
miniSink?.onInitDecl(MiniInitDecl(MiniRange(id.pos, range.end), id.pos)) miniSink?.onInitDecl(MiniInitDecl(MiniRange(id.pos, range.end), id.pos))
} }
val initStmt = statement(id.pos) { scp -> val initPos = id.pos
val cls = scp.thisObj.objClass val initStmt = object : Statement() {
val saved = scp.currentClassCtx override val pos: Pos = initPos
scp.currentClassCtx = cls override suspend fun execute(scope: Scope): Obj {
val cls = scope.thisObj.objClass
val saved = scope.currentClassCtx
scope.currentClassCtx = cls
try { try {
block.execute(scp) block.execute(scope)
} finally { } finally {
scp.currentClassCtx = saved scope.currentClassCtx = saved
} }
ObjVoid return ObjVoid
}
}
object : Statement() {
override val pos: Pos = id.pos
override suspend fun execute(scope: Scope): Obj {
scope.currentClassCtx?.instanceInitializers?.add(initStmt)
return ObjVoid
} }
statement {
currentClassCtx?.instanceInitializers?.add(initStmt)
ObjVoid
} }
} else null } else null
} }
@ -1695,9 +1826,13 @@ class Compiler(
// we need a copy in the closure: // we need a copy in the closure:
val isIn = t.type == Token.Type.IN val isIn = t.type == Token.Type.IN
val container = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected") val container = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected")
currentCondition += statement { val condPos = t.pos
val r = container.execute(this).contains(this, whenValue) currentCondition += object : Statement() {
ObjBool(if (isIn) r else !r) override val pos: Pos = condPos
override suspend fun execute(scope: Scope): Obj {
val r = container.execute(scope).contains(scope, whenValue)
return ObjBool(if (isIn) r else !r)
}
} }
} }
@ -1705,9 +1840,13 @@ class Compiler(
// we need a copy in the closure: // we need a copy in the closure:
val isIn = t.type == Token.Type.IS val isIn = t.type == Token.Type.IS
val caseType = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected") val caseType = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected")
currentCondition += statement { val condPos = t.pos
val r = whenValue.isInstanceOf(caseType.execute(this)) currentCondition += object : Statement() {
ObjBool(if (isIn) r else !r) override val pos: Pos = condPos
override suspend fun execute(scope: Scope): Obj {
val r = whenValue.isInstanceOf(caseType.execute(scope))
return ObjBool(if (isIn) r else !r)
}
} }
} }
@ -1737,8 +1876,12 @@ class Compiler(
cc.previous() cc.previous()
val x = parseExpression() val x = parseExpression()
?: throw ScriptError(cc.currentPos(), "when case condition expected") ?: throw ScriptError(cc.currentPos(), "when case condition expected")
currentCondition += statement { val condPos = t.pos
ObjBool(x.execute(this).compareTo(this, whenValue) == 0) currentCondition += object : Statement() {
override val pos: Pos = condPos
override suspend fun execute(scope: Scope): Obj {
return ObjBool(x.execute(scope).compareTo(scope, whenValue) == 0)
}
} }
} }
} }
@ -1750,19 +1893,24 @@ class Compiler(
for (c in currentCondition) cases += WhenCase(c, block) for (c in currentCondition) cases += WhenCase(c, block)
} }
} }
statement { val whenPos = t.pos
object : Statement() {
override val pos: Pos = whenPos
override suspend fun execute(scope: Scope): Obj {
var result: Obj = ObjVoid var result: Obj = ObjVoid
// in / is and like uses whenValue from closure: // in / is and like uses whenValue from closure:
whenValue = value.execute(this) whenValue = value.execute(scope)
var found = false var found = false
for (c in cases) for (c in cases) {
if (c.condition.execute(this).toBool()) { if (c.condition.execute(scope).toBool()) {
result = c.block.execute(this) result = c.block.execute(scope)
found = true found = true
break break
} }
if (!found && elseCase != null) result = elseCase.execute(this) }
result if (!found && elseCase != null) result = elseCase.execute(scope)
return result
}
} }
} else { } else {
// when { cond -> ... } // when { cond -> ... }
@ -1774,10 +1922,12 @@ class Compiler(
val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected") val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected")
// Important: bind the created statement to the position of the `throw` keyword so that // Important: bind the created statement to the position of the `throw` keyword so that
// any raised error reports the correct source location. // any raised error reports the correct source location.
return statement(start) { sc -> return object : Statement() {
var errorObject = throwStatement.execute(sc) override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
var errorObject = throwStatement.execute(scope)
// Rebind error scope to the throw-site position so ScriptError.pos is accurate // Rebind error scope to the throw-site position so ScriptError.pos is accurate
val throwScope = sc.createChildScope(pos = start) val throwScope = scope.createChildScope(pos = start)
if (errorObject is ObjString) { if (errorObject is ObjString) {
errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() } errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() }
} }
@ -1794,9 +1944,11 @@ class Compiler(
).apply { getStackTrace() } ).apply { getStackTrace() }
throwScope.raiseError(errorObject) throwScope.raiseError(errorObject)
} else { } else {
val msg = errorObject.invokeInstanceMethod(sc, "message").toString(sc).value val msg = errorObject.invokeInstanceMethod(scope, "message").toString(scope).value
throwScope.raiseError(errorObject, start, msg) throwScope.raiseError(errorObject, start, msg)
} }
return ObjVoid
}
} }
} }
@ -1868,11 +2020,14 @@ class Compiler(
if (catches.isEmpty() && finallyClause == null) if (catches.isEmpty() && finallyClause == null)
throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both") throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both")
return statement { val stmtPos = body.pos
return object : Statement() {
override val pos: Pos = stmtPos
override suspend fun execute(scope: Scope): Obj {
var result: Obj = ObjVoid var result: Obj = ObjVoid
try { try {
// body is a parsed block, it already has separate context // body is a parsed block, it already has separate context
result = body.execute(this) result = body.execute(scope)
} catch (e: ReturnException) { } catch (e: ReturnException) {
throw e throw e
} catch (e: LoopBreakContinueException) { } catch (e: LoopBreakContinueException) {
@ -1881,22 +2036,22 @@ class Compiler(
// convert to appropriate exception // convert to appropriate exception
val caughtObj = when (e) { val caughtObj = when (e) {
is ExecutionError -> e.errorObject is ExecutionError -> e.errorObject
else -> ObjUnknownException(this, e.message ?: e.toString()) else -> ObjUnknownException(scope, e.message ?: e.toString())
} }
// let's see if we should catch it: // let's see if we should catch it:
var isCaught = false var isCaught = false
for (cdata in catches) { for (cdata in catches) {
var match: Obj? = null var match: Obj? = null
for (exceptionClassName in cdata.classNames) { for (exceptionClassName in cdata.classNames) {
val exObj = this[exceptionClassName]?.value as? ObjClass val exObj = scope[exceptionClassName]?.value as? ObjClass
?: raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName") ?: scope.raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName")
if (caughtObj.isInstanceOf(exObj)) { if (caughtObj.isInstanceOf(exObj)) {
match = caughtObj match = caughtObj
break break
} }
} }
if (match != null) { if (match != null) {
val catchContext = this.createChildScope(pos = cdata.catchVar.pos) val catchContext = scope.createChildScope(pos = cdata.catchVar.pos)
catchContext.addItem(cdata.catchVar.value, false, caughtObj) catchContext.addItem(cdata.catchVar.value, false, caughtObj)
result = cdata.block.execute(catchContext) result = cdata.block.execute(catchContext)
isCaught = true isCaught = true
@ -1908,9 +2063,10 @@ class Compiler(
throw e throw e
} finally { } finally {
// finally clause does not alter result! // finally clause does not alter result!
finallyClause?.execute(this) finallyClause?.execute(scope)
}
return result
} }
result
} }
} }
@ -1964,9 +2120,13 @@ class Compiler(
) )
) )
return statement { val stmtPos = startPos
ObjEnumClass.createSimpleEnum(nameToken.value, names).also { return object : Statement() {
addItem(nameToken.value, false, it, recordType = ObjRecord.Type.Enum) override val pos: Pos = stmtPos
override suspend fun execute(scope: Scope): Obj {
val enumClass = ObjEnumClass.createSimpleEnum(nameToken.value, names)
scope.addItem(nameToken.value, false, enumClass, recordType = ObjRecord.Type.Enum)
return enumClass
} }
} }
} }
@ -2052,9 +2212,11 @@ class Compiler(
val initScope = popInitScope() val initScope = popInitScope()
return statement(startPos) { context -> return object : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
val parentClasses = baseSpecs.map { baseSpec -> val parentClasses = baseSpecs.map { baseSpec ->
val rec = context[baseSpec.name] ?: throw ScriptError(startPos, "unknown base class: ${baseSpec.name}") val rec = scope[baseSpec.name] ?: throw ScriptError(startPos, "unknown base class: ${baseSpec.name}")
(rec.value as? ObjClass) ?: throw ScriptError(startPos, "${baseSpec.name} is not a class") (rec.value as? ObjClass) ?: throw ScriptError(startPos, "${baseSpec.name} is not a class")
} }
@ -2067,7 +2229,7 @@ class Compiler(
if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList
} }
val classScope = context.createChildScope(newThisObj = newClass) val classScope = scope.createChildScope(newThisObj = newClass)
classScope.currentClassCtx = newClass classScope.currentClassCtx = newClass
newClass.classScope = classScope newClass.classScope = classScope
classScope.addConst("object", newClass) classScope.addConst("object", newClass)
@ -2075,10 +2237,11 @@ class Compiler(
bodyInit?.execute(classScope) bodyInit?.execute(classScope)
// Create instance (singleton) // Create instance (singleton)
val instance = newClass.callOn(context.createChildScope(Arguments.EMPTY)) val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
if (nameToken != null) if (nameToken != null)
context.addItem(className, false, instance) scope.addItem(className, false, instance)
instance return instance
}
} }
} }
@ -2198,24 +2361,29 @@ class Compiler(
// create instance constructor // create instance constructor
// create custom objClass with all fields and instance constructor // create custom objClass with all fields and instance constructor
val constructorCode = statement { val constructorCode = object : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
// constructor code is registered with class instance and is called over // constructor code is registered with class instance and is called over
// new `thisObj` already set by class to ObjInstance.instanceContext // new `thisObj` already set by class to ObjInstance.instanceContext
val instance = thisObj as ObjInstance val instance = scope.thisObj as ObjInstance
// Constructor parameters have been assigned to instance scope by ObjClass.callOn before // Constructor parameters have been assigned to instance scope by ObjClass.callOn before
// invoking parent/child constructors. // invoking parent/child constructors.
// IMPORTANT: do not execute class body here; class body was executed once in the class scope // IMPORTANT: do not execute class body here; class body was executed once in the class scope
// to register methods and prepare initializers. Instance constructor should be empty unless // to register methods and prepare initializers. Instance constructor should be empty unless
// we later add explicit constructor body syntax. // we later add explicit constructor body syntax.
instance return instance
} }
statement { }
object : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
// the main statement should create custom ObjClass instance with field // the main statement should create custom ObjClass instance with field
// accessors, constructor registration, etc. // accessors, constructor registration, etc.
// Resolve parent classes by name at execution time // Resolve parent classes by name at execution time
val parentClasses = baseSpecs.map { baseSpec -> val parentClasses = baseSpecs.map { baseSpec ->
val rec = val rec =
this[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}") scope[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}")
(rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class") (rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class")
} }
@ -2247,9 +2415,9 @@ class Compiler(
} }
} }
addItem(className, false, newClass) scope.addItem(className, false, newClass)
// Prepare class scope for class-scope members (static) and future registrations // Prepare class scope for class-scope members (static) and future registrations
val classScope = createChildScope(newThisObj = newClass) val classScope = scope.createChildScope(newThisObj = newClass)
// Set lexical class context for visibility tagging inside class body // Set lexical class context for visibility tagging inside class body
classScope.currentClassCtx = newClass classScope.currentClassCtx = newClass
newClass.classScope = classScope newClass.classScope = classScope
@ -2261,7 +2429,8 @@ class Compiler(
} }
newClass.checkAbstractSatisfaction(nameToken.pos) newClass.checkAbstractSatisfaction(nameToken.pos)
// Debug summary: list registered instance methods and class-scope functions for this class // Debug summary: list registered instance methods and class-scope functions for this class
newClass return newClass
}
} }
} }
@ -2319,8 +2488,10 @@ class Compiler(
Triple(loopParsed.first, loopParsed.second, elseStmt) Triple(loopParsed.first, loopParsed.second, elseStmt)
} }
return statement(body.pos) { cxt -> return object : Statement() {
val forContext = cxt.createChildScope(start) override val pos: Pos = body.pos
override suspend fun execute(scope: Scope): Obj {
val forContext = scope.createChildScope(start)
// loop var: StoredObject // loop var: StoredObject
val loopSO = forContext.addItem(tVar.value, true, ObjNull) val loopSO = forContext.addItem(tVar.value, true, ObjNull)
@ -2329,7 +2500,7 @@ class Compiler(
val sourceObj = source.execute(forContext) val sourceObj = source.execute(forContext)
if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) { if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) {
loopIntRange( return loopIntRange(
forContext, forContext,
sourceObj.start!!.toLong(), sourceObj.start!!.toLong(),
if (sourceObj.isEndInclusive) if (sourceObj.isEndInclusive)
@ -2343,7 +2514,7 @@ class Compiler(
canBreak canBreak
) )
} else if (sourceObj.isInstanceOf(ObjIterable)) { } else if (sourceObj.isInstanceOf(ObjIterable)) {
loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak) return loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
} else { } else {
val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() } val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() }
.getOrElse { .getOrElse {
@ -2362,7 +2533,7 @@ class Compiler(
.getOrElse { .getOrElse {
throw ScriptError( throw ScriptError(
tOp.pos, tOp.pos,
"object is not enumerable: no index access for ${sourceObj.inspect(cxt)}", "object is not enumerable: no index access for ${sourceObj.inspect(scope)}",
it it
) )
} }
@ -2387,9 +2558,10 @@ class Compiler(
} }
} }
if (!breakCaught && elseStatement != null) { if (!breakCaught && elseStatement != null) {
result = elseStatement.execute(cxt) result = elseStatement.execute(scope)
}
return result
} }
result
} }
} }
} else { } else {
@ -2484,11 +2656,13 @@ class Compiler(
null null
} }
return statement(body.pos) { return object : Statement() {
override val pos: Pos = body.pos
override suspend fun execute(scope: Scope): Obj {
var wasBroken = false var wasBroken = false
var result: Obj = ObjVoid var result: Obj = ObjVoid
while (true) { while (true) {
val doScope = it.createChildScope().apply { skipScopeCreation = true } val doScope = scope.createChildScope().apply { skipScopeCreation = true }
try { try {
result = body.execute(doScope) result = body.execute(doScope)
} catch (e: LoopBreakContinueException) { } catch (e: LoopBreakContinueException) {
@ -2507,8 +2681,9 @@ class Compiler(
break break
} }
} }
if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) } if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) }
result return result
}
} }
} }
@ -2532,11 +2707,13 @@ class Compiler(
cc.previous() cc.previous()
null null
} }
return statement(body.pos) { return object : Statement() {
override val pos: Pos = body.pos
override suspend fun execute(scope: Scope): Obj {
var result: Obj = ObjVoid var result: Obj = ObjVoid
var wasBroken = false var wasBroken = false
while (condition.execute(it).toBool()) { while (condition.execute(scope).toBool()) {
val loopScope = it.createChildScope() val loopScope = scope.createChildScope()
if (canBreak) { if (canBreak) {
try { try {
result = body.execute(loopScope) result = body.execute(loopScope)
@ -2554,8 +2731,9 @@ class Compiler(
} else } else
result = body.execute(loopScope) result = body.execute(loopScope)
} }
if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) } if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) }
result return result
}
} }
} }
@ -2590,8 +2768,10 @@ class Compiler(
cc.addBreak() cc.addBreak()
return statement(start) { return object : Statement() {
val returnValue = resultExpr?.execute(it)// ?: ObjVoid override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
val returnValue = resultExpr?.execute(scope)// ?: ObjVoid
throw LoopBreakContinueException( throw LoopBreakContinueException(
doContinue = false, doContinue = false,
label = label, label = label,
@ -2599,6 +2779,7 @@ class Compiler(
) )
} }
} }
}
private fun parseContinueStatement(start: Pos): Statement { private fun parseContinueStatement(start: Pos): Statement {
val t = cc.next() val t = cc.next()
@ -2614,13 +2795,16 @@ class Compiler(
} }
cc.addBreak() cc.addBreak()
return statement(start) { return object : Statement() {
override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
throw LoopBreakContinueException( throw LoopBreakContinueException(
doContinue = true, doContinue = true,
label = label, label = label,
) )
} }
} }
}
private suspend fun parseReturnStatement(start: Pos): Statement { private suspend fun parseReturnStatement(start: Pos): Statement {
var t = cc.next() var t = cc.next()
@ -2648,11 +2832,14 @@ class Compiler(
parseExpression() parseExpression()
} else null } else null
return statement(start) { return object : Statement() {
val returnValue = resultExpr?.execute(it) ?: ObjVoid override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
val returnValue = resultExpr?.execute(scope) ?: ObjVoid
throw ReturnException(returnValue, label) throw ReturnException(returnValue, label)
} }
} }
}
private fun ensureRparen(): Pos { private fun ensureRparen(): Pos {
val t = cc.next() val t = cc.next()
@ -2686,19 +2873,24 @@ class Compiler(
return if (t2.type == Token.Type.ID && t2.value == "else") { return if (t2.type == Token.Type.ID && t2.value == "else") {
val elseBody = val elseBody =
parseStatement() ?: throw ScriptError(pos, "Bad else statement: expected statement") parseStatement() ?: throw ScriptError(pos, "Bad else statement: expected statement")
return statement(start) { return object : Statement() {
if (condition.execute(it).toBool()) override val pos: Pos = start
ifBody.execute(it) override suspend fun execute(scope: Scope): Obj {
return if (condition.execute(scope).toBool())
ifBody.execute(scope)
else else
elseBody.execute(it) elseBody.execute(scope)
}
} }
} else { } else {
cc.previous() cc.previous()
statement(start) { object : Statement() {
if (condition.execute(it).toBool()) override val pos: Pos = start
ifBody.execute(it) override suspend fun execute(scope: Scope): Obj {
else if (condition.execute(scope).toBool())
ObjVoid return ifBody.execute(scope)
return ObjVoid
}
} }
} }
} }
@ -2803,13 +2995,22 @@ class Compiler(
cc.labels.add(name) cc.labels.add(name)
outerLabel?.let { cc.labels.add(it) } outerLabel?.let { cc.labels.add(it) }
val paramNames: Set<String> = argsDeclaration.params.map { it.name }.toSet() val paramNamesList = argsDeclaration.params.map { it.name }
val paramNames: Set<String> = paramNamesList.toSet()
val paramSlotPlan = buildParamSlotPlan(paramNamesList)
// Parse function body while tracking declared locals to compute precise capacity hints // Parse function body while tracking declared locals to compute precise capacity hints
currentLocalDeclCount currentLocalDeclCount
localDeclCountStack.add(0) localDeclCountStack.add(0)
val fnStatements = if (actualExtern) slotPlanStack.add(paramSlotPlan)
statement { raiseError("extern function not provided: $name") } val fnStatements = try {
if (actualExtern)
object : Statement() {
override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
scope.raiseError("extern function not provided: $name")
}
}
else if (isAbstract || isDelegated) { else if (isAbstract || isDelegated) {
null null
} else } else
@ -2821,19 +3022,26 @@ class Compiler(
throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function") throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function")
val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression") val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression")
// Shorthand function returns the expression value // Shorthand function returns the expression value
statement(expr.pos) { scope -> object : Statement() {
expr.execute(scope) override val pos: Pos = expr.pos
override suspend fun execute(scope: Scope): Obj = expr.execute(scope)
} }
} else { } else {
parseBlock() parseBlock()
} }
} }
} finally {
slotPlanStack.removeLast()
}
// Capture and pop the local declarations count for this function // Capture and pop the local declarations count for this function
val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0 val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0
var closure: Scope? = null var closure: Scope? = null
val fnBody = statement(t.pos) { callerContext -> val paramSlotPlanSnapshot = if (paramSlotPlan.slots.isEmpty()) emptyMap() else paramSlotPlan.slots.toMap()
val fnBody = object : Statement() {
override val pos: Pos = t.pos
override suspend fun execute(callerContext: Scope): Obj {
callerContext.pos = start callerContext.pos = start
// restore closure where the function was defined, and making a copy of it // restore closure where the function was defined, and making a copy of it
@ -2845,23 +3053,27 @@ class Compiler(
// Capacity hint: parameters + declared locals + small overhead // Capacity hint: parameters + declared locals + small overhead
val capacityHint = paramNames.size + fnLocalDecls + 4 val capacityHint = paramNames.size + fnLocalDecls + 4
context.hintLocalCapacity(capacityHint) context.hintLocalCapacity(capacityHint)
if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot)
// load params from caller context // load params from caller context
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val) argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
if (extTypeName != null) { if (extTypeName != null) {
context.thisObj = callerContext.thisObj context.thisObj = callerContext.thisObj
} }
try { return try {
fnStatements?.execute(context) ?: ObjVoid fnStatements?.execute(context) ?: ObjVoid
} catch (e: ReturnException) { } catch (e: ReturnException) {
if (e.label == null || e.label == name || e.label == outerLabel) e.result if (e.label == null || e.label == name || e.label == outerLabel) e.result
else throw e else throw e
} }
} }
}
cc.labels.remove(name) cc.labels.remove(name)
outerLabel?.let { cc.labels.remove(it) } outerLabel?.let { cc.labels.remove(it) }
// parentContext // parentContext
val fnCreateStatement = statement(start) { context -> val fnCreateStatement = object : Statement() {
override val pos: Pos = start
override suspend fun execute(context: Scope): Obj {
if (isDelegated) { if (isDelegated) {
val accessType = context.resolveQualifiedIdentifier("DelegateAccess.Callable") val accessType = context.resolveQualifiedIdentifier("DelegateAccess.Callable")
val initValue = delegateExpression!!.execute(context) val initValue = delegateExpression!!.execute(context)
@ -2877,7 +3089,7 @@ class Compiler(
context.addExtension(type, name, ObjRecord(ObjUnset, isMutable = false, visibility = visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply { context.addExtension(type, name, ObjRecord(ObjUnset, isMutable = false, visibility = visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply {
delegate = finalDelegate delegate = finalDelegate
}) })
return@statement ObjVoid return ObjVoid
} }
val th = context.thisObj val th = context.thisObj
@ -2892,7 +3104,9 @@ class Compiler(
val cls: ObjClass = th val cls: ObjClass = th
val storageName = "${cls.className}::$name" val storageName = "${cls.className}::$name"
cls.createField(name, ObjUnset, false, visibility, null, start, declaringClass = cls, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient, type = ObjRecord.Type.Delegated) cls.createField(name, ObjUnset, false, visibility, null, start, declaringClass = cls, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient, type = ObjRecord.Type.Delegated)
cls.instanceInitializers += statement(start) { scp -> cls.instanceInitializers += object : Statement() {
override val pos: Pos = start
override suspend fun execute(scp: Scope): Obj {
val accessType2 = scp.resolveQualifiedIdentifier("DelegateAccess.Callable") val accessType2 = scp.resolveQualifiedIdentifier("DelegateAccess.Callable")
val initValue2 = delegateExpression.execute(scp) val initValue2 = delegateExpression.execute(scp)
val finalDelegate2 = try { val finalDelegate2 = try {
@ -2903,14 +3117,15 @@ class Compiler(
scp.addItem(storageName, false, ObjUnset, visibility, null, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient).apply { scp.addItem(storageName, false, ObjUnset, visibility, null, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient).apply {
delegate = finalDelegate2 delegate = finalDelegate2
} }
ObjVoid return ObjVoid
}
} }
} else { } else {
context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply {
delegate = finalDelegate delegate = finalDelegate
} }
} }
return@statement ObjVoid return ObjVoid
} }
// we added fn in the context. now we must save closure // we added fn in the context. now we must save closure
@ -2925,13 +3140,17 @@ class Compiler(
// class extension method // class extension method
val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found") val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found")
if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance") if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance")
val stmt = statement { val stmt = object : Statement() {
override val pos: Pos = start
override suspend fun execute(scope: Scope): Obj {
// ObjInstance has a fixed instance scope, so we need to build a closure // ObjInstance has a fixed instance scope, so we need to build a closure
(thisObj as? ObjInstance)?.let { i -> val result = (scope.thisObj as? ObjInstance)?.let { i ->
annotatedFnBody.execute(ClosureScope(this, i.instanceScope)) annotatedFnBody.execute(ClosureScope(scope, i.instanceScope))
} }
// other classes can create one-time scope for this rare case: // other classes can create one-time scope for this rare case:
?: annotatedFnBody.execute(thisObj.autoInstanceScope(this)) ?: annotatedFnBody.execute(scope.thisObj.autoInstanceScope(scope))
return result
}
} }
context.addExtension(type, name, ObjRecord(stmt, isMutable = false, visibility = visibility, declaringClass = null)) context.addExtension(type, name, ObjRecord(stmt, isMutable = false, visibility = visibility, declaringClass = null))
} }
@ -2971,7 +3190,8 @@ class Compiler(
} }
// as the function can be called from anywhere, we have // as the function can be called from anywhere, we have
// saved the proper context in the closure // saved the proper context in the closure
annotatedFnBody return annotatedFnBody
}
} }
if (isStatic) { if (isStatic) {
currentInitScope += fnCreateStatement currentInitScope += fnCreateStatement
@ -3013,11 +3233,24 @@ class Compiler(
if (t.type != Token.Type.LBRACE) if (t.type != Token.Type.LBRACE)
throw ScriptError(t.pos, "Expected block body start: {") throw ScriptError(t.pos, "Expected block body start: {")
} }
val block = parseScript() val blockSlotPlan = SlotPlan(mutableMapOf(), 0)
return statement(startPos) { slotPlanStack.add(blockSlotPlan)
val block = try {
parseScript()
} finally {
slotPlanStack.removeLast()
}
val planSnapshot = if (blockSlotPlan.slots.isEmpty()) emptyMap() else blockSlotPlan.slots.toMap()
val stmt = object : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
// block run on inner context: // block run on inner context:
block.execute(if (it.skipScopeCreation) it else it.createChildScope(startPos)) val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos)
}.also { if (planSnapshot.isNotEmpty()) target.applySlotPlan(planSnapshot)
return block.execute(target)
}
}
return stmt.also {
val t1 = cc.next() val t1 = cc.next()
if (t1.type != Token.Type.RBRACE) if (t1.type != Token.Type.RBRACE)
throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }") throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }")
@ -3079,7 +3312,9 @@ class Compiler(
val names = mutableListOf<String>() val names = mutableListOf<String>()
pattern.forEachVariable { names.add(it) } pattern.forEachVariable { names.add(it) }
return statement(start) { context -> return object : Statement() {
override val pos: Pos = start
override suspend fun execute(context: Scope): Obj {
val value = initialExpression.execute(context) val value = initialExpression.execute(context)
for (name in names) { for (name in names) {
context.addItem(name, true, ObjVoid, visibility, isTransient = isTransient) context.addItem(name, true, ObjVoid, visibility, isTransient = isTransient)
@ -3094,7 +3329,8 @@ class Compiler(
context.updateSlotFor(name, immutableRec) context.updateSlotFor(name, immutableRec)
} }
} }
ObjVoid return ObjVoid
}
} }
} }

View File

@ -392,6 +392,24 @@ open class Scope(
nameToSlot[name]?.let { slots[it] = record } nameToSlot[name]?.let { slots[it] = record }
} }
/**
* Apply a precomputed slot plan (name -> slot index) for this scope.
* This enables direct slot references to bypass name-based lookup.
*/
fun applySlotPlan(plan: Map<String, Int>) {
if (plan.isEmpty()) return
val maxIndex = plan.values.maxOrNull() ?: return
if (slots.size <= maxIndex) {
val targetSize = maxIndex + 1
while (slots.size < targetSize) {
slots.add(ObjRecord(ObjUnset, isMutable = true))
}
}
for ((name, idx) in plan) {
nameToSlot[name] = idx
}
}
/** /**
* Clear all references and maps to prevent memory leaks when pooled. * Clear all references and maps to prevent memory leaks when pooled.
*/ */
@ -503,6 +521,7 @@ open class Scope(
if (this is ClosureScope) { if (this is ClosureScope) {
callScope.localBindings[name] = it callScope.localBindings[name] = it
} }
bumpClassLayoutIfNeeded(name, value, recordType)
it it
} ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride) } ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride)
@ -529,6 +548,24 @@ open class Scope(
isTransient = isTransient isTransient = isTransient
) )
objects[name] = rec objects[name] = rec
bumpClassLayoutIfNeeded(name, value, recordType)
if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) {
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
if (inst != null) {
val slot = inst.objClass.fieldSlotForKey(name)
if (slot != null) inst.setFieldSlotRecord(slot.slot, rec)
}
}
if (value is Statement ||
recordType == ObjRecord.Type.Fun ||
recordType == ObjRecord.Type.Delegated ||
recordType == ObjRecord.Type.Property) {
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
if (inst != null) {
val slot = inst.objClass.methodSlotForKey(name)
if (slot != null) inst.setMethodSlotRecord(slot.slot, rec)
}
}
// Index this binding within the current frame to help resolve locals across suspension // Index this binding within the current frame to help resolve locals across suspension
localBindings[name] = rec localBindings[name] = rec
// If we are a ClosureScope, mirror binding into the caller frame to keep it discoverable // If we are a ClosureScope, mirror binding into the caller frame to keep it discoverable
@ -558,6 +595,14 @@ open class Scope(
return rec return rec
} }
private fun bumpClassLayoutIfNeeded(name: String, value: Obj, recordType: ObjRecord.Type) {
val cls = thisObj as? net.sergeych.lyng.obj.ObjClass ?: return
if (cls.classScope !== this) return
if (!(value is Statement || recordType == ObjRecord.Type.Fun || recordType == ObjRecord.Type.Delegated)) return
if (cls.members.containsKey(name)) return
cls.layoutVersion += 1
}
fun getOrCreateNamespace(name: String): ObjClass { fun getOrCreateNamespace(name: String): ObjClass {
val ns = objects.getOrPut(name) { ObjRecord(ObjNamespace(name), isMutable = false) }.value val ns = objects.getOrPut(name) { ObjRecord(ObjNamespace(name), isMutable = false) }.value
return ns.objClass return ns.objClass

View File

@ -114,8 +114,7 @@ open class ObjClass(
val classId: Long = ClassIdGen.nextId() val classId: Long = ClassIdGen.nextId()
var layoutVersion: Int = 0 var layoutVersion: Int = 0
private val mangledNameCache = mutableMapOf<String, String>() fun mangledName(name: String): String = "$className::$name"
fun mangledName(name: String): String = mangledNameCache.getOrPut(name) { "$className::$name" }
/** /**
* Map of public member names to their effective storage keys in instanceScope.objects. * Map of public member names to their effective storage keys in instanceScope.objects.
@ -128,7 +127,7 @@ open class ObjClass(
if (cls.className == "Obj") continue if (cls.className == "Obj") continue
for ((name, rec) in cls.members) { for ((name, rec) in cls.members) {
if (rec.visibility == Visibility.Public) { if (rec.visibility == Visibility.Public) {
val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
res[name] = key res[name] = key
} }
} }
@ -267,6 +266,119 @@ open class ObjClass(
*/ */
internal val members = mutableMapOf<String, ObjRecord>() internal val members = mutableMapOf<String, ObjRecord>()
internal data class FieldSlot(val slot: Int, val record: ObjRecord)
internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass)
internal data class MethodSlot(val slot: Int, val record: ObjRecord)
private var fieldSlotLayoutVersion: Int = -1
private var fieldSlotMap: Map<String, FieldSlot> = emptyMap()
private var fieldSlotCount: Int = 0
private var instanceMemberLayoutVersion: Int = -1
private var instanceMemberCache: Map<String, ResolvedMember> = emptyMap()
private var methodSlotLayoutVersion: Int = -1
private var methodSlotMap: Map<String, MethodSlot> = emptyMap()
private var methodSlotCount: Int = 0
private fun ensureFieldSlots(): Map<String, FieldSlot> {
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
val res = mutableMapOf<String, FieldSlot>()
var idx = 0
for (cls in mro) {
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
val key = cls.mangledName(name)
if (res.containsKey(key)) continue
res[key] = FieldSlot(idx, rec)
idx += 1
}
}
fieldSlotMap = res
fieldSlotCount = idx
fieldSlotLayoutVersion = layoutVersion
return fieldSlotMap
}
private fun ensureInstanceMemberCache(): Map<String, ResolvedMember> {
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
val res = mutableMapOf<String, ResolvedMember>()
for (cls in mro) {
if (cls.className == "Obj") break
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (res.containsKey(name)) continue
val decl = rec.declaringClass ?: cls
res[name] = ResolvedMember(rec, decl)
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach
if (res.containsKey(name)) return@forEach
val decl = rec.declaringClass ?: cls
res[name] = ResolvedMember(rec, decl)
}
}
instanceMemberCache = res
instanceMemberLayoutVersion = layoutVersion
return instanceMemberCache
}
private fun ensureMethodSlots(): Map<String, MethodSlot> {
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
val res = mutableMapOf<String, MethodSlot>()
var idx = 0
for (cls in mro) {
if (cls.className == "Obj") break
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (rec.value !is Statement &&
rec.type != ObjRecord.Type.Delegated &&
rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property) {
continue
}
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (res.containsKey(key)) continue
res[key] = MethodSlot(idx, rec)
idx += 1
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach
if (rec.value !is Statement &&
rec.type != ObjRecord.Type.Delegated &&
rec.type != ObjRecord.Type.Property) return@forEach
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (res.containsKey(key)) return@forEach
res[key] = MethodSlot(idx, rec)
idx += 1
}
}
methodSlotMap = res
methodSlotCount = idx
methodSlotLayoutVersion = layoutVersion
return methodSlotMap
}
internal fun fieldSlotCount(): Int {
ensureFieldSlots()
return fieldSlotCount
}
internal fun fieldSlotForKey(key: String): FieldSlot? {
ensureFieldSlots()
return fieldSlotMap[key]
}
internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots()
internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name]
internal fun methodSlotCount(): Int {
ensureMethodSlots()
return methodSlotCount
}
internal fun methodSlotForKey(key: String): MethodSlot? {
ensureMethodSlots()
return methodSlotMap[key]
}
internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots()
override fun toString(): String = className override fun toString(): String = className
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1 override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
@ -284,8 +396,8 @@ open class ObjClass(
for (cls in mro) { for (cls in mro) {
// 1) members-defined methods and fields // 1) members-defined methods and fields
for ((k, v) in cls.members) { for ((k, v) in cls.members) {
if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field)) { if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField)) {
val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k
if (!res.containsKey(key)) { if (!res.containsKey(key)) {
res[key] = v res[key] = v
} }
@ -327,12 +439,47 @@ open class ObjClass(
val stableParent = classScope ?: scope.parent val stableParent = classScope ?: scope.parent
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance) instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
instance.instanceScope.currentClassCtx = null instance.instanceScope.currentClassCtx = null
val fieldSlots = fieldSlotMap()
if (fieldSlots.isNotEmpty()) {
instance.initFieldSlots(fieldSlotCount())
}
val methodSlots = methodSlotMap()
if (methodSlots.isNotEmpty()) {
instance.initMethodSlots(methodSlotCount())
}
// Expose instance methods (and other callable members) directly in the instance scope for fast lookup // Expose instance methods (and other callable members) directly in the instance scope for fast lookup
// This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust // This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust
instance.instanceScope.objects.putAll(templateMethods) instance.instanceScope.objects.putAll(templateMethods)
if (methodSlots.isNotEmpty()) {
for ((key, rec) in templateMethods) {
val slot = methodSlots[key]
if (slot != null) {
instance.setMethodSlotRecord(slot.slot, rec)
}
}
}
for (p in templateOthers) { for (p in templateOthers) {
instance.instanceScope.objects[p.first] = p.second.copy() val rec = p.second.copy()
instance.instanceScope.objects[p.first] = rec
val slot = fieldSlots[p.first]
if (slot != null) {
instance.setFieldSlotRecord(slot.slot, rec)
}
if (methodSlots.isNotEmpty()) {
val mSlot = methodSlots[p.first]
if (mSlot != null) {
instance.setMethodSlotRecord(mSlot.slot, rec)
}
}
}
if (methodSlots.isNotEmpty()) {
for ((_, mSlot) in methodSlots) {
val idx = mSlot.slot
if (idx >= 0 && idx < instance.methodSlots.size && instance.methodSlots[idx] == null) {
instance.setMethodSlotRecord(idx, mSlot.record)
}
}
} }
return instance return instance
} }
@ -417,6 +564,10 @@ open class ObjClass(
if (rec != null) { if (rec != null) {
val mangled = c.mangledName(p.name) val mangled = c.mangledName(p.name)
instance.instanceScope.objects[mangled] = rec instance.instanceScope.objects[mangled] = rec
val slot = instance.objClass.fieldSlotForKey(mangled)
if (slot != null) {
instance.setFieldSlotRecord(slot.slot, rec)
}
} }
} }
} }
@ -738,5 +889,3 @@ open class ObjClass(
scope.raiseNotImplemented() scope.raiseNotImplemented()
} }

View File

@ -30,6 +30,36 @@ import net.sergeych.lynon.LynonType
class ObjInstance(override val objClass: ObjClass) : Obj() { class ObjInstance(override val objClass: ObjClass) : Obj() {
internal lateinit var instanceScope: Scope internal lateinit var instanceScope: Scope
internal var fieldSlots: Array<ObjRecord?> = emptyArray()
internal var methodSlots: Array<ObjRecord?> = emptyArray()
internal fun initFieldSlots(size: Int) {
fieldSlots = arrayOfNulls(size)
}
internal fun setFieldSlotRecord(slot: Int, rec: ObjRecord) {
if (slot >= 0 && slot < fieldSlots.size) fieldSlots[slot] = rec
}
internal fun initMethodSlots(size: Int) {
methodSlots = arrayOfNulls(size)
}
internal fun setMethodSlotRecord(slot: Int, rec: ObjRecord) {
if (slot >= 0 && slot < methodSlots.size) methodSlots[slot] = rec
}
internal fun fieldRecordForKey(key: String): ObjRecord? {
val slot = objClass.fieldSlotForKey(key) ?: return null
val idx = slot.slot
return if (idx >= 0 && idx < fieldSlots.size) fieldSlots[idx] else null
}
internal fun methodRecordForKey(key: String): ObjRecord? {
val slot = objClass.methodSlotForKey(key) ?: return null
val idx = slot.slot
return if (idx >= 0 && idx < methodSlots.size) methodSlots[idx] else null
}
override suspend fun readField(scope: Scope, name: String): ObjRecord { override suspend fun readField(scope: Scope, name: String): ObjRecord {
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
@ -37,6 +67,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// Fast path for public members when outside any class context // Fast path for public members when outside any class context
if (caller == null) { if (caller == null) {
objClass.publicMemberResolution[name]?.let { key -> objClass.publicMemberResolution[name]?.let { key ->
fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) ?: objClass
return resolveRecord(scope, rec, name, decl)
}
}
instanceScope.objects[key]?.let { rec -> instanceScope.objects[key]?.let { rec ->
// Directly return fields to bypass resolveRecord overhead // Directly return fields to bypass resolveRecord overhead
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
@ -56,6 +96,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
} }
// Check for private fields (stored in instanceScope) // Check for private fields (stored in instanceScope)
val mangled = c.mangledName(name) val mangled = c.mangledName(name)
fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return resolveRecord(scope, rec, name, c)
}
}
methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return resolveRecord(scope, rec, name, c)
}
}
instanceScope.objects[mangled]?.let { rec -> instanceScope.objects[mangled]?.let { rec ->
if (rec.visibility == Visibility.Private) { if (rec.visibility == Visibility.Private) {
return resolveRecord(scope, rec, name, c) return resolveRecord(scope, rec, name, c)
@ -67,6 +117,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
for (cls in objClass.mro) { for (cls in objClass.mro) {
if (cls.className == "Obj") break if (cls.className == "Obj") break
val mangled = cls.mangledName(name) val mangled = cls.mangledName(name)
fieldRecordForKey(mangled)?.let { rec ->
if (canAccessMember(rec.visibility, cls, caller, name)) {
return resolveRecord(scope, rec, name, cls)
}
}
methodRecordForKey(mangled)?.let { rec ->
if (canAccessMember(rec.visibility, cls, caller, name)) {
return resolveRecord(scope, rec, name, cls)
}
}
instanceScope.objects[mangled]?.let { rec -> instanceScope.objects[mangled]?.let { rec ->
if (canAccessMember(rec.visibility, cls, caller, name)) { if (canAccessMember(rec.visibility, cls, caller, name)) {
return resolveRecord(scope, rec, name, cls) return resolveRecord(scope, rec, name, cls)
@ -81,6 +141,12 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return resolveRecord(scope, rec, name, decl) return resolveRecord(scope, rec, name, decl)
} }
} }
methodRecordForKey(name)?.let { rec ->
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.visibility, decl, caller, name)) {
return resolveRecord(scope, rec, name, decl)
}
}
// 3. Fall back to super (handles class members and extensions) // 3. Fall back to super (handles class members and extensions)
return super.readField(scope, name) return super.readField(scope, name)
@ -109,10 +175,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
val d = decl ?: obj.declaringClass val d = decl ?: obj.declaringClass
if (d != null) { if (d != null) {
val mangled = d.mangledName(name) val mangled = d.mangledName(name)
fieldRecordForKey(mangled)?.let {
targetRec = it
}
if (targetRec === obj) {
instanceScope.objects[mangled]?.let { instanceScope.objects[mangled]?.let {
targetRec = it targetRec = it
} }
} }
}
if (targetRec === obj) { if (targetRec === obj) {
instanceScope.objects[name]?.let { rec -> instanceScope.objects[name]?.let { rec ->
// Check if this record in instanceScope is the one we want. // Check if this record in instanceScope is the one we want.
@ -134,10 +205,29 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// Fast path for public members when outside any class context // Fast path for public members when outside any class context
if (caller == null) { if (caller == null) {
objClass.publicMemberResolution[name]?.let { key -> objClass.publicMemberResolution[name]?.let { key ->
fieldRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public) {
// Skip property/delegated overhead if it's a plain mutable field
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && rec.isMutable && !rec.isAbstract) {
if (rec.value.assign(scope, newValue) == null)
rec.value = newValue
return
}
updateRecord(scope, rec, name, newValue, rec.declaringClass)
return
}
}
methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
updateRecord(scope, rec, name, newValue, rec.declaringClass)
return
}
}
instanceScope.objects[key]?.let { rec -> instanceScope.objects[key]?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public) { if (rec.effectiveWriteVisibility == Visibility.Public) {
// Skip property/delegated overhead if it's a plain mutable field // Skip property/delegated overhead if it's a plain mutable field
if (rec.type == ObjRecord.Type.Field && rec.isMutable && !rec.isAbstract) { if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && rec.isMutable && !rec.isAbstract) {
if (rec.value.assign(scope, newValue) == null) if (rec.value.assign(scope, newValue) == null)
rec.value = newValue rec.value = newValue
return return
@ -160,6 +250,19 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
} }
// Check for private fields (stored in instanceScope) // Check for private fields (stored in instanceScope)
val mangled = c.mangledName(name) val mangled = c.mangledName(name)
fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
updateRecord(scope, rec, name, newValue, c)
return
}
}
methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
updateRecord(scope, rec, name, newValue, c)
return
}
}
instanceScope.objects[mangled]?.let { rec -> instanceScope.objects[mangled]?.let { rec ->
if (rec.visibility == Visibility.Private) { if (rec.visibility == Visibility.Private) {
updateRecord(scope, rec, name, newValue, c) updateRecord(scope, rec, name, newValue, c)
@ -172,6 +275,19 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
for (cls in objClass.mro) { for (cls in objClass.mro) {
if (cls.className == "Obj") break if (cls.className == "Obj") break
val mangled = cls.mangledName(name) val mangled = cls.mangledName(name)
fieldRecordForKey(mangled)?.let { rec ->
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) {
updateRecord(scope, rec, name, newValue, cls)
return
}
}
methodRecordForKey(mangled)?.let { rec ->
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name) &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
updateRecord(scope, rec, name, newValue, cls)
return
}
}
instanceScope.objects[mangled]?.let { rec -> instanceScope.objects[mangled]?.let { rec ->
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) { if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) {
updateRecord(scope, rec, name, newValue, cls) updateRecord(scope, rec, name, newValue, cls)
@ -188,6 +304,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return return
} }
} }
methodRecordForKey(name)?.let { rec ->
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name) &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
updateRecord(scope, rec, name, newValue, decl)
return
}
}
super.writeField(scope, name, newValue) super.writeField(scope, name, newValue)
} }
@ -225,6 +349,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// Fast path for public members when outside any class context // Fast path for public members when outside any class context
if (caller == null) { if (caller == null) {
objClass.publicMemberResolution[name]?.let { key -> objClass.publicMemberResolution[name]?.let { key ->
methodRecordForKey(key)?.let { rec ->
if (rec.visibility == Visibility.Public && !rec.isAbstract) {
val decl = rec.declaringClass
if (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(instanceScope, this, args, decl)
}
}
}
instanceScope.objects[key]?.let { rec -> instanceScope.objects[key]?.let { rec ->
if (rec.visibility == Visibility.Public && !rec.isAbstract) { if (rec.visibility == Visibility.Public && !rec.isAbstract) {
val decl = rec.declaringClass val decl = rec.declaringClass
@ -241,6 +375,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
// 0. Prefer private member of current class context // 0. Prefer private member of current class context
caller?.let { c -> caller?.let { c ->
val mangled = c.mangledName(name) val mangled = c.mangledName(name)
methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(instanceScope, this, args, c)
}
}
}
instanceScope.objects[mangled]?.let { rec -> instanceScope.objects[mangled]?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) { if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) { if (rec.type == ObjRecord.Type.Property) {
@ -261,13 +404,23 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
} }
} }
// 1. Walk MRO to find member, handling delegation // Fast path for non-delegated instance methods in class context
for (cls in objClass.mro) { methodRecordForKey(name)?.let { rec ->
if (cls.className == "Obj") break if (!rec.isAbstract && rec.type == ObjRecord.Type.Fun) {
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name) val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) ?: objClass
if (rec != null && !rec.isAbstract) { val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
if (canAccessMember(rec.visibility, decl, effectiveCaller, name)) {
return rec.value.invoke(instanceScope, this, args, decl)
}
}
}
// 1. Resolve instance member via cached MRO lookup, handling delegation
objClass.resolveInstanceMember(name)?.let { resolvedMember ->
val rec = resolvedMember.record
val decl = resolvedMember.declaringClass
if (rec.type == ObjRecord.Type.Delegated) { if (rec.type == ObjRecord.Type.Delegated) {
val storageName = cls.mangledName(name) val storageName = decl.mangledName(name)
val del = instanceScope[storageName]?.delegate ?: rec.delegate val del = instanceScope[storageName]?.delegate ?: rec.delegate
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)") ?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
@ -276,10 +429,9 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = { return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
// Fallback: property delegation (getValue then call result) // Fallback: property delegation (getValue then call result)
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))) val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
propVal.invoke(scope, this, args, rec.declaringClass ?: cls) propVal.invoke(scope, this, args, rec.declaringClass ?: decl)
}) })
} }
val decl = rec.declaringClass ?: cls
val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name)) if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError( scope.raiseError(
@ -303,7 +455,6 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return resolved.value.invoke(scope, this, args, resolved.declaringClass) return resolved.value.invoke(scope, this, args, resolved.declaringClass)
} }
} }
}
// 2. Fall back to super (handles extensions and root fallback) // 2. Fall back to super (handles extensions and root fallback)
return super.invokeInstanceMethod(scope, name, args, onNotFoundResult) return super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
@ -431,6 +582,14 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
override suspend fun readField(scope: Scope, name: String): ObjRecord { override suspend fun readField(scope: Scope, name: String): ObjRecord {
// Qualified field access: prefer mangled storage for the qualified ancestor // Qualified field access: prefer mangled storage for the qualified ancestor
val mangled = "${startClass.className}::$name" val mangled = "${startClass.className}::$name"
instance.fieldRecordForKey(mangled)?.let { rec ->
// Visibility: declaring class is the qualified ancestor for mangled storage
val decl = rec.declaringClass ?: startClass
val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
return instance.resolveRecord(scope, rec, name, decl)
}
instance.instanceScope.objects[mangled]?.let { rec -> instance.instanceScope.objects[mangled]?.let { rec ->
// Visibility: declaring class is the qualified ancestor for mangled storage // Visibility: declaring class is the qualified ancestor for mangled storage
val decl = rec.declaringClass ?: startClass val decl = rec.declaringClass ?: startClass
@ -467,6 +626,18 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
// Qualified write: target mangled storage for the ancestor // Qualified write: target mangled storage for the ancestor
val mangled = "${startClass.className}::$name" val mangled = "${startClass.className}::$name"
instance.fieldRecordForKey(mangled)?.let { f ->
val decl = f.declaringClass ?: startClass
val caller = scope.currentClassCtx
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
ObjIllegalAccessException(
scope,
"can't assign to field $name (declared in ${decl.className})"
).raise()
if (!f.isMutable && f.value !== ObjUnset) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
if (f.value.assign(scope, newValue) == null) f.value = newValue
return
}
instance.instanceScope.objects[mangled]?.let { f -> instance.instanceScope.objects[mangled]?.let { f ->
val decl = f.declaringClass ?: startClass val decl = f.declaringClass ?: startClass
val caller = scope.currentClassCtx val caller = scope.currentClassCtx

View File

@ -381,7 +381,7 @@ class CastRef(
} }
/** Qualified `this@Type`: resolves to a view of current `this` starting dispatch from the ancestor Type. */ /** Qualified `this@Type`: resolves to a view of current `this` starting dispatch from the ancestor Type. */
class QualifiedThisRef(private val typeName: String, private val atPos: Pos) : ObjRef { class QualifiedThisRef(val typeName: String, private val atPos: Pos) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord { override suspend fun get(scope: Scope): ObjRecord {
val t = scope[typeName]?.value as? ObjClass val t = scope[typeName]?.value as? ObjClass
?: scope.raiseError("unknown type $typeName") ?: scope.raiseError("unknown type $typeName")
@ -403,6 +403,188 @@ class QualifiedThisRef(private val typeName: String, private val atPos: Pos) : O
} }
} }
private suspend fun resolveQualifiedThisInstance(scope: Scope, typeName: String): Pair<ObjInstance, ObjClass> {
val t = scope[typeName]?.value as? ObjClass
?: scope.raiseError("unknown type $typeName")
var s: Scope? = scope
while (s != null) {
val inst = s.thisObj as? ObjInstance
if (inst != null && (inst.objClass === t || inst.objClass.allParentsSet.contains(t))) {
return inst to t
}
s = s.parent
}
scope.raiseClassCastError(
"No instance of type ${t.className} found in the scope chain"
)
}
/**
* Fast path for direct `this@Type.name` access using slot maps when possible.
*/
class QualifiedThisFieldSlotRef(
private val typeName: String,
val name: String,
private val isOptional: Boolean
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
if (isOptional && inst == ObjNull) return ObjNull.asMutable
if (startClass !== inst.objClass) {
return ObjQualifiedView(inst, startClass).readField(scope, name)
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return inst.resolveRecord(scope, rec, name, caller)
}
}
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return inst.resolveRecord(scope, rec, name, caller)
}
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
inst.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
return inst.resolveRecord(scope, rec, name, decl)
}
}
return inst.readField(scope, name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
if (isOptional && inst == ObjNull) return
if (startClass !== inst.objClass) {
ObjQualifiedView(inst, startClass).writeField(scope, name, newValue)
return
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
writeDirectOrFallback(scope, inst, rec, name, newValue, caller)
return
}
}
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
inst.writeField(scope, name, newValue)
return
}
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
writeDirectOrFallback(scope, inst, rec, name, newValue, decl)
return
}
}
inst.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
inst.writeField(scope, name, newValue)
return
}
}
inst.writeField(scope, name, newValue)
}
private suspend fun writeDirectOrFallback(
scope: Scope,
inst: ObjInstance,
rec: ObjRecord,
name: String,
newValue: Obj,
decl: ObjClass?
) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
inst.writeField(scope, name, newValue)
}
}
}
/**
* Fast path for direct `this@Type.method(...)` calls using slots when the qualifier is the
* dynamic class. Otherwise falls back to a qualified view dispatch.
*/
class QualifiedThisMethodSlotCallRef(
private val typeName: String,
private val name: String,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
override suspend fun evalValue(scope: Scope): Obj {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
if (isOptional && inst == ObjNull) return ObjNull
val callArgs = args.toArguments(scope, tailBlock)
if (startClass !== inst.objClass) {
return ObjQualifiedView(inst, startClass).invokeInstanceMethod(scope, name, callArgs, null)
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, caller)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(inst.instanceScope, inst, callArgs, caller)
}
}
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
val effectiveCaller = caller ?: if (scope.thisObj === inst) inst.objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
}
}
}
return inst.invokeInstanceMethod(scope, name, callArgs)
}
}
/** Assignment compound op: target op= value */ /** Assignment compound op: target op= value */
class AssignOpRef( class AssignOpRef(
private val op: BinOp, private val op: BinOp,
@ -691,9 +873,20 @@ class FieldRef(
if (effectiveKey != null) { if (effectiveKey != null) {
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc ->
if (obj is ObjInstance && obj.objClass === cls) { if (obj is ObjInstance && obj.objClass === cls) {
val rec = obj.instanceScope.objects[effectiveKey] val slot = cls.fieldSlotForKey(effectiveKey)
if (slot != null) {
val idx = slot.slot
val rec = if (idx >= 0 && idx < obj.fieldSlots.size) obj.fieldSlots[idx] else null
if (rec != null &&
(rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) &&
!rec.isAbstract) {
rec
} else obj.readField(sc, name)
} else {
val rec = obj.fieldRecordForKey(effectiveKey) ?: obj.instanceScope.objects[effectiveKey]
if (rec != null && rec.type != ObjRecord.Type.Delegated) rec if (rec != null && rec.type != ObjRecord.Type.Delegated) rec
else obj.readField(sc, name) else obj.readField(sc, name)
}
} else obj.readField(sc, name) } else obj.readField(sc, name)
} }
} else { } else {
@ -809,10 +1002,24 @@ class FieldRef(
if (effectiveKey != null) { if (effectiveKey != null) {
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, nv -> wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, nv ->
if (obj is ObjInstance && obj.objClass === cls) { if (obj is ObjInstance && obj.objClass === cls) {
val rec = obj.instanceScope.objects[effectiveKey] val slot = cls.fieldSlotForKey(effectiveKey)
if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && rec.type == ObjRecord.Type.Field) { if (slot != null) {
val idx = slot.slot
val rec = if (idx >= 0 && idx < obj.fieldSlots.size) obj.fieldSlots[idx] else null
if (rec != null &&
rec.effectiveWriteVisibility == Visibility.Public &&
rec.isMutable &&
(rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) &&
!rec.isAbstract) {
if (rec.value.assign(sc, nv) == null) rec.value = nv if (rec.value.assign(sc, nv) == null) rec.value = nv
} else obj.writeField(sc, name, nv) } else obj.writeField(sc, name, nv)
} else {
val rec = obj.fieldRecordForKey(effectiveKey) ?: obj.instanceScope.objects[effectiveKey]
if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable &&
(rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField)) {
if (rec.value.assign(sc, nv) == null) rec.value = nv
} else obj.writeField(sc, name, nv)
}
} else obj.writeField(sc, name, nv) } else obj.writeField(sc, name, nv)
} }
} else { } else {
@ -890,6 +1097,113 @@ class FieldRef(
} }
} }
/**
* Fast path for direct `this.name` access using slot maps.
* Falls back to normal member resolution when needed.
*/
class ThisFieldSlotRef(
val name: String,
private val isOptional: Boolean
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord {
val th = scope.thisObj
if (th == ObjNull && isOptional) return ObjNull.asMutable
if (th !is ObjInstance) return th.readField(scope, name)
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, caller)
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, caller)
}
}
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
th.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
return th.resolveRecord(scope, rec, name, decl)
}
}
return th.readField(scope, name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val th = scope.thisObj
if (th == ObjNull && isOptional) return
if (th !is ObjInstance) {
th.writeField(scope, name, newValue)
return
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
writeDirectOrFallback(scope, th, rec, name, newValue, caller)
return
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
writeDirectOrFallback(scope, th, rec, name, newValue, decl)
return
}
}
th.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
th.writeField(scope, name, newValue)
}
private suspend fun writeDirectOrFallback(
scope: Scope,
inst: ObjInstance,
rec: ObjRecord,
name: String,
newValue: Obj,
decl: ObjClass?
) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
inst.writeField(scope, name, newValue)
}
}
}
/** /**
* Reference to index access (a[i]) with optional chaining. * Reference to index access (a[i]) with optional chaining.
*/ */
@ -1336,37 +1650,51 @@ class MethodCallRef(
is ObjInstance -> { is ObjInstance -> {
// Prefer resolved class member to avoid per-call lookup on hit // Prefer resolved class member to avoid per-call lookup on hit
// BUT only if it's NOT a root object member (which can be shadowed by extensions) // BUT only if it's NOT a root object member (which can be shadowed by extensions)
var hierarchyMember: ObjRecord? = null
val cls0 = base.objClass val cls0 = base.objClass
val keyInScope = cls0.publicMemberResolution[name] val keyInScope = cls0.publicMemberResolution[name]
if (keyInScope != null) { val methodSlot = if (keyInScope != null) cls0.methodSlotForKey(keyInScope) else null
val rec = base.instanceScope.objects[keyInScope] val fastRec = if (methodSlot != null) {
if (rec != null && rec.type == ObjRecord.Type.Fun) { val idx = methodSlot.slot
hierarchyMember = rec if (idx >= 0 && idx < base.methodSlots.size) base.methodSlots[idx] else null
} } else if (keyInScope != null) {
} base.methodRecordForKey(keyInScope) ?: base.instanceScope.objects[keyInScope]
} else null
val resolved = if (fastRec != null) null else cls0.resolveInstanceMember(name)
if (hierarchyMember == null) { val targetRec = when {
for (cls in base.objClass.mro) { fastRec != null && fastRec.type == ObjRecord.Type.Fun -> fastRec
if (cls.className == "Obj") break resolved != null && resolved.record.type == ObjRecord.Type.Fun && !resolved.record.isAbstract -> resolved.record
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name) else -> null
if (rec != null && !rec.isAbstract && rec.type != ObjRecord.Type.Field) { }
hierarchyMember = rec if (targetRec != null) {
break val visibility = targetRec.visibility
val decl = targetRec.declaringClass ?: (resolved?.declaringClass ?: cls0)
if (methodSlot != null && targetRec.type == ObjRecord.Type.Fun) {
val slotIndex = methodSlot.slot
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
val inst = obj as ObjInstance
if (inst.objClass === cls0) {
val rec = if (slotIndex >= 0 && slotIndex < inst.methodSlots.size) inst.methodSlots[slotIndex] else null
if (rec != null && rec.type == ObjRecord.Type.Fun && !rec.isAbstract) {
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
rec.value.invoke(inst.instanceScope, inst, a, decl)
} else {
obj.invokeInstanceMethod(sc, name, a)
}
} else {
obj.invokeInstanceMethod(sc, name, a)
} }
} }
} } else {
val callable = targetRec.value
if (hierarchyMember != null) {
val visibility = hierarchyMember.visibility
val callable = hierarchyMember.value
val decl = hierarchyMember.declaringClass ?: base.objClass
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
val inst = obj as ObjInstance val inst = obj as ObjInstance
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name")) sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
callable.invoke(inst.instanceScope, inst, a) callable.invoke(inst.instanceScope, inst, a)
} }
}
} else { } else {
// Fallback to name-based lookup per call (handles extensions and root members) // Fallback to name-based lookup per call (handles extensions and root members)
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) } mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
@ -1399,6 +1727,57 @@ class MethodCallRef(
} }
} }
/**
* Fast path for direct `this.method(...)` calls using slot maps.
* Falls back to normal invoke semantics when needed.
*/
class ThisMethodSlotCallRef(
private val name: String,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
override suspend fun evalValue(scope: Scope): Obj {
val base = scope.thisObj
if (base == ObjNull && isOptional) return ObjNull
val callArgs = args.toArguments(scope, tailBlock)
if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs)
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
base.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, caller)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(base.instanceScope, base, callArgs, caller)
}
}
}
}
val key = base.objClass.publicMemberResolution[name] ?: name
base.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: base.objClass.findDeclaringClassOf(name) ?: base.objClass
val effectiveCaller = caller ?: if (scope.thisObj === base) base.objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(base.instanceScope, base, callArgs, decl)
}
}
}
return base.invokeInstanceMethod(scope, name, callArgs)
}
}
/** /**
* Reference to a local/visible variable by name (Phase A: scope lookup). * Reference to a local/visible variable by name (Phase A: scope lookup).
*/ */
@ -1729,6 +2108,240 @@ class FastLocalVarRef(
} }
} }
/**
* Identifier reference in class context that prefers member slots on `this` after local lookup.
* Falls back to normal scope lookup for globals/outer scopes.
*/
class ImplicitThisMemberRef(
val name: String,
val atPos: Pos
) : ObjRef {
override fun forEachVariable(block: (String) -> Unit) {
block(name)
}
override fun forEachVariableWithPos(block: (String, Pos) -> Unit) {
block(name, atPos)
}
override suspend fun get(scope: Scope): ObjRecord {
scope.pos = atPos
val caller = scope.currentClassCtx
val th = scope.thisObj
// 1) locals in the same `this` chain
var s: Scope? = scope
while (s != null && s.thisObj === th) {
scope.tryGetLocalRecord(s, name, caller)?.let { return it }
s = s.parent
}
// 2) member slots on this instance
if (th is ObjInstance) {
// private member access for current class context
caller?.let { c ->
val mangled = c.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, c)
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, c)
}
}
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
th.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
return th.resolveRecord(scope, rec, name, decl)
}
}
}
// 3) fallback to normal scope resolution (globals/outer scopes)
scope[name]?.let { return it }
try {
return th.readField(scope, name)
} catch (e: ExecutionError) {
if ((e.message ?: "").contains("no such field: $name")) scope.raiseSymbolNotFound(name)
throw e
}
}
override suspend fun evalValue(scope: Scope): Obj {
val rec = get(scope)
return scope.resolve(rec, name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
scope.pos = atPos
val caller = scope.currentClassCtx
val th = scope.thisObj
// 1) locals in the same `this` chain
var s: Scope? = scope
while (s != null && s.thisObj === th) {
val rec = scope.tryGetLocalRecord(s, name, caller)
if (rec != null) {
scope.assign(rec, name, newValue)
return
}
s = s.parent
}
// 2) member slots on this instance
if (th is ObjInstance) {
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
th.writeField(scope, name, newValue)
}
return
}
}
th.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
}
// 3) fallback to normal scope resolution
scope[name]?.let { stored ->
scope.assign(stored, name, newValue)
return
}
th.writeField(scope, name, newValue)
}
}
/**
* Fast path for implicit member calls in class bodies: `foo(...)` resolves locals first,
* then falls back to member lookup on `this`.
*/
class ImplicitThisMethodCallRef(
private val name: String,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean,
private val atPos: Pos
) : ObjRef {
private val memberRef = ImplicitThisMemberRef(name, atPos)
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
override suspend fun evalValue(scope: Scope): Obj {
scope.pos = atPos
val callee = memberRef.evalValue(scope)
if (callee == ObjNull && isOptional) return ObjNull
val callArgs = args.toArguments(scope, tailBlock)
val usePool = PerfFlags.SCOPE_POOL
return if (usePool) {
scope.withChildFrame(callArgs) { child ->
callee.callOn(child)
}
} else {
callee.callOn(scope.createChildScope(scope.pos, callArgs))
}
}
}
/**
* Direct local slot reference with known slot index and lexical depth.
* Depth=0 means current scope, depth=1 means parent scope, etc.
*/
class LocalSlotRef(
val name: String,
private val slot: Int,
private val depth: Int,
private val atPos: Pos,
) : ObjRef {
override fun forEachVariable(block: (String) -> Unit) {
block(name)
}
private val fallbackRef = LocalVarRef(name, atPos)
private var cachedFrameId: Long = 0L
private var cachedOwner: Scope? = null
private var cachedOwnerVerified: Boolean = false
private fun resolveOwner(scope: Scope): Scope? {
if (cachedOwner != null && cachedFrameId == scope.frameId && cachedOwnerVerified) return cachedOwner
var s: Scope? = scope
var remaining = depth
while (s != null && remaining > 0) {
s = s.parent
remaining--
}
if (s == null || s.getSlotIndexOf(name) != slot) {
cachedOwner = null
cachedOwnerVerified = false
cachedFrameId = scope.frameId
return null
}
cachedOwner = s
cachedOwnerVerified = true
cachedFrameId = scope.frameId
return s
}
override suspend fun get(scope: Scope): ObjRecord {
scope.pos = atPos
val owner = resolveOwner(scope) ?: return fallbackRef.get(scope)
if (slot < 0 || slot >= owner.slotCount()) return fallbackRef.get(scope)
val rec = owner.getSlotRecord(slot)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
}
return rec
}
override suspend fun evalValue(scope: Scope): Obj {
scope.pos = atPos
val owner = resolveOwner(scope) ?: return fallbackRef.evalValue(scope)
if (slot < 0 || slot >= owner.slotCount()) return fallbackRef.evalValue(scope)
val rec = owner.getSlotRecord(slot)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
}
return scope.resolve(rec, name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
scope.pos = atPos
val owner = resolveOwner(scope) ?: run {
fallbackRef.setAt(pos, scope, newValue)
return
}
if (slot < 0 || slot >= owner.slotCount()) {
fallbackRef.setAt(pos, scope, newValue)
return
}
val rec = owner.getSlotRecord(slot)
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
}
scope.assign(rec, name, newValue)
}
}
class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef { class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
override fun forEachVariable(block: (String) -> Unit) { override fun forEachVariable(block: (String) -> Unit) {
for (e in entries) { for (e in entries) {
@ -1910,7 +2523,15 @@ class AssignRef(
val v = value.evalValue(scope) val v = value.evalValue(scope)
// For properties, we should not call get() on target because it invokes the getter. // For properties, we should not call get() on target because it invokes the getter.
// Instead, we call setAt directly. // Instead, we call setAt directly.
if (target is FieldRef || target is IndexRef || target is LocalVarRef || target is FastLocalVarRef || target is BoundLocalVarRef) { if (target is FieldRef ||
target is IndexRef ||
target is LocalVarRef ||
target is FastLocalVarRef ||
target is BoundLocalVarRef ||
target is LocalSlotRef ||
target is ThisFieldSlotRef ||
target is QualifiedThisFieldSlotRef ||
target is ImplicitThisMemberRef) {
target.setAt(atPos, scope, v) target.setAt(atPos, scope, v)
} else { } else {
val rec = target.get(scope) val rec = target.get(scope)