Finalize bytecode-only lambdas and frame binding
This commit is contained in:
parent
a481371349
commit
db4f7d0973
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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,27 +2913,25 @@ 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"
|
||||
)
|
||||
body
|
||||
} else {
|
||||
returnLabelStack.addLast(returnLabels)
|
||||
try {
|
||||
wrapFunctionBytecode(body, "<lambda>", paramKnownClasses)
|
||||
wrapFunctionBytecode(
|
||||
body,
|
||||
"<lambda>",
|
||||
paramKnownClasses,
|
||||
forcedLocalSlots = paramSlotPlanSnapshot,
|
||||
forcedLocalScopeId = paramSlotPlan.id
|
||||
)
|
||||
} catch (e: net.sergeych.lyng.bytecode.BytecodeCompileException) {
|
||||
val pos = e.pos ?: body.pos
|
||||
bytecodeFallbackReporter?.invoke(
|
||||
pos,
|
||||
"lambda bytecode compile failed: ${e.message}"
|
||||
)
|
||||
body
|
||||
throw e
|
||||
} finally {
|
||||
returnLabelStack.removeLast()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
body
|
||||
}
|
||||
@ -3001,6 +3002,48 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
} else {
|
||||
if (argsDeclaration == null) {
|
||||
val l = scope.args.list
|
||||
val itValue: Obj = when (l.size) {
|
||||
@ -3012,15 +3055,15 @@ class Compiler(
|
||||
} else {
|
||||
argsDeclaration.assignToContext(context, scope.args, defaultAccessType = AccessType.Val)
|
||||
}
|
||||
val effectiveStatements = if (usesBytecodeBody) fnStatements else body
|
||||
return try {
|
||||
effectiveStatements.execute(context)
|
||||
body.execute(context)
|
||||
} catch (e: ReturnException) {
|
||||
if (e.label == null || returnLabels.contains(e.label)) e.result
|
||||
else throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val callable: Obj = if (wrapAsExtensionCallable) {
|
||||
ObjExtensionMethodCallable("<lambda>", stmt)
|
||||
} else {
|
||||
@ -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,6 +7122,36 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@ -7069,6 +7166,7 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cc.labels.remove(name)
|
||||
outerLabel?.let { cc.labels.remove(it) }
|
||||
val spec = FunctionDeclSpec(
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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,25 +651,13 @@ 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
|
||||
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
|
||||
)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
private fun resolveCaptureSlot(entry: LambdaCaptureEntry): Int {
|
||||
val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
// Bound in the bytecode entry binder.
|
||||
} else {
|
||||
// args bound into frame slots in the bytecode entry binder
|
||||
}
|
||||
return try {
|
||||
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())
|
||||
}
|
||||
context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument)
|
||||
} else {
|
||||
argsDeclaration.assignToContext(context, scope.args, defaultAccessType = AccessType.Val)
|
||||
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)
|
||||
}
|
||||
return try {
|
||||
CmdVm().execute(fn, context, scope.args.list)
|
||||
} 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,15 +2453,17 @@ class CmdFrame(
|
||||
target.setSlotValue(index, value)
|
||||
} else {
|
||||
val localIndex = slot - fn.scopeSlotCount
|
||||
val existing = frame.getObj(localIndex)
|
||||
if (existing is FrameSlotRef) {
|
||||
when (val existing = frame.getRawObj(localIndex)) {
|
||||
is FrameSlotRef -> {
|
||||
existing.write(value)
|
||||
return
|
||||
}
|
||||
if (existing is RecordSlotRef) {
|
||||
is RecordSlotRef -> {
|
||||
existing.write(value)
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
frame.setObj(localIndex, value)
|
||||
}
|
||||
}
|
||||
@ -2405,15 +2499,17 @@ class CmdFrame(
|
||||
target.setSlotValue(index, ObjInt.of(value))
|
||||
} else {
|
||||
val localIndex = slot - fn.scopeSlotCount
|
||||
val existing = frame.getObj(localIndex)
|
||||
if (existing is FrameSlotRef) {
|
||||
when (val existing = frame.getRawObj(localIndex)) {
|
||||
is FrameSlotRef -> {
|
||||
existing.write(ObjInt.of(value))
|
||||
return
|
||||
}
|
||||
if (existing is RecordSlotRef) {
|
||||
is RecordSlotRef -> {
|
||||
existing.write(ObjInt.of(value))
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
frame.setInt(localIndex, value)
|
||||
}
|
||||
}
|
||||
@ -2451,15 +2547,17 @@ class CmdFrame(
|
||||
target.setSlotValue(index, ObjReal.of(value))
|
||||
} else {
|
||||
val localIndex = slot - fn.scopeSlotCount
|
||||
val existing = frame.getObj(localIndex)
|
||||
if (existing is FrameSlotRef) {
|
||||
when (val existing = frame.getRawObj(localIndex)) {
|
||||
is FrameSlotRef -> {
|
||||
existing.write(ObjReal.of(value))
|
||||
return
|
||||
}
|
||||
if (existing is RecordSlotRef) {
|
||||
is RecordSlotRef -> {
|
||||
existing.write(ObjReal.of(value))
|
||||
return
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
frame.setReal(localIndex, value)
|
||||
}
|
||||
}
|
||||
@ -2495,15 +2593,17 @@ 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) {
|
||||
when (val existing = frame.getRawObj(localIndex)) {
|
||||
is FrameSlotRef -> {
|
||||
existing.write(if (value) ObjTrue else ObjFalse)
|
||||
return
|
||||
}
|
||||
if (existing is RecordSlotRef) {
|
||||
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)
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user