From db4f7d0973584958450143e38dd112fe26460380 Mon Sep 17 00:00:00 2001 From: sergeych Date: Thu, 12 Feb 2026 17:47:05 +0300 Subject: [PATCH] Finalize bytecode-only lambdas and frame binding --- bytecode_migration_plan.md | 11 +- .../net/sergeych/lyng/ArgsDeclaration.kt | 206 +++++++++ .../kotlin/net/sergeych/lyng/Compiler.kt | 208 ++++++--- .../kotlin/net/sergeych/lyng/Script.kt | 2 +- .../lyng/bytecode/BytecodeCompiler.kt | 70 +--- .../sergeych/lyng/bytecode/BytecodeConst.kt | 6 - .../sergeych/lyng/bytecode/BytecodeFrame.kt | 18 +- .../lyng/bytecode/BytecodeStatement.kt | 6 +- .../net/sergeych/lyng/bytecode/CmdBuilder.kt | 6 +- .../sergeych/lyng/bytecode/CmdDisassembler.kt | 6 +- .../net/sergeych/lyng/bytecode/CmdFunction.kt | 19 + .../net/sergeych/lyng/bytecode/CmdRuntime.kt | 395 ++++++++++-------- .../net/sergeych/lyng/bytecode/Opcode.kt | 2 - lynglib/stdlib/lyng/root.lyng | 12 +- 14 files changed, 653 insertions(+), 314 deletions(-) diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index 4d240c7..91b5257 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -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 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt index 16703ba..a4b2cd1 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt @@ -250,6 +250,212 @@ data class ArgsDeclaration(val params: List, 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, + 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 + 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(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 { + 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. * diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index de1d509..2a09c88 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -1892,10 +1892,11 @@ class Compiler( private fun wrapFunctionBytecode( stmt: Statement, name: String, - extraKnownNameObjClass: Map = emptyMap() + extraKnownNameObjClass: Map = emptyMap(), + forcedLocalSlots: Map = 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, + "", + paramKnownClasses, + forcedLocalSlots = paramSlotPlanSnapshot, + forcedLocalScopeId = paramSlotPlan.id ) - body - } else { - returnLabelStack.addLast(returnLabels) - try { - wrapFunctionBytecode(body, "", 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 - ) { - if (typeParams.isEmpty()) return + ): Map { + if (typeParams.isEmpty()) return emptyMap() val inferred = mutableMapOf() 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(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() + 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 + } } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index c0a1d63..351cd98 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -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) { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index c332425..022de45 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -26,6 +26,8 @@ class BytecodeCompiler( private val rangeLocalNames: Set = emptySet(), private val allowedScopeNames: Set? = null, private val moduleScopeId: Int? = null, + private val forcedLocalSlots: Map = emptyMap(), + private val forcedLocalScopeId: Int? = null, private val slotTypeByScopeId: Map> = emptyMap(), private val knownNameObjClass: Map = emptyMap(), private val knownObjectNames: Set = 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 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt index e50cba3..55d2fab 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt @@ -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) : 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, val pos: Pos, ) : BytecodeConst() - data class DeclExec(val executable: net.sergeych.lyng.DeclExecutable) : BytecodeConst() data class EnumDecl( val declaredName: String, val qualifiedName: String, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt index 0f1a818..ac465bd 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeFrame.kt @@ -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 } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt index 08d075f..202d8f1 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt @@ -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 = emptySet(), allowedScopeNames: Set? = null, moduleScopeId: Int? = null, + forcedLocalSlots: Map = emptyMap(), + forcedLocalScopeId: Int? = null, slotTypeByScopeId: Map> = emptyMap(), knownNameObjClass: Map = emptyMap(), knownObjectNames: Set = 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, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt index 718368a..1f5b84a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -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]) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt index 6534f7e..d2f327f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -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, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt index 2590756..fcd047e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt @@ -47,4 +47,23 @@ data class CmdFunction( require(posByIp.size == cmds.size) { "posByIp size mismatch" } } } + + fun localSlotPlanByName(): Map { + val result = LinkedHashMap() + 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 + } + } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index 925cd60..fb7c262 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -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 { + 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 { + 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) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt index fbb879e..1b07167 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -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), diff --git a/lynglib/stdlib/lyng/root.lyng b/lynglib/stdlib/lyng/root.lyng index 441d86c..13a8fe0 100644 --- a/lynglib/stdlib/lyng/root.lyng +++ b/lynglib/stdlib/lyng/root.lyng @@ -31,6 +31,12 @@ extern class List : Array { fun add(value: T, more...): Void } +extern class RingBuffer : Iterable { + val size: Int + fun first(): T + fun add(value: T): Void +} + extern class Set : Collection { } @@ -270,8 +276,10 @@ fun Iterable.shuffled(): List { */ fun Iterable>.flatten(): List { var result: List = List() - forEach { i -> - i.forEach { result += it } + for (i in this) { + for (item in i) { + result += item + } } result }