Finalize bytecode-only lambdas and frame binding

This commit is contained in:
Sergey Chernov 2026-02-12 17:47:05 +03:00
parent a481371349
commit db4f7d0973
14 changed files with 653 additions and 314 deletions

View File

@ -125,15 +125,16 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
- [x] Remove `containsValueFnRef` helper now that lambdas are bytecode-backed.
- [x] Remove `forceScopeSlots` branches once no bytecode paths depend on scope slots.
- [x] Add JVM tests for captured locals and delegated locals inside lambdas on the bytecode path.
- [ ] Step 27: Remove interpreter opcodes and constants from bytecode runtime.
- [ ] Delete `BytecodeConst.ValueFn`, `CmdMakeValueFn`, and `MAKE_VALUE_FN` (blocked: some lambdas still fall back to non-bytecode bodies).
- [x] Step 27: Remove interpreter opcodes and constants from bytecode runtime.
- [x] Delete `BytecodeConst.ValueFn`, `CmdMakeValueFn`, and `MAKE_VALUE_FN`.
- [x] Delete `BytecodeConst.StatementVal`, `CmdEvalStmt`, and `EVAL_STMT`.
- [x] Add bytecode-backed `::class` via `ClassOperatorRef` + `GET_OBJ_CLASS` to avoid ValueFn for class operator.
- [x] Add a bytecode fallback reporter hook for lambdas to locate remaining non-bytecode cases.
- [ ] Remove `emitStatementCall`/`emitStatementEval` once unused.
- [x] Remove `emitStatementCall`/`emitStatementEval` once unused.
- [ ] Step 28: Scope as facade only.
- [ ] Audit bytecode execution paths for `Statement.execute` usage and remove remaining calls.
- [ ] Keep scope sync only for reflection/Kotlin interop, not for execution.
- [x] Audit bytecode execution paths for `Statement.execute` usage and remove remaining calls.
- [x] Keep scope sync only for reflection/Kotlin interop, not for execution.
- [x] Replace bytecode entry seeding from Scope with frame-only arg/local binding.
## Notes

View File

@ -250,6 +250,212 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
}
}
/**
* Assign arguments directly into frame slots using [paramSlotPlan] without creating scope locals.
* Still allows default expressions to evaluate by exposing FrameSlotRef facades in [scope].
*/
suspend fun assignToFrame(
scope: Scope,
arguments: Arguments = scope.args,
paramSlotPlan: Map<String, Int>,
frame: FrameAccess,
slotOffset: Int = 0,
defaultAccessType: AccessType = AccessType.Var,
defaultVisibility: Visibility = Visibility.Public,
declaringClass: net.sergeych.lyng.obj.ObjClass? = scope.currentClassCtx
) {
fun slotFor(name: String): Int {
val full = paramSlotPlan[name] ?: scope.raiseIllegalState("parameter slot for '$name' is missing")
val slot = full - slotOffset
if (slot < 0) scope.raiseIllegalState("parameter slot for '$name' is out of range")
return slot
}
fun ensureScopeRef(a: Item, slot: Int, recordType: ObjRecord.Type) {
if (scope.getLocalRecordDirect(a.name) != null) return
scope.addItem(
a.name,
(a.accessType ?: defaultAccessType).isMutable,
FrameSlotRef(frame, slot),
a.visibility ?: defaultVisibility,
recordType = recordType,
declaringClass = declaringClass,
isTransient = a.isTransient
)
}
fun setFrameValue(slot: Int, value: Obj) {
when (value) {
is net.sergeych.lyng.obj.ObjInt -> frame.setInt(slot, value.value)
is net.sergeych.lyng.obj.ObjReal -> frame.setReal(slot, value.value)
is net.sergeych.lyng.obj.ObjBool -> frame.setBool(slot, value.value)
else -> frame.setObj(slot, value)
}
}
fun assign(a: Item, value: Obj) {
val recordType = if (declaringClass != null && a.accessType != null) {
ObjRecord.Type.ConstructorField
} else {
ObjRecord.Type.Argument
}
val slot = slotFor(a.name)
setFrameValue(slot, value.byValueCopy())
ensureScopeRef(a, slot, recordType)
}
suspend fun missingValue(a: Item, error: String): Obj {
return a.defaultValue?.execute(scope)
?: if (a.type.isNullable) ObjNull else scope.raiseIllegalArgument(error)
}
// Fast path for simple positional-only calls with no ellipsis and no defaults
if (arguments.named.isEmpty() && !arguments.tailBlockMode) {
var hasComplex = false
for (p in params) {
if (p.isEllipsis || p.defaultValue != null) {
hasComplex = true
break
}
}
if (!hasComplex) {
if (arguments.list.size > params.size)
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
if (arguments.list.size < params.size) {
for (i in arguments.list.size until params.size) {
val a = params[i]
if (!a.type.isNullable) {
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
}
}
}
for (i in params.indices) {
val a = params[i]
val value = if (i < arguments.list.size) arguments.list[i] else ObjNull
assign(a, value)
}
return
}
}
// Prepare positional args and parameter count, handle tail-block binding
val callArgs: List<Obj>
val paramsSize: Int
if (arguments.tailBlockMode) {
val lastParam = params.last()
if (arguments.named.containsKey(lastParam.name))
scope.raiseIllegalArgument("trailing block cannot be used when the last parameter is already assigned by a named argument")
paramsSize = params.size - 1
assign(lastParam, arguments.list.last())
callArgs = arguments.list.dropLast(1)
} else {
paramsSize = params.size
callArgs = arguments.list
}
val coveredByPositional = BooleanArray(paramsSize)
run {
var headRequired = 0
var tailRequired = 0
val ellipsisIdx = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
if (ellipsisIdx >= 0) {
for (i in 0 until ellipsisIdx) if (!params[i].isEllipsis && params[i].defaultValue == null) headRequired++
for (i in paramsSize - 1 downTo ellipsisIdx + 1) if (params[i].defaultValue == null) tailRequired++
} else {
for (i in 0 until paramsSize) if (params[i].defaultValue == null) headRequired++
}
val P = callArgs.size
if (ellipsisIdx < 0) {
val k = minOf(P, paramsSize)
for (i in 0 until k) coveredByPositional[i] = true
} else {
val headTake = minOf(P, headRequired)
for (i in 0 until headTake) coveredByPositional[i] = true
val remaining = P - headTake
val tailTake = minOf(remaining, tailRequired)
var j = paramsSize - 1
var taken = 0
while (j > ellipsisIdx && taken < tailTake) {
coveredByPositional[j] = true
j--
taken++
}
}
}
val assignedByName = BooleanArray(paramsSize)
val namedValues = arrayOfNulls<Obj>(paramsSize)
if (arguments.named.isNotEmpty()) {
for ((k, v) in arguments.named) {
val idx = params.subList(0, paramsSize).indexOfFirst { it.name == k }
if (idx < 0) scope.raiseIllegalArgument("unknown parameter '$k'")
if (params[idx].isEllipsis) scope.raiseIllegalArgument("ellipsis (variadic) parameter cannot be assigned by name: '$k'")
if (coveredByPositional[idx]) scope.raiseIllegalArgument("argument '$k' is already set by positional argument")
if (assignedByName[idx]) scope.raiseIllegalArgument("argument '$k' is already set")
assignedByName[idx] = true
namedValues[idx] = v
}
}
suspend fun processHead(index: Int, headPos: Int): Pair<Int, Int> {
var i = index
var hp = headPos
while (i < paramsSize) {
val a = params[i]
if (a.isEllipsis) break
if (assignedByName[i]) {
assign(a, namedValues[i]!!)
} else {
val value = if (hp < callArgs.size) callArgs[hp++]
else missingValue(a, "too few arguments for the call (missing ${a.name})")
assign(a, value)
}
i++
}
return i to hp
}
suspend fun processTail(startExclusive: Int, tailStart: Int, headPosBound: Int): Int {
var i = paramsSize - 1
var tp = tailStart
while (i > startExclusive) {
val a = params[i]
if (a.isEllipsis) break
if (i < assignedByName.size && assignedByName[i]) {
assign(a, namedValues[i]!!)
} else {
val value = if (tp >= headPosBound) callArgs[tp--]
else missingValue(a, "too few arguments for the call")
assign(a, value)
}
i--
}
return tp
}
fun processEllipsis(index: Int, headPos: Int, tailPos: Int) {
val a = params[index]
val from = headPos
val to = tailPos
val l = if (from > to) ObjList()
else ObjList(callArgs.subList(from, to + 1).toMutableList())
assign(a, l)
}
val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
if (ellipsisIndex >= 0) {
val (_, headConsumedTo) = processHead(0, 0)
val tailConsumedFrom = processTail(ellipsisIndex, callArgs.size - 1, headConsumedTo)
processEllipsis(ellipsisIndex, headConsumedTo, tailConsumedFrom)
} else {
val (_, headConsumedTo) = processHead(0, 0)
if (headConsumedTo != callArgs.size)
scope.raiseIllegalArgument("too many arguments for the call")
}
}
/**
* Single argument declaration descriptor.
*

View File

@ -1892,10 +1892,11 @@ class Compiler(
private fun wrapFunctionBytecode(
stmt: Statement,
name: String,
extraKnownNameObjClass: Map<String, ObjClass> = emptyMap()
extraKnownNameObjClass: Map<String, ObjClass> = emptyMap(),
forcedLocalSlots: Map<String, Int> = emptyMap(),
forcedLocalScopeId: Int? = null
): Statement {
if (!useBytecodeStatements) return stmt
if (containsUnsupportedForBytecode(stmt)) return stmt
val returnLabels = returnLabelStack.lastOrNull() ?: emptySet()
val allowedScopeNames = moduleSlotPlan()?.slots?.keys
val knownNames = if (extraKnownNameObjClass.isEmpty()) {
@ -1914,6 +1915,8 @@ class Compiler(
rangeLocalNames = currentRangeParamNames,
allowedScopeNames = allowedScopeNames,
moduleScopeId = moduleSlotPlan()?.id,
forcedLocalSlots = forcedLocalSlots,
forcedLocalScopeId = forcedLocalScopeId,
slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownNames,
knownObjectNames = objectDeclNames,
@ -2910,26 +2913,24 @@ class Compiler(
}
val returnLabels = label?.let { setOf(it) } ?: emptySet()
val fnStatements = if (useBytecodeStatements) {
if (containsUnsupportedForBytecode(body)) {
bytecodeFallbackReporter?.invoke(
body.pos,
"lambda contains unsupported bytecode statements"
returnLabelStack.addLast(returnLabels)
try {
wrapFunctionBytecode(
body,
"<lambda>",
paramKnownClasses,
forcedLocalSlots = paramSlotPlanSnapshot,
forcedLocalScopeId = paramSlotPlan.id
)
body
} else {
returnLabelStack.addLast(returnLabels)
try {
wrapFunctionBytecode(body, "<lambda>", paramKnownClasses)
} catch (e: net.sergeych.lyng.bytecode.BytecodeCompileException) {
val pos = e.pos ?: body.pos
bytecodeFallbackReporter?.invoke(
pos,
"lambda bytecode compile failed: ${e.message}"
)
body
} finally {
returnLabelStack.removeLast()
}
} catch (e: net.sergeych.lyng.bytecode.BytecodeCompileException) {
val pos = e.pos ?: body.pos
bytecodeFallbackReporter?.invoke(
pos,
"lambda bytecode compile failed: ${e.message}"
)
throw e
} finally {
returnLabelStack.removeLast()
}
} else {
body
@ -3001,23 +3002,65 @@ class Compiler(
}
}
}
if (argsDeclaration == null) {
val l = scope.args.list
val itValue: Obj = when (l.size) {
0 -> ObjVoid
1 -> l[0]
else -> ObjList(l.toMutableList())
if (usesBytecodeBody && fnStatements is BytecodeStatement) {
val bytecodeFn = fnStatements.bytecodeFunction()
val binder: suspend (net.sergeych.lyng.bytecode.CmdFrame, Arguments) -> Unit = { frame, arguments ->
val slotPlan = bytecodeFn.localSlotPlanByName()
if (argsDeclaration == null) {
val l = arguments.list
val itValue: Obj = when (l.size) {
0 -> ObjVoid
1 -> l[0]
else -> ObjList(l.toMutableList())
}
val itSlot = slotPlan["it"]
if (itSlot != null) {
frame.frame.setObj(itSlot, itValue)
if (context.getLocalRecordDirect("it") == null) {
context.addItem(
"it",
false,
FrameSlotRef(frame.frame, itSlot),
recordType = ObjRecord.Type.Argument
)
}
} else if (context.getLocalRecordDirect("it") == null) {
context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument)
}
} else {
argsDeclaration.assignToFrame(
context,
arguments,
slotPlan,
frame.frame,
defaultAccessType = AccessType.Val
)
}
}
return try {
net.sergeych.lyng.bytecode.CmdVm().execute(bytecodeFn, context, scope.args, binder)
} catch (e: ReturnException) {
if (e.label == null || returnLabels.contains(e.label)) e.result
else throw e
}
context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument)
} else {
argsDeclaration.assignToContext(context, scope.args, defaultAccessType = AccessType.Val)
}
val effectiveStatements = if (usesBytecodeBody) fnStatements else body
return try {
effectiveStatements.execute(context)
} catch (e: ReturnException) {
if (e.label == null || returnLabels.contains(e.label)) e.result
else throw e
if (argsDeclaration == null) {
val l = scope.args.list
val itValue: Obj = when (l.size) {
0 -> ObjVoid
1 -> l[0]
else -> ObjList(l.toMutableList())
}
context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument)
} else {
argsDeclaration.assignToContext(context, scope.args, defaultAccessType = AccessType.Val)
}
return try {
body.execute(context)
} catch (e: ReturnException) {
if (e.label == null || returnLabels.contains(e.label)) e.result
else throw e
}
}
}
}
@ -4716,30 +4759,43 @@ class Compiler(
context: Scope,
argsDeclaration: ArgsDeclaration,
typeParams: List<TypeDecl.TypeParam>
) {
if (typeParams.isEmpty()) return
): Map<String, Obj> {
if (typeParams.isEmpty()) return emptyMap()
val inferred = mutableMapOf<String, TypeDecl>()
for (param in argsDeclaration.params) {
val rec = context.getLocalRecordDirect(param.name) ?: continue
val value = rec.value
val direct = rec.value
val value = when (direct) {
is FrameSlotRef -> direct.read()
is RecordSlotRef -> direct.read()
else -> direct
}
if (value is Obj) {
collectRuntimeTypeVarBindings(param.type, value, inferred)
}
}
val boundValues = LinkedHashMap<String, Obj>(typeParams.size)
for (tp in typeParams) {
val inferredType = inferred[tp.name] ?: tp.defaultType ?: TypeDecl.TypeAny
val normalized = normalizeRuntimeTypeDecl(inferredType)
val cls = resolveTypeDeclObjClass(normalized)
if (cls != null && !normalized.isNullable && normalized !is TypeDecl.Union && normalized !is TypeDecl.Intersection) {
context.addConst(tp.name, cls)
val boundValue = if (cls != null &&
!normalized.isNullable &&
normalized !is TypeDecl.Union &&
normalized !is TypeDecl.Intersection
) {
cls
} else {
context.addConst(tp.name, net.sergeych.lyng.obj.ObjTypeExpr(normalized))
net.sergeych.lyng.obj.ObjTypeExpr(normalized)
}
context.addConst(tp.name, boundValue)
boundValues[tp.name] = boundValue
val bound = tp.bound ?: continue
if (!typeDeclSatisfiesBound(normalized, bound)) {
context.raiseError("type argument ${typeDeclName(normalized)} does not satisfy bound ${typeDeclName(bound)}")
}
}
return boundValues
}
private fun collectRuntimeTypeVarBindings(
@ -6994,6 +7050,12 @@ class Compiler(
inferredReturnClass
}
}
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
val forcedLocalSlots = LinkedHashMap<String, Int>()
for (name in paramNamesList) {
val idx = paramSlotPlanSnapshot[name] ?: continue
forcedLocalSlots[name] = idx
}
val fnStatements = rawFnStatements?.let { stmt ->
if (useBytecodeStatements &&
parentContext !is CodeContext.ClassBody &&
@ -7004,7 +7066,13 @@ class Compiler(
val cls = resolveTypeDeclObjClass(param.type) ?: continue
paramKnownClasses[param.name] = cls
}
wrapFunctionBytecode(stmt, name, paramKnownClasses)
wrapFunctionBytecode(
stmt,
name,
paramKnownClasses,
forcedLocalSlots = forcedLocalSlots,
forcedLocalScopeId = paramSlotPlan.id
)
} else {
stmt
}
@ -7014,7 +7082,6 @@ class Compiler(
val closureBox = FunctionClosureBox()
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
val captureSlots = capturePlan.captures.toList()
val fnBody = object : Statement(), BytecodeBodyProvider {
override val pos: Pos = start
@ -7055,17 +7122,48 @@ class Compiler(
}
}
// load params from caller context
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
bindTypeParamsAtRuntime(context, argsDeclaration, mergedTypeParamDecls)
if (extTypeName != null) {
context.thisObj = callerContext.thisObj
}
return try {
fnStatements?.execute(context) ?: ObjVoid
} catch (e: ReturnException) {
if (e.label == null || e.label == name || e.label == outerLabel) e.result
else throw e
val bytecodeBody = (fnStatements as? BytecodeStatement)
if (bytecodeBody != null) {
val bytecodeFn = bytecodeBody.bytecodeFunction()
val binder: suspend (net.sergeych.lyng.bytecode.CmdFrame, Arguments) -> Unit = { frame, arguments ->
val slotPlan = bytecodeFn.localSlotPlanByName()
argsDeclaration.assignToFrame(
context,
arguments,
slotPlan,
frame.frame,
defaultAccessType = AccessType.Val
)
val typeBindings = bindTypeParamsAtRuntime(context, argsDeclaration, mergedTypeParamDecls)
if (typeBindings.isNotEmpty()) {
for ((name, bound) in typeBindings) {
val slot = slotPlan[name] ?: continue
frame.frame.setObj(slot, bound)
}
}
if (extTypeName != null) {
context.thisObj = callerContext.thisObj
}
}
return try {
net.sergeych.lyng.bytecode.CmdVm().execute(bytecodeFn, context, callerContext.args, binder)
} catch (e: ReturnException) {
if (e.label == null || e.label == name || e.label == outerLabel) e.result
else throw e
}
} else {
// load params from caller context
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
bindTypeParamsAtRuntime(context, argsDeclaration, mergedTypeParamDecls)
if (extTypeName != null) {
context.thisObj = callerContext.thisObj
}
return try {
fnStatements?.execute(context) ?: ObjVoid
} catch (e: ReturnException) {
if (e.label == null || e.label == name || e.label == outerLabel) e.result
else throw e
}
}
}
}

View File

@ -84,7 +84,7 @@ class Script(
seedModuleSlots(moduleTarget)
}
moduleBytecode?.let { fn ->
return CmdVm().execute(fn, scope, scope.args.list)
return CmdVm().execute(fn, scope, scope.args)
}
var lastResult: Obj = ObjVoid
for (s in statements) {

View File

@ -26,6 +26,8 @@ class BytecodeCompiler(
private val rangeLocalNames: Set<String> = emptySet(),
private val allowedScopeNames: Set<String>? = null,
private val moduleScopeId: Int? = null,
private val forcedLocalSlots: Map<String, Int> = emptyMap(),
private val forcedLocalScopeId: Int? = null,
private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
private val knownObjectNames: Set<String> = emptySet(),
@ -649,24 +651,12 @@ class BytecodeCompiler(
updateSlotType(slot, SlotType.OBJ)
return CompiledValue(slot, SlotType.OBJ)
}
val captureTableId = lambdaCaptureEntriesByRef[ref]?.let { captures ->
if (captures.isEmpty()) return@let null
val resolved = captures.map { entry ->
val slotIndex = resolveCaptureSlot(entry)
BytecodeCaptureEntry(
ownerKind = entry.ownerKind,
ownerScopeId = entry.ownerScopeId,
ownerSlotId = entry.ownerSlotId,
slotIndex = slotIndex
)
}
builder.addConst(BytecodeConst.CaptureTable(resolved))
}
val id = builder.addConst(BytecodeConst.ValueFn(ref.valueFn(), captureTableId))
val slot = allocSlot()
builder.emit(Opcode.MAKE_VALUE_FN, id, slot)
updateSlotType(slot, SlotType.OBJ)
return CompiledValue(slot, SlotType.OBJ)
val pos = (ref as? LambdaFnRef)?.pos ?: Pos.builtIn
val refName = ref::class.simpleName ?: "ValueFnRef"
throw BytecodeCompileException(
"Bytecode compile error: non-bytecode lambda $refName encountered",
pos
)
}
private fun resolveCaptureSlot(entry: LambdaCaptureEntry): Int {
@ -4130,36 +4120,6 @@ class BytecodeCompiler(
)
}
private fun emitStatementEval(stmt: Statement): CompiledValue {
val stmtName = stmt::class.simpleName ?: "UnknownStatement"
throw BytecodeCompileException("Unsupported statement in bytecode: $stmtName", stmt.pos)
}
private fun emitStatementCall(stmt: Statement): CompiledValue {
val constId = builder.addConst(BytecodeConst.ObjRef(stmt))
val calleeSlot = allocSlot()
builder.emit(Opcode.CONST_OBJ, constId, calleeSlot)
updateSlotType(calleeSlot, SlotType.OBJ)
val dst = allocSlot()
builder.emit(Opcode.CALL_SLOT, calleeSlot, 0, 0, dst)
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
private fun emitDeclExec(stmt: Statement): CompiledValue {
val executable = when (stmt) {
else -> throw BytecodeCompileException(
"Bytecode compile error: unsupported declaration ${stmt::class.simpleName}",
stmt.pos
)
}
val constId = builder.addConst(BytecodeConst.DeclExec(executable))
val dst = allocSlot()
builder.emit(Opcode.DECL_EXEC, constId, dst)
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
private fun emitDeclEnum(stmt: net.sergeych.lyng.EnumDeclStatement): CompiledValue {
val constId = builder.addConst(
BytecodeConst.EnumDecl(
@ -5746,13 +5706,15 @@ class BytecodeCompiler(
if (knownObjectNames.contains(ref.name)) {
return nameObjClass[ref.name] ?: ObjDynamic.type
}
val ownerScopeId = ref.captureOwnerScopeId ?: ref.scopeId
val ownerSlot = ref.captureOwnerSlot ?: ref.slot
val slot = resolveSlot(ref)
val fromSlot = slot?.let { slotObjClass[it] }
fromSlot
?: slotTypeByScopeId[refScopeId(ref)]?.get(refSlot(ref))
?: slotTypeByScopeId[ownerScopeId]?.get(ownerSlot)
?: nameObjClass[ref.name]
?: resolveTypeNameClass(ref.name)
?: slotInitClassByKey[ScopeSlotKey(refScopeId(ref), refSlot(ref))]
?: slotInitClassByKey[ScopeSlotKey(ownerScopeId, ownerSlot)]
?: run {
val match = slotInitClassByKey.entries.firstOrNull { (key, _) ->
val name = localSlotInfoMap[key]?.name ?: scopeSlotNameMap[key]
@ -6216,6 +6178,14 @@ class BytecodeCompiler(
if (allowLocalSlots) {
collectLoopSlotPlans(stmt, 0)
}
if (allowLocalSlots && forcedLocalSlots.isNotEmpty() && forcedLocalScopeId != null) {
for ((name, slotIndex) in forcedLocalSlots) {
val key = ScopeSlotKey(forcedLocalScopeId, slotIndex)
if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(name, isMutable = false, isDelegated = false)
}
}
}
if (allowLocalSlots && valueFnRefs.isNotEmpty() && lambdaCaptureEntriesByRef.isNotEmpty()) {
for (ref in valueFnRefs) {
val entries = lambdaCaptureEntriesByRef[ref] ?: continue

View File

@ -31,12 +31,7 @@ sealed class BytecodeConst {
data class StringVal(val value: String) : BytecodeConst()
data class PosVal(val pos: Pos) : BytecodeConst()
data class ObjRef(val value: Obj) : BytecodeConst()
data class Ref(val value: net.sergeych.lyng.obj.ObjRef) : BytecodeConst()
data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst()
data class ValueFn(
val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord,
val captureTableId: Int? = null,
) : BytecodeConst()
data class LambdaFn(
val fn: CmdFunction,
val captureTableId: Int?,
@ -48,7 +43,6 @@ sealed class BytecodeConst {
val returnLabels: Set<String>,
val pos: Pos,
) : BytecodeConst()
data class DeclExec(val executable: net.sergeych.lyng.DeclExecutable) : BytecodeConst()
data class EnumDecl(
val declaredName: String,
val qualifiedName: String,

View File

@ -40,9 +40,23 @@ class BytecodeFrame(
slotTypes[slot] = type.code
}
override fun getObj(slot: Int): Obj = objSlots[slot] ?: ObjNull
override fun getObj(slot: Int): Obj {
val value = objSlots[slot] ?: return ObjNull
return when (value) {
is net.sergeych.lyng.FrameSlotRef -> value.read()
is net.sergeych.lyng.RecordSlotRef -> value.read()
else -> value
}
}
internal fun getRawObj(slot: Int): Obj? = objSlots[slot]
override fun setObj(slot: Int, value: Obj) {
objSlots[slot] = value
when (val current = objSlots[slot]) {
is net.sergeych.lyng.FrameSlotRef -> current.write(value)
is net.sergeych.lyng.RecordSlotRef -> current.write(value)
else -> objSlots[slot] = value
}
slotTypes[slot] = SlotType.OBJ.code
}

View File

@ -30,7 +30,7 @@ class BytecodeStatement private constructor(
override suspend fun execute(scope: Scope): Obj {
scope.pos = pos
return CmdVm().execute(function, scope, scope.args.list)
return CmdVm().execute(function, scope, scope.args)
}
internal fun bytecodeFunction(): CmdFunction = function
@ -44,6 +44,8 @@ class BytecodeStatement private constructor(
rangeLocalNames: Set<String> = emptySet(),
allowedScopeNames: Set<String>? = null,
moduleScopeId: Int? = null,
forcedLocalSlots: Map<String, Int> = emptyMap(),
forcedLocalScopeId: Int? = null,
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
knownNameObjClass: Map<String, ObjClass> = emptyMap(),
knownObjectNames: Set<String> = emptySet(),
@ -69,6 +71,8 @@ class BytecodeStatement private constructor(
rangeLocalNames = rangeLocalNames,
allowedScopeNames = allowedScopeNames,
moduleScopeId = moduleScopeId,
forcedLocalSlots = forcedLocalSlots,
forcedLocalScopeId = forcedLocalScopeId,
slotTypeByScopeId = slotTypeByScopeId,
knownNameObjClass = knownNameObjClass,
knownObjectNames = knownObjectNames,

View File

@ -154,14 +154,14 @@ class CmdBuilder {
Opcode.CONST_NULL ->
listOf(OperandKind.SLOT)
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL,
Opcode.MAKE_VALUE_FN, Opcode.MAKE_LAMBDA_FN ->
Opcode.MAKE_LAMBDA_FN ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST)
Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
Opcode.DECL_EXEC, Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS,
Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS,
Opcode.ASSIGN_DESTRUCTURE ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
@ -254,7 +254,6 @@ class CmdBuilder {
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
Opcode.CONST_NULL -> CmdConstNull(operands[0])
Opcode.MAKE_VALUE_FN -> CmdMakeValueFn(operands[0], operands[1])
Opcode.MAKE_LAMBDA_FN -> CmdMakeLambda(operands[0], operands[1])
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
@ -416,7 +415,6 @@ class CmdBuilder {
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
Opcode.DECL_EXEC -> CmdDeclExec(operands[0], operands[1])
Opcode.DECL_ENUM -> CmdDeclEnum(operands[0], operands[1])
Opcode.DECL_FUNCTION -> CmdDeclFunction(operands[0], operands[1])
Opcode.DECL_CLASS -> CmdDeclClass(operands[0], operands[1])

View File

@ -86,7 +86,6 @@ object CmdDisassembler {
is CmdLoadThis -> Opcode.LOAD_THIS to intArrayOf(cmd.dst)
is CmdLoadThisVariant -> Opcode.LOAD_THIS_VARIANT to intArrayOf(cmd.typeId, cmd.dst)
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
is CmdMakeValueFn -> Opcode.MAKE_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
is CmdMakeLambda -> Opcode.MAKE_LAMBDA_FN to intArrayOf(cmd.id, cmd.dst)
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
@ -213,7 +212,6 @@ object CmdDisassembler {
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclExec -> Opcode.DECL_EXEC to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclEnum -> Opcode.DECL_ENUM to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclFunction -> Opcode.DECL_FUNCTION to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclClass -> Opcode.DECL_CLASS to intArrayOf(cmd.constId, cmd.slot)
@ -282,14 +280,14 @@ object CmdDisassembler {
Opcode.CONST_NULL ->
listOf(OperandKind.SLOT)
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL,
Opcode.MAKE_VALUE_FN, Opcode.MAKE_LAMBDA_FN ->
Opcode.MAKE_LAMBDA_FN ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST)
Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
Opcode.DECL_EXEC, Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS,
Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS,
Opcode.ASSIGN_DESTRUCTURE ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,

View File

@ -47,4 +47,23 @@ data class CmdFunction(
require(posByIp.size == cmds.size) { "posByIp size mismatch" }
}
}
fun localSlotPlanByName(): Map<String, Int> {
val result = LinkedHashMap<String, Int>()
for (i in localSlotNames.indices) {
val name = localSlotNames[i] ?: continue
val existing = result[name]
if (existing == null) {
result[name] = i
continue
}
val existingIsCapture = localSlotCaptures.getOrNull(existing) == true
val currentIsCapture = localSlotCaptures.getOrNull(i) == true
if (existingIsCapture && !currentIsCapture) {
result[name] = i
}
}
return result
}
}

View File

@ -23,14 +23,17 @@ import net.sergeych.lyng.obj.*
class CmdVm {
var result: Obj? = null
suspend fun execute(fn: CmdFunction, scope0: Scope, args: List<Obj>): Obj {
suspend fun execute(
fn: CmdFunction,
scope0: Scope,
args: Arguments,
binder: (suspend (CmdFrame, Arguments) -> Unit)? = null
): Obj {
result = null
val frame = CmdFrame(this, fn, scope0, args)
val frame = CmdFrame(this, fn, scope0, args.list)
frame.applyCaptureRecords()
binder?.invoke(frame, args)
val cmds = fn.cmds
if (fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
try {
while (result == null) {
val cmd = cmds[frame.ip]
@ -51,6 +54,10 @@ class CmdVm {
frame.cancelIterators()
return result ?: ObjVoid
}
suspend fun execute(fn: CmdFunction, scope0: Scope, args: List<Obj>): Obj {
return execute(fn, scope0, Arguments.from(args))
}
}
sealed class Cmd {
@ -1339,16 +1346,6 @@ class CmdDeclDelegated(internal val constId: Int, internal val slot: Int) : Cmd(
}
}
class CmdDeclExec(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.DeclExec
?: error("DECL_EXEC expects DeclExec at $constId")
val result = decl.executable.execute(frame.ensureScope())
frame.storeObjResult(slot, result)
return
}
}
class CmdDeclEnum(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.EnumDecl
@ -1394,20 +1391,7 @@ class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cm
val decl = frame.fn.constants[constId] as? BytecodeConst.DestructureDecl
?: error("DECL_DESTRUCTURE expects DestructureDecl at $constId")
val value = frame.slotToObj(slot)
val scope = frame.ensureScope()
for (name in decl.names) {
scope.addItem(name, true, ObjVoid, decl.visibility, isTransient = decl.isTransient)
}
decl.pattern.setAt(decl.pos, scope, value)
if (!decl.isMutable) {
for (name in decl.names) {
val rec = scope.objects[name] ?: continue
val immutableRec = rec.copy(isMutable = false)
scope.objects[name] = immutableRec
scope.localBindings[name] = immutableRec
scope.updateSlotFor(name, immutableRec)
}
}
assignDestructurePattern(frame, decl.pattern, value, decl.pos)
if (slot >= frame.fn.scopeSlotCount) {
frame.storeObjResult(slot, ObjVoid)
}
@ -1417,21 +1401,136 @@ class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cm
class CmdAssignDestructure(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope(useRefs = true)
}
val decl = frame.fn.constants[constId] as? BytecodeConst.DestructureAssign
?: error("ASSIGN_DESTRUCTURE expects DestructureAssign at $constId")
val value = frame.slotToObj(slot)
decl.pattern.setAt(decl.pos, frame.ensureScope(), value)
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
assignDestructurePattern(frame, decl.pattern, value, decl.pos)
frame.storeObjResult(slot, value)
return
}
}
private suspend fun assignDestructurePattern(frame: CmdFrame, pattern: ListLiteralRef, value: Obj, pos: Pos) {
val sourceList = (value as? ObjList)?.list
?: throw ScriptError(pos, "destructuring assignment requires a list on the right side")
val entries = pattern.entries()
val ellipsisIdx = entries.indexOfFirst { it is ListEntry.Spread }
if (entries.count { it is ListEntry.Spread } > 1) {
throw ScriptError(pos, "destructuring pattern can have only one splat")
}
if (ellipsisIdx < 0) {
if (sourceList.size < entries.size) {
throw ScriptError(pos, "too few elements for destructuring")
}
for (i in entries.indices) {
val entry = entries[i]
if (entry is ListEntry.Element) {
assignDestructureTarget(frame, entry.ref, sourceList[i], pos)
}
}
return
}
val headCount = ellipsisIdx
val tailCount = entries.size - ellipsisIdx - 1
if (sourceList.size < headCount + tailCount) {
throw ScriptError(pos, "too few elements for destructuring")
}
for (i in 0 until headCount) {
val entry = entries[i]
if (entry is ListEntry.Element) {
assignDestructureTarget(frame, entry.ref, sourceList[i], pos)
}
}
for (i in 0 until tailCount) {
val entry = entries[entries.size - 1 - i]
if (entry is ListEntry.Element) {
assignDestructureTarget(frame, entry.ref, sourceList[sourceList.size - 1 - i], pos)
}
}
val spreadEntry = entries[ellipsisIdx] as ListEntry.Spread
val spreadList = sourceList.subList(headCount, sourceList.size - tailCount)
assignDestructureTarget(frame, spreadEntry.ref, ObjList(spreadList.toMutableList()), pos)
}
private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value: Obj, pos: Pos) {
when (ref) {
is ListLiteralRef -> assignDestructurePattern(frame, ref, value, pos)
is LocalSlotRef -> {
val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = ref.captureOwnerScopeId != null)
if (index != null) {
frame.frame.setObj(index, value)
return
}
}
is LocalVarRef -> {
val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false)
if (index != null) {
frame.frame.setObj(index, value)
return
}
}
is FastLocalVarRef -> {
val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false)
if (index != null) {
frame.frame.setObj(index, value)
return
}
}
else -> {}
}
ref.setAt(pos, frame.ensureScope(), value)
}
private fun resolveLocalSlotIndex(fn: CmdFunction, name: String, preferCapture: Boolean): Int? {
val names = fn.localSlotNames
if (preferCapture) {
for (i in names.indices) {
if (names[i] == name && fn.localSlotCaptures.getOrNull(i) == true) return i
}
} else {
for (i in names.indices) {
if (names[i] == name && fn.localSlotCaptures.getOrNull(i) != true) return i
}
}
for (i in names.indices) {
if (names[i] == name) return i
}
return null
}
private fun isAstStatement(stmt: Statement): Boolean {
return when (stmt) {
is ExpressionStatement,
is IfStatement,
is ForInStatement,
is WhileStatement,
is DoWhileStatement,
is BlockStatement,
is InlineBlockStatement,
is VarDeclStatement,
is DelegatedVarDeclStatement,
is DestructuringVarDeclStatement,
is BreakStatement,
is ContinueStatement,
is ReturnStatement,
is ThrowStatement,
is net.sergeych.lyng.NopStatement,
is ExtensionPropertyDeclStatement,
is ClassDeclStatement,
is FunctionDeclStatement,
is EnumDeclStatement,
is TryStatement,
is WhenStatement -> true
else -> false
}
}
class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl
@ -1511,10 +1610,12 @@ class CmdCallSlot(
val result = if (canPool) {
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
} else {
// Pooling for Statement-based callables (lambdas) can still alter closure semantics; keep safe path for now.
val scope = frame.ensureScope()
if (callee is Statement && callee !is BytecodeStatement && callee !is BytecodeCallable) {
frame.syncFrameToScope(useRefs = true)
if (callee is Statement) {
val bytecodeBody = (callee as? BytecodeBodyProvider)?.bytecodeBody()
if (callee !is BytecodeStatement && callee !is BytecodeCallable && bytecodeBody == null && isAstStatement(callee)) {
scope.raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
}
}
callee.callOn(scope.createChildScope(scope.pos, args = args))
}
@ -1853,37 +1954,6 @@ class CmdSetIndex(
}
}
class CmdEvalRef(internal val id: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope(useRefs = true)
}
val ref = frame.fn.constants[id] as? BytecodeConst.Ref
?: error("EVAL_REF expects Ref at $id")
val result = ref.value.evalValue(frame.ensureScope())
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
frame.storeObjResult(dst, result)
return
}
}
class CmdMakeValueFn(internal val id: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val valueFn = frame.fn.constants.getOrNull(id) as? BytecodeConst.ValueFn
?: error("MAKE_VALUE_FN expects ValueFn at $id")
val scope = frame.ensureScope()
val previousCaptures = scope.captureRecords
val captureRecords = valueFn.captureTableId?.let { frame.buildCaptureRecords(it) }
scope.captureRecords = captureRecords
val result = valueFn.fn(scope).value
scope.captureRecords = previousCaptures
frame.storeObjResult(dst, result)
return
}
}
class CmdMakeLambda(internal val id: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val lambdaConst = frame.fn.constants.getOrNull(id) as? BytecodeConst.LambdaFn
@ -1934,18 +2004,40 @@ class BytecodeLambdaCallable(
closureScope.raiseIllegalState("bytecode lambda capture records missing")
}
if (argsDeclaration == null) {
val l = scope.args.list
val itValue: Obj = when (l.size) {
0 -> ObjVoid
1 -> l[0]
else -> ObjList(l.toMutableList())
}
context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument)
// Bound in the bytecode entry binder.
} else {
argsDeclaration.assignToContext(context, scope.args, defaultAccessType = AccessType.Val)
// args bound into frame slots in the bytecode entry binder
}
return try {
CmdVm().execute(fn, context, scope.args.list)
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
val slotPlan = fn.localSlotPlanByName()
if (argsDeclaration == null) {
val l = arguments.list
val itValue: Obj = when (l.size) {
0 -> ObjVoid
1 -> l[0]
else -> ObjList(l.toMutableList())
}
val itSlot = slotPlan["it"]
if (itSlot != null) {
frame.frame.setObj(itSlot, itValue)
if (context.getLocalRecordDirect("it") == null) {
context.addItem("it", false, FrameSlotRef(frame.frame, itSlot), recordType = ObjRecord.Type.Argument)
}
} else if (context.getLocalRecordDirect("it") == null) {
context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument)
}
} else {
argsDeclaration.assignToFrame(
context,
arguments,
slotPlan,
frame.frame,
defaultAccessType = AccessType.Val
)
}
}
CmdVm().execute(fn, context, scope.args, binder)
} catch (e: ReturnException) {
if (e.label == null || returnLabels.contains(e.label)) e.result else throw e
}
@ -2361,14 +2453,16 @@ class CmdFrame(
target.setSlotValue(index, value)
} else {
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
when (val existing = frame.getRawObj(localIndex)) {
is FrameSlotRef -> {
existing.write(value)
return
}
is RecordSlotRef -> {
existing.write(value)
return
}
else -> {}
}
frame.setObj(localIndex, value)
}
@ -2405,14 +2499,16 @@ class CmdFrame(
target.setSlotValue(index, ObjInt.of(value))
} else {
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
when (val existing = frame.getRawObj(localIndex)) {
is FrameSlotRef -> {
existing.write(ObjInt.of(value))
return
}
is RecordSlotRef -> {
existing.write(ObjInt.of(value))
return
}
else -> {}
}
frame.setInt(localIndex, value)
}
@ -2451,14 +2547,16 @@ class CmdFrame(
target.setSlotValue(index, ObjReal.of(value))
} else {
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
when (val existing = frame.getRawObj(localIndex)) {
is FrameSlotRef -> {
existing.write(ObjReal.of(value))
return
}
is RecordSlotRef -> {
existing.write(ObjReal.of(value))
return
}
else -> {}
}
frame.setReal(localIndex, value)
}
@ -2495,14 +2593,16 @@ class CmdFrame(
target.setSlotValue(index, if (value) ObjTrue else ObjFalse)
} else {
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
when (val existing = frame.getRawObj(localIndex)) {
is FrameSlotRef -> {
existing.write(if (value) ObjTrue else ObjFalse)
return
}
is RecordSlotRef -> {
existing.write(if (value) ObjTrue else ObjFalse)
return
}
else -> {}
}
frame.setBool(localIndex, value)
}
@ -2560,13 +2660,6 @@ class CmdFrame(
if (fn.localSlotCaptures.getOrNull(local) == true) {
return localSlotToObj(local)
}
val localName = fn.localSlotNames.getOrNull(local)
if (localName != null && fn.localSlotDelegated.getOrNull(local) != true) {
val rec = scope.getLocalRecordDirect(localName) ?: scope.localBindings[localName]
if (rec != null && (rec.type == ObjRecord.Type.Delegated || rec.type == ObjRecord.Type.Property || rec.value is ObjProperty)) {
return scope.resolve(rec, localName)
}
}
return when (frame.getSlotTypeCode(local)) {
SlotType.INT.code -> ObjInt.of(frame.getInt(local))
SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
@ -2612,68 +2705,6 @@ class CmdFrame(
}
}
fun syncFrameToScope(useRefs: Boolean = false) {
val names = fn.localSlotNames
if (names.isEmpty()) return
for (i in names.indices) {
val name = names[i] ?: continue
if (fn.localSlotCaptures.getOrNull(i) == true) continue
if (scopeSlotNames.contains(name)) continue
val target = resolveLocalScope(i) ?: continue
val isDelegated = fn.localSlotDelegated.getOrNull(i) == true
val value = if (useRefs) FrameSlotRef(frame, i) else localSlotToObj(i)
val rec = target.getLocalRecordDirect(name)
if (rec == null) {
val isMutable = fn.localSlotMutables.getOrElse(i) { true }
if (isDelegated) {
val delegatedRec = target.addItem(
name,
isMutable,
ObjNull,
recordType = ObjRecord.Type.Delegated
)
delegatedRec.delegate = localSlotToObj(i)
} else {
target.addItem(name, isMutable, value)
}
} else {
if (isDelegated && rec.type == ObjRecord.Type.Delegated) {
rec.delegate = localSlotToObj(i)
continue
}
val existing = rec.value
if (existing is FrameSlotRef && !useRefs) continue
rec.value = value
}
}
}
fun syncScopeToFrame() {
val names = fn.localSlotNames
if (names.isEmpty()) return
for (i in names.indices) {
val name = names[i] ?: continue
if (fn.localSlotCaptures.getOrNull(i) == true) continue
val target = resolveLocalScope(i) ?: continue
val rec = target.getLocalRecordDirect(name) ?: continue
if (fn.localSlotDelegated.getOrNull(i) == true && rec.type == ObjRecord.Type.Delegated) {
val delegate = rec.delegate ?: ObjNull
frame.setObj(i, delegate)
continue
}
val value = rec.value
if (value is FrameSlotRef) {
continue
}
when (value) {
is ObjInt -> frame.setInt(i, value.value)
is ObjReal -> frame.setReal(i, value.value)
is ObjBool -> frame.setBool(i, value.value)
else -> frame.setObj(i, value)
}
}
}
suspend fun buildArguments(argBase: Int, argCount: Int): Arguments {
if (argCount == 0) return Arguments.EMPTY
if ((argCount and ARG_PLAN_FLAG) != 0) {
@ -2747,7 +2778,7 @@ class CmdFrame(
return scope
}
private fun scopeTarget(slot: Int): Scope {
internal fun scopeTarget(slot: Int): Scope {
return if (slot < fn.scopeSlotCount && fn.scopeSlotIsModule.getOrNull(slot) == true) {
moduleScope
} else {
@ -2828,7 +2859,7 @@ class CmdFrame(
target.setSlotValue(index, value)
}
private fun ensureScopeSlot(target: Scope, slot: Int): Int {
internal fun ensureScopeSlot(target: Scope, slot: Int): Int {
val name = fn.scopeSlotNames[slot]
if (name != null) {
val existing = target.getSlotIndexOf(name)

View File

@ -31,7 +31,6 @@ enum class Opcode(val code: Int) {
RANGE_INT_BOUNDS(0x0B),
MAKE_RANGE(0x0C),
LOAD_THIS(0x0D),
MAKE_VALUE_FN(0x0E),
LOAD_THIS_VARIANT(0x0F),
INT_TO_REAL(0x10),
@ -160,7 +159,6 @@ enum class Opcode(val code: Int) {
STORE_BOOL_ADDR(0xB9),
THROW(0xBB),
RETHROW_PENDING(0xBC),
DECL_EXEC(0xBD),
DECL_ENUM(0xBE),
ITER_PUSH(0xBF),
ITER_POP(0xC0),

View File

@ -31,6 +31,12 @@ extern class List<T> : Array<T> {
fun add(value: T, more...): Void
}
extern class RingBuffer<T> : Iterable<T> {
val size: Int
fun first(): T
fun add(value: T): Void
}
extern class Set<T> : Collection<T> {
}
@ -270,8 +276,10 @@ fun Iterable<T>.shuffled(): List<T> {
*/
fun Iterable<Iterable<T>>.flatten(): List<T> {
var result: List<T> = List()
forEach { i ->
i.forEach { result += it }
for (i in this) {
for (item in i) {
result += item
}
}
result
}