Step 24E: frame capture refs

This commit is contained in:
Sergey Chernov 2026-02-11 20:33:58 +03:00
parent c14c7d43d9
commit 99ca15d20f
15 changed files with 547 additions and 173 deletions

View File

@ -106,9 +106,9 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
- [x] Keep interpreter path using `ClosureScope` until interpreter removal. - [x] Keep interpreter path using `ClosureScope` until interpreter removal.
- [x] JVM tests must be green before commit. - [x] JVM tests must be green before commit.
- [x] Step 24E: Isolate interpreter-only capture logic. - [x] Step 24E: Isolate interpreter-only capture logic.
- [ ] Mark `resolveCaptureRecord` paths as interpreter-only. - [x] Mark `resolveCaptureRecord` paths as interpreter-only.
- [ ] Guard or delete any bytecode path that tries to sync captures into scopes. - [x] Guard or delete any bytecode path that tries to sync captures into scopes.
- [ ] JVM tests must be green before commit. - [x] JVM tests must be green before commit.
## Interpreter Removal (next) ## Interpreter Removal (next)

View File

@ -31,23 +31,33 @@ class BlockStatement(
val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos) val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos)
if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan) if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan)
if (captureSlots.isNotEmpty()) { if (captureSlots.isNotEmpty()) {
val applyScope = scope as? ApplyScope val captureRecords = scope.captureRecords
for (capture in captureSlots) { if (captureRecords != null) {
// Interpreter-only capture resolution; bytecode paths must use captureRecords instead. for (i in captureSlots.indices) {
val rec = if (applyScope != null) { val capture = captureSlots[i]
applyScope.resolveCaptureRecord(capture.name) val rec = captureRecords.getOrNull(i)
?: applyScope.callScope.resolveCaptureRecord(capture.name) ?: scope.raiseSymbolNotFound("capture ${capture.name} not found")
} else { target.updateSlotFor(capture.name, rec)
scope.resolveCaptureRecord(capture.name)
} }
if (rec == null) { } else {
if (scope.getSlotIndexOf(capture.name) == null && scope.getLocalRecordDirect(capture.name) == null) { val applyScope = scope as? ApplyScope
continue for (capture in captureSlots) {
// Interpreter-only capture resolution; bytecode paths must use captureRecords instead.
val rec = if (applyScope != null) {
applyScope.resolveCaptureRecord(capture.name)
?: applyScope.callScope.resolveCaptureRecord(capture.name)
} else {
scope.resolveCaptureRecord(capture.name)
} }
(applyScope?.callScope ?: scope) if (rec == null) {
.raiseSymbolNotFound("symbol ${capture.name} not found") if (scope.getSlotIndexOf(capture.name) == null && scope.getLocalRecordDirect(capture.name) == null) {
continue
}
(applyScope?.callScope ?: scope)
.raiseSymbolNotFound("symbol ${capture.name} not found")
}
target.updateSlotFor(capture.name, rec)
} }
target.updateSlotFor(capture.name, rec)
} }
} }
return block.execute(target) return block.execute(target)

View File

@ -1705,9 +1705,12 @@ class Compiler(
private val currentRangeParamNames: Set<String> private val currentRangeParamNames: Set<String>
get() = rangeParamNamesStack.lastOrNull() ?: emptySet() get() = rangeParamNamesStack.lastOrNull() ?: emptySet()
private val capturePlanStack = mutableListOf<CapturePlan>() private val capturePlanStack = mutableListOf<CapturePlan>()
private var lambdaDepth = 0
private data class CapturePlan( private data class CapturePlan(
val slotPlan: SlotPlan, val slotPlan: SlotPlan,
val isFunction: Boolean,
val propagateToParentFunction: Boolean,
val captures: MutableList<CaptureSlot> = mutableListOf(), val captures: MutableList<CaptureSlot> = mutableListOf(),
val captureMap: MutableMap<String, CaptureSlot> = mutableMapOf(), val captureMap: MutableMap<String, CaptureSlot> = mutableMapOf(),
val captureOwners: MutableMap<String, SlotLocation> = mutableMapOf() val captureOwners: MutableMap<String, SlotLocation> = mutableMapOf()
@ -1715,6 +1718,19 @@ class Compiler(
private fun recordCaptureSlot(name: String, slotLoc: SlotLocation) { private fun recordCaptureSlot(name: String, slotLoc: SlotLocation) {
val plan = capturePlanStack.lastOrNull() ?: return val plan = capturePlanStack.lastOrNull() ?: return
recordCaptureSlotInto(plan, name, slotLoc)
if (plan.propagateToParentFunction) {
for (i in capturePlanStack.size - 2 downTo 0) {
val parent = capturePlanStack[i]
if (parent.isFunction) {
recordCaptureSlotInto(parent, name, slotLoc)
break
}
}
}
}
private fun recordCaptureSlotInto(plan: CapturePlan, name: String, slotLoc: SlotLocation) {
if (plan.captureMap.containsKey(name)) return if (plan.captureMap.containsKey(name)) return
val capture = CaptureSlot( val capture = CaptureSlot(
name = name, name = name,
@ -1734,6 +1750,12 @@ class Compiler(
private fun captureLocalRef(name: String, slotLoc: SlotLocation, pos: Pos): LocalSlotRef? { private fun captureLocalRef(name: String, slotLoc: SlotLocation, pos: Pos): LocalSlotRef? {
if (capturePlanStack.isEmpty() || slotLoc.depth == 0) return null if (capturePlanStack.isEmpty() || slotLoc.depth == 0) return null
val functionPlan = capturePlanStack.asReversed().firstOrNull { it.isFunction } ?: return null
val functionIndex = functionPlan?.let { plan ->
slotPlanStack.indexOfLast { it.id == plan.slotPlan.id }
} ?: -1
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
val moduleId = moduleSlotPlan()?.id val moduleId = moduleSlotPlan()?.id
if (moduleId != null && slotLoc.scopeId == moduleId) return null if (moduleId != null && slotLoc.scopeId == moduleId) return null
recordCaptureSlot(name, slotLoc) recordCaptureSlot(name, slotLoc)
@ -2158,6 +2180,7 @@ class Compiler(
stmt.label, stmt.label,
stmt.canBreak, stmt.canBreak,
stmt.loopSlotPlan, stmt.loopSlotPlan,
stmt.loopScopeId,
stmt.pos stmt.pos
) )
} }
@ -2843,7 +2866,7 @@ class Compiler(
label?.let { cc.labels.add(it) } label?.let { cc.labels.add(it) }
slotPlanStack.add(paramSlotPlan) slotPlanStack.add(paramSlotPlan)
val capturePlan = CapturePlan(paramSlotPlan) val capturePlan = CapturePlan(paramSlotPlan, isFunction = true, propagateToParentFunction = false)
capturePlanStack.add(capturePlan) capturePlanStack.add(capturePlan)
val parsedBody = try { val parsedBody = try {
inCodeContext(CodeContext.Function("<lambda>", implicitThisMembers = true, implicitThisTypeName = expectedReceiverType)) { inCodeContext(CodeContext.Function("<lambda>", implicitThisMembers = true, implicitThisTypeName = expectedReceiverType)) {
@ -2854,8 +2877,13 @@ class Compiler(
for (param in slotParamNames) { for (param in slotParamNames) {
resolutionSink?.declareSymbol(param, SymbolKind.PARAM, isMutable = false, pos = startPos) resolutionSink?.declareSymbol(param, SymbolKind.PARAM, isMutable = false, pos = startPos)
} }
withLocalNames(slotParamNames.toSet()) { lambdaDepth += 1
parseBlock(skipLeadingBrace = true) try {
withLocalNames(slotParamNames.toSet()) {
parseBlock(skipLeadingBrace = true)
}
} finally {
lambdaDepth -= 1
} }
} finally { } finally {
resolutionSink?.exitScope(cc.currentPos()) resolutionSink?.exitScope(cc.currentPos())
@ -2872,25 +2900,56 @@ class Compiler(
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan) val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
val captureSlots = capturePlan.captures.toList() val captureSlots = capturePlan.captures.toList()
val returnClass = inferReturnClassFromStatement(body) val returnClass = inferReturnClassFromStatement(body)
val paramKnownClasses = mutableMapOf<String, ObjClass>()
argsDeclaration?.params?.forEach { param ->
val cls = resolveTypeDeclObjClass(param.type) ?: return@forEach
paramKnownClasses[param.name] = cls
}
val returnLabels = label?.let { setOf(it) } ?: emptySet()
val fnStatements = if (useBytecodeStatements && !containsUnsupportedForBytecode(body)) {
returnLabelStack.addLast(returnLabels)
try {
wrapFunctionBytecode(body, "<lambda>", paramKnownClasses)
} catch (e: net.sergeych.lyng.bytecode.BytecodeCompileException) {
body
} finally {
returnLabelStack.removeLast()
}
} else {
body
}
val ref = ValueFnRef { closureScope -> val ref = ValueFnRef { closureScope ->
val stmt = object : Statement() { val captureRecords = closureScope.captureRecords
override val pos: Pos = body.pos val stmt = object : Statement(), BytecodeBodyProvider {
override val pos: Pos = fnStatements.pos
override fun bytecodeBody(): BytecodeStatement? = fnStatements as? BytecodeStatement
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
// and the source closure of the lambda which might have other thisObj. // TODO(bytecode): remove this fallback once lambdas are fully bytecode-backed (Step 26).
val useBytecodeClosure = closureScope.captureRecords != null val usesBytecodeBody = fnStatements is BytecodeStatement &&
val context = if (useBytecodeClosure) { (captureSlots.isEmpty() || captureRecords != null)
scope.applyClosureForBytecode(closureScope, preferredThisType = expectedReceiverType) val useBytecodeClosure = captureRecords != null && usesBytecodeBody
val context = if (usesBytecodeBody) {
scope.applyClosureForBytecode(closureScope, preferredThisType = expectedReceiverType).also {
it.args = scope.args
}
} else { } else {
scope.applyClosure(closureScope, preferredThisType = expectedReceiverType) scope.applyClosure(closureScope, preferredThisType = expectedReceiverType)
} }
if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot) if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot)
if (captureSlots.isNotEmpty()) { if (captureSlots.isNotEmpty()) {
val captureRecords = closureScope.captureRecords
if (captureRecords != null) { if (captureRecords != null) {
for (i in captureSlots.indices) { val records = captureRecords as List<ObjRecord>
val rec = captureRecords.getOrNull(i) if (useBytecodeClosure) {
?: closureScope.raiseSymbolNotFound("capture ${captureSlots[i].name} not found") context.captureRecords = records
context.updateSlotFor(captureSlots[i].name, rec) context.captureNames = captureSlots.map { it.name }
} else {
for (i in captureSlots.indices) {
val rec = records.getOrNull(i)
?: closureScope.raiseSymbolNotFound("capture ${captureSlots[i].name} not found")
context.updateSlotFor(captureSlots[i].name, rec)
}
} }
} else { } else {
val moduleScope = if (context is ApplyScope) { val moduleScope = if (context is ApplyScope) {
@ -2902,38 +2961,44 @@ class Compiler(
} else { } else {
null null
} }
val usesBytecodeBody = fnStatements is BytecodeStatement
val resolvedRecords = if (usesBytecodeBody) ArrayList<ObjRecord>() else null
val resolvedNames = if (usesBytecodeBody) ArrayList<String>() else null
for (capture in captureSlots) { for (capture in captureSlots) {
if (moduleScope != null && moduleScope.getLocalRecordDirect(capture.name) != null) { if (moduleScope != null && moduleScope.getLocalRecordDirect(capture.name) != null) {
continue continue
} }
val rec = closureScope.resolveCaptureRecord(capture.name) val rec = closureScope.resolveCaptureRecord(capture.name)
?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found") ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found")
context.updateSlotFor(capture.name, rec) if (usesBytecodeBody) {
resolvedRecords?.add(rec)
resolvedNames?.add(capture.name)
} else {
context.updateSlotFor(capture.name, rec)
}
}
if (usesBytecodeBody) {
context.captureRecords = resolvedRecords
context.captureNames = resolvedNames
} }
} }
} }
// Execute lambda body in a closure-aware context. Blocks inside the lambda
// will create child scopes as usual, so re-declarations inside loops work.
if (argsDeclaration == null) { if (argsDeclaration == null) {
// no args: automatic var 'it'
val l = scope.args.list val l = scope.args.list
val itValue: Obj = when (l.size) { val itValue: Obj = when (l.size) {
// no args: it == void
0 -> ObjVoid 0 -> ObjVoid
// one args: it is this arg
1 -> l[0] 1 -> l[0]
// more args: it is a list of args
else -> ObjList(l.toMutableList()) else -> ObjList(l.toMutableList())
} }
context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument) context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument)
} else { } else {
// assign vars as declared the standard way argsDeclaration.assignToContext(context, scope.args, defaultAccessType = AccessType.Val)
argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val)
} }
val effectiveStatements = if (usesBytecodeBody) fnStatements else body
return try { return try {
body.execute(context) effectiveStatements.execute(context)
} catch (e: ReturnException) { } catch (e: ReturnException) {
if (e.label == null || e.label == label) e.result if (e.label == null || returnLabels.contains(e.label)) e.result
else throw e else throw e
} }
} }
@ -2948,22 +3013,27 @@ class Compiler(
if (returnClass != null) { if (returnClass != null) {
lambdaReturnTypeByRef[ref] = returnClass lambdaReturnTypeByRef[ref] = returnClass
} }
val moduleScopeId = moduleSlotPlan()?.id if (captureSlots.isNotEmpty()) {
val captureEntries = captureSlots.map { capture -> val moduleScopeId = moduleSlotPlan()?.id
val owner = capturePlan.captureOwners[capture.name] val captureEntries = captureSlots.map { capture ->
?: error("Missing capture owner for ${capture.name}") val owner = capturePlan.captureOwners[capture.name]
val kind = if (moduleScopeId != null && owner.scopeId == moduleScopeId) { ?: error("Missing capture owner for ${capture.name}")
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.MODULE val kind = if (moduleScopeId != null && owner.scopeId == moduleScopeId) {
} else { net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.MODULE
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL } else {
net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL
}
net.sergeych.lyng.bytecode.LambdaCaptureEntry(
ownerKind = kind,
ownerScopeId = owner.scopeId,
ownerSlotId = owner.slot,
ownerName = capture.name,
ownerIsMutable = owner.isMutable,
ownerIsDelegated = owner.isDelegated
)
} }
net.sergeych.lyng.bytecode.LambdaCaptureEntry( lambdaCaptureEntriesByRef[ref] = captureEntries
ownerKind = kind,
ownerScopeId = owner.scopeId,
ownerSlotId = owner.slot
)
} }
lambdaCaptureEntriesByRef[ref] = captureEntries
return ref return ref
} }
@ -3039,7 +3109,7 @@ class Compiler(
return when (t.value) { return when (t.value) {
"class" -> { "class" -> {
val ref = ValueFnRef { scope -> val ref = ValueFnRef { scope ->
operand.get(scope).value.objClass.asReadonly operand.evalValue(scope).objClass.asReadonly
} }
lambdaReturnTypeByRef[ref] = ObjClassType lambdaReturnTypeByRef[ref] = ObjClassType
ref ref
@ -4162,6 +4232,7 @@ class Compiler(
val payload = inferEncodedPayloadClass(ref.args) val payload = inferEncodedPayloadClass(ref.args)
if (payload != null) return payload if (payload != null) return payload
} }
val receiverClass = resolveReceiverClassForMember(ref.receiver)
return inferMethodCallReturnClass(ref.name) return inferMethodCallReturnClass(ref.name)
} }
@ -6393,6 +6464,7 @@ class Compiler(
label = label, label = label,
canBreak = canBreak, canBreak = canBreak,
loopSlotPlan = loopSlotPlanSnapshot, loopSlotPlan = loopSlotPlanSnapshot,
loopScopeId = loopSlotPlan.id,
pos = body.pos pos = body.pos
) )
} else { } else {
@ -6818,7 +6890,7 @@ class Compiler(
val typeParamNames = mergedTypeParamDecls.map { it.name } val typeParamNames = mergedTypeParamDecls.map { it.name }
val paramNames: Set<String> = paramNamesList.toSet() val paramNames: Set<String> = paramNamesList.toSet()
val paramSlotPlan = buildParamSlotPlan(paramNamesList + typeParamNames) val paramSlotPlan = buildParamSlotPlan(paramNamesList + typeParamNames)
val capturePlan = CapturePlan(paramSlotPlan) val capturePlan = CapturePlan(paramSlotPlan, isFunction = true, propagateToParentFunction = false)
val rangeParamNames = argsDeclaration.params val rangeParamNames = argsDeclaration.params
.filter { isRangeType(it.type) } .filter { isRangeType(it.type) }
.map { it.name } .map { it.name }
@ -6945,11 +7017,17 @@ class Compiler(
if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot) if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot)
val captureBase = closureBox.captureContext ?: closureBox.closure val captureBase = closureBox.captureContext ?: closureBox.closure
if (captureBase != null && captureSlots.isNotEmpty()) { if (captureBase != null && captureSlots.isNotEmpty()) {
for (capture in captureSlots) { if (fnStatements is BytecodeStatement) {
// Interpreter-only capture resolution; bytecode functions do not use resolveCaptureRecord. captureBase.raiseIllegalState(
val rec = captureBase.resolveCaptureRecord(capture.name) "bytecode function captures require frame slots; scope capture resolution disabled"
?: captureBase.raiseSymbolNotFound("symbol ${capture.name} not found") )
context.updateSlotFor(capture.name, rec) } else {
for (capture in captureSlots) {
// Interpreter-only capture resolution; bytecode functions do not use resolveCaptureRecord.
val rec = captureBase.resolveCaptureRecord(capture.name)
?: captureBase.raiseSymbolNotFound("symbol ${capture.name} not found")
context.updateSlotFor(capture.name, rec)
}
} }
} }
@ -7040,7 +7118,7 @@ class Compiler(
resolutionSink?.declareSymbol(name, SymbolKind.LOCAL, isMutable, startPos, isOverride = false) resolutionSink?.declareSymbol(name, SymbolKind.LOCAL, isMutable, startPos, isOverride = false)
} }
slotPlanStack.add(exprSlotPlan) slotPlanStack.add(exprSlotPlan)
val capturePlan = CapturePlan(exprSlotPlan) val capturePlan = CapturePlan(exprSlotPlan, isFunction = false, propagateToParentFunction = lambdaDepth > 0)
capturePlanStack.add(capturePlan) capturePlanStack.add(capturePlan)
val expr = try { val expr = try {
parseExpression() ?: throw ScriptError(cc.current().pos, "Expected expression") parseExpression() ?: throw ScriptError(cc.current().pos, "Expected expression")
@ -7062,7 +7140,7 @@ class Compiler(
declareSlotNameIn(blockSlotPlan, name, isMutable, isDelegated = false) declareSlotNameIn(blockSlotPlan, name, isMutable, isDelegated = false)
} }
slotPlanStack.add(blockSlotPlan) slotPlanStack.add(blockSlotPlan)
val capturePlan = CapturePlan(blockSlotPlan) val capturePlan = CapturePlan(blockSlotPlan, isFunction = false, propagateToParentFunction = lambdaDepth > 0)
capturePlanStack.add(capturePlan) capturePlanStack.add(capturePlan)
val expr = try { val expr = try {
parseExpression() ?: throw ScriptError(cc.current().pos, "Expected expression") parseExpression() ?: throw ScriptError(cc.current().pos, "Expected expression")
@ -7379,7 +7457,7 @@ class Compiler(
} }
} }
slotPlanStack.add(blockSlotPlan) slotPlanStack.add(blockSlotPlan)
val capturePlan = CapturePlan(blockSlotPlan) val capturePlan = CapturePlan(blockSlotPlan, isFunction = false, propagateToParentFunction = lambdaDepth > 0)
capturePlanStack.add(capturePlan) capturePlanStack.add(capturePlan)
val block = try { val block = try {
parseScript() parseScript()
@ -7410,7 +7488,7 @@ class Compiler(
resolutionSink?.enterScope(ScopeKind.BLOCK, startPos, null) resolutionSink?.enterScope(ScopeKind.BLOCK, startPos, null)
val blockSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++) val blockSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++)
slotPlanStack.add(blockSlotPlan) slotPlanStack.add(blockSlotPlan)
val capturePlan = CapturePlan(blockSlotPlan) val capturePlan = CapturePlan(blockSlotPlan, isFunction = false, propagateToParentFunction = lambdaDepth > 0)
capturePlanStack.add(capturePlan) capturePlanStack.add(capturePlan)
val block = try { val block = try {
parseScript() parseScript()
@ -7440,7 +7518,7 @@ class Compiler(
resolutionSink?.enterScope(ScopeKind.BLOCK, startPos, null) resolutionSink?.enterScope(ScopeKind.BLOCK, startPos, null)
val blockSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++) val blockSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++)
slotPlanStack.add(blockSlotPlan) slotPlanStack.add(blockSlotPlan)
val capturePlan = CapturePlan(blockSlotPlan) val capturePlan = CapturePlan(blockSlotPlan, isFunction = false, propagateToParentFunction = lambdaDepth > 0)
capturePlanStack.add(capturePlan) capturePlanStack.add(capturePlan)
val block = try { val block = try {
parseScript() parseScript()

View File

@ -55,3 +55,16 @@ class FrameSlotRef(
} }
} }
} }
class RecordSlotRef(
private val record: ObjRecord,
) : net.sergeych.lyng.obj.Obj() {
fun read(): Obj {
val direct = record.value
return if (direct is FrameSlotRef) direct.read() else direct
}
fun write(value: Obj) {
record.value = value
}
}

View File

@ -60,6 +60,7 @@ open class Scope(
private val slots: MutableList<ObjRecord> = mutableListOf() private val slots: MutableList<ObjRecord> = mutableListOf()
private val nameToSlot: MutableMap<String, Int> = mutableMapOf() private val nameToSlot: MutableMap<String, Int> = mutableMapOf()
internal var captureRecords: List<ObjRecord>? = null internal var captureRecords: List<ObjRecord>? = null
internal var captureNames: List<String>? = null
/** /**
* Auxiliary per-frame map of local bindings (locals declared in this frame). * Auxiliary per-frame map of local bindings (locals declared in this frame).
* This helps resolving locals across suspension when slot ownership isn't * This helps resolving locals across suspension when slot ownership isn't

View File

@ -53,10 +53,13 @@ class BytecodeCompiler(
private val localSlotInfoMap = LinkedHashMap<ScopeSlotKey, LocalSlotInfo>() private val localSlotInfoMap = LinkedHashMap<ScopeSlotKey, LocalSlotInfo>()
private val localSlotIndexByKey = LinkedHashMap<ScopeSlotKey, Int>() private val localSlotIndexByKey = LinkedHashMap<ScopeSlotKey, Int>()
private val localSlotIndexByName = LinkedHashMap<String, Int>() private val localSlotIndexByName = LinkedHashMap<String, Int>()
private val captureSlotKeys = LinkedHashSet<ScopeSlotKey>()
private val forcedObjSlots = LinkedHashSet<Int>()
private val loopSlotOverrides = LinkedHashMap<String, Int>() private val loopSlotOverrides = LinkedHashMap<String, Int>()
private var localSlotNames = emptyArray<String?>() private var localSlotNames = emptyArray<String?>()
private var localSlotMutables = BooleanArray(0) private var localSlotMutables = BooleanArray(0)
private var localSlotDelegated = BooleanArray(0) private var localSlotDelegated = BooleanArray(0)
private var localSlotCaptures = BooleanArray(0)
private val declaredLocalKeys = LinkedHashSet<ScopeSlotKey>() private val declaredLocalKeys = LinkedHashSet<ScopeSlotKey>()
private val localRangeRefs = LinkedHashMap<ScopeSlotKey, RangeRef>() private val localRangeRefs = LinkedHashMap<ScopeSlotKey, RangeRef>()
private val slotTypes = mutableMapOf<Int, SlotType>() private val slotTypes = mutableMapOf<Int, SlotType>()
@ -65,6 +68,7 @@ class BytecodeCompiler(
private val knownClassNames = knownNameObjClass.keys.toSet() private val knownClassNames = knownNameObjClass.keys.toSet()
private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>() private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>()
private val intLoopVarNames = LinkedHashSet<String>() private val intLoopVarNames = LinkedHashSet<String>()
private val valueFnRefs = LinkedHashSet<ValueFnRef>()
private val loopStack = ArrayDeque<LoopContext>() private val loopStack = ArrayDeque<LoopContext>()
private var forceScopeSlots = false private var forceScopeSlots = false
private var currentPos: Pos? = null private var currentPos: Pos? = null
@ -100,8 +104,9 @@ class BytecodeCompiler(
scopeSlotNames, scopeSlotNames,
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
localSlotCaptures
) )
} }
is BlockStatement -> compileBlock(name, stmt) is BlockStatement -> compileBlock(name, stmt)
@ -120,8 +125,9 @@ class BytecodeCompiler(
scopeSlotNames, scopeSlotNames,
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
localSlotCaptures
) )
} }
is DestructuringVarDeclStatement -> { is DestructuringVarDeclStatement -> {
@ -137,8 +143,9 @@ class BytecodeCompiler(
scopeSlotNames, scopeSlotNames,
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
localSlotCaptures
) )
} }
is net.sergeych.lyng.ThrowStatement -> compileThrowStatement(name, stmt) is net.sergeych.lyng.ThrowStatement -> compileThrowStatement(name, stmt)
@ -156,8 +163,9 @@ class BytecodeCompiler(
scopeSlotNames, scopeSlotNames,
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
localSlotCaptures
) )
} }
is net.sergeych.lyng.ClassDeclStatement -> { is net.sergeych.lyng.ClassDeclStatement -> {
@ -173,8 +181,9 @@ class BytecodeCompiler(
scopeSlotNames, scopeSlotNames,
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
localSlotCaptures
) )
} }
is net.sergeych.lyng.FunctionDeclStatement -> { is net.sergeych.lyng.FunctionDeclStatement -> {
@ -190,8 +199,9 @@ class BytecodeCompiler(
scopeSlotNames, scopeSlotNames,
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
localSlotCaptures
) )
} }
is net.sergeych.lyng.EnumDeclStatement -> { is net.sergeych.lyng.EnumDeclStatement -> {
@ -207,8 +217,9 @@ class BytecodeCompiler(
scopeSlotNames, scopeSlotNames,
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
localSlotCaptures
) )
} }
is net.sergeych.lyng.NopStatement -> { is net.sergeych.lyng.NopStatement -> {
@ -225,8 +236,9 @@ class BytecodeCompiler(
scopeSlotNames, scopeSlotNames,
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
localSlotCaptures
) )
} }
else -> null else -> null
@ -246,8 +258,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
private fun compileExtensionPropertyDecl( private fun compileExtensionPropertyDecl(
@ -268,8 +281,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
fun compileExpression(name: String, stmt: ExpressionStatement): CmdFunction? { fun compileExpression(name: String, stmt: ExpressionStatement): CmdFunction? {
@ -287,8 +301,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
private data class CompiledValue(val slot: Int, val type: SlotType) private data class CompiledValue(val slot: Int, val type: SlotType)
@ -358,16 +373,16 @@ class BytecodeCompiler(
} }
if (allowLocalSlots) { if (allowLocalSlots) {
if (!forceScopeSlots) { if (!forceScopeSlots) {
scopeSlotIndexByName[ref.name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
val localIndex = localSlotIndexByName[ref.name] val localIndex = localSlotIndexByName[ref.name]
if (localIndex != null) { if (localIndex != null) {
val slot = scopeSlotCount + localIndex val slot = scopeSlotCount + localIndex
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved) return CompiledValue(slot, resolved)
} }
scopeSlotIndexByName[ref.name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
} }
if (forceScopeSlots) { if (forceScopeSlots) {
scopeSlotIndexByName[ref.name]?.let { slot -> scopeSlotIndexByName[ref.name]?.let { slot ->
@ -388,16 +403,16 @@ class BytecodeCompiler(
} }
if (allowLocalSlots) { if (allowLocalSlots) {
if (!forceScopeSlots) { if (!forceScopeSlots) {
scopeSlotIndexByName[ref.name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
val localIndex = localSlotIndexByName[ref.name] val localIndex = localSlotIndexByName[ref.name]
if (localIndex != null) { if (localIndex != null) {
val slot = scopeSlotCount + localIndex val slot = scopeSlotCount + localIndex
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved) return CompiledValue(slot, resolved)
} }
scopeSlotIndexByName[ref.name]?.let { slot ->
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
return CompiledValue(slot, resolved)
}
} }
if (forceScopeSlots) { if (forceScopeSlots) {
scopeSlotIndexByName[ref.name]?.let { slot -> scopeSlotIndexByName[ref.name]?.let { slot ->
@ -3970,8 +3985,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): CmdFunction? { private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): CmdFunction? {
@ -3988,8 +4004,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
private fun compileWhile(name: String, stmt: net.sergeych.lyng.WhileStatement): CmdFunction? { private fun compileWhile(name: String, stmt: net.sergeych.lyng.WhileStatement): CmdFunction? {
@ -4007,8 +4024,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
private fun compileDoWhile(name: String, stmt: net.sergeych.lyng.DoWhileStatement): CmdFunction? { private fun compileDoWhile(name: String, stmt: net.sergeych.lyng.DoWhileStatement): CmdFunction? {
@ -4026,8 +4044,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
private fun compileBlock(name: String, stmt: BlockStatement): CmdFunction? { private fun compileBlock(name: String, stmt: BlockStatement): CmdFunction? {
@ -4048,8 +4067,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
private fun compileVarDecl(name: String, stmt: VarDeclStatement): CmdFunction? { private fun compileVarDecl(name: String, stmt: VarDeclStatement): CmdFunction? {
@ -4066,8 +4086,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
private fun compileStatementValue(stmt: Statement): CompiledValue? { private fun compileStatementValue(stmt: Statement): CompiledValue? {
@ -4520,8 +4541,9 @@ class BytecodeCompiler(
scopeSlotIsModule, scopeSlotIsModule,
localSlotNames, localSlotNames,
localSlotMutables, localSlotMutables,
localSlotDelegated localSlotDelegated,
) localSlotCaptures
)
} }
private fun compileLoopBody(stmt: Statement, needResult: Boolean): CompiledValue? { private fun compileLoopBody(stmt: Statement, needResult: Boolean): CompiledValue? {
@ -4788,7 +4810,9 @@ class BytecodeCompiler(
val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
val loopSlotPlan = stmt.loopSlotPlan val loopSlotPlan = stmt.loopSlotPlan
var useLoopScope = loopSlotPlan.isNotEmpty() var useLoopScope = loopSlotPlan.isNotEmpty()
val loopLocalIndex = localSlotIndexByName[stmt.loopVarName] val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
val loopKey = loopSlotIndex?.let { ScopeSlotKey(stmt.loopScopeId, it) }
val loopLocalIndex = loopKey?.let { localSlotIndexByKey[it] } ?: localSlotIndexByName[stmt.loopVarName]
var usedOverride = false var usedOverride = false
var loopSlotId = when { var loopSlotId = when {
loopLocalIndex != null -> scopeSlotCount + loopLocalIndex loopLocalIndex != null -> scopeSlotCount + loopLocalIndex
@ -4905,7 +4929,7 @@ class BytecodeCompiler(
val nextSlot = allocSlot() val nextSlot = allocSlot()
builder.emit(Opcode.CALL_MEMBER_SLOT, iterSlot, nextMethodId, 0, 0, nextSlot) builder.emit(Opcode.CALL_MEMBER_SLOT, iterSlot, nextMethodId, 0, 0, nextSlot)
val nextObj = ensureObjSlot(CompiledValue(nextSlot, SlotType.UNKNOWN)) val nextObj = ensureObjSlot(CompiledValue(nextSlot, SlotType.UNKNOWN))
builder.emit(Opcode.MOVE_OBJ, nextObj.slot, loopSlotId) emitMove(CompiledValue(nextObj.slot, SlotType.OBJ), loopSlotId)
updateSlotType(loopSlotId, SlotType.OBJ) updateSlotType(loopSlotId, SlotType.OBJ)
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ) updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
if (emitDeclLocal) { if (emitDeclLocal) {
@ -5012,7 +5036,7 @@ class BytecodeCompiler(
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel)) listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
) )
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId) emitMove(CompiledValue(iSlot, SlotType.INT), loopSlotId)
updateSlotType(loopSlotId, SlotType.INT) updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
if (emitDeclLocal) { if (emitDeclLocal) {
@ -6084,16 +6108,22 @@ class BytecodeCompiler(
scopeSlotIndexByName[ref.name]?.let { return it } scopeSlotIndexByName[ref.name]?.let { return it }
} }
if (ref.captureOwnerScopeId != null) { if (ref.captureOwnerScopeId != null) {
val ownerKey = ScopeSlotKey(ref.captureOwnerScopeId, ref.captureOwnerSlot ?: refSlot(ref)) val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
val ownerLocal = localSlotIndexByKey[ownerKey] val localIndex = localSlotIndexByKey[scopeKey]
if (ownerLocal != null) { if (localIndex != null) {
return scopeSlotCount + ownerLocal if (localSlotCaptures.getOrNull(localIndex) == true) {
return scopeSlotCount + localIndex
}
} }
val nameLocal = localSlotIndexByName[ref.name] val nameLocal = localSlotIndexByName[ref.name]
if (nameLocal != null) { if (nameLocal != null && localSlotCaptures.getOrNull(nameLocal) == true) {
return scopeSlotCount + nameLocal return scopeSlotCount + nameLocal
} }
val scopeKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) for (idx in localSlotNames.indices) {
if (localSlotNames[idx] != ref.name) continue
if (localSlotCaptures.getOrNull(idx) != true) continue
return scopeSlotCount + idx
}
return scopeSlotMap[scopeKey] return scopeSlotMap[scopeKey]
} }
if (ref.isDelegated) { if (ref.isDelegated) {
@ -6117,6 +6147,7 @@ class BytecodeCompiler(
} }
private fun updateSlotType(slot: Int, type: SlotType) { private fun updateSlotType(slot: Int, type: SlotType) {
if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return
if (type == SlotType.UNKNOWN) { if (type == SlotType.UNKNOWN) {
slotTypes.remove(slot) slotTypes.remove(slot)
} else { } else {
@ -6141,18 +6172,22 @@ class BytecodeCompiler(
localSlotInfoMap.clear() localSlotInfoMap.clear()
localSlotIndexByKey.clear() localSlotIndexByKey.clear()
localSlotIndexByName.clear() localSlotIndexByName.clear()
captureSlotKeys.clear()
forcedObjSlots.clear()
loopSlotOverrides.clear() loopSlotOverrides.clear()
scopeSlotIndexByName.clear() scopeSlotIndexByName.clear()
pendingScopeNameRefs.clear() pendingScopeNameRefs.clear()
localSlotNames = emptyArray() localSlotNames = emptyArray()
localSlotMutables = BooleanArray(0) localSlotMutables = BooleanArray(0)
localSlotDelegated = BooleanArray(0) localSlotDelegated = BooleanArray(0)
localSlotCaptures = BooleanArray(0)
declaredLocalKeys.clear() declaredLocalKeys.clear()
localRangeRefs.clear() localRangeRefs.clear()
intLoopVarNames.clear() intLoopVarNames.clear()
valueFnRefs.clear()
addrSlotByScopeSlot.clear() addrSlotByScopeSlot.clear()
loopStack.clear() loopStack.clear()
forceScopeSlots = allowLocalSlots && containsValueFnRef(stmt) forceScopeSlots = false
if (slotTypeByScopeId.isNotEmpty()) { if (slotTypeByScopeId.isNotEmpty()) {
for ((scopeId, slots) in slotTypeByScopeId) { for ((scopeId, slots) in slotTypeByScopeId) {
for ((slotIndex, cls) in slots) { for ((slotIndex, cls) in slots) {
@ -6167,6 +6202,22 @@ class BytecodeCompiler(
if (allowLocalSlots) { if (allowLocalSlots) {
collectLoopSlotPlans(stmt, 0) collectLoopSlotPlans(stmt, 0)
} }
if (allowLocalSlots && valueFnRefs.isNotEmpty() && lambdaCaptureEntriesByRef.isNotEmpty()) {
for (ref in valueFnRefs) {
val entries = lambdaCaptureEntriesByRef[ref] ?: continue
for (entry in entries) {
if (entry.ownerKind != CaptureOwnerFrameKind.LOCAL) continue
val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(
entry.ownerName,
entry.ownerIsMutable,
entry.ownerIsDelegated
)
}
}
}
}
if (pendingScopeNameRefs.isNotEmpty()) { if (pendingScopeNameRefs.isNotEmpty()) {
val existingNames = HashSet<String>(scopeSlotNameMap.values) val existingNames = HashSet<String>(scopeSlotNameMap.values)
var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1 var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1
@ -6209,6 +6260,21 @@ class BytecodeCompiler(
localSlotMutables = mutables localSlotMutables = mutables
localSlotDelegated = delegated localSlotDelegated = delegated
} }
localSlotCaptures = BooleanArray(localSlotNames.size)
if (captureSlotKeys.isNotEmpty()) {
for (key in captureSlotKeys) {
val localIndex = localSlotIndexByKey[key] ?: continue
val slot = scopeSlotCount + localIndex
localSlotCaptures[localIndex] = true
forcedObjSlots.add(slot)
slotTypes[slot] = SlotType.OBJ
}
}
for (i in localSlotNames.indices) {
if (localSlotCaptures.getOrNull(i) != true) continue
val name = localSlotNames[i] ?: continue
localSlotIndexByName[name] = i
}
if (scopeSlotCount > 0) { if (scopeSlotCount > 0) {
for ((key, index) in scopeSlotMap) { for ((key, index) in scopeSlotMap) {
val name = scopeSlotNameMap[key] ?: continue val name = scopeSlotNameMap[key] ?: continue
@ -6572,12 +6638,12 @@ class BytecodeCompiler(
val scopeId = refScopeId(ref) val scopeId = refScopeId(ref)
val key = ScopeSlotKey(scopeId, refSlot(ref)) val key = ScopeSlotKey(scopeId, refSlot(ref))
if (ref.captureOwnerScopeId != null) { if (ref.captureOwnerScopeId != null) {
if (!scopeSlotMap.containsKey(key)) { if (allowLocalSlots) {
scopeSlotMap[key] = scopeSlotMap.size if (!localSlotInfoMap.containsKey(key)) {
} localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated)
if (!scopeSlotNameMap.containsKey(key)) { }
scopeSlotNameMap[key] = ref.name
} }
captureSlotKeys.add(key)
return return
} }
val shouldLocalize = ref.isDelegated || !forceScopeSlots || intLoopVarNames.contains(ref.name) val shouldLocalize = ref.isDelegated || !forceScopeSlots || intLoopVarNames.contains(ref.name)
@ -6622,12 +6688,12 @@ class BytecodeCompiler(
val scopeId = refScopeId(target) val scopeId = refScopeId(target)
val key = ScopeSlotKey(scopeId, refSlot(target)) val key = ScopeSlotKey(scopeId, refSlot(target))
if (target.captureOwnerScopeId != null) { if (target.captureOwnerScopeId != null) {
if (!scopeSlotMap.containsKey(key)) { if (allowLocalSlots) {
scopeSlotMap[key] = scopeSlotMap.size if (!localSlotInfoMap.containsKey(key)) {
} localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated)
if (!scopeSlotNameMap.containsKey(key)) { }
scopeSlotNameMap[key] = target.name
} }
captureSlotKeys.add(key)
} else { } else {
val shouldLocalize = target.isDelegated || !forceScopeSlots || intLoopVarNames.contains(target.name) val shouldLocalize = target.isDelegated || !forceScopeSlots || intLoopVarNames.contains(target.name)
val isModuleSlot = if (target.isDelegated) false else isModuleSlot(scopeId, target.name) val isModuleSlot = if (target.isDelegated) false else isModuleSlot(scopeId, target.name)
@ -6656,6 +6722,9 @@ class BytecodeCompiler(
collectScopeSlotsRef(ref.target) collectScopeSlotsRef(ref.target)
collectScopeSlotsRef(ref.value) collectScopeSlotsRef(ref.value)
} }
is ValueFnRef -> {
valueFnRefs.add(ref)
}
is AssignIfNullRef -> { is AssignIfNullRef -> {
collectScopeSlotsRef(ref.target) collectScopeSlotsRef(ref.target)
collectScopeSlotsRef(ref.value) collectScopeSlotsRef(ref.value)

View File

@ -212,6 +212,7 @@ class BytecodeStatement private constructor(
stmt.label, stmt.label,
stmt.canBreak, stmt.canBreak,
stmt.loopSlotPlan, stmt.loopSlotPlan,
stmt.loopScopeId,
stmt.pos stmt.pos
) )
} }

View File

@ -69,7 +69,8 @@ class CmdBuilder {
scopeSlotIsModule: BooleanArray = BooleanArray(0), scopeSlotIsModule: BooleanArray = BooleanArray(0),
localSlotNames: Array<String?> = emptyArray(), localSlotNames: Array<String?> = emptyArray(),
localSlotMutables: BooleanArray = BooleanArray(0), localSlotMutables: BooleanArray = BooleanArray(0),
localSlotDelegated: BooleanArray = BooleanArray(0) localSlotDelegated: BooleanArray = BooleanArray(0),
localSlotCaptures: BooleanArray = BooleanArray(0)
): CmdFunction { ): CmdFunction {
val scopeSlotCount = scopeSlotIndices.size val scopeSlotCount = scopeSlotIndices.size
require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) { require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) {
@ -80,6 +81,7 @@ class CmdBuilder {
} }
require(localSlotNames.size == localSlotMutables.size) { "local slot metadata size mismatch" } require(localSlotNames.size == localSlotMutables.size) { "local slot metadata size mismatch" }
require(localSlotNames.size == localSlotDelegated.size) { "local slot delegation size mismatch" } require(localSlotNames.size == localSlotDelegated.size) { "local slot delegation size mismatch" }
require(localSlotNames.size == localSlotCaptures.size) { "local slot capture size mismatch" }
val labelIps = mutableMapOf<Label, Int>() val labelIps = mutableMapOf<Label, Int>()
for ((label, idx) in labelPositions) { for ((label, idx) in labelPositions) {
labelIps[label] = idx labelIps[label] = idx
@ -114,6 +116,7 @@ class CmdBuilder {
localSlotNames = localSlotNames, localSlotNames = localSlotNames,
localSlotMutables = localSlotMutables, localSlotMutables = localSlotMutables,
localSlotDelegated = localSlotDelegated, localSlotDelegated = localSlotDelegated,
localSlotCaptures = localSlotCaptures,
constants = constPool.toList(), constants = constPool.toList(),
cmds = cmds.toTypedArray(), cmds = cmds.toTypedArray(),
posByIp = posByInstr.toTypedArray() posByIp = posByInstr.toTypedArray()
@ -150,7 +153,8 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.ADDR) listOf(OperandKind.SLOT, OperandKind.ADDR)
Opcode.CONST_NULL -> Opcode.CONST_NULL ->
listOf(OperandKind.SLOT) listOf(OperandKind.SLOT)
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN -> Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL,
Opcode.MAKE_VALUE_FN ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST) listOf(OperandKind.CONST)

View File

@ -279,7 +279,8 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.ADDR) listOf(OperandKind.SLOT, OperandKind.ADDR)
Opcode.CONST_NULL -> Opcode.CONST_NULL ->
listOf(OperandKind.SLOT) listOf(OperandKind.SLOT)
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN -> Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL,
Opcode.MAKE_VALUE_FN ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN -> Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST) listOf(OperandKind.CONST)

View File

@ -29,6 +29,7 @@ data class CmdFunction(
val localSlotNames: Array<String?>, val localSlotNames: Array<String?>,
val localSlotMutables: BooleanArray, val localSlotMutables: BooleanArray,
val localSlotDelegated: BooleanArray, val localSlotDelegated: BooleanArray,
val localSlotCaptures: BooleanArray,
val constants: List<BytecodeConst>, val constants: List<BytecodeConst>,
val cmds: Array<Cmd>, val cmds: Array<Cmd>,
val posByIp: Array<net.sergeych.lyng.Pos?>, val posByIp: Array<net.sergeych.lyng.Pos?>,
@ -39,6 +40,7 @@ data class CmdFunction(
require(scopeSlotIsModule.size == scopeSlotCount) { "scopeSlotIsModule size mismatch" } require(scopeSlotIsModule.size == scopeSlotCount) { "scopeSlotIsModule size mismatch" }
require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" } require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" }
require(localSlotNames.size == localSlotDelegated.size) { "localSlot delegation size mismatch" } require(localSlotNames.size == localSlotDelegated.size) { "localSlot delegation size mismatch" }
require(localSlotNames.size == localSlotCaptures.size) { "localSlot capture size mismatch" }
require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" } require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" }
require(addrCount >= 0) { "addrCount must be non-negative" } require(addrCount >= 0) { "addrCount must be non-negative" }
if (posByIp.isNotEmpty()) { if (posByIp.isNotEmpty()) {

View File

@ -26,6 +26,7 @@ class CmdVm {
suspend fun execute(fn: CmdFunction, scope0: Scope, args: List<Obj>): Obj { suspend fun execute(fn: CmdFunction, scope0: Scope, args: List<Obj>): Obj {
result = null result = null
val frame = CmdFrame(this, fn, scope0, args) val frame = CmdFrame(this, fn, scope0, args)
frame.applyCaptureRecords()
val cmds = fn.cmds val cmds = fn.cmds
if (fn.localSlotNames.isNotEmpty()) { if (fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame() frame.syncScopeToFrame()
@ -236,12 +237,23 @@ class CmdAssertIs(internal val objSlot: Int, internal val typeSlot: Int) : Cmd()
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val obj = frame.slotToObj(objSlot) val obj = frame.slotToObj(objSlot)
val typeObj = frame.slotToObj(typeSlot) val typeObj = frame.slotToObj(typeSlot)
val clazz = typeObj as? ObjClass ?: frame.ensureScope().raiseClassCastError( when (typeObj) {
"${typeObj.inspect(frame.ensureScope())} is not the class instance" is ObjClass -> {
) if (!obj.isInstanceOf(typeObj)) {
if (!obj.isInstanceOf(clazz)) { frame.ensureScope().raiseClassCastError(
frame.ensureScope().raiseClassCastError( "Cannot cast ${obj.objClass.className} to ${typeObj.className}"
"Cannot cast ${obj.objClass.className} to ${clazz.className}" )
}
}
is ObjTypeExpr -> {
if (!matchesTypeDecl(frame.ensureScope(), obj, typeObj.typeDecl)) {
frame.ensureScope().raiseClassCastError(
"Cannot cast ${obj.objClass.className} to ${typeObj.typeDecl}"
)
}
}
else -> frame.ensureScope().raiseClassCastError(
"${typeObj.inspect(frame.ensureScope())} is not the class instance"
) )
} }
return return
@ -256,17 +268,22 @@ class CmdMakeQualifiedView(
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val obj0 = frame.slotToObj(objSlot) val obj0 = frame.slotToObj(objSlot)
val typeObj = frame.slotToObj(typeSlot) val typeObj = frame.slotToObj(typeSlot)
val clazz = typeObj as? ObjClass ?: frame.ensureScope().raiseClassCastError(
"${typeObj.inspect(frame.ensureScope())} is not the class instance"
)
val base = when (obj0) { val base = when (obj0) {
is ObjQualifiedView -> obj0.instance is ObjQualifiedView -> obj0.instance
else -> obj0 else -> obj0
} }
val result = if (base is ObjInstance && base.isInstanceOf(clazz)) { val result = when (typeObj) {
ObjQualifiedView(base, clazz) is ObjClass -> {
} else { if (base is ObjInstance && base.isInstanceOf(typeObj)) {
base ObjQualifiedView(base, typeObj)
} else {
base
}
}
is ObjTypeExpr -> base
else -> frame.ensureScope().raiseClassCastError(
"${typeObj.inspect(frame.ensureScope())} is not the class instance"
)
} }
frame.storeObjResult(dst, result) frame.storeObjResult(dst, result)
return return
@ -1488,6 +1505,9 @@ class CmdCallSlot(
} else { } else {
// Pooling for Statement-based callables (lambdas) can still alter closure semantics; keep safe path for now. // Pooling for Statement-based callables (lambdas) can still alter closure semantics; keep safe path for now.
val scope = frame.ensureScope() val scope = frame.ensureScope()
if (callee is Statement && callee !is BytecodeStatement) {
frame.syncFrameToScope(useRefs = true)
}
callee.callOn(scope.createChildScope(scope.pos, args = args)) callee.callOn(scope.createChildScope(scope.pos, args = args))
} }
frame.storeObjResult(dst, result) frame.storeObjResult(dst, result)
@ -1938,6 +1958,57 @@ class CmdFrame(
} }
} }
internal fun applyCaptureRecords() {
val captureRecords = scope.captureRecords ?: return
val captureNames = scope.captureNames ?: return
val localNames = fn.localSlotNames
if (localNames.isEmpty()) return
for (i in captureNames.indices) {
val name = captureNames[i]
val record = captureRecords.getOrNull(i) ?: continue
var localIndex = -1
for (idx in localNames.indices) {
if (localNames[idx] != name) continue
if (fn.localSlotCaptures.getOrNull(idx) != true) continue
localIndex = idx
break
}
if (localIndex < 0) {
for (idx in localNames.indices) {
if (localNames[idx] != name) continue
localIndex = idx
break
}
}
if (localIndex < 0) continue
if (record.type == ObjRecord.Type.Delegated) {
frame.setObj(localIndex, record.delegate ?: ObjNull)
} else {
val value = record.value
if (value is FrameSlotRef) {
frame.setObj(localIndex, value)
} else {
frame.setObj(localIndex, RecordSlotRef(record))
}
}
for (idx in localNames.indices) {
if (idx == localIndex) continue
if (localNames[idx] != name) continue
if (fn.localSlotCaptures.getOrNull(idx) == true) continue
if (record.type == ObjRecord.Type.Delegated) {
frame.setObj(idx, record.delegate ?: ObjNull)
} else {
val value = record.value
if (value is FrameSlotRef) {
frame.setObj(idx, value)
} else {
frame.setObj(idx, RecordSlotRef(record))
}
}
}
}
}
internal fun buildCaptureRecords(captureTableId: Int): List<ObjRecord> { internal fun buildCaptureRecords(captureTableId: Int): List<ObjRecord> {
val table = fn.constants.getOrNull(captureTableId) as? BytecodeConst.CaptureTable val table = fn.constants.getOrNull(captureTableId) as? BytecodeConst.CaptureTable
?: error("Capture table $captureTableId missing") ?: error("Capture table $captureTableId missing")
@ -1987,7 +2058,11 @@ class CmdFrame(
.firstOrNull { fn.scopeSlotIsModule.getOrNull(it) == true } .firstOrNull { fn.scopeSlotIsModule.getOrNull(it) == true }
?.let { fn.scopeSlotNames[it] } ?.let { fn.scopeSlotNames[it] }
if (moduleSlotName != null) { if (moduleSlotName != null) {
findScopeWithSlot(scope, moduleSlotName)?.let { return it } val bySlot = findScopeWithSlot(scope, moduleSlotName)
bySlot?.let { return it }
val byRecord = findScopeWithRecord(scope, moduleSlotName)
byRecord?.let { return it }
return scope
} }
findModuleScope(scope)?.let { return it } findModuleScope(scope)?.let { return it }
return scope return scope
@ -2013,15 +2088,14 @@ class CmdFrame(
return null return null
} }
private fun findModuleScope(scope: Scope): Scope? { private fun findScopeWithRecord(scope: Scope, name: String): Scope? {
val visited = HashSet<Scope>(16) val visited = HashSet<Scope>(16)
val queue = ArrayDeque<Scope>() val queue = ArrayDeque<Scope>()
queue.add(scope) queue.add(scope)
while (queue.isNotEmpty()) { while (queue.isNotEmpty()) {
val current = queue.removeFirst() val current = queue.removeFirst()
if (!visited.add(current)) continue if (!visited.add(current)) continue
if (current is ModuleScope) return current if (current.getLocalRecordDirect(name) != null) return current
if (current.parent is ModuleScope) return current
current.parent?.let { queue.add(it) } current.parent?.let { queue.add(it) }
if (current is ClosureScope) { if (current is ClosureScope) {
queue.add(current.closureScope) queue.add(current.closureScope)
@ -2034,14 +2108,15 @@ class CmdFrame(
return null return null
} }
private fun findScopeWithRecord(scope: Scope, name: String): Scope? { private fun findModuleScope(scope: Scope): Scope? {
val visited = HashSet<Scope>(16) val visited = HashSet<Scope>(16)
val queue = ArrayDeque<Scope>() val queue = ArrayDeque<Scope>()
queue.add(scope) queue.add(scope)
while (queue.isNotEmpty()) { while (queue.isNotEmpty()) {
val current = queue.removeFirst() val current = queue.removeFirst()
if (!visited.add(current)) continue if (!visited.add(current)) continue
if (current.getLocalRecordDirect(name) != null) return current if (current is ModuleScope) return current
if (current.parent is ModuleScope) return current
current.parent?.let { queue.add(it) } current.parent?.let { queue.add(it) }
if (current is ClosureScope) { if (current is ClosureScope) {
queue.add(current.closureScope) queue.add(current.closureScope)
@ -2215,7 +2290,7 @@ class CmdFrame(
return if (slot < fn.scopeSlotCount) { return if (slot < fn.scopeSlotCount) {
getScopeSlotValue(slot) getScopeSlotValue(slot)
} else { } else {
frame.getObj(slot - fn.scopeSlotCount) localSlotToObj(slot - fn.scopeSlotCount)
} }
} }
@ -2225,7 +2300,17 @@ class CmdFrame(
val index = ensureScopeSlot(target, slot) val index = ensureScopeSlot(target, slot)
target.setSlotValue(index, value) target.setSlotValue(index, value)
} else { } else {
frame.setObj(slot - fn.scopeSlotCount, value) val localIndex = slot - fn.scopeSlotCount
val existing = frame.getObj(localIndex)
if (existing is FrameSlotRef) {
existing.write(value)
return
}
if (existing is RecordSlotRef) {
existing.write(value)
return
}
frame.setObj(localIndex, value)
} }
} }
@ -2238,7 +2323,14 @@ class CmdFrame(
SlotType.INT.code -> frame.getInt(local) SlotType.INT.code -> frame.getInt(local)
SlotType.REAL.code -> frame.getReal(local).toLong() SlotType.REAL.code -> frame.getReal(local).toLong()
SlotType.BOOL.code -> if (frame.getBool(local)) 1L else 0L SlotType.BOOL.code -> if (frame.getBool(local)) 1L else 0L
SlotType.OBJ.code -> frame.getObj(local).toLong() SlotType.OBJ.code -> {
val obj = frame.getObj(local)
when (obj) {
is FrameSlotRef -> obj.read().toLong()
is RecordSlotRef -> obj.read().toLong()
else -> obj.toLong()
}
}
else -> 0L else -> 0L
} }
} }
@ -2252,7 +2344,17 @@ class CmdFrame(
val index = ensureScopeSlot(target, slot) val index = ensureScopeSlot(target, slot)
target.setSlotValue(index, ObjInt.of(value)) target.setSlotValue(index, ObjInt.of(value))
} else { } else {
frame.setInt(slot - fn.scopeSlotCount, value) val localIndex = slot - fn.scopeSlotCount
val existing = frame.getObj(localIndex)
if (existing is FrameSlotRef) {
existing.write(ObjInt.of(value))
return
}
if (existing is RecordSlotRef) {
existing.write(ObjInt.of(value))
return
}
frame.setInt(localIndex, value)
} }
} }
@ -2269,7 +2371,14 @@ class CmdFrame(
SlotType.REAL.code -> frame.getReal(local) SlotType.REAL.code -> frame.getReal(local)
SlotType.INT.code -> frame.getInt(local).toDouble() SlotType.INT.code -> frame.getInt(local).toDouble()
SlotType.BOOL.code -> if (frame.getBool(local)) 1.0 else 0.0 SlotType.BOOL.code -> if (frame.getBool(local)) 1.0 else 0.0
SlotType.OBJ.code -> frame.getObj(local).toDouble() SlotType.OBJ.code -> {
val obj = frame.getObj(local)
when (obj) {
is FrameSlotRef -> obj.read().toDouble()
is RecordSlotRef -> obj.read().toDouble()
else -> obj.toDouble()
}
}
else -> 0.0 else -> 0.0
} }
} }
@ -2281,7 +2390,17 @@ class CmdFrame(
val index = ensureScopeSlot(target, slot) val index = ensureScopeSlot(target, slot)
target.setSlotValue(index, ObjReal.of(value)) target.setSlotValue(index, ObjReal.of(value))
} else { } else {
frame.setReal(slot - fn.scopeSlotCount, value) val localIndex = slot - fn.scopeSlotCount
val existing = frame.getObj(localIndex)
if (existing is FrameSlotRef) {
existing.write(ObjReal.of(value))
return
}
if (existing is RecordSlotRef) {
existing.write(ObjReal.of(value))
return
}
frame.setReal(localIndex, value)
} }
} }
@ -2294,7 +2413,14 @@ class CmdFrame(
SlotType.BOOL.code -> frame.getBool(local) SlotType.BOOL.code -> frame.getBool(local)
SlotType.INT.code -> frame.getInt(local) != 0L SlotType.INT.code -> frame.getInt(local) != 0L
SlotType.REAL.code -> frame.getReal(local) != 0.0 SlotType.REAL.code -> frame.getReal(local) != 0.0
SlotType.OBJ.code -> frame.getObj(local).toBool() SlotType.OBJ.code -> {
val obj = frame.getObj(local)
when (obj) {
is FrameSlotRef -> obj.read().toBool()
is RecordSlotRef -> obj.read().toBool()
else -> obj.toBool()
}
}
else -> false else -> false
} }
} }
@ -2308,7 +2434,17 @@ class CmdFrame(
val index = ensureScopeSlot(target, slot) val index = ensureScopeSlot(target, slot)
target.setSlotValue(index, if (value) ObjTrue else ObjFalse) target.setSlotValue(index, if (value) ObjTrue else ObjFalse)
} else { } else {
frame.setBool(slot - fn.scopeSlotCount, value) val localIndex = slot - fn.scopeSlotCount
val existing = frame.getObj(localIndex)
if (existing is FrameSlotRef) {
existing.write(if (value) ObjTrue else ObjFalse)
return
}
if (existing is RecordSlotRef) {
existing.write(if (value) ObjTrue else ObjFalse)
return
}
frame.setBool(localIndex, value)
} }
} }
@ -2361,6 +2497,9 @@ class CmdFrame(
return getScopeSlotValue(slot) return getScopeSlotValue(slot)
} }
val local = slot - fn.scopeSlotCount val local = slot - fn.scopeSlotCount
if (fn.localSlotCaptures.getOrNull(local) == true) {
return localSlotToObj(local)
}
val localName = fn.localSlotNames.getOrNull(local) val localName = fn.localSlotNames.getOrNull(local)
if (localName != null && fn.localSlotDelegated.getOrNull(local) != true) { if (localName != null && fn.localSlotDelegated.getOrNull(local) != true) {
val rec = scope.getLocalRecordDirect(localName) ?: scope.localBindings[localName] val rec = scope.getLocalRecordDirect(localName) ?: scope.localBindings[localName]
@ -2372,7 +2511,10 @@ class CmdFrame(
SlotType.INT.code -> ObjInt.of(frame.getInt(local)) SlotType.INT.code -> ObjInt.of(frame.getInt(local))
SlotType.REAL.code -> ObjReal.of(frame.getReal(local)) SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
SlotType.OBJ.code -> frame.getObj(local) SlotType.OBJ.code -> {
val obj = frame.getObj(local)
if (obj is FrameSlotRef) obj.read() else obj
}
else -> ObjVoid else -> ObjVoid
} }
} }
@ -2415,6 +2557,7 @@ class CmdFrame(
if (names.isEmpty()) return if (names.isEmpty()) return
for (i in names.indices) { for (i in names.indices) {
val name = names[i] ?: continue val name = names[i] ?: continue
if (fn.localSlotCaptures.getOrNull(i) == true) continue
if (scopeSlotNames.contains(name)) continue if (scopeSlotNames.contains(name)) continue
val target = resolveLocalScope(i) ?: continue val target = resolveLocalScope(i) ?: continue
val isDelegated = fn.localSlotDelegated.getOrNull(i) == true val isDelegated = fn.localSlotDelegated.getOrNull(i) == true
@ -2450,6 +2593,7 @@ class CmdFrame(
if (names.isEmpty()) return if (names.isEmpty()) return
for (i in names.indices) { for (i in names.indices) {
val name = names[i] ?: continue val name = names[i] ?: continue
if (fn.localSlotCaptures.getOrNull(i) == true) continue
val target = resolveLocalScope(i) ?: continue val target = resolveLocalScope(i) ?: continue
val rec = target.getLocalRecordDirect(name) ?: continue val rec = target.getLocalRecordDirect(name) ?: continue
if (fn.localSlotDelegated.getOrNull(i) == true && rec.type == ObjRecord.Type.Delegated) { if (fn.localSlotDelegated.getOrNull(i) == true && rec.type == ObjRecord.Type.Delegated) {
@ -2459,13 +2603,6 @@ class CmdFrame(
} }
val value = rec.value val value = rec.value
if (value is FrameSlotRef) { if (value is FrameSlotRef) {
val resolved = value.read()
when (resolved) {
is ObjInt -> frame.setInt(i, resolved.value)
is ObjReal -> frame.setReal(i, resolved.value)
is ObjBool -> frame.setBool(i, resolved.value)
else -> frame.setObj(i, resolved)
}
continue continue
} }
when (value) { when (value) {
@ -2563,8 +2700,22 @@ class CmdFrame(
SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex)) SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex))
SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex)) SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex))
SlotType.BOOL.code -> if (frame.getBool(localIndex)) ObjTrue else ObjFalse SlotType.BOOL.code -> if (frame.getBool(localIndex)) ObjTrue else ObjFalse
SlotType.OBJ.code -> frame.getObj(localIndex) SlotType.OBJ.code -> {
else -> ObjNull val obj = frame.getObj(localIndex)
when (obj) {
is FrameSlotRef -> obj.read()
is RecordSlotRef -> obj.read()
else -> obj
}
}
else -> {
val obj = frame.getObj(localIndex)
when (obj) {
is FrameSlotRef -> obj.read()
is RecordSlotRef -> obj.read()
else -> obj
}
}
} }
} }

View File

@ -22,6 +22,9 @@ data class LambdaCaptureEntry(
val ownerKind: CaptureOwnerFrameKind, val ownerKind: CaptureOwnerFrameKind,
val ownerScopeId: Int, val ownerScopeId: Int,
val ownerSlotId: Int, val ownerSlotId: Int,
val ownerName: String,
val ownerIsMutable: Boolean,
val ownerIsDelegated: Boolean,
) )
data class BytecodeCaptureEntry( data class BytecodeCaptureEntry(

View File

@ -20,6 +20,8 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.* import net.sergeych.lyng.*
import net.sergeych.lyng.FrameSlotRef
import net.sergeych.lyng.RecordSlotRef
/** /**
* A reference to a value with optional write-back path. * A reference to a value with optional write-back path.
@ -43,7 +45,12 @@ sealed interface ObjRef {
if (rec.receiver != null && rec.declaringClass != null) { if (rec.receiver != null && rec.declaringClass != null) {
return rec.receiver!!.resolveRecord(scope, rec, "unknown", rec.declaringClass).value return rec.receiver!!.resolveRecord(scope, rec, "unknown", rec.declaringClass).value
} }
return rec.value val value = rec.value
return when (value) {
is FrameSlotRef -> value.read()
is RecordSlotRef -> value.read()
else -> value
}
} }
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
throw ScriptError(pos, "can't assign value") throw ScriptError(pos, "can't assign value")

View File

@ -99,6 +99,7 @@ class ForInStatement(
val label: String?, val label: String?,
val canBreak: Boolean, val canBreak: Boolean,
val loopSlotPlan: Map<String, Int>, val loopSlotPlan: Map<String, Int>,
val loopScopeId: Int,
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {

View File

@ -180,6 +180,39 @@ class BytecodeRecentOpsTest {
) )
} }
@Test
fun lambdaCapturesLocalByReference() = runTest {
eval(
"""
fun make() {
var base = 3
val f = { x -> x + base }
base = 7
return f(1)
}
assertEquals(8, make())
""".trimIndent()
)
}
@Test
fun lambdaCapturesDelegatedLocal() = runTest {
eval(
"""
class BoxDelegate(var v) : Delegate {
override fun getValue(thisRef: Object, name: String): Object = v
override fun setValue(thisRef: Object, name: String, value: Object) { v = value }
}
fun make() {
var x by BoxDelegate(1)
val f = { y -> x += y; return x }
return f(2)
}
assertEquals(3, make())
""".trimIndent()
)
}
@Test @Test
fun delegatedMemberAccessAndCall() = runTest { fun delegatedMemberAccessAndCall() = runTest {
eval( eval(