Migrate runtime locals to frame slots
This commit is contained in:
parent
34678068ac
commit
cd145f6a96
@ -1,175 +1,7 @@
|
|||||||
# Bytecode Migration Plan
|
# Bytecode Migration Plan (Archived)
|
||||||
|
|
||||||
Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM tests green after each step.
|
Status: completed.
|
||||||
|
|
||||||
## Steps
|
Historical reference:
|
||||||
|
- `notes/archive/bytecode_migration_plan.md` (full plan)
|
||||||
- [x] Step 1: Module/import slots seeded into module frame; bytecode module resolution works across closures.
|
- `notes/archive/bytecode_migration_plan_completed.md` (summary)
|
||||||
- [x] Step 2: Allow implicit/qualified `this` member refs to compile to bytecode.
|
|
||||||
- [x] Enable bytecode for `ImplicitThisMethodCallRef`, `QualifiedThisMethodSlotCallRef`, `QualifiedThisFieldSlotRef`.
|
|
||||||
- [x] Keep unsupported cases blocked: `ClassScopeMemberRef`, dynamic receivers, delegated members.
|
|
||||||
- [x] JVM tests must be green before commit.
|
|
||||||
- [x] Step 3: Bytecode support for `try/catch/finally`.
|
|
||||||
- [x] Implement bytecode emission for try/catch and finally blocks.
|
|
||||||
- [x] Preserve existing error/stack semantics.
|
|
||||||
- [x] JVM tests must be green before commit.
|
|
||||||
|
|
||||||
## Remaining Migration (prioritized)
|
|
||||||
|
|
||||||
- [x] Step 4: Allow bytecode wrapping for supported declaration statements.
|
|
||||||
- [x] Enable `DestructuringVarDeclStatement` and `ExtensionPropertyDeclStatement` in `containsUnsupportedForBytecode`.
|
|
||||||
- [x] Keep JVM tests green before commit.
|
|
||||||
- [x] Step 5: Enable bytecode for delegated var declarations.
|
|
||||||
- [x] Revisit `containsDelegatedRefs` guard for `DelegatedVarDeclStatement`.
|
|
||||||
- [x] Ensure delegate binding uses explicit `Statement` objects (no inline suspend lambdas).
|
|
||||||
- [x] Keep JVM tests green before commit.
|
|
||||||
- [x] Step 6: Map literal spread in bytecode.
|
|
||||||
- [x] Replace `MapLiteralEntry.Spread` bytecode exception with runtime `putAll`/merge logic.
|
|
||||||
- [x] Step 7: Class-scope member refs in bytecode.
|
|
||||||
- [x] Support `ClassScopeMemberRef` without scope-map fallback.
|
|
||||||
- [x] Step 8: ObjDynamic member access in bytecode.
|
|
||||||
- [x] Allow dynamic receiver field/method lookup without falling back to interpreter.
|
|
||||||
- [x] Step 9: Module-level bytecode execution.
|
|
||||||
- [x] Compile `Script` bodies to bytecode instead of interpreting at module scope.
|
|
||||||
- [x] Keep import/module slot seeding in frame-only flow.
|
|
||||||
- [x] Step 10: Bytecode for declaration statements in module scripts.
|
|
||||||
- [x] Support `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement` in bytecode compilation.
|
|
||||||
- [x] Keep a mixed execution path for declarations (module bytecode calls statement bodies via `CALL_SLOT`).
|
|
||||||
- [x] Ensure module object member refs compile as instance access (not class-scope).
|
|
||||||
- [x] Step 11: Destructuring assignment bytecode.
|
|
||||||
- [x] Handle `[a, b] = expr` (AssignRef target `ListLiteralRef`) without interpreter fallback.
|
|
||||||
- [x] Step 12: Optional member assign-ops and inc/dec in bytecode.
|
|
||||||
- [x] Support `a?.b += 1` and `a?.b++` for `FieldRef` targets.
|
|
||||||
- [x] Fix post-inc return value for object slots stored in scope frames.
|
|
||||||
- [x] Handle optional receivers for member assign-ops and inc/dec without evaluating operands on null.
|
|
||||||
- [x] Support class-scope and index optional inc/dec paths in bytecode.
|
|
||||||
- [x] Step 13: Qualified `this` value refs in bytecode.
|
|
||||||
- [x] Compile `QualifiedThisRef` (`this@Type`) via `LOAD_THIS_VARIANT`.
|
|
||||||
- [x] Add a JVM test that evaluates `this@Type` as a value inside nested classes.
|
|
||||||
- [x] Step 14: Fast local ref reads in bytecode.
|
|
||||||
- [x] Support `FastLocalVarRef` reads with the same slot resolution as `LocalVarRef`.
|
|
||||||
- [x] If `BoundLocalVarRef` is still emitted, map it to a direct slot read instead of failing.
|
|
||||||
- [x] Add a JVM test that exercises fast-local reads in a bytecode-compiled function.
|
|
||||||
- [x] Step 15: Class-scope `?=` in bytecode.
|
|
||||||
- [x] Handle `C.x ?= v` and `C?.x ?= v` for class-scope members without falling back.
|
|
||||||
- [x] Add a JVM test for class-scope `?=` on static vars.
|
|
||||||
- [x] Step 16: Remove dead `ToBoolStatement`.
|
|
||||||
- [x] Confirm no parser/compiler paths construct `ToBoolStatement` and delete it plus interpreter hooks.
|
|
||||||
- [x] Keep JVM tests green after removal.
|
|
||||||
- [x] Step 17: Callable property calls in bytecode.
|
|
||||||
- [x] Support `CallRef` where the target is a `FieldRef` (e.g., `(obj.fn)()`), keeping compile-time resolution.
|
|
||||||
- [x] Add a JVM test for a callable property call compiled to bytecode.
|
|
||||||
- [x] Step 18: Delegated member access in bytecode.
|
|
||||||
- [x] Remove `containsDelegatedRefs` guard once bytecode emits delegated get/set/call correctly.
|
|
||||||
- [x] Add JVM coverage for delegated member get/set/call in bytecode.
|
|
||||||
- [x] Step 19: Unknown receiver member access in bytecode.
|
|
||||||
- [x] Reject Object/unknown receiver member calls without explicit cast or Dynamic.
|
|
||||||
- [x] Add union-member dispatch with ordered type checks and runtime mismatch error.
|
|
||||||
- [x] Add JVM tests for unknown receiver and union member access.
|
|
||||||
- [x] Step 20: Bytecode support for `NopStatement`.
|
|
||||||
- [x] Allow `NopStatement` in `containsUnsupportedForBytecode`.
|
|
||||||
- [x] Emit `ObjVoid` directly in bytecode for `NopStatement` in statement/value contexts.
|
|
||||||
- [x] Add a JVM test that exercises a code path returning `NopStatement` in bytecode (e.g., static class member decl in class body).
|
|
||||||
- [x] Step 21: Union mismatch path in bytecode.
|
|
||||||
- [x] Replace `UnionTypeMismatchStatement` branch with a bytecode-compilable throw path (no custom `StatementRef` that blocks bytecode).
|
|
||||||
- [x] Add a JVM test that forces the union mismatch at runtime and asserts the error message.
|
|
||||||
- [x] Step 22: Delegated local slots in bytecode.
|
|
||||||
- [x] Support reads/writes/assign-ops/inc/dec for delegated locals (`LocalSlotRef.isDelegated`) in `BytecodeCompiler`.
|
|
||||||
- [x] Remove `containsDelegatedRefs` guard once delegated locals are bytecode-safe.
|
|
||||||
- [x] Add JVM tests that use delegated locals inside bytecode-compiled functions.
|
|
||||||
- [x] Step 23: Refactor delegated locals to keep delegate objects in frame slots.
|
|
||||||
- [x] Add bytecode ops to bind/get/set delegated locals without scope storage.
|
|
||||||
- [x] Store delegated locals in frame slots and compile get/set/assign ops with new ops.
|
|
||||||
- [x] Preserve reflection facade by syncing delegated locals into scope only when needed.
|
|
||||||
- [x] Step 24: Remove `ASSIGN_SCOPE_SLOT` now that delegated locals are always frame-backed.
|
|
||||||
- [x] Force delegated locals into local slots (even module) and avoid scope-slot resolution.
|
|
||||||
- [x] Drop opcode/runtime support for `ASSIGN_SCOPE_SLOT`.
|
|
||||||
|
|
||||||
## Frame-Only Execution (new, before interpreter removal)
|
|
||||||
|
|
||||||
- [x] Step 24A: Bytecode capture tables for lambdas (compiler only).
|
|
||||||
- [x] Emit per-lambda capture tables containing (ownerFrameKind, ownerSlotId).
|
|
||||||
- [x] Create captures only when detected; do not allocate scope slots.
|
|
||||||
- [x] Add disassembler output for capture tables.
|
|
||||||
- [x] JVM tests must be green before commit.
|
|
||||||
- [x] Step 24B: Frame-slot captures in bytecode runtime.
|
|
||||||
- [x] Build lambdas from bytecode + capture table (no capture names).
|
|
||||||
- [x] Read captured values via `FrameSlotRef` only.
|
|
||||||
- [x] Forbid `resolveCaptureRecord` in bytecode paths; keep only in interpreter.
|
|
||||||
- [x] JVM tests must be green before commit.
|
|
||||||
- [x] Step 24C: Remove scope local mirroring in bytecode execution.
|
|
||||||
- [x] Remove/disable any bytecode runtime code that writes locals into Scope for execution.
|
|
||||||
- [x] Keep Scope creation only for reflection/Kotlin interop paths.
|
|
||||||
- [x] JVM tests must be green before commit.
|
|
||||||
- [x] Step 24D: Eliminate `ClosureScope` usage on bytecode execution paths.
|
|
||||||
- [x] Avoid `ClosureScope` in bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty).
|
|
||||||
- [x] Keep interpreter path using `ClosureScope` until interpreter removal.
|
|
||||||
- [x] JVM tests must be green before commit.
|
|
||||||
- [x] Step 24E: Isolate interpreter-only capture logic.
|
|
||||||
- [x] Mark `resolveCaptureRecord` paths as interpreter-only.
|
|
||||||
- [x] Guard or delete any bytecode path that tries to sync captures into scopes.
|
|
||||||
- [x] JVM tests must be green before commit.
|
|
||||||
|
|
||||||
## Interpreter Removal (next)
|
|
||||||
|
|
||||||
- [ ] Step 25: Replace Statement-based declaration calls in bytecode.
|
|
||||||
- [x] Add bytecode const/op for enum declarations (no `Statement` objects in constants).
|
|
||||||
- [x] Add bytecode const/op for class declarations (no `Statement` objects in constants).
|
|
||||||
- [x] Add bytecode const/op for function declarations (no `Statement` objects in constants).
|
|
||||||
- [x] Replace `emitStatementCall` usage for `EnumDeclStatement`.
|
|
||||||
- [x] Replace `emitStatementCall` usage for `ClassDeclStatement`.
|
|
||||||
- [x] Replace `emitStatementCall` usage for `FunctionDeclStatement`.
|
|
||||||
- [x] Add JVM disasm coverage to ensure module init has no `CALL_SLOT` to `Callable@...` for declarations.
|
|
||||||
- [ ] Step 26: Bytecode-backed lambdas (remove `ValueFnRef` runtime execution).
|
|
||||||
- [x] Compile lambda bodies to bytecode and emit an opcode to create a callable from bytecode + capture plan.
|
|
||||||
- [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.
|
|
||||||
- [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.
|
|
||||||
- [x] Remove `emitStatementCall`/`emitStatementEval` once unused.
|
|
||||||
- [ ] Step 28: Scope as facade only.
|
|
||||||
- [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.
|
|
||||||
|
|
||||||
## Interpreter Removal v2 (2026-02-12)
|
|
||||||
|
|
||||||
Goal: remove interpreter execution paths entirely without regressing semantics; all runtime execution must be bytecode + frame slots.
|
|
||||||
|
|
||||||
- [ ] Step A1: Interpreter path audit (map all remaining runtime `Statement.execute` and scope-lookup paths).
|
|
||||||
- [x] Audit results (current hotspots):
|
|
||||||
- `ClassDeclStatement.executeClassDecl`: executes class body/init via `spec.bodyInit?.execute` and `spec.initScope` (Statement list).
|
|
||||||
- `FunctionDeclStatement.executeFunctionDecl`: class-body delegated function init uses `Statement.execute` in initializer thunk.
|
|
||||||
- `EnumDeclStatement.execute`: direct execution path exists (bytecode also emits DECL_ENUM).
|
|
||||||
- `BlockStatement.execute`: creates child scope, applies slot plan/captures, then executes `Script` (which may interpret).
|
|
||||||
- `Script.execute`: interpreter loop when `moduleBytecode` is null or disabled.
|
|
||||||
- `ObjClass.addFn/addProperty` wrappers use `ObjNativeCallable` calling into `Statement.execute` for declared bodies.
|
|
||||||
- Object expressions: class body executed via `executeClassDecl` path (same as above).
|
|
||||||
- Extension wrappers: `ObjExtensionMethodCallable` uses callable that executes `Statement`.
|
|
||||||
|
|
||||||
- [ ] Step A2: Bytecode-backed class + init.
|
|
||||||
- Replace class-body and instance init execution with bytecode functions (per-class + per-instance).
|
|
||||||
- Remove all class init `Statement.execute` calls.
|
|
||||||
- [x] Introduce `ClassStaticFieldInitStatement` + bytecode ops `DECL_CLASS_FIELD`/`DECL_CLASS_DELEGATED` for static class field init.
|
|
||||||
|
|
||||||
- [ ] Step A3: Bytecode-safe delegated properties/functions and object expressions.
|
|
||||||
- Use explicit `object : Statement()` where needed.
|
|
||||||
- No inline suspend lambdas in hot paths.
|
|
||||||
- Remove interpreter fallbacks.
|
|
||||||
|
|
||||||
- [ ] Step A4: Bytecode for all blocks/lambdas (including class bodies).
|
|
||||||
- Compile non-module blocks/lambdas to bytecode; eliminate interpreter gate flags.
|
|
||||||
|
|
||||||
- [ ] Step A5: Delete interpreter execution path and dead code.
|
|
||||||
- Remove interpreter ops/constants and any runtime name-lookup fallbacks.
|
|
||||||
- Full test suite green.
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Keep imports bound to module frame slots; no scope map writes for imports.
|
|
||||||
- Avoid inline suspend lambdas in compiler hot paths; use explicit `object : Statement()`.
|
|
||||||
- Do not reintroduce bytecode fallback opcodes; all symbol resolution remains compile-time only.
|
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
|
import net.sergeych.lyng.bytecode.CmdVm
|
||||||
|
import net.sergeych.lyng.bytecode.seedFrameLocalsFromScope
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
internal suspend fun executeBytecodeWithSeed(scope: Scope, stmt: Statement, label: String): Obj {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
} ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
scope.pos = bytecode.pos
|
||||||
|
return CmdVm().execute(bytecode.bytecodeFunction(), scope, scope.args) { frame, _ ->
|
||||||
|
seedFrameLocalsFromScope(frame, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -74,7 +74,7 @@ internal suspend fun executeClassDecl(
|
|||||||
newClass.classScope = classScope
|
newClass.classScope = classScope
|
||||||
classScope.addConst("object", newClass)
|
classScope.addConst("object", newClass)
|
||||||
|
|
||||||
spec.bodyInit?.let { requireBytecodeBody(scope, it, "object body init").execute(classScope) }
|
spec.bodyInit?.let { executeBytecodeWithSeed(classScope, it, "object body init") }
|
||||||
|
|
||||||
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
|
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
|
||||||
if (spec.declaredName != null) {
|
if (spec.declaredName != null) {
|
||||||
@ -140,7 +140,17 @@ internal suspend fun executeClassDecl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec.declaredName?.let { scope.addItem(it, false, newClass) }
|
spec.declaredName?.let { name ->
|
||||||
|
scope.addItem(name, false, newClass)
|
||||||
|
val module = scope as? ModuleScope
|
||||||
|
val frame = module?.moduleFrame
|
||||||
|
if (module != null && frame != null) {
|
||||||
|
val idx = module.moduleFrameLocalSlotNames.indexOf(name)
|
||||||
|
if (idx >= 0) {
|
||||||
|
frame.setObj(idx, newClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val classScope = scope.createChildScope(newThisObj = newClass)
|
val classScope = scope.createChildScope(newThisObj = newClass)
|
||||||
if (!bodyCaptureRecords.isNullOrEmpty() && !bodyCaptureNames.isNullOrEmpty()) {
|
if (!bodyCaptureRecords.isNullOrEmpty() && !bodyCaptureNames.isNullOrEmpty()) {
|
||||||
classScope.captureRecords = bodyCaptureRecords
|
classScope.captureRecords = bodyCaptureRecords
|
||||||
@ -148,10 +158,10 @@ internal suspend fun executeClassDecl(
|
|||||||
}
|
}
|
||||||
classScope.currentClassCtx = newClass
|
classScope.currentClassCtx = newClass
|
||||||
newClass.classScope = classScope
|
newClass.classScope = classScope
|
||||||
spec.bodyInit?.let { requireBytecodeBody(scope, it, "class body init").execute(classScope) }
|
spec.bodyInit?.let { executeBytecodeWithSeed(classScope, it, "class body init") }
|
||||||
if (spec.initScope.isNotEmpty()) {
|
if (spec.initScope.isNotEmpty()) {
|
||||||
for (s in spec.initScope) {
|
for (s in spec.initScope) {
|
||||||
requireBytecodeBody(scope, s, "class init").execute(classScope)
|
executeBytecodeWithSeed(classScope, s, "class init")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newClass.checkAbstractSatisfaction(spec.startPos)
|
newClass.checkAbstractSatisfaction(spec.startPos)
|
||||||
|
|||||||
@ -19,6 +19,7 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
import net.sergeych.lyng.Compiler.Companion.compile
|
import net.sergeych.lyng.Compiler.Companion.compile
|
||||||
import net.sergeych.lyng.bytecode.BytecodeStatement
|
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
|
import net.sergeych.lyng.bytecode.ForcedLocalSlotInfo
|
||||||
import net.sergeych.lyng.bytecode.CmdListLiteral
|
import net.sergeych.lyng.bytecode.CmdListLiteral
|
||||||
import net.sergeych.lyng.bytecode.CmdMakeRange
|
import net.sergeych.lyng.bytecode.CmdMakeRange
|
||||||
import net.sergeych.lyng.bytecode.CmdRangeIntBounds
|
import net.sergeych.lyng.bytecode.CmdRangeIntBounds
|
||||||
@ -133,15 +134,15 @@ class Compiler(
|
|||||||
if (plan.slots.containsKey(name)) return
|
if (plan.slots.containsKey(name)) return
|
||||||
plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated)
|
plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated)
|
||||||
plan.nextIndex += 1
|
plan.nextIndex += 1
|
||||||
|
if (!seedingSlotPlan && plan == moduleSlotPlan()) {
|
||||||
|
moduleDeclaredNames.add(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun declareSlotNameIn(plan: SlotPlan, name: String, isMutable: Boolean, isDelegated: Boolean) {
|
private fun declareSlotNameIn(plan: SlotPlan, name: String, isMutable: Boolean, isDelegated: Boolean) {
|
||||||
if (plan.slots.containsKey(name)) return
|
if (plan.slots.containsKey(name)) return
|
||||||
plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated)
|
plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated)
|
||||||
plan.nextIndex += 1
|
plan.nextIndex += 1
|
||||||
if (!seedingSlotPlan && plan == moduleSlotPlan()) {
|
|
||||||
moduleDeclaredNames.add(name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun declareSlotNameAt(
|
private fun declareSlotNameAt(
|
||||||
@ -187,6 +188,20 @@ class Compiler(
|
|||||||
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
|
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
|
||||||
private var seedingSlotPlan: Boolean = false
|
private var seedingSlotPlan: Boolean = false
|
||||||
|
|
||||||
|
private fun moduleForcedLocalSlotInfo(): Map<String, ForcedLocalSlotInfo> {
|
||||||
|
val plan = moduleSlotPlan() ?: return emptyMap()
|
||||||
|
if (plan.slots.isEmpty()) return emptyMap()
|
||||||
|
val result = LinkedHashMap<String, ForcedLocalSlotInfo>(plan.slots.size)
|
||||||
|
for ((name, entry) in plan.slots) {
|
||||||
|
result[name] = ForcedLocalSlotInfo(
|
||||||
|
index = entry.index,
|
||||||
|
isMutable = entry.isMutable,
|
||||||
|
isDelegated = entry.isDelegated
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
|
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
|
||||||
val plan = moduleSlotPlan() ?: return
|
val plan = moduleSlotPlan() ?: return
|
||||||
seedingSlotPlan = true
|
seedingSlotPlan = true
|
||||||
@ -963,15 +978,29 @@ class Compiler(
|
|||||||
resolutionSink?.reference(name, pos)
|
resolutionSink?.reference(name, pos)
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
val ref = LocalSlotRef(
|
val ref = if (capturePlanStack.isEmpty() && moduleLoc.depth > 0) {
|
||||||
name,
|
LocalSlotRef(
|
||||||
moduleLoc.slot,
|
name,
|
||||||
moduleLoc.scopeId,
|
moduleLoc.slot,
|
||||||
moduleLoc.isMutable,
|
moduleLoc.scopeId,
|
||||||
moduleLoc.isDelegated,
|
moduleLoc.isMutable,
|
||||||
pos,
|
moduleLoc.isDelegated,
|
||||||
strictSlotRefs
|
pos,
|
||||||
)
|
strictSlotRefs,
|
||||||
|
captureOwnerScopeId = moduleLoc.scopeId,
|
||||||
|
captureOwnerSlot = moduleLoc.slot
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
LocalSlotRef(
|
||||||
|
name,
|
||||||
|
moduleLoc.slot,
|
||||||
|
moduleLoc.scopeId,
|
||||||
|
moduleLoc.isMutable,
|
||||||
|
moduleLoc.isDelegated,
|
||||||
|
pos,
|
||||||
|
strictSlotRefs
|
||||||
|
)
|
||||||
|
}
|
||||||
resolutionSink?.reference(name, pos)
|
resolutionSink?.reference(name, pos)
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
@ -983,11 +1012,13 @@ class Compiler(
|
|||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
if (seedSlotIndex != null) {
|
val seedSlotFree = seedSlotIndex != null &&
|
||||||
|
modulePlan.slots.values.none { it.index == seedSlotIndex }
|
||||||
|
if (seedSlotFree) {
|
||||||
declareSlotNameAt(
|
declareSlotNameAt(
|
||||||
modulePlan,
|
modulePlan,
|
||||||
name,
|
name,
|
||||||
seedSlotIndex,
|
seedSlotIndex!!,
|
||||||
sourceRecord.isMutable,
|
sourceRecord.isMutable,
|
||||||
sourceRecord.type == ObjRecord.Type.Delegated
|
sourceRecord.type == ObjRecord.Type.Delegated
|
||||||
)
|
)
|
||||||
@ -1003,15 +1034,33 @@ class Compiler(
|
|||||||
registerImportBinding(name, resolved.binding, pos)
|
registerImportBinding(name, resolved.binding, pos)
|
||||||
val slot = lookupSlotLocation(name)
|
val slot = lookupSlotLocation(name)
|
||||||
if (slot != null) {
|
if (slot != null) {
|
||||||
val ref = LocalSlotRef(
|
captureLocalRef(name, slot, pos)?.let { ref ->
|
||||||
name,
|
resolutionSink?.reference(name, pos)
|
||||||
slot.slot,
|
return ref
|
||||||
slot.scopeId,
|
}
|
||||||
slot.isMutable,
|
val ref = if (capturePlanStack.isEmpty() && slot.depth > 0) {
|
||||||
slot.isDelegated,
|
LocalSlotRef(
|
||||||
pos,
|
name,
|
||||||
strictSlotRefs
|
slot.slot,
|
||||||
)
|
slot.scopeId,
|
||||||
|
slot.isMutable,
|
||||||
|
slot.isDelegated,
|
||||||
|
pos,
|
||||||
|
strictSlotRefs,
|
||||||
|
captureOwnerScopeId = slot.scopeId,
|
||||||
|
captureOwnerSlot = slot.slot
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
LocalSlotRef(
|
||||||
|
name,
|
||||||
|
slot.slot,
|
||||||
|
slot.scopeId,
|
||||||
|
slot.isMutable,
|
||||||
|
slot.isDelegated,
|
||||||
|
pos,
|
||||||
|
strictSlotRefs
|
||||||
|
)
|
||||||
|
}
|
||||||
resolutionSink?.reference(name, pos)
|
resolutionSink?.reference(name, pos)
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
@ -1202,6 +1251,7 @@ class Compiler(
|
|||||||
private fun registerImportBinding(name: String, binding: ImportBinding, pos: Pos) {
|
private fun registerImportBinding(name: String, binding: ImportBinding, pos: Pos) {
|
||||||
val existing = importBindings[name] ?: run {
|
val existing = importBindings[name] ?: run {
|
||||||
importBindings[name] = binding
|
importBindings[name] = binding
|
||||||
|
scopeSeedNames.add(name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!sameImportBinding(existing, binding)) {
|
if (!sameImportBinding(existing, binding)) {
|
||||||
@ -1569,6 +1619,11 @@ class Compiler(
|
|||||||
|
|
||||||
} while (true)
|
} while (true)
|
||||||
val modulePlan = if (needsSlotPlan) slotPlanIndices(slotPlanStack.last()) else emptyMap()
|
val modulePlan = if (needsSlotPlan) slotPlanIndices(slotPlanStack.last()) else emptyMap()
|
||||||
|
val forcedLocalInfo = if (useScopeSlots) emptyMap() else moduleForcedLocalSlotInfo()
|
||||||
|
val forcedLocalScopeId = if (useScopeSlots) null else moduleSlotPlan()?.id
|
||||||
|
val allowedScopeNames = if (useScopeSlots) modulePlan.keys else null
|
||||||
|
val scopeSlotNameSet = if (useScopeSlots) scopeSeedNames else null
|
||||||
|
val moduleScopeId = if (useScopeSlots) null else moduleSlotPlan()?.id
|
||||||
val isModuleScript = codeContexts.lastOrNull() is CodeContext.Module && resolutionScriptDepth == 1
|
val isModuleScript = codeContexts.lastOrNull() is CodeContext.Module && resolutionScriptDepth == 1
|
||||||
val wrapScriptBytecode = compileBytecode && isModuleScript
|
val wrapScriptBytecode = compileBytecode && isModuleScript
|
||||||
val (finalStatements, moduleBytecode) = if (wrapScriptBytecode) {
|
val (finalStatements, moduleBytecode) = if (wrapScriptBytecode) {
|
||||||
@ -1578,9 +1633,11 @@ class Compiler(
|
|||||||
block,
|
block,
|
||||||
"<script>",
|
"<script>",
|
||||||
allowLocalSlots = true,
|
allowLocalSlots = true,
|
||||||
allowedScopeNames = modulePlan.keys,
|
allowedScopeNames = allowedScopeNames,
|
||||||
scopeSlotNameSet = scopeSeedNames,
|
scopeSlotNameSet = scopeSlotNameSet,
|
||||||
moduleScopeId = moduleSlotPlan()?.id,
|
moduleScopeId = moduleScopeId,
|
||||||
|
forcedLocalSlotInfo = forcedLocalInfo,
|
||||||
|
forcedLocalScopeId = forcedLocalScopeId,
|
||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = objectDeclNames,
|
||||||
@ -1599,6 +1656,7 @@ class Compiler(
|
|||||||
start,
|
start,
|
||||||
finalStatements,
|
finalStatements,
|
||||||
modulePlan,
|
modulePlan,
|
||||||
|
moduleDeclaredNames.toSet(),
|
||||||
importBindings.toMap(),
|
importBindings.toMap(),
|
||||||
moduleRefs,
|
moduleRefs,
|
||||||
moduleBytecode
|
moduleBytecode
|
||||||
@ -1645,6 +1703,7 @@ class Compiler(
|
|||||||
private val rangeParamNamesStack = mutableListOf<Set<String>>()
|
private val rangeParamNamesStack = mutableListOf<Set<String>>()
|
||||||
private val extensionNames = mutableSetOf<String>()
|
private val extensionNames = mutableSetOf<String>()
|
||||||
private val extensionNamesByType = mutableMapOf<String, MutableSet<String>>()
|
private val extensionNamesByType = mutableMapOf<String, MutableSet<String>>()
|
||||||
|
private val useScopeSlots: Boolean = seedScope != null && seedScope !is ModuleScope
|
||||||
|
|
||||||
private fun registerExtensionName(typeName: String, memberName: String) {
|
private fun registerExtensionName(typeName: String, memberName: String) {
|
||||||
extensionNamesByType.getOrPut(typeName) { mutableSetOf() }.add(memberName)
|
extensionNamesByType.getOrPut(typeName) { mutableSetOf() }.add(memberName)
|
||||||
@ -1788,10 +1847,10 @@ class Compiler(
|
|||||||
} ?: -1
|
} ?: -1
|
||||||
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
|
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
|
||||||
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
|
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
|
||||||
if (scopeSeedNames.contains(name)) return null
|
|
||||||
val modulePlan = moduleSlotPlan()
|
val modulePlan = moduleSlotPlan()
|
||||||
if (modulePlan != null && slotLoc.scopeId == modulePlan.id) {
|
if (scopeSeedNames.contains(name)) {
|
||||||
return null
|
val isModuleSlot = modulePlan != null && slotLoc.scopeId == modulePlan.id
|
||||||
|
if (!isModuleSlot || useScopeSlots) return null
|
||||||
}
|
}
|
||||||
recordCaptureSlot(name, slotLoc)
|
recordCaptureSlot(name, slotLoc)
|
||||||
val plan = capturePlanStack.lastOrNull() ?: return null
|
val plan = capturePlanStack.lastOrNull() ?: return null
|
||||||
@ -1864,7 +1923,10 @@ class Compiler(
|
|||||||
) return stmt
|
) return stmt
|
||||||
val allowLocals = codeContexts.lastOrNull() !is CodeContext.ClassBody
|
val allowLocals = codeContexts.lastOrNull() !is CodeContext.ClassBody
|
||||||
val returnLabels = returnLabelStack.lastOrNull() ?: emptySet()
|
val returnLabels = returnLabelStack.lastOrNull() ?: emptySet()
|
||||||
val allowedScopeNames = moduleSlotPlan()?.slots?.keys
|
val modulePlan = moduleSlotPlan()
|
||||||
|
val allowedScopeNames = if (useScopeSlots) modulePlan?.slots?.keys else null
|
||||||
|
val scopeSlotNameSet = if (useScopeSlots) scopeSeedNames else null
|
||||||
|
val moduleScopeId = modulePlan?.id
|
||||||
return BytecodeStatement.wrap(
|
return BytecodeStatement.wrap(
|
||||||
stmt,
|
stmt,
|
||||||
"stmt@${stmt.pos}",
|
"stmt@${stmt.pos}",
|
||||||
@ -1872,8 +1934,8 @@ class Compiler(
|
|||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = currentRangeParamNames,
|
rangeLocalNames = currentRangeParamNames,
|
||||||
allowedScopeNames = allowedScopeNames,
|
allowedScopeNames = allowedScopeNames,
|
||||||
scopeSlotNameSet = scopeSeedNames,
|
scopeSlotNameSet = scopeSlotNameSet,
|
||||||
moduleScopeId = moduleSlotPlan()?.id,
|
moduleScopeId = moduleScopeId,
|
||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = objectDeclNames,
|
||||||
@ -1889,7 +1951,10 @@ class Compiler(
|
|||||||
private fun wrapClassBodyBytecode(stmt: Statement, name: String): Statement {
|
private fun wrapClassBodyBytecode(stmt: Statement, name: String): Statement {
|
||||||
val target = if (stmt is Script) InlineBlockStatement(stmt.statements(), stmt.pos) else stmt
|
val target = if (stmt is Script) InlineBlockStatement(stmt.statements(), stmt.pos) else stmt
|
||||||
val returnLabels = returnLabelStack.lastOrNull() ?: emptySet()
|
val returnLabels = returnLabelStack.lastOrNull() ?: emptySet()
|
||||||
val allowedScopeNames = moduleSlotPlan()?.slots?.keys
|
val modulePlan = moduleSlotPlan()
|
||||||
|
val allowedScopeNames = if (useScopeSlots) modulePlan?.slots?.keys else null
|
||||||
|
val scopeSlotNameSet = if (useScopeSlots) scopeSeedNames else null
|
||||||
|
val moduleScopeId = modulePlan?.id
|
||||||
return BytecodeStatement.wrap(
|
return BytecodeStatement.wrap(
|
||||||
target,
|
target,
|
||||||
name,
|
name,
|
||||||
@ -1897,8 +1962,8 @@ class Compiler(
|
|||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = currentRangeParamNames,
|
rangeLocalNames = currentRangeParamNames,
|
||||||
allowedScopeNames = allowedScopeNames,
|
allowedScopeNames = allowedScopeNames,
|
||||||
scopeSlotNameSet = scopeSeedNames,
|
scopeSlotNameSet = scopeSlotNameSet,
|
||||||
moduleScopeId = moduleSlotPlan()?.id,
|
moduleScopeId = moduleScopeId,
|
||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = objectDeclNames,
|
||||||
@ -1925,7 +1990,12 @@ class Compiler(
|
|||||||
forcedLocalScopeId: Int? = null
|
forcedLocalScopeId: Int? = null
|
||||||
): Statement {
|
): Statement {
|
||||||
val returnLabels = returnLabelStack.lastOrNull() ?: emptySet()
|
val returnLabels = returnLabelStack.lastOrNull() ?: emptySet()
|
||||||
val allowedScopeNames = moduleSlotPlan()?.slots?.keys
|
val modulePlan = moduleSlotPlan()
|
||||||
|
val allowedScopeNames = if (useScopeSlots) modulePlan?.slots?.keys else null
|
||||||
|
val scopeSlotNameSet = if (useScopeSlots) scopeSeedNames else null
|
||||||
|
val moduleScopeId = modulePlan?.id
|
||||||
|
val globalSlotInfo = if (useScopeSlots) emptyMap() else moduleForcedLocalSlotInfo()
|
||||||
|
val globalSlotScopeId = if (useScopeSlots) null else moduleScopeId
|
||||||
val knownNames = if (extraKnownNameObjClass.isEmpty()) {
|
val knownNames = if (extraKnownNameObjClass.isEmpty()) {
|
||||||
knownClassMapForBytecode()
|
knownClassMapForBytecode()
|
||||||
} else {
|
} else {
|
||||||
@ -1941,10 +2011,12 @@ class Compiler(
|
|||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
rangeLocalNames = currentRangeParamNames,
|
rangeLocalNames = currentRangeParamNames,
|
||||||
allowedScopeNames = allowedScopeNames,
|
allowedScopeNames = allowedScopeNames,
|
||||||
scopeSlotNameSet = scopeSeedNames,
|
scopeSlotNameSet = scopeSlotNameSet,
|
||||||
moduleScopeId = moduleSlotPlan()?.id,
|
moduleScopeId = moduleScopeId,
|
||||||
forcedLocalSlots = forcedLocalSlots,
|
forcedLocalSlots = forcedLocalSlots,
|
||||||
forcedLocalScopeId = forcedLocalScopeId,
|
forcedLocalScopeId = forcedLocalScopeId,
|
||||||
|
globalSlotInfo = globalSlotInfo,
|
||||||
|
globalSlotScopeId = globalSlotScopeId,
|
||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
knownNameObjClass = knownNames,
|
knownNameObjClass = knownNames,
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = objectDeclNames,
|
||||||
@ -2860,7 +2932,7 @@ class Compiler(
|
|||||||
|
|
||||||
label?.let { cc.labels.add(it) }
|
label?.let { cc.labels.add(it) }
|
||||||
slotPlanStack.add(paramSlotPlan)
|
slotPlanStack.add(paramSlotPlan)
|
||||||
val capturePlan = CapturePlan(paramSlotPlan, isFunction = true, propagateToParentFunction = false)
|
val capturePlan = CapturePlan(paramSlotPlan, isFunction = true, propagateToParentFunction = lambdaDepth > 0)
|
||||||
capturePlanStack.add(capturePlan)
|
capturePlanStack.add(capturePlan)
|
||||||
val parsedBody = try {
|
val parsedBody = try {
|
||||||
inCodeContext(CodeContext.Function("<lambda>", implicitThisMembers = true, implicitThisTypeName = expectedReceiverType)) {
|
inCodeContext(CodeContext.Function("<lambda>", implicitThisMembers = true, implicitThisTypeName = expectedReceiverType)) {
|
||||||
@ -2893,6 +2965,22 @@ class Compiler(
|
|||||||
|
|
||||||
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
|
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
|
||||||
val captureSlots = capturePlan.captures.toList()
|
val captureSlots = capturePlan.captures.toList()
|
||||||
|
val captureEntries = if (captureSlots.isNotEmpty()) {
|
||||||
|
captureSlots.map { capture ->
|
||||||
|
val owner = capturePlan.captureOwners[capture.name]
|
||||||
|
?: error("Missing capture owner for ${capture.name}")
|
||||||
|
net.sergeych.lyng.bytecode.LambdaCaptureEntry(
|
||||||
|
ownerKind = net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL,
|
||||||
|
ownerScopeId = owner.scopeId,
|
||||||
|
ownerSlotId = owner.slot,
|
||||||
|
ownerName = capture.name,
|
||||||
|
ownerIsMutable = owner.isMutable,
|
||||||
|
ownerIsDelegated = owner.isDelegated
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
val returnClass = inferReturnClassFromStatement(body)
|
val returnClass = inferReturnClassFromStatement(body)
|
||||||
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
val paramKnownClasses = mutableMapOf<String, ObjClass>()
|
||||||
argsDeclaration?.params?.forEach { param ->
|
argsDeclaration?.params?.forEach { param ->
|
||||||
@ -2993,6 +3081,7 @@ class Compiler(
|
|||||||
bytecodeFn = bytecodeFn,
|
bytecodeFn = bytecodeFn,
|
||||||
paramSlotPlan = paramSlotPlanSnapshot,
|
paramSlotPlan = paramSlotPlanSnapshot,
|
||||||
argsDeclaration = argsDeclaration,
|
argsDeclaration = argsDeclaration,
|
||||||
|
captureEntries = captureEntries,
|
||||||
preferredThisType = expectedReceiverType,
|
preferredThisType = expectedReceiverType,
|
||||||
wrapAsExtensionCallable = wrapAsExtensionCallable,
|
wrapAsExtensionCallable = wrapAsExtensionCallable,
|
||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
@ -3001,19 +3090,7 @@ class Compiler(
|
|||||||
if (returnClass != null) {
|
if (returnClass != null) {
|
||||||
lambdaReturnTypeByRef[ref] = returnClass
|
lambdaReturnTypeByRef[ref] = returnClass
|
||||||
}
|
}
|
||||||
if (captureSlots.isNotEmpty()) {
|
if (captureEntries.isNotEmpty()) {
|
||||||
val captureEntries = captureSlots.map { capture ->
|
|
||||||
val owner = capturePlan.captureOwners[capture.name]
|
|
||||||
?: error("Missing capture owner for ${capture.name}")
|
|
||||||
net.sergeych.lyng.bytecode.LambdaCaptureEntry(
|
|
||||||
ownerKind = net.sergeych.lyng.bytecode.CaptureOwnerFrameKind.LOCAL,
|
|
||||||
ownerScopeId = owner.scopeId,
|
|
||||||
ownerSlotId = owner.slot,
|
|
||||||
ownerName = capture.name,
|
|
||||||
ownerIsMutable = owner.isMutable,
|
|
||||||
ownerIsDelegated = owner.isDelegated
|
|
||||||
)
|
|
||||||
}
|
|
||||||
lambdaCaptureEntriesByRef[ref] = captureEntries
|
lambdaCaptureEntriesByRef[ref] = captureEntries
|
||||||
}
|
}
|
||||||
return ref
|
return ref
|
||||||
@ -6056,6 +6133,9 @@ class Compiler(
|
|||||||
val outerClassName = currentEnclosingClassName()
|
val outerClassName = currentEnclosingClassName()
|
||||||
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
|
val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName
|
||||||
resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
|
resolutionSink?.declareSymbol(declaredName, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
|
||||||
|
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
||||||
|
declareLocalName(declaredName, isMutable = false)
|
||||||
|
}
|
||||||
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
if (codeContexts.lastOrNull() is CodeContext.Module) {
|
||||||
scopeSeedNames.add(declaredName)
|
scopeSeedNames.add(declaredName)
|
||||||
}
|
}
|
||||||
@ -6856,6 +6936,13 @@ class Compiler(
|
|||||||
delegateExpression = wrapFunctionBytecode(delegateExpression!!, "delegate@$name")
|
delegateExpression = wrapFunctionBytecode(delegateExpression!!, "delegate@$name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isDelegated && declKind != SymbolKind.MEMBER) {
|
||||||
|
val plan = slotPlanStack.lastOrNull()
|
||||||
|
val entry = plan?.slots?.get(name)
|
||||||
|
if (entry != null && !entry.isDelegated) {
|
||||||
|
plan.slots[name] = SlotEntry(entry.index, entry.isMutable, isDelegated = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isDelegated && argsDeclaration.endTokenType != Token.Type.RPAREN)
|
if (!isDelegated && argsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||||
throw ScriptError(
|
throw ScriptError(
|
||||||
@ -7313,6 +7400,14 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
val directRef = unwrapDirectRef(unwrapped)
|
val directRef = unwrapDirectRef(unwrapped)
|
||||||
return when (directRef) {
|
return when (directRef) {
|
||||||
|
is ConstRef -> when (directRef.constValue) {
|
||||||
|
is ObjInt -> ObjInt.type
|
||||||
|
is ObjReal -> ObjReal.type
|
||||||
|
is ObjBool -> ObjBool.type
|
||||||
|
is ObjString -> ObjString.type
|
||||||
|
is ObjRange -> ObjRange.type
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
is ListLiteralRef -> ObjList.type
|
is ListLiteralRef -> ObjList.type
|
||||||
is MapLiteralRef -> ObjMap.type
|
is MapLiteralRef -> ObjMap.type
|
||||||
is RangeRef -> ObjRange.type
|
is RangeRef -> ObjRange.type
|
||||||
|
|||||||
@ -36,16 +36,30 @@ class FrameSlotRef(
|
|||||||
private val frame: FrameAccess,
|
private val frame: FrameAccess,
|
||||||
private val slot: Int,
|
private val slot: Int,
|
||||||
) : net.sergeych.lyng.obj.Obj() {
|
) : net.sergeych.lyng.obj.Obj() {
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
val resolvedOther = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
return read().compareTo(scope, resolvedOther)
|
||||||
|
}
|
||||||
|
|
||||||
fun read(): Obj {
|
fun read(): Obj {
|
||||||
return when (frame.getSlotTypeCode(slot)) {
|
val typeCode = frame.getSlotTypeCode(slot)
|
||||||
|
return when (typeCode) {
|
||||||
SlotType.INT.code -> ObjInt.of(frame.getInt(slot))
|
SlotType.INT.code -> ObjInt.of(frame.getInt(slot))
|
||||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(slot))
|
SlotType.REAL.code -> ObjReal.of(frame.getReal(slot))
|
||||||
SlotType.BOOL.code -> if (frame.getBool(slot)) ObjTrue else ObjFalse
|
SlotType.BOOL.code -> if (frame.getBool(slot)) ObjTrue else ObjFalse
|
||||||
SlotType.OBJ.code -> frame.getObj(slot)
|
SlotType.OBJ.code -> frame.getObj(slot)
|
||||||
else -> ObjNull
|
else -> frame.getObj(slot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun refersTo(frame: FrameAccess, slot: Int): Boolean {
|
||||||
|
return this.frame === frame && this.slot == slot
|
||||||
|
}
|
||||||
|
|
||||||
fun write(value: Obj) {
|
fun write(value: Obj) {
|
||||||
when (value) {
|
when (value) {
|
||||||
is ObjInt -> frame.setInt(slot, value.value)
|
is ObjInt -> frame.setInt(slot, value.value)
|
||||||
@ -59,6 +73,15 @@ class FrameSlotRef(
|
|||||||
class RecordSlotRef(
|
class RecordSlotRef(
|
||||||
private val record: ObjRecord,
|
private val record: ObjRecord,
|
||||||
) : net.sergeych.lyng.obj.Obj() {
|
) : net.sergeych.lyng.obj.Obj() {
|
||||||
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
val resolvedOther = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
return read().compareTo(scope, resolvedOther)
|
||||||
|
}
|
||||||
|
|
||||||
fun read(): Obj {
|
fun read(): Obj {
|
||||||
val direct = record.value
|
val direct = record.value
|
||||||
return if (direct is FrameSlotRef) direct.read() else direct
|
return if (direct is FrameSlotRef) direct.read() else direct
|
||||||
|
|||||||
@ -82,7 +82,7 @@ internal suspend fun executeFunctionDecl(
|
|||||||
if (spec.isDelegated) {
|
if (spec.isDelegated) {
|
||||||
val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate")
|
val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate")
|
||||||
val accessType = ObjString("Callable")
|
val accessType = ObjString("Callable")
|
||||||
val initValue = requireBytecodeBody(scope, delegateExpr, "delegated function").execute(scope)
|
val initValue = executeBytecodeWithSeed(scope, delegateExpr, "delegated function")
|
||||||
val finalDelegate = try {
|
val finalDelegate = try {
|
||||||
initValue.invokeInstanceMethod(scope, "bind", Arguments(ObjString(spec.name), accessType, scope.thisObj))
|
initValue.invokeInstanceMethod(scope, "bind", Arguments(ObjString(spec.name), accessType, scope.thisObj))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -99,8 +99,8 @@ internal suspend fun executeFunctionDecl(
|
|||||||
delegate = finalDelegate
|
delegate = finalDelegate
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return ObjVoid
|
return finalDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
val th = scope.thisObj
|
val th = scope.thisObj
|
||||||
if (spec.isStatic) {
|
if (spec.isStatic) {
|
||||||
@ -158,7 +158,7 @@ internal suspend fun executeFunctionDecl(
|
|||||||
delegate = finalDelegate
|
delegate = finalDelegate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ObjVoid
|
return finalDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec.isStatic || !spec.parentIsClassBody) {
|
if (spec.isStatic || !spec.parentIsClassBody) {
|
||||||
|
|||||||
@ -109,7 +109,7 @@ class InstanceDelegatedInitStatement(
|
|||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
val initValue = execBytecodeOnly(scope, initializer, "instance delegated init")
|
val initValue = executeBytecodeWithSeed(scope, initializer, "instance delegated init")
|
||||||
val accessType = ObjString(accessTypeLabel)
|
val accessType = ObjString(accessTypeLabel)
|
||||||
val finalDelegate = try {
|
val finalDelegate = try {
|
||||||
initValue.invokeInstanceMethod(
|
initValue.invokeInstanceMethod(
|
||||||
@ -136,13 +136,4 @@ class InstanceDelegatedInitStatement(
|
|||||||
}
|
}
|
||||||
return ObjVoid
|
return ObjVoid
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun execBytecodeOnly(scope: Scope, stmt: Statement, label: String): Obj {
|
|
||||||
val bytecode = when (stmt) {
|
|
||||||
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
|
||||||
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
|
||||||
else -> null
|
|
||||||
} ?: scope.raiseIllegalState("$label requires bytecode statement")
|
|
||||||
return bytecode.execute(scope)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeFrame
|
||||||
|
import net.sergeych.lyng.bytecode.CmdFunction
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
import net.sergeych.lyng.obj.ObjString
|
import net.sergeych.lyng.obj.ObjString
|
||||||
import net.sergeych.lyng.pacman.ImportProvider
|
import net.sergeych.lyng.pacman.ImportProvider
|
||||||
@ -34,6 +36,27 @@ class ModuleScope(
|
|||||||
constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName)
|
constructor(importProvider: ImportProvider, source: Source) : this(importProvider, source.startPos, source.fileName)
|
||||||
|
|
||||||
internal var importedModules: List<ModuleScope> = emptyList()
|
internal var importedModules: List<ModuleScope> = emptyList()
|
||||||
|
internal var moduleFrame: BytecodeFrame? = null
|
||||||
|
internal var moduleFrameLocalCount: Int = -1
|
||||||
|
internal var moduleFrameLocalSlotNames: Array<String?> = emptyArray()
|
||||||
|
internal var moduleFrameLocalSlotMutables: BooleanArray = BooleanArray(0)
|
||||||
|
internal var moduleFrameLocalSlotDelegated: BooleanArray = BooleanArray(0)
|
||||||
|
|
||||||
|
internal fun ensureModuleFrame(fn: CmdFunction): BytecodeFrame {
|
||||||
|
val current = moduleFrame
|
||||||
|
val frame = if (current == null || moduleFrameLocalCount != fn.localCount) {
|
||||||
|
BytecodeFrame(fn.localCount, 0).also {
|
||||||
|
moduleFrame = it
|
||||||
|
moduleFrameLocalCount = fn.localCount
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current
|
||||||
|
}
|
||||||
|
moduleFrameLocalSlotNames = fn.localSlotNames
|
||||||
|
moduleFrameLocalSlotMutables = fn.localSlotMutables
|
||||||
|
moduleFrameLocalSlotDelegated = fn.localSlotDelegated
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import symbols into the scope. It _is called_ after the module is imported by [ImportProvider.prepareImport]
|
* Import symbols into the scope. It _is called_ after the module is imported by [ImportProvider.prepareImport]
|
||||||
|
|||||||
@ -35,6 +35,7 @@ class Script(
|
|||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
private val statements: List<Statement> = emptyList(),
|
private val statements: List<Statement> = emptyList(),
|
||||||
private val moduleSlotPlan: Map<String, Int> = emptyMap(),
|
private val moduleSlotPlan: Map<String, Int> = emptyMap(),
|
||||||
|
private val moduleDeclaredNames: Set<String> = emptySet(),
|
||||||
private val importBindings: Map<String, ImportBinding> = emptyMap(),
|
private val importBindings: Map<String, ImportBinding> = emptyMap(),
|
||||||
private val importedModules: List<ImportBindingSource.Module> = emptyList(),
|
private val importedModules: List<ImportBindingSource.Module> = emptyList(),
|
||||||
private val moduleBytecode: CmdFunction? = null,
|
private val moduleBytecode: CmdFunction? = null,
|
||||||
@ -44,49 +45,19 @@ class Script(
|
|||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
scope.pos = pos
|
scope.pos = pos
|
||||||
val isModuleScope = scope is ModuleScope
|
val execScope = resolveModuleScope(scope) ?: scope
|
||||||
val shouldSeedModule = isModuleScope || scope.thisObj === ObjVoid
|
val isModuleScope = execScope is ModuleScope
|
||||||
val moduleTarget = scope
|
val shouldSeedModule = isModuleScope || execScope.thisObj === ObjVoid
|
||||||
if (moduleSlotPlan.isNotEmpty() && shouldSeedModule) {
|
val moduleTarget = execScope
|
||||||
val hasPlanMapping = moduleSlotPlan.keys.any { moduleTarget.getSlotIndexOf(it) != null }
|
|
||||||
val needsReset = moduleTarget is ModuleScope ||
|
|
||||||
moduleTarget.slotCount() == 0 ||
|
|
||||||
moduleTarget.hasSlotPlanConflict(moduleSlotPlan) ||
|
|
||||||
(!hasPlanMapping && moduleTarget.slotCount() > 0)
|
|
||||||
if (needsReset) {
|
|
||||||
val preserved = LinkedHashMap<String, ObjRecord>()
|
|
||||||
for (name in moduleSlotPlan.keys) {
|
|
||||||
moduleTarget.getLocalRecordDirect(name)?.let { preserved[name] = it }
|
|
||||||
}
|
|
||||||
moduleTarget.applySlotPlanReset(moduleSlotPlan, preserved)
|
|
||||||
for (name in moduleSlotPlan.keys) {
|
|
||||||
if (preserved.containsKey(name)) continue
|
|
||||||
val inherited = findSeedRecord(moduleTarget.parent, name)
|
|
||||||
if (inherited != null && inherited.value !== ObjUnset) {
|
|
||||||
moduleTarget.updateSlotFor(name, inherited)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
moduleTarget.applySlotPlan(moduleSlotPlan)
|
|
||||||
for (name in moduleSlotPlan.keys) {
|
|
||||||
val local = moduleTarget.getLocalRecordDirect(name)
|
|
||||||
if (local != null && local.value !== ObjUnset) {
|
|
||||||
moduleTarget.updateSlotFor(name, local)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val inherited = findSeedRecord(moduleTarget.parent, name)
|
|
||||||
if (inherited != null && inherited.value !== ObjUnset) {
|
|
||||||
moduleTarget.updateSlotFor(name, inherited)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldSeedModule) {
|
if (shouldSeedModule) {
|
||||||
seedModuleSlots(moduleTarget)
|
seedModuleSlots(moduleTarget, scope)
|
||||||
}
|
}
|
||||||
moduleBytecode?.let { fn ->
|
moduleBytecode?.let { fn ->
|
||||||
return CmdVm().execute(fn, scope, scope.args) { frame, _ ->
|
if (execScope is ModuleScope) {
|
||||||
seedModuleLocals(frame, moduleTarget)
|
execScope.ensureModuleFrame(fn)
|
||||||
|
}
|
||||||
|
return CmdVm().execute(fn, execScope, scope.args) { frame, _ ->
|
||||||
|
seedModuleLocals(frame, moduleTarget, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (statements.isNotEmpty()) {
|
if (statements.isNotEmpty()) {
|
||||||
@ -95,37 +66,34 @@ class Script(
|
|||||||
return ObjVoid
|
return ObjVoid
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun seedModuleSlots(scope: Scope) {
|
private suspend fun seedModuleSlots(scope: Scope, seedScope: Scope) {
|
||||||
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
||||||
seedImportBindings(scope)
|
seedImportBindings(scope, seedScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun seedModuleLocals(frame: net.sergeych.lyng.bytecode.CmdFrame, scope: Scope) {
|
private suspend fun seedModuleLocals(
|
||||||
if (importBindings.isEmpty()) return
|
frame: net.sergeych.lyng.bytecode.CmdFrame,
|
||||||
|
scope: Scope,
|
||||||
|
seedScope: Scope
|
||||||
|
) {
|
||||||
val localNames = frame.fn.localSlotNames
|
val localNames = frame.fn.localSlotNames
|
||||||
if (localNames.isEmpty()) return
|
if (localNames.isEmpty()) return
|
||||||
val values = HashMap<String, Obj>(importBindings.size)
|
val values = HashMap<String, Obj>()
|
||||||
for (name in importBindings.keys) {
|
val base = frame.fn.scopeSlotCount
|
||||||
val record = scope.getLocalRecordDirect(name) ?: continue
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
if (moduleDeclaredNames.contains(name)) continue
|
||||||
|
val record = seedScope.getLocalRecordDirect(name) ?: findSeedRecord(seedScope, name) ?: continue
|
||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
scope.resolve(record, name)
|
scope.resolve(record, name)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
}
|
}
|
||||||
if (value !== ObjUnset) {
|
|
||||||
values[name] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (values.isEmpty()) return
|
|
||||||
val base = frame.fn.scopeSlotCount
|
|
||||||
for (i in localNames.indices) {
|
|
||||||
val name = localNames[i] ?: continue
|
|
||||||
val value = values[name] ?: continue
|
|
||||||
frame.setObjUnchecked(base + i, value)
|
frame.setObjUnchecked(base + i, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun seedImportBindings(scope: Scope) {
|
private suspend fun seedImportBindings(scope: Scope, seedScope: Scope) {
|
||||||
val provider = scope.currentImportProvider
|
val provider = scope.currentImportProvider
|
||||||
val importedModules = LinkedHashSet<ModuleScope>()
|
val importedModules = LinkedHashSet<ModuleScope>()
|
||||||
for (moduleRef in this.importedModules) {
|
for (moduleRef in this.importedModules) {
|
||||||
@ -147,7 +115,7 @@ class Script(
|
|||||||
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
||||||
}
|
}
|
||||||
ImportBindingSource.Seed -> {
|
ImportBindingSource.Seed -> {
|
||||||
findSeedRecord(scope, binding.symbol)
|
findSeedRecord(seedScope, binding.symbol)
|
||||||
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
?: scope.raiseSymbolNotFound("symbol ${binding.symbol} not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,12 +152,7 @@ class Script(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveModuleScope(scope: Scope): ModuleScope? {
|
private fun resolveModuleScope(scope: Scope): ModuleScope? {
|
||||||
var current: Scope? = scope
|
return scope as? ModuleScope
|
||||||
while (current != null) {
|
|
||||||
if (current is ModuleScope) return current
|
|
||||||
current = current.parent
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun debugStatements(): List<Statement> = statements
|
internal fun debugStatements(): List<Statement> = statements
|
||||||
@ -365,21 +328,31 @@ class Script(
|
|||||||
raiseError(ObjAssertionFailedException(requireScope(), "Assertion failed$message"))
|
raiseError(ObjAssertionFailedException(requireScope(), "Assertion failed$message"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun unwrapCompareArg(value: Obj): Obj {
|
||||||
|
val resolved = when (value) {
|
||||||
|
is FrameSlotRef -> value.read()
|
||||||
|
is RecordSlotRef -> value.read()
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
|
return if (resolved === ObjUnset.objClass) ObjUnset else resolved
|
||||||
|
}
|
||||||
|
|
||||||
addVoidFn("assertEquals") {
|
addVoidFn("assertEquals") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = unwrapCompareArg(requiredArg(0))
|
||||||
val b = requiredArg<Obj>(1)
|
val b = unwrapCompareArg(requiredArg(1))
|
||||||
if (a.compareTo(requireScope(), b) != 0)
|
if (a.compareTo(requireScope(), b) != 0) {
|
||||||
raiseError(
|
raiseError(
|
||||||
ObjAssertionFailedException(
|
ObjAssertionFailedException(
|
||||||
requireScope(),
|
requireScope(),
|
||||||
"Assertion failed: ${inspect(a)} == ${inspect(b)}"
|
"Assertion failed: ${inspect(a)} == ${inspect(b)}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// alias used in tests
|
// alias used in tests
|
||||||
addVoidFn("assertEqual") {
|
addVoidFn("assertEqual") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = unwrapCompareArg(requiredArg(0))
|
||||||
val b = requiredArg<Obj>(1)
|
val b = unwrapCompareArg(requiredArg(1))
|
||||||
if (a.compareTo(requireScope(), b) != 0)
|
if (a.compareTo(requireScope(), b) != 0)
|
||||||
raiseError(
|
raiseError(
|
||||||
ObjAssertionFailedException(
|
ObjAssertionFailedException(
|
||||||
@ -389,8 +362,8 @@ class Script(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
addVoidFn("assertNotEquals") {
|
addVoidFn("assertNotEquals") {
|
||||||
val a = requiredArg<Obj>(0)
|
val a = unwrapCompareArg(requiredArg(0))
|
||||||
val b = requiredArg<Obj>(1)
|
val b = unwrapCompareArg(requiredArg(1))
|
||||||
if (a.compareTo(requireScope(), b) == 0)
|
if (a.compareTo(requireScope(), b) == 0)
|
||||||
raiseError(
|
raiseError(
|
||||||
ObjAssertionFailedException(
|
ObjAssertionFailedException(
|
||||||
|
|||||||
@ -29,6 +29,9 @@ class BytecodeCompiler(
|
|||||||
private val moduleScopeId: Int? = null,
|
private val moduleScopeId: Int? = null,
|
||||||
private val forcedLocalSlots: Map<String, Int> = emptyMap(),
|
private val forcedLocalSlots: Map<String, Int> = emptyMap(),
|
||||||
private val forcedLocalScopeId: Int? = null,
|
private val forcedLocalScopeId: Int? = null,
|
||||||
|
private val forcedLocalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
|
||||||
|
private val globalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
|
||||||
|
private val globalSlotScopeId: Int? = null,
|
||||||
private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
||||||
private val knownObjectNames: Set<String> = emptySet(),
|
private val knownObjectNames: Set<String> = emptySet(),
|
||||||
@ -39,6 +42,7 @@ class BytecodeCompiler(
|
|||||||
private val externCallableNames: Set<String> = emptySet(),
|
private val externCallableNames: Set<String> = emptySet(),
|
||||||
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
) {
|
) {
|
||||||
|
private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
|
||||||
private var builder = CmdBuilder()
|
private var builder = CmdBuilder()
|
||||||
private var nextSlot = 0
|
private var nextSlot = 0
|
||||||
private var nextAddrSlot = 0
|
private var nextAddrSlot = 0
|
||||||
@ -501,6 +505,21 @@ class BytecodeCompiler(
|
|||||||
if (ref.name.isEmpty()) return null
|
if (ref.name.isEmpty()) return null
|
||||||
val mapped = resolveSlot(ref) ?: return null
|
val mapped = resolveSlot(ref) ?: return null
|
||||||
var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
|
var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
|
||||||
|
if (resolved == SlotType.UNKNOWN) {
|
||||||
|
val key = ScopeSlotKey(refScopeId(ref), refSlot(ref))
|
||||||
|
val inferred = slotTypeFromClass(slotInitClassByKey[key])
|
||||||
|
if (inferred != null) {
|
||||||
|
updateSlotType(mapped, inferred)
|
||||||
|
resolved = inferred
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resolved == SlotType.UNKNOWN) {
|
||||||
|
val inferred = slotTypeFromClass(nameObjClass[ref.name])
|
||||||
|
if (inferred != null) {
|
||||||
|
updateSlotType(mapped, inferred)
|
||||||
|
resolved = inferred
|
||||||
|
}
|
||||||
|
}
|
||||||
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
|
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
|
||||||
updateSlotType(mapped, SlotType.INT)
|
updateSlotType(mapped, SlotType.INT)
|
||||||
resolved = SlotType.INT
|
resolved = SlotType.INT
|
||||||
@ -768,7 +787,7 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun compileValueFnRef(ref: ValueFnRef): CompiledValue? {
|
private fun compileValueFnRef(ref: ValueFnRef): CompiledValue? {
|
||||||
if (ref is LambdaFnRef && ref.bytecodeFn != null) {
|
if (ref is LambdaFnRef && ref.bytecodeFn != null) {
|
||||||
val captures = lambdaCaptureEntriesByRef[ref].orEmpty()
|
val captures = (lambdaCaptureEntriesByRef[ref] ?: ref.captureEntries).orEmpty()
|
||||||
val captureTableId = if (captures.isEmpty()) {
|
val captureTableId = if (captures.isEmpty()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
@ -819,8 +838,18 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
CaptureOwnerFrameKind.LOCAL -> {
|
CaptureOwnerFrameKind.LOCAL -> {
|
||||||
val localIndex = localSlotIndexByKey[key]
|
val localIndex = localSlotIndexByKey[key]
|
||||||
?: throw BytecodeCompileException("Missing local capture slot for ${entry.ownerScopeId}:${entry.ownerSlotId}", Pos.builtIn)
|
if (localIndex != null) {
|
||||||
scopeSlotCount + localIndex
|
return scopeSlotCount + localIndex
|
||||||
|
}
|
||||||
|
val captureName = entry.ownerName
|
||||||
|
if (captureName.isNotEmpty()) {
|
||||||
|
for (i in localSlotCaptures.indices) {
|
||||||
|
if (!localSlotCaptures[i]) continue
|
||||||
|
if (localSlotNames.getOrNull(i) != captureName) continue
|
||||||
|
return scopeSlotCount + i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw BytecodeCompileException("Missing local capture slot for ${entry.ownerScopeId}:${entry.ownerSlotId}", Pos.builtIn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3928,13 +3957,19 @@ class BytecodeCompiler(
|
|||||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||||
return CompiledValue(slot, resolved)
|
return CompiledValue(slot, resolved)
|
||||||
}
|
}
|
||||||
|
if (useScopeSlots && allowedScopeNames?.contains(name) == true) {
|
||||||
|
scopeSlotIndexByName[name]?.let { slot ->
|
||||||
|
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||||
|
return CompiledValue(slot, resolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!allowLocalSlots) return null
|
if (!allowLocalSlots) return null
|
||||||
scopeSlotIndexByName[name]?.let { slot ->
|
localSlotIndexByName[name]?.let { localIndex ->
|
||||||
|
val slot = scopeSlotCount + localIndex
|
||||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||||
return CompiledValue(slot, resolved)
|
return CompiledValue(slot, resolved)
|
||||||
}
|
}
|
||||||
localSlotIndexByName[name]?.let { localIndex ->
|
scopeSlotIndexByName[name]?.let { slot ->
|
||||||
val slot = scopeSlotCount + localIndex
|
|
||||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||||
return CompiledValue(slot, resolved)
|
return CompiledValue(slot, resolved)
|
||||||
}
|
}
|
||||||
@ -4518,7 +4553,9 @@ class BytecodeCompiler(
|
|||||||
lifted = stmt.lifted
|
lifted = stmt.lifted
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val dst = allocSlot()
|
val dst = stmt.declaredName?.let { name ->
|
||||||
|
resolveDirectNameSlot(name)?.slot
|
||||||
|
} ?: allocSlot()
|
||||||
builder.emit(Opcode.DECL_ENUM, constId, dst)
|
builder.emit(Opcode.DECL_ENUM, constId, dst)
|
||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
@ -5138,7 +5175,8 @@ class BytecodeCompiler(
|
|||||||
updateSlotObjClass(localSlot, stmt.initializer, stmt.initializerObjClass)
|
updateSlotObjClass(localSlot, stmt.initializer, stmt.initializerObjClass)
|
||||||
updateNameObjClassFromSlot(stmt.name, localSlot)
|
updateNameObjClassFromSlot(stmt.name, localSlot)
|
||||||
val shadowedScopeSlot = scopeSlotIndexByName.containsKey(stmt.name)
|
val shadowedScopeSlot = scopeSlotIndexByName.containsKey(stmt.name)
|
||||||
if (!shadowedScopeSlot) {
|
val isModuleScope = moduleScopeId != null && scopeId == moduleScopeId
|
||||||
|
if (!shadowedScopeSlot || isModuleScope) {
|
||||||
val declId = builder.addConst(
|
val declId = builder.addConst(
|
||||||
BytecodeConst.LocalDecl(
|
BytecodeConst.LocalDecl(
|
||||||
stmt.name,
|
stmt.name,
|
||||||
@ -5331,6 +5369,9 @@ class BytecodeCompiler(
|
|||||||
if (range == null && rangeRef == null) {
|
if (range == null && rangeRef == null) {
|
||||||
rangeRef = extractRangeFromLocal(stmt.source)
|
rangeRef = extractRangeFromLocal(stmt.source)
|
||||||
}
|
}
|
||||||
|
if (rangeRef != null && !isConstIntRange(rangeRef)) {
|
||||||
|
rangeRef = null
|
||||||
|
}
|
||||||
val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
|
val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
|
||||||
val loopSlotPlan = stmt.loopSlotPlan
|
val loopSlotPlan = stmt.loopSlotPlan
|
||||||
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
|
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
|
||||||
@ -6534,6 +6575,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun queueExtensionCallableNames(receiverClass: ObjClass, memberName: String) {
|
private fun queueExtensionCallableNames(receiverClass: ObjClass, memberName: String) {
|
||||||
|
if (!useScopeSlots && globalSlotInfo.isEmpty()) return
|
||||||
for (cls in receiverClass.mro) {
|
for (cls in receiverClass.mro) {
|
||||||
val name = extensionCallableName(cls.className, memberName)
|
val name = extensionCallableName(cls.className, memberName)
|
||||||
if (allowedScopeNames == null || allowedScopeNames.contains(name)) {
|
if (allowedScopeNames == null || allowedScopeNames.contains(name)) {
|
||||||
@ -6543,6 +6585,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun queueExtensionPropertyNames(receiverClass: ObjClass, memberName: String) {
|
private fun queueExtensionPropertyNames(receiverClass: ObjClass, memberName: String) {
|
||||||
|
if (!useScopeSlots && globalSlotInfo.isEmpty()) return
|
||||||
for (cls in receiverClass.mro) {
|
for (cls in receiverClass.mro) {
|
||||||
val getter = extensionPropertyGetterName(cls.className, memberName)
|
val getter = extensionPropertyGetterName(cls.className, memberName)
|
||||||
if (allowedScopeNames == null || allowedScopeNames.contains(getter)) {
|
if (allowedScopeNames == null || allowedScopeNames.contains(getter)) {
|
||||||
@ -6601,6 +6644,15 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun slotTypeFromClass(cls: ObjClass?): SlotType? {
|
||||||
|
return when (cls) {
|
||||||
|
ObjInt.type -> SlotType.INT
|
||||||
|
ObjReal.type -> SlotType.REAL
|
||||||
|
ObjBool.type -> SlotType.BOOL
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun prepareCompilation(stmt: Statement) {
|
private fun prepareCompilation(stmt: Statement) {
|
||||||
builder = CmdBuilder()
|
builder = CmdBuilder()
|
||||||
nextSlot = 0
|
nextSlot = 0
|
||||||
@ -6655,6 +6707,14 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (allowLocalSlots && forcedLocalSlotInfo.isNotEmpty() && forcedLocalScopeId != null) {
|
||||||
|
for ((name, info) in forcedLocalSlotInfo) {
|
||||||
|
val key = ScopeSlotKey(forcedLocalScopeId, info.index)
|
||||||
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
|
localSlotInfoMap[key] = LocalSlotInfo(name, info.isMutable, info.isDelegated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (allowLocalSlots && valueFnRefs.isNotEmpty() && lambdaCaptureEntriesByRef.isNotEmpty()) {
|
if (allowLocalSlots && valueFnRefs.isNotEmpty() && lambdaCaptureEntriesByRef.isNotEmpty()) {
|
||||||
for (ref in valueFnRefs) {
|
for (ref in valueFnRefs) {
|
||||||
val entries = lambdaCaptureEntriesByRef[ref] ?: continue
|
val entries = lambdaCaptureEntriesByRef[ref] ?: continue
|
||||||
@ -6672,14 +6732,31 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pendingScopeNameRefs.isNotEmpty()) {
|
if (pendingScopeNameRefs.isNotEmpty()) {
|
||||||
val existingNames = HashSet<String>(scopeSlotNameMap.values)
|
if (useScopeSlots) {
|
||||||
var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1
|
val existingNames = HashSet<String>(scopeSlotNameMap.values)
|
||||||
for (name in pendingScopeNameRefs) {
|
var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1
|
||||||
if (!existingNames.add(name)) continue
|
for (name in pendingScopeNameRefs) {
|
||||||
maxSlotIndex += 1
|
if (!existingNames.add(name)) continue
|
||||||
val key = ScopeSlotKey(0, maxSlotIndex)
|
maxSlotIndex += 1
|
||||||
scopeSlotMap[key] = scopeSlotMap.size
|
val key = ScopeSlotKey(0, maxSlotIndex)
|
||||||
scopeSlotNameMap[key] = name
|
scopeSlotMap[key] = scopeSlotMap.size
|
||||||
|
scopeSlotNameMap[key] = name
|
||||||
|
}
|
||||||
|
} else if (globalSlotInfo.isNotEmpty() && globalSlotScopeId != null) {
|
||||||
|
for (name in pendingScopeNameRefs) {
|
||||||
|
val info = globalSlotInfo[name] ?: continue
|
||||||
|
val key = ScopeSlotKey(globalSlotScopeId, info.index)
|
||||||
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
|
localSlotInfoMap[key] = LocalSlotInfo(name, info.isMutable, info.isDelegated)
|
||||||
|
}
|
||||||
|
captureSlotKeys.add(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (slotInitClassByKey.isNotEmpty() && scopeSlotMap.isNotEmpty()) {
|
||||||
|
for ((key, index) in scopeSlotMap) {
|
||||||
|
val type = slotTypeFromClass(slotInitClassByKey[key]) ?: continue
|
||||||
|
slotTypes[index] = type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scopeSlotCount = scopeSlotMap.size
|
scopeSlotCount = scopeSlotMap.size
|
||||||
@ -6691,27 +6768,104 @@ class BytecodeCompiler(
|
|||||||
val name = scopeSlotNameMap[key]
|
val name = scopeSlotNameMap[key]
|
||||||
scopeSlotIndices[index] = key.slot
|
scopeSlotIndices[index] = key.slot
|
||||||
scopeSlotNames[index] = name
|
scopeSlotNames[index] = name
|
||||||
scopeSlotIsModule[index] = key.scopeId == (moduleScopeId ?: 0)
|
scopeSlotIsModule[index] = moduleScopeId != null && key.scopeId == moduleScopeId
|
||||||
scopeSlotMutableMap[key]?.let { scopeSlotMutables[index] = it }
|
scopeSlotMutableMap[key]?.let { scopeSlotMutables[index] = it }
|
||||||
}
|
}
|
||||||
if (allowLocalSlots && localSlotInfoMap.isNotEmpty()) {
|
if (allowLocalSlots && localSlotInfoMap.isNotEmpty()) {
|
||||||
val names = ArrayList<String?>(localSlotInfoMap.size)
|
val moduleId = moduleScopeId
|
||||||
val mutables = BooleanArray(localSlotInfoMap.size)
|
if (moduleId != null) {
|
||||||
val delegated = BooleanArray(localSlotInfoMap.size)
|
val moduleEntries = ArrayList<Map.Entry<ScopeSlotKey, LocalSlotInfo>>()
|
||||||
var index = 0
|
val nonModuleEntries = ArrayList<Map.Entry<ScopeSlotKey, LocalSlotInfo>>()
|
||||||
for ((key, info) in localSlotInfoMap) {
|
for (entry in localSlotInfoMap.entries) {
|
||||||
localSlotIndexByKey[key] = index
|
if (entry.key.scopeId == moduleId) {
|
||||||
if (!localSlotIndexByName.containsKey(info.name)) {
|
moduleEntries.add(entry)
|
||||||
localSlotIndexByName[info.name] = index
|
} else {
|
||||||
|
nonModuleEntries.add(entry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
names.add(info.name)
|
if (moduleEntries.isNotEmpty()) {
|
||||||
mutables[index] = info.isMutable
|
val maxModuleIndex = moduleEntries.maxOf { it.key.slot }
|
||||||
delegated[index] = info.isDelegated
|
val size = maxModuleIndex + 1 + nonModuleEntries.size
|
||||||
index += 1
|
val names = arrayOfNulls<String>(size)
|
||||||
|
val mutables = BooleanArray(size)
|
||||||
|
val delegated = BooleanArray(size)
|
||||||
|
val used = BooleanArray(size)
|
||||||
|
for (entry in moduleEntries) {
|
||||||
|
val idx = entry.key.slot
|
||||||
|
localSlotIndexByKey[entry.key] = idx
|
||||||
|
if (!localSlotIndexByName.containsKey(entry.value.name)) {
|
||||||
|
localSlotIndexByName[entry.value.name] = idx
|
||||||
|
}
|
||||||
|
names[idx] = entry.value.name
|
||||||
|
mutables[idx] = entry.value.isMutable
|
||||||
|
delegated[idx] = entry.value.isDelegated
|
||||||
|
used[idx] = true
|
||||||
|
}
|
||||||
|
var next = maxModuleIndex + 1
|
||||||
|
for (entry in nonModuleEntries) {
|
||||||
|
while (next < size && used[next]) {
|
||||||
|
next += 1
|
||||||
|
}
|
||||||
|
val idx = next
|
||||||
|
localSlotIndexByKey[entry.key] = idx
|
||||||
|
if (!localSlotIndexByName.containsKey(entry.value.name)) {
|
||||||
|
localSlotIndexByName[entry.value.name] = idx
|
||||||
|
}
|
||||||
|
names[idx] = entry.value.name
|
||||||
|
mutables[idx] = entry.value.isMutable
|
||||||
|
delegated[idx] = entry.value.isDelegated
|
||||||
|
used[idx] = true
|
||||||
|
next += 1
|
||||||
|
}
|
||||||
|
localSlotNames = names
|
||||||
|
localSlotMutables = mutables
|
||||||
|
localSlotDelegated = delegated
|
||||||
|
} else {
|
||||||
|
val names = ArrayList<String?>(localSlotInfoMap.size)
|
||||||
|
val mutables = BooleanArray(localSlotInfoMap.size)
|
||||||
|
val delegated = BooleanArray(localSlotInfoMap.size)
|
||||||
|
var index = 0
|
||||||
|
for ((key, info) in localSlotInfoMap) {
|
||||||
|
localSlotIndexByKey[key] = index
|
||||||
|
if (!localSlotIndexByName.containsKey(info.name)) {
|
||||||
|
localSlotIndexByName[info.name] = index
|
||||||
|
}
|
||||||
|
names.add(info.name)
|
||||||
|
mutables[index] = info.isMutable
|
||||||
|
delegated[index] = info.isDelegated
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
localSlotNames = names.toTypedArray()
|
||||||
|
localSlotMutables = mutables
|
||||||
|
localSlotDelegated = delegated
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val names = ArrayList<String?>(localSlotInfoMap.size)
|
||||||
|
val mutables = BooleanArray(localSlotInfoMap.size)
|
||||||
|
val delegated = BooleanArray(localSlotInfoMap.size)
|
||||||
|
var index = 0
|
||||||
|
for ((key, info) in localSlotInfoMap) {
|
||||||
|
localSlotIndexByKey[key] = index
|
||||||
|
if (!localSlotIndexByName.containsKey(info.name)) {
|
||||||
|
localSlotIndexByName[info.name] = index
|
||||||
|
}
|
||||||
|
names.add(info.name)
|
||||||
|
mutables[index] = info.isMutable
|
||||||
|
delegated[index] = info.isDelegated
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
|
localSlotNames = names.toTypedArray()
|
||||||
|
localSlotMutables = mutables
|
||||||
|
localSlotDelegated = delegated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allowLocalSlots && captureSlotKeys.isNotEmpty() && slotInitClassByKey.isNotEmpty()) {
|
||||||
|
val scopeSlotsBase = scopeSlotMap.size
|
||||||
|
for (key in captureSlotKeys) {
|
||||||
|
val localIndex = localSlotIndexByKey[key] ?: continue
|
||||||
|
val type = slotTypeFromClass(slotInitClassByKey[key]) ?: continue
|
||||||
|
slotTypes[scopeSlotsBase + localIndex] = type
|
||||||
}
|
}
|
||||||
localSlotNames = names.toTypedArray()
|
|
||||||
localSlotMutables = mutables
|
|
||||||
localSlotDelegated = delegated
|
|
||||||
}
|
}
|
||||||
localSlotCaptures = BooleanArray(localSlotNames.size)
|
localSlotCaptures = BooleanArray(localSlotNames.size)
|
||||||
if (captureSlotKeys.isNotEmpty()) {
|
if (captureSlotKeys.isNotEmpty()) {
|
||||||
@ -6723,6 +6877,36 @@ class BytecodeCompiler(
|
|||||||
slotTypes[slot] = SlotType.OBJ
|
slotTypes[slot] = SlotType.OBJ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (allowLocalSlots && valueFnRefs.isNotEmpty() && lambdaCaptureEntriesByRef.isNotEmpty()) {
|
||||||
|
val declaredLocalNames = run {
|
||||||
|
val names = LinkedHashSet<String>()
|
||||||
|
if (declaredLocalKeys.isNotEmpty()) {
|
||||||
|
for (key in declaredLocalKeys) {
|
||||||
|
localSlotInfoMap[key]?.name?.let { names.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (forcedLocalSlots.isNotEmpty()) {
|
||||||
|
for (name in forcedLocalSlots.keys) {
|
||||||
|
names.add(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names
|
||||||
|
}
|
||||||
|
for (ref in valueFnRefs) {
|
||||||
|
val entries = lambdaCaptureEntriesByRef[ref] ?: continue
|
||||||
|
for (entry in entries) {
|
||||||
|
if (entry.ownerKind != CaptureOwnerFrameKind.LOCAL) continue
|
||||||
|
val name = entry.ownerName
|
||||||
|
if (name.isEmpty()) continue
|
||||||
|
if (declaredLocalNames.contains(name)) continue
|
||||||
|
val localIndex = localSlotIndexByName[name] ?: continue
|
||||||
|
val slot = scopeSlotCount + localIndex
|
||||||
|
localSlotCaptures[localIndex] = true
|
||||||
|
forcedObjSlots.add(slot)
|
||||||
|
slotTypes[slot] = SlotType.OBJ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for (i in localSlotNames.indices) {
|
for (i in localSlotNames.indices) {
|
||||||
if (localSlotCaptures.getOrNull(i) != true) continue
|
if (localSlotCaptures.getOrNull(i) != true) continue
|
||||||
val name = localSlotNames[i] ?: continue
|
val name = localSlotNames[i] ?: continue
|
||||||
@ -6775,7 +6959,6 @@ class BytecodeCompiler(
|
|||||||
is VarDeclStatement -> {
|
is VarDeclStatement -> {
|
||||||
val slotIndex = stmt.slotIndex
|
val slotIndex = stmt.slotIndex
|
||||||
val scopeId = stmt.scopeId ?: 0
|
val scopeId = stmt.scopeId ?: 0
|
||||||
val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
|
|
||||||
val cls = stmt.initializerObjClass ?: objClassForInitializer(stmt.initializer)
|
val cls = stmt.initializerObjClass ?: objClassForInitializer(stmt.initializer)
|
||||||
if (cls != null) {
|
if (cls != null) {
|
||||||
nameObjClass[stmt.name] = cls
|
nameObjClass[stmt.name] = cls
|
||||||
@ -6783,7 +6966,7 @@ class BytecodeCompiler(
|
|||||||
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (allowLocalSlots && slotIndex != null && !isModuleDecl) {
|
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) {
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
declaredLocalKeys.add(key)
|
declaredLocalKeys.add(key)
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
@ -6810,8 +6993,7 @@ class BytecodeCompiler(
|
|||||||
val scopeId = stmt.spec.scopeId ?: 0
|
val scopeId = stmt.spec.scopeId ?: 0
|
||||||
if (slotIndex != null) {
|
if (slotIndex != null) {
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
|
if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId)) {
|
||||||
if (allowLocalSlots && !isModuleDecl) {
|
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
|
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
|
||||||
}
|
}
|
||||||
@ -6828,8 +7010,7 @@ class BytecodeCompiler(
|
|||||||
is DelegatedVarDeclStatement -> {
|
is DelegatedVarDeclStatement -> {
|
||||||
val slotIndex = stmt.slotIndex
|
val slotIndex = stmt.slotIndex
|
||||||
val scopeId = stmt.scopeId ?: 0
|
val scopeId = stmt.scopeId ?: 0
|
||||||
val isModuleDecl = moduleScopeId != null && scopeId == moduleScopeId
|
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) {
|
||||||
if (allowLocalSlots && slotIndex != null && !isModuleDecl) {
|
|
||||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||||
declaredLocalKeys.add(key)
|
declaredLocalKeys.add(key)
|
||||||
if (!localSlotInfoMap.containsKey(key)) {
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
@ -6852,6 +7033,16 @@ class BytecodeCompiler(
|
|||||||
stmt.elseBody?.let { collectScopeSlots(it) }
|
stmt.elseBody?.let { collectScopeSlots(it) }
|
||||||
}
|
}
|
||||||
is net.sergeych.lyng.ForInStatement -> {
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
if (allowLocalSlots) {
|
||||||
|
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
|
||||||
|
if (loopSlotIndex != null && !shouldUseScopeSlotFor(stmt.loopScopeId)) {
|
||||||
|
val key = ScopeSlotKey(stmt.loopScopeId, loopSlotIndex)
|
||||||
|
declaredLocalKeys.add(key)
|
||||||
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
|
localSlotInfoMap[key] = LocalSlotInfo(stmt.loopVarName, isMutable = true, isDelegated = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
collectScopeSlots(stmt.source)
|
collectScopeSlots(stmt.source)
|
||||||
collectScopeSlots(stmt.body)
|
collectScopeSlots(stmt.body)
|
||||||
stmt.elseStatement?.let { collectScopeSlots(it) }
|
stmt.elseStatement?.let { collectScopeSlots(it) }
|
||||||
@ -6879,6 +7070,21 @@ class BytecodeCompiler(
|
|||||||
is net.sergeych.lyng.TryStatement -> {
|
is net.sergeych.lyng.TryStatement -> {
|
||||||
collectScopeSlots(stmt.body)
|
collectScopeSlots(stmt.body)
|
||||||
for (catchBlock in stmt.catches) {
|
for (catchBlock in stmt.catches) {
|
||||||
|
if (allowLocalSlots) {
|
||||||
|
val block = catchBlock.block as? BlockStatement
|
||||||
|
val catchSlotIndex = block?.slotPlan?.get(catchBlock.catchVarName)
|
||||||
|
if (block != null && catchSlotIndex != null && !shouldUseScopeSlotFor(block.scopeId)) {
|
||||||
|
val key = ScopeSlotKey(block.scopeId, catchSlotIndex)
|
||||||
|
declaredLocalKeys.add(key)
|
||||||
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
|
localSlotInfoMap[key] = LocalSlotInfo(
|
||||||
|
catchBlock.catchVarName,
|
||||||
|
isMutable = false,
|
||||||
|
isDelegated = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
collectScopeSlots(catchBlock.block)
|
collectScopeSlots(catchBlock.block)
|
||||||
}
|
}
|
||||||
stmt.finallyClause?.let { collectScopeSlots(it) }
|
stmt.finallyClause?.let { collectScopeSlots(it) }
|
||||||
@ -6965,13 +7171,13 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
||||||
val moduleId = moduleScopeId
|
val scopeNames = scopeSlotNameSet ?: allowedScopeNames
|
||||||
if (moduleId == null) {
|
if (scopeNames == null || name == null) return false
|
||||||
val scopeNames = scopeSlotNameSet ?: allowedScopeNames
|
return scopeNames.contains(name)
|
||||||
if (scopeNames == null || name == null) return false
|
}
|
||||||
return scopeNames.contains(name)
|
|
||||||
}
|
private fun shouldUseScopeSlotFor(scopeId: Int): Boolean {
|
||||||
return scopeId == moduleId
|
return useScopeSlots && moduleScopeId != null && scopeId == moduleScopeId
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectLoopVarNames(stmt: Statement) {
|
private fun collectLoopVarNames(stmt: Statement) {
|
||||||
@ -7265,6 +7471,12 @@ class BytecodeCompiler(
|
|||||||
return if (ref.step != null) null else ref
|
return if (ref.step != null) null else ref
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isConstIntRange(ref: RangeRef): Boolean {
|
||||||
|
val left = ref.left as? ConstRef ?: return false
|
||||||
|
val right = ref.right as? ConstRef ?: return false
|
||||||
|
return left.constValue is ObjInt && right.constValue is ObjInt
|
||||||
|
}
|
||||||
|
|
||||||
private fun extractDeclaredRange(stmt: Statement?): RangeRef? {
|
private fun extractDeclaredRange(stmt: Statement?): RangeRef? {
|
||||||
if (stmt == null) return null
|
if (stmt == null) return null
|
||||||
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||||
|
|||||||
@ -53,7 +53,13 @@ class BytecodeFrame(
|
|||||||
|
|
||||||
override fun setObj(slot: Int, value: Obj) {
|
override fun setObj(slot: Int, value: Obj) {
|
||||||
when (val current = objSlots[slot]) {
|
when (val current = objSlots[slot]) {
|
||||||
is net.sergeych.lyng.FrameSlotRef -> current.write(value)
|
is net.sergeych.lyng.FrameSlotRef -> {
|
||||||
|
if (current.refersTo(this, slot)) {
|
||||||
|
objSlots[slot] = value
|
||||||
|
} else {
|
||||||
|
current.write(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
is net.sergeych.lyng.RecordSlotRef -> current.write(value)
|
is net.sergeych.lyng.RecordSlotRef -> current.write(value)
|
||||||
else -> objSlots[slot] = value
|
else -> objSlots[slot] = value
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,6 +47,9 @@ class BytecodeStatement private constructor(
|
|||||||
moduleScopeId: Int? = null,
|
moduleScopeId: Int? = null,
|
||||||
forcedLocalSlots: Map<String, Int> = emptyMap(),
|
forcedLocalSlots: Map<String, Int> = emptyMap(),
|
||||||
forcedLocalScopeId: Int? = null,
|
forcedLocalScopeId: Int? = null,
|
||||||
|
forcedLocalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
|
||||||
|
globalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
|
||||||
|
globalSlotScopeId: Int? = null,
|
||||||
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
||||||
knownObjectNames: Set<String> = emptySet(),
|
knownObjectNames: Set<String> = emptySet(),
|
||||||
@ -76,6 +79,9 @@ class BytecodeStatement private constructor(
|
|||||||
moduleScopeId = moduleScopeId,
|
moduleScopeId = moduleScopeId,
|
||||||
forcedLocalSlots = forcedLocalSlots,
|
forcedLocalSlots = forcedLocalSlots,
|
||||||
forcedLocalScopeId = forcedLocalScopeId,
|
forcedLocalScopeId = forcedLocalScopeId,
|
||||||
|
forcedLocalSlotInfo = forcedLocalSlotInfo,
|
||||||
|
globalSlotInfo = globalSlotInfo,
|
||||||
|
globalSlotScopeId = globalSlotScopeId,
|
||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
knownNameObjClass = knownNameObjClass,
|
knownNameObjClass = knownNameObjClass,
|
||||||
knownObjectNames = knownObjectNames,
|
knownObjectNames = knownObjectNames,
|
||||||
|
|||||||
@ -1034,7 +1034,19 @@ class CmdAddObj(internal val a: Int, internal val b: Int, internal val dst: Int)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = frame.slotToObj(a).plus(frame.ensureScope(), frame.slotToObj(b))
|
val left = frame.slotToObj(a)
|
||||||
|
val right = frame.slotToObj(b)
|
||||||
|
if (left is ObjInt && right is ObjInt) {
|
||||||
|
frame.setInt(dst, left.value + right.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((left is ObjInt || left is ObjReal) && (right is ObjInt || right is ObjReal)) {
|
||||||
|
val av = if (left is ObjReal) left.value else (left as ObjInt).value.toDouble()
|
||||||
|
val bv = if (right is ObjReal) right.value else (right as ObjInt).value.toDouble()
|
||||||
|
frame.setReal(dst, av + bv)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val result = left.plus(frame.ensureScope(), right)
|
||||||
when (result) {
|
when (result) {
|
||||||
is ObjInt -> frame.setInt(dst, result.value)
|
is ObjInt -> frame.setInt(dst, result.value)
|
||||||
is ObjReal -> frame.setReal(dst, result.value)
|
is ObjReal -> frame.setReal(dst, result.value)
|
||||||
@ -1065,7 +1077,19 @@ class CmdSubObj(internal val a: Int, internal val b: Int, internal val dst: Int)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = frame.slotToObj(a).minus(frame.ensureScope(), frame.slotToObj(b))
|
val left = frame.slotToObj(a)
|
||||||
|
val right = frame.slotToObj(b)
|
||||||
|
if (left is ObjInt && right is ObjInt) {
|
||||||
|
frame.setInt(dst, left.value - right.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((left is ObjInt || left is ObjReal) && (right is ObjInt || right is ObjReal)) {
|
||||||
|
val av = if (left is ObjReal) left.value else (left as ObjInt).value.toDouble()
|
||||||
|
val bv = if (right is ObjReal) right.value else (right as ObjInt).value.toDouble()
|
||||||
|
frame.setReal(dst, av - bv)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val result = left.minus(frame.ensureScope(), right)
|
||||||
when (result) {
|
when (result) {
|
||||||
is ObjInt -> frame.setInt(dst, result.value)
|
is ObjInt -> frame.setInt(dst, result.value)
|
||||||
is ObjReal -> frame.setReal(dst, result.value)
|
is ObjReal -> frame.setReal(dst, result.value)
|
||||||
@ -1096,7 +1120,19 @@ class CmdMulObj(internal val a: Int, internal val b: Int, internal val dst: Int)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = frame.slotToObj(a).mul(frame.ensureScope(), frame.slotToObj(b))
|
val left = frame.slotToObj(a)
|
||||||
|
val right = frame.slotToObj(b)
|
||||||
|
if (left is ObjInt && right is ObjInt) {
|
||||||
|
frame.setInt(dst, left.value * right.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((left is ObjInt || left is ObjReal) && (right is ObjInt || right is ObjReal)) {
|
||||||
|
val av = if (left is ObjReal) left.value else (left as ObjInt).value.toDouble()
|
||||||
|
val bv = if (right is ObjReal) right.value else (right as ObjInt).value.toDouble()
|
||||||
|
frame.setReal(dst, av * bv)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val result = left.mul(frame.ensureScope(), right)
|
||||||
when (result) {
|
when (result) {
|
||||||
is ObjInt -> frame.setInt(dst, result.value)
|
is ObjInt -> frame.setInt(dst, result.value)
|
||||||
is ObjReal -> frame.setReal(dst, result.value)
|
is ObjReal -> frame.setReal(dst, result.value)
|
||||||
@ -1127,7 +1163,19 @@ class CmdDivObj(internal val a: Int, internal val b: Int, internal val dst: Int)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = frame.slotToObj(a).div(frame.ensureScope(), frame.slotToObj(b))
|
val left = frame.slotToObj(a)
|
||||||
|
val right = frame.slotToObj(b)
|
||||||
|
if (left is ObjInt && right is ObjInt) {
|
||||||
|
frame.setInt(dst, left.value / right.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((left is ObjInt || left is ObjReal) && (right is ObjInt || right is ObjReal)) {
|
||||||
|
val av = if (left is ObjReal) left.value else (left as ObjInt).value.toDouble()
|
||||||
|
val bv = if (right is ObjReal) right.value else (right as ObjInt).value.toDouble()
|
||||||
|
frame.setReal(dst, av / bv)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val result = left.div(frame.ensureScope(), right)
|
||||||
when (result) {
|
when (result) {
|
||||||
is ObjInt -> frame.setInt(dst, result.value)
|
is ObjInt -> frame.setInt(dst, result.value)
|
||||||
is ObjReal -> frame.setReal(dst, result.value)
|
is ObjReal -> frame.setReal(dst, result.value)
|
||||||
@ -1158,7 +1206,19 @@ class CmdModObj(internal val a: Int, internal val b: Int, internal val dst: Int)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = frame.slotToObj(a).mod(frame.ensureScope(), frame.slotToObj(b))
|
val left = frame.slotToObj(a)
|
||||||
|
val right = frame.slotToObj(b)
|
||||||
|
if (left is ObjInt && right is ObjInt) {
|
||||||
|
frame.setInt(dst, left.value % right.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ((left is ObjInt || left is ObjReal) && (right is ObjInt || right is ObjReal)) {
|
||||||
|
val av = if (left is ObjReal) left.value else (left as ObjInt).value.toDouble()
|
||||||
|
val bv = if (right is ObjReal) right.value else (right as ObjInt).value.toDouble()
|
||||||
|
frame.setReal(dst, av % bv)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val result = left.mod(frame.ensureScope(), right)
|
||||||
when (result) {
|
when (result) {
|
||||||
is ObjInt -> frame.setInt(dst, result.value)
|
is ObjInt -> frame.setInt(dst, result.value)
|
||||||
is ObjReal -> frame.setReal(dst, result.value)
|
is ObjReal -> frame.setReal(dst, result.value)
|
||||||
@ -1342,9 +1402,9 @@ class CmdClearPendingThrowable : Cmd() {
|
|||||||
|
|
||||||
class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
|
class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
|
val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl
|
||||||
|
?: error("DECL_LOCAL expects LocalDecl at $constId")
|
||||||
if (slot < frame.fn.scopeSlotCount) {
|
if (slot < frame.fn.scopeSlotCount) {
|
||||||
val decl = frame.fn.constants[constId] as? BytecodeConst.LocalDecl
|
|
||||||
?: error("DECL_LOCAL expects LocalDecl at $constId")
|
|
||||||
val target = frame.scopeTarget(slot)
|
val target = frame.scopeTarget(slot)
|
||||||
frame.ensureScopeSlot(target, slot)
|
frame.ensureScopeSlot(target, slot)
|
||||||
val value = frame.slotToObj(slot).byValueCopy()
|
val value = frame.slotToObj(slot).byValueCopy()
|
||||||
@ -1358,6 +1418,27 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
|
|||||||
type = ObjRecord.Type.Other
|
type = ObjRecord.Type.Other
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val localIndex = slot - frame.fn.scopeSlotCount
|
||||||
|
if (localIndex < 0) return
|
||||||
|
val record = ObjRecord(
|
||||||
|
FrameSlotRef(frame.frame, localIndex),
|
||||||
|
decl.isMutable,
|
||||||
|
decl.visibility,
|
||||||
|
isTransient = decl.isTransient,
|
||||||
|
type = ObjRecord.Type.Other
|
||||||
|
)
|
||||||
|
val moduleScope = frame.scope as? ModuleScope
|
||||||
|
if (moduleScope != null) {
|
||||||
|
moduleScope.updateSlotFor(decl.name, record)
|
||||||
|
moduleScope.objects[decl.name] = record
|
||||||
|
moduleScope.localBindings[decl.name] = record
|
||||||
|
} else if (frame.fn.name == "<script>") {
|
||||||
|
val target = frame.ensureScope()
|
||||||
|
target.updateSlotFor(decl.name, record)
|
||||||
|
target.objects[decl.name] = record
|
||||||
|
target.localBindings[decl.name] = record
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1391,6 +1472,20 @@ class CmdDeclDelegated(internal val constId: Int, internal val slot: Int) : Cmd(
|
|||||||
type = ObjRecord.Type.Delegated
|
type = ObjRecord.Type.Delegated
|
||||||
).also { it.delegate = finalDelegate }
|
).also { it.delegate = finalDelegate }
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
val moduleScope = frame.scope as? ModuleScope
|
||||||
|
if (moduleScope != null) {
|
||||||
|
moduleScope.updateSlotFor(
|
||||||
|
decl.name,
|
||||||
|
ObjRecord(
|
||||||
|
ObjNull,
|
||||||
|
decl.isMutable,
|
||||||
|
decl.visibility,
|
||||||
|
isTransient = decl.isTransient,
|
||||||
|
type = ObjRecord.Type.Delegated
|
||||||
|
).also { it.delegate = finalDelegate }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
frame.setObjUnchecked(slot, finalDelegate)
|
frame.setObjUnchecked(slot, finalDelegate)
|
||||||
return
|
return
|
||||||
@ -1425,6 +1520,16 @@ class CmdDeclFunction(internal val constId: Int, internal val slot: Int) : Cmd()
|
|||||||
val captureRecords = buildFunctionCaptureRecords(frame, captureNames)
|
val captureRecords = buildFunctionCaptureRecords(frame, captureNames)
|
||||||
val result = executeFunctionDecl(frame.ensureScope(), decl.spec, captureRecords)
|
val result = executeFunctionDecl(frame.ensureScope(), decl.spec, captureRecords)
|
||||||
frame.setObjUnchecked(slot, result)
|
frame.setObjUnchecked(slot, result)
|
||||||
|
val wrapperName = decl.spec.extensionWrapperName
|
||||||
|
if (wrapperName != null) {
|
||||||
|
val wrapperRecord = frame.ensureScope()[wrapperName]
|
||||||
|
if (wrapperRecord != null) {
|
||||||
|
val localIndex = resolveLocalSlotIndex(frame.fn, wrapperName, preferCapture = false)
|
||||||
|
if (localIndex != null) {
|
||||||
|
frame.setObjUnchecked(frame.fn.scopeSlotCount + localIndex, wrapperRecord.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1724,7 +1829,11 @@ class CmdDeclInstanceField(internal val constId: Int, internal val slot: Int) :
|
|||||||
isTransient = decl.isTransient
|
isTransient = decl.isTransient
|
||||||
)
|
)
|
||||||
if (slot >= frame.fn.scopeSlotCount) {
|
if (slot >= frame.fn.scopeSlotCount) {
|
||||||
frame.storeObjResult(slot, ObjVoid)
|
val localIndex = slot - frame.fn.scopeSlotCount
|
||||||
|
val isMutable = frame.fn.localSlotMutables.getOrNull(localIndex) ?: true
|
||||||
|
if (isMutable) {
|
||||||
|
frame.storeObjResult(slot, ObjVoid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1749,7 +1858,11 @@ class CmdDeclInstanceProperty(internal val constId: Int, internal val slot: Int)
|
|||||||
isTransient = decl.isTransient
|
isTransient = decl.isTransient
|
||||||
)
|
)
|
||||||
if (slot >= frame.fn.scopeSlotCount) {
|
if (slot >= frame.fn.scopeSlotCount) {
|
||||||
frame.storeObjResult(slot, ObjVoid)
|
val localIndex = slot - frame.fn.scopeSlotCount
|
||||||
|
val isMutable = frame.fn.localSlotMutables.getOrNull(localIndex) ?: true
|
||||||
|
if (isMutable) {
|
||||||
|
frame.storeObjResult(slot, ObjVoid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1800,7 +1913,11 @@ class CmdDeclInstanceDelegated(internal val constId: Int, internal val slot: Int
|
|||||||
delegate = finalDelegate
|
delegate = finalDelegate
|
||||||
}
|
}
|
||||||
if (slot >= frame.fn.scopeSlotCount) {
|
if (slot >= frame.fn.scopeSlotCount) {
|
||||||
frame.storeObjResult(slot, ObjVoid)
|
val localIndex = slot - frame.fn.scopeSlotCount
|
||||||
|
val isMutable = frame.fn.localSlotMutables.getOrNull(localIndex) ?: true
|
||||||
|
if (isMutable) {
|
||||||
|
frame.storeObjResult(slot, ObjVoid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1812,9 +1929,6 @@ class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cm
|
|||||||
?: error("DECL_DESTRUCTURE expects DestructureDecl at $constId")
|
?: error("DECL_DESTRUCTURE expects DestructureDecl at $constId")
|
||||||
val value = frame.slotToObj(slot)
|
val value = frame.slotToObj(slot)
|
||||||
assignDestructurePattern(frame, decl.pattern, value, decl.pos)
|
assignDestructurePattern(frame, decl.pattern, value, decl.pos)
|
||||||
if (slot >= frame.fn.scopeSlotCount) {
|
|
||||||
frame.storeObjResult(slot, ObjVoid)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1972,10 +2086,18 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
|
|||||||
val getterName = extensionPropertyGetterName(decl.extTypeName, decl.property.name)
|
val getterName = extensionPropertyGetterName(decl.extTypeName, decl.property.name)
|
||||||
val getterWrapper = ObjExtensionPropertyGetterCallable(decl.property.name, decl.property)
|
val getterWrapper = ObjExtensionPropertyGetterCallable(decl.property.name, decl.property)
|
||||||
frame.ensureScope().addItem(getterName, false, getterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun)
|
frame.ensureScope().addItem(getterName, false, getterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun)
|
||||||
|
val getterLocal = resolveLocalSlotIndex(frame.fn, getterName, preferCapture = false)
|
||||||
|
if (getterLocal != null) {
|
||||||
|
frame.setObjUnchecked(frame.fn.scopeSlotCount + getterLocal, getterWrapper)
|
||||||
|
}
|
||||||
if (decl.property.setter != null) {
|
if (decl.property.setter != null) {
|
||||||
val setterName = extensionPropertySetterName(decl.extTypeName, decl.property.name)
|
val setterName = extensionPropertySetterName(decl.extTypeName, decl.property.name)
|
||||||
val setterWrapper = ObjExtensionPropertySetterCallable(decl.property.name, decl.property)
|
val setterWrapper = ObjExtensionPropertySetterCallable(decl.property.name, decl.property)
|
||||||
frame.ensureScope().addItem(setterName, false, setterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun)
|
frame.ensureScope().addItem(setterName, false, setterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun)
|
||||||
|
val setterLocal = resolveLocalSlotIndex(frame.fn, setterName, preferCapture = false)
|
||||||
|
if (setterLocal != null) {
|
||||||
|
frame.setObjUnchecked(frame.fn.scopeSlotCount + setterLocal, setterWrapper)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
frame.setObj(slot, decl.property)
|
frame.setObj(slot, decl.property)
|
||||||
return
|
return
|
||||||
@ -2446,7 +2568,7 @@ class CmdMakeLambda(internal val id: Int, internal val dst: Int) : Cmd() {
|
|||||||
val lambdaConst = frame.fn.constants.getOrNull(id) as? BytecodeConst.LambdaFn
|
val lambdaConst = frame.fn.constants.getOrNull(id) as? BytecodeConst.LambdaFn
|
||||||
?: error("MAKE_LAMBDA_FN expects LambdaFn at $id")
|
?: error("MAKE_LAMBDA_FN expects LambdaFn at $id")
|
||||||
val scope = frame.ensureScope()
|
val scope = frame.ensureScope()
|
||||||
val captureRecords = lambdaConst.captureTableId?.let { frame.buildCaptureRecords(it) }
|
val captureRecords = lambdaConst.captureTableId?.let { frame.buildCaptureRecords(it, lambdaConst.captureNames) }
|
||||||
val stmt = BytecodeLambdaCallable(
|
val stmt = BytecodeLambdaCallable(
|
||||||
fn = lambdaConst.fn,
|
fn = lambdaConst.fn,
|
||||||
closureScope = scope,
|
closureScope = scope,
|
||||||
@ -2495,6 +2617,9 @@ class BytecodeLambdaCallable(
|
|||||||
// args bound into frame slots in the bytecode entry binder
|
// args bound into frame slots in the bytecode entry binder
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
|
val declaredNames = fn.constants
|
||||||
|
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
||||||
|
.mapTo(mutableSetOf()) { it.name }
|
||||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||||
val slotPlan = fn.localSlotPlanByName()
|
val slotPlan = fn.localSlotPlanByName()
|
||||||
if (argsDeclaration == null) {
|
if (argsDeclaration == null) {
|
||||||
@ -2516,6 +2641,28 @@ class BytecodeLambdaCallable(
|
|||||||
frame.frame
|
frame.frame
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
val localNames = frame.fn.localSlotNames
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
if (declaredNames.contains(name)) continue
|
||||||
|
val slotType = frame.getLocalSlotTypeCode(i)
|
||||||
|
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val record = context.getLocalRecordDirect(name)
|
||||||
|
?: context.parent?.get(name)
|
||||||
|
?: context.get(name)
|
||||||
|
?: continue
|
||||||
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
|
context.resolve(record, name)
|
||||||
|
} else {
|
||||||
|
record.value
|
||||||
|
}
|
||||||
|
frame.frame.setObj(i, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CmdVm().execute(fn, context, scope.args, binder)
|
CmdVm().execute(fn, context, scope.args, binder)
|
||||||
} catch (e: ReturnException) {
|
} catch (e: ReturnException) {
|
||||||
@ -2579,17 +2726,29 @@ class CmdFrame(
|
|||||||
internal val tryStack = ArrayDeque<TryHandler>()
|
internal val tryStack = ArrayDeque<TryHandler>()
|
||||||
private var pendingThrowable: Throwable? = null
|
private var pendingThrowable: Throwable? = null
|
||||||
|
|
||||||
internal val frame = BytecodeFrame(fn.localCount, args.size)
|
internal val frame: BytecodeFrame
|
||||||
private val addrScopes: Array<Scope?> = arrayOfNulls(fn.addrCount)
|
private val addrScopes: Array<Scope?> = arrayOfNulls(fn.addrCount)
|
||||||
private val addrIndices: IntArray = IntArray(fn.addrCount)
|
private val addrIndices: IntArray = IntArray(fn.addrCount)
|
||||||
private val addrScopeSlots: IntArray = IntArray(fn.addrCount)
|
private val addrScopeSlots: IntArray = IntArray(fn.addrCount)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
frame = resolveFrame(scope0, fn, args)
|
||||||
for (i in args.indices) {
|
for (i in args.indices) {
|
||||||
|
if (i >= frame.slotCount - frame.argBase) break
|
||||||
frame.setObj(frame.argBase + i, args[i])
|
frame.setObj(frame.argBase + i, args[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resolveFrame(scope: Scope, fn: CmdFunction, args: List<Obj>): BytecodeFrame {
|
||||||
|
val moduleScope = scope as? ModuleScope
|
||||||
|
if (moduleScope != null && args.isEmpty()) {
|
||||||
|
return moduleScope.ensureModuleFrame(fn)
|
||||||
|
}
|
||||||
|
return BytecodeFrame(fn.localCount, args.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getLocalSlotTypeCode(localIndex: Int): Byte = frame.getSlotTypeCode(localIndex)
|
||||||
|
|
||||||
internal fun applyCaptureRecords() {
|
internal fun applyCaptureRecords() {
|
||||||
val captureRecords = scope.captureRecords ?: return
|
val captureRecords = scope.captureRecords ?: return
|
||||||
val captureNames = scope.captureNames ?: return
|
val captureNames = scope.captureNames ?: return
|
||||||
@ -2618,6 +2777,7 @@ class CmdFrame(
|
|||||||
} else {
|
} else {
|
||||||
val value = record.value
|
val value = record.value
|
||||||
if (value is FrameSlotRef) {
|
if (value is FrameSlotRef) {
|
||||||
|
if (value.refersTo(frame, localIndex)) continue
|
||||||
frame.setObj(localIndex, value)
|
frame.setObj(localIndex, value)
|
||||||
} else {
|
} else {
|
||||||
frame.setObj(localIndex, RecordSlotRef(record))
|
frame.setObj(localIndex, RecordSlotRef(record))
|
||||||
@ -2641,16 +2801,38 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun buildCaptureRecords(captureTableId: Int): List<ObjRecord> {
|
internal fun buildCaptureRecords(captureTableId: Int, captureNames: List<String>? = null): List<ObjRecord> {
|
||||||
val table = fn.constants.getOrNull(captureTableId) as? BytecodeConst.CaptureTable
|
val table = fn.constants.getOrNull(captureTableId) as? BytecodeConst.CaptureTable
|
||||||
?: error("Capture table $captureTableId missing")
|
?: error("Capture table $captureTableId missing")
|
||||||
return table.entries.map { entry ->
|
return table.entries.mapIndexed { index, entry ->
|
||||||
when (entry.ownerKind) {
|
when (entry.ownerKind) {
|
||||||
CaptureOwnerFrameKind.LOCAL -> {
|
CaptureOwnerFrameKind.LOCAL -> {
|
||||||
val localIndex = entry.slotIndex - fn.scopeSlotCount
|
val localIndex = entry.slotIndex - fn.scopeSlotCount
|
||||||
if (localIndex < 0) {
|
if (localIndex < 0) {
|
||||||
error("Invalid local capture slot ${entry.slotIndex}")
|
error("Invalid local capture slot ${entry.slotIndex}")
|
||||||
}
|
}
|
||||||
|
val name = captureNames?.getOrNull(index)
|
||||||
|
if (name != null) {
|
||||||
|
val inheritedNames = scope.captureNames
|
||||||
|
val inheritedRecords = scope.captureRecords
|
||||||
|
if (inheritedNames != null && inheritedRecords != null) {
|
||||||
|
val inheritedIndex = inheritedNames.indexOf(name)
|
||||||
|
if (inheritedIndex >= 0) {
|
||||||
|
val inherited = inheritedRecords.getOrNull(inheritedIndex)
|
||||||
|
if (inherited != null) {
|
||||||
|
val copied = ObjRecord(
|
||||||
|
value = inherited.value,
|
||||||
|
isMutable = inherited.isMutable,
|
||||||
|
visibility = inherited.visibility,
|
||||||
|
isTransient = inherited.isTransient,
|
||||||
|
type = inherited.type
|
||||||
|
)
|
||||||
|
copied.delegate = inherited.delegate
|
||||||
|
return@mapIndexed copied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val isMutable = fn.localSlotMutables.getOrNull(localIndex) ?: false
|
val isMutable = fn.localSlotMutables.getOrNull(localIndex) ?: false
|
||||||
val isDelegated = fn.localSlotDelegated.getOrNull(localIndex) ?: false
|
val isDelegated = fn.localSlotDelegated.getOrNull(localIndex) ?: false
|
||||||
if (isDelegated) {
|
if (isDelegated) {
|
||||||
@ -2659,7 +2841,23 @@ class CmdFrame(
|
|||||||
it.delegate = delegate
|
it.delegate = delegate
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ObjRecord(FrameSlotRef(frame, localIndex), isMutable)
|
val raw = frame.getRawObj(localIndex)
|
||||||
|
if (raw == null && name != null) {
|
||||||
|
val record = scope.get(name)
|
||||||
|
if (record != null) {
|
||||||
|
val value = record.value
|
||||||
|
return@mapIndexed when (value) {
|
||||||
|
is FrameSlotRef -> ObjRecord(value, isMutable)
|
||||||
|
is RecordSlotRef -> ObjRecord(value, isMutable)
|
||||||
|
else -> ObjRecord(value, isMutable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (raw) {
|
||||||
|
is FrameSlotRef -> ObjRecord(raw, isMutable)
|
||||||
|
is RecordSlotRef -> ObjRecord(raw, isMutable)
|
||||||
|
else -> ObjRecord(FrameSlotRef(frame, localIndex), isMutable)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CaptureOwnerFrameKind.MODULE -> {
|
CaptureOwnerFrameKind.MODULE -> {
|
||||||
@ -3239,7 +3437,7 @@ class CmdFrame(
|
|||||||
else -> obj
|
else -> obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> ObjVoid
|
else -> localSlotToObj(local)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
|
data class ForcedLocalSlotInfo(
|
||||||
|
val index: Int,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val isDelegated: Boolean,
|
||||||
|
)
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
|
internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
||||||
|
val localNames = frame.fn.localSlotNames
|
||||||
|
if (localNames.isEmpty()) return
|
||||||
|
val base = frame.fn.scopeSlotCount
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
val slotType = frame.getLocalSlotTypeCode(i)
|
||||||
|
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) continue
|
||||||
|
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) continue
|
||||||
|
val record = scope.getLocalRecordDirect(name)
|
||||||
|
?: scope.chainLookupIgnoreClosure(name, followClosure = true)
|
||||||
|
?: continue
|
||||||
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
|
scope.resolve(record, name)
|
||||||
|
} else {
|
||||||
|
record.value
|
||||||
|
}
|
||||||
|
if (value is net.sergeych.lyng.FrameSlotRef && value.refersTo(frame.frame, base + i)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
frame.setObjUnchecked(base + i, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,12 +20,14 @@ import net.sergeych.lyng.ArgsDeclaration
|
|||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.bytecode.CmdFunction
|
import net.sergeych.lyng.bytecode.CmdFunction
|
||||||
|
import net.sergeych.lyng.bytecode.LambdaCaptureEntry
|
||||||
|
|
||||||
class LambdaFnRef(
|
class LambdaFnRef(
|
||||||
valueFn: suspend (Scope) -> ObjRecord,
|
valueFn: suspend (Scope) -> ObjRecord,
|
||||||
val bytecodeFn: CmdFunction?,
|
val bytecodeFn: CmdFunction?,
|
||||||
val paramSlotPlan: Map<String, Int>,
|
val paramSlotPlan: Map<String, Int>,
|
||||||
val argsDeclaration: ArgsDeclaration?,
|
val argsDeclaration: ArgsDeclaration?,
|
||||||
|
val captureEntries: List<LambdaCaptureEntry>,
|
||||||
val preferredThisType: String?,
|
val preferredThisType: String?,
|
||||||
val wrapAsExtensionCallable: Boolean,
|
val wrapAsExtensionCallable: Boolean,
|
||||||
val returnLabels: Set<String>,
|
val returnLabels: Set<String>,
|
||||||
|
|||||||
@ -977,7 +977,14 @@ object ObjNull : Obj() {
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("unset")
|
@SerialName("unset")
|
||||||
object ObjUnset : Obj() {
|
object ObjUnset : Obj() {
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
|
val resolved = when (other) {
|
||||||
|
is FrameSlotRef -> other.read()
|
||||||
|
is RecordSlotRef -> other.read()
|
||||||
|
else -> other
|
||||||
|
}
|
||||||
|
return if (resolved === this) 0 else -1
|
||||||
|
}
|
||||||
override fun equals(other: Any?): Boolean = other === this
|
override fun equals(other: Any?): Boolean = other === this
|
||||||
override fun toString(): String = "Unset"
|
override fun toString(): String = "Unset"
|
||||||
|
|
||||||
|
|||||||
@ -575,6 +575,11 @@ open class ObjClass(
|
|||||||
val stableParent = classScope ?: scope.parent
|
val stableParent = classScope ?: scope.parent
|
||||||
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
|
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
|
||||||
instance.instanceScope.currentClassCtx = null
|
instance.instanceScope.currentClassCtx = null
|
||||||
|
val classCaptureRecords = classScope?.captureRecords
|
||||||
|
if (classCaptureRecords != null) {
|
||||||
|
instance.instanceScope.captureRecords = classCaptureRecords
|
||||||
|
instance.instanceScope.captureNames = classScope?.captureNames
|
||||||
|
}
|
||||||
val fieldSlots = fieldSlotMap()
|
val fieldSlots = fieldSlotMap()
|
||||||
if (fieldSlots.isNotEmpty()) {
|
if (fieldSlots.isNotEmpty()) {
|
||||||
instance.initFieldSlots(fieldSlotCount())
|
instance.initFieldSlots(fieldSlotCount())
|
||||||
@ -714,7 +719,11 @@ open class ObjClass(
|
|||||||
instance.instanceScope.currentClassCtx = c
|
instance.instanceScope.currentClassCtx = c
|
||||||
try {
|
try {
|
||||||
for (initStmt in c.instanceInitializers) {
|
for (initStmt in c.instanceInitializers) {
|
||||||
initStmt.callOn(instance.instanceScope)
|
if (initStmt is net.sergeych.lyng.Statement) {
|
||||||
|
executeBytecodeWithSeed(instance.instanceScope, initStmt, "instance init")
|
||||||
|
} else {
|
||||||
|
initStmt.callOn(instance.instanceScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
instance.instanceScope.currentClassCtx = savedCtx
|
instance.instanceScope.currentClassCtx = savedCtx
|
||||||
|
|||||||
@ -17,7 +17,12 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.Arguments
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.Statement
|
||||||
|
import net.sergeych.lyng.Visibility
|
||||||
|
import net.sergeych.lyng.executeBytecodeWithSeed
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lazy delegate used by `val x by lazy { ... }`.
|
* Lazy delegate used by `val x by lazy { ... }`.
|
||||||
@ -41,7 +46,11 @@ class ObjLazyDelegate(
|
|||||||
"getValue" -> {
|
"getValue" -> {
|
||||||
if (!calculated) {
|
if (!calculated) {
|
||||||
val callScope = capturedScope.createChildScope(capturedScope.pos, args = Arguments.EMPTY)
|
val callScope = capturedScope.createChildScope(capturedScope.pos, args = Arguments.EMPTY)
|
||||||
cachedValue = builder.callOn(callScope)
|
cachedValue = if (builder is Statement) {
|
||||||
|
executeBytecodeWithSeed(callScope, builder, "lazy delegate")
|
||||||
|
} else {
|
||||||
|
builder.callOn(callScope)
|
||||||
|
}
|
||||||
calculated = true
|
calculated = true
|
||||||
}
|
}
|
||||||
cachedValue
|
cachedValue
|
||||||
|
|||||||
@ -18,7 +18,11 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
import net.sergeych.lyng.Arguments
|
||||||
|
import net.sergeych.lyng.BytecodeBodyProvider
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.Statement
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
|
import net.sergeych.lyng.executeBytecodeWithSeed
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property accessor storage. Per instructions, properties do NOT have
|
* Property accessor storage. Per instructions, properties do NOT have
|
||||||
@ -37,7 +41,15 @@ class ObjProperty(
|
|||||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
||||||
val execScope = scope.applyClosure(instanceScope).createChildScope(newThisObj = instance)
|
val execScope = scope.applyClosure(instanceScope).createChildScope(newThisObj = instance)
|
||||||
execScope.currentClassCtx = declaringClass
|
execScope.currentClassCtx = declaringClass
|
||||||
return g.callOn(execScope)
|
return when (g) {
|
||||||
|
is BytecodeStatement -> executeBytecodeWithSeed(execScope, g, "property getter")
|
||||||
|
is BytecodeBodyProvider -> {
|
||||||
|
val body = g.bytecodeBody()
|
||||||
|
if (body != null) executeBytecodeWithSeed(execScope, body, "property getter") else g.callOn(execScope)
|
||||||
|
}
|
||||||
|
is Statement -> g.callOn(execScope)
|
||||||
|
else -> g.callOn(execScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun callSetter(scope: Scope, instance: Obj, value: Obj, declaringClass: ObjClass? = null) {
|
suspend fun callSetter(scope: Scope, instance: Obj, value: Obj, declaringClass: ObjClass? = null) {
|
||||||
@ -47,7 +59,15 @@ class ObjProperty(
|
|||||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
||||||
val execScope = scope.applyClosure(instanceScope).createChildScope(args = Arguments(value), newThisObj = instance)
|
val execScope = scope.applyClosure(instanceScope).createChildScope(args = Arguments(value), newThisObj = instance)
|
||||||
execScope.currentClassCtx = declaringClass
|
execScope.currentClassCtx = declaringClass
|
||||||
s.callOn(execScope)
|
when (s) {
|
||||||
|
is BytecodeStatement -> executeBytecodeWithSeed(execScope, s, "property setter")
|
||||||
|
is BytecodeBodyProvider -> {
|
||||||
|
val body = s.bytecodeBody()
|
||||||
|
if (body != null) executeBytecodeWithSeed(execScope, body, "property setter") else s.callOn(execScope)
|
||||||
|
}
|
||||||
|
is Statement -> s.callOn(execScope)
|
||||||
|
else -> s.callOn(execScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "Property($name)"
|
override fun toString(): String = "Property($name)"
|
||||||
|
|||||||
@ -17,8 +17,10 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
|
import net.sergeych.lyng.FrameSlotRef
|
||||||
import net.sergeych.lyng.PerfFlags
|
import net.sergeych.lyng.PerfFlags
|
||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
|
import net.sergeych.lyng.RecordSlotRef
|
||||||
import net.sergeych.lyng.RegexCache
|
import net.sergeych.lyng.RegexCache
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.requireScope
|
import net.sergeych.lyng.requireScope
|
||||||
@ -29,7 +31,20 @@ class ObjRegex(val regex: Regex) : Obj() {
|
|||||||
|
|
||||||
override suspend fun operatorMatch(scope: Scope, other: Obj): Obj {
|
override suspend fun operatorMatch(scope: Scope, other: Obj): Obj {
|
||||||
return regex.find(other.cast<ObjString>(scope).value)?.let {
|
return regex.find(other.cast<ObjString>(scope).value)?.let {
|
||||||
scope.addOrUpdateItem("$~", ObjRegexMatch(it))
|
val match = ObjRegexMatch(it)
|
||||||
|
val record = scope.chainLookupIgnoreClosure("$~", followClosure = true)
|
||||||
|
if (record != null) {
|
||||||
|
if (!record.isMutable) {
|
||||||
|
scope.raiseIllegalAssignment("symbol is readonly: $~")
|
||||||
|
}
|
||||||
|
when (val value = record.value) {
|
||||||
|
is FrameSlotRef -> value.write(match)
|
||||||
|
is RecordSlotRef -> value.write(match)
|
||||||
|
else -> record.value = match
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.addOrUpdateItem("$~", match)
|
||||||
|
}
|
||||||
ObjTrue
|
ObjTrue
|
||||||
} ?: ObjFalse
|
} ?: ObjFalse
|
||||||
}
|
}
|
||||||
|
|||||||
@ -64,21 +64,27 @@ abstract class ImportProvider(
|
|||||||
fun newModuleAt(pos: Pos): ModuleScope =
|
fun newModuleAt(pos: Pos): ModuleScope =
|
||||||
ModuleScope(this, pos, "unknown")
|
ModuleScope(this, pos, "unknown")
|
||||||
|
|
||||||
private var cachedStdScope = CachedExpression<Scope>()
|
private data class StdScopeSeed(
|
||||||
|
val stdlib: ModuleScope,
|
||||||
|
val plan: Map<String, Int>
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun newStdScope(pos: Pos = Pos.builtIn): Scope =
|
private var cachedStdScope = CachedExpression<StdScopeSeed>()
|
||||||
cachedStdScope.get {
|
|
||||||
val module = newModuleAt(pos)
|
suspend fun newStdScope(pos: Pos = Pos.builtIn): Scope {
|
||||||
|
val seed = cachedStdScope.get {
|
||||||
val stdlib = prepareImport(pos, "lyng.stdlib", null)
|
val stdlib = prepareImport(pos, "lyng.stdlib", null)
|
||||||
val plan = LinkedHashMap<String, Int>()
|
val plan = LinkedHashMap<String, Int>()
|
||||||
for ((name, record) in stdlib.objects) {
|
for ((name, record) in stdlib.objects) {
|
||||||
if (!record.visibility.isPublic) continue
|
if (!record.visibility.isPublic) continue
|
||||||
plan[name] = plan.size
|
plan[name] = plan.size
|
||||||
}
|
}
|
||||||
if (plan.isNotEmpty()) module.applySlotPlan(plan)
|
StdScopeSeed(stdlib, plan)
|
||||||
stdlib.importInto(module, null)
|
}
|
||||||
module
|
val module = newModuleAt(pos)
|
||||||
}.createChildScope()
|
if (seed.plan.isNotEmpty()) module.applySlotPlan(seed.plan)
|
||||||
|
seed.stdlib.importInto(module, null)
|
||||||
|
return module
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4025,6 +4025,37 @@ class ScriptTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun moduleFramePersistsAcrossEval() = runTest {
|
||||||
|
val scope = ModuleScope(Script.defaultImportManager, Pos.builtIn, "test.mod")
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
var x = 1
|
||||||
|
x
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertEquals(1, scope.eval("x").toInt())
|
||||||
|
scope.eval("x = x + 1")
|
||||||
|
assertEquals(2, scope.eval("x").toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun moduleExtensionWrapperUsesFrameSlots() = runTest {
|
||||||
|
val scope = ModuleScope(Script.defaultImportManager, Pos.builtIn, "test.mod.ext")
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
fun String.foo() { this + "_m" }
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertEquals("a_m", scope.eval("""__ext__String__foo("a")""").toString())
|
||||||
|
scope.eval(
|
||||||
|
"""
|
||||||
|
fun String.foo() { this + "_n" }
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
assertEquals("a_n", scope.eval("""__ext__String__foo("a")""").toString())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testThrowReportsSource() = runTest {
|
fun testThrowReportsSource() = runTest {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -7,7 +7,6 @@ Current focus
|
|||||||
- Enforce compile-time name/member resolution only; no runtime scope lookup or fallback.
|
- Enforce compile-time name/member resolution only; no runtime scope lookup or fallback.
|
||||||
- Bytecode uses memberId-based ops (CALL_MEMBER_SLOT/GET_MEMBER_SLOT/SET_MEMBER_SLOT).
|
- Bytecode uses memberId-based ops (CALL_MEMBER_SLOT/GET_MEMBER_SLOT/SET_MEMBER_SLOT).
|
||||||
- Runtime lookup opcodes (CALL_VIRTUAL/GET_FIELD/SET_FIELD) and fallback callsites are removed.
|
- Runtime lookup opcodes (CALL_VIRTUAL/GET_FIELD/SET_FIELD) and fallback callsites are removed.
|
||||||
- Migrate bytecode to frame-first locals with lazy scope reflection; avoid eager scope slot plans in compiled functions.
|
|
||||||
- Use FrameSlotRef for captures and only materialize Scope for Kotlin interop; use frame.ip -> pos mapping for diagnostics.
|
- Use FrameSlotRef for captures and only materialize Scope for Kotlin interop; use frame.ip -> pos mapping for diagnostics.
|
||||||
|
|
||||||
Key recent changes
|
Key recent changes
|
||||||
|
|||||||
175
notes/archive/bytecode_migration_plan.md
Normal file
175
notes/archive/bytecode_migration_plan.md
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
# Bytecode Migration Plan
|
||||||
|
|
||||||
|
Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM tests green after each step.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
- [x] Step 1: Module/import slots seeded into module frame; bytecode module resolution works across closures.
|
||||||
|
- [x] Step 2: Allow implicit/qualified `this` member refs to compile to bytecode.
|
||||||
|
- [x] Enable bytecode for `ImplicitThisMethodCallRef`, `QualifiedThisMethodSlotCallRef`, `QualifiedThisFieldSlotRef`.
|
||||||
|
- [x] Keep unsupported cases blocked: `ClassScopeMemberRef`, dynamic receivers, delegated members.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
- [x] Step 3: Bytecode support for `try/catch/finally`.
|
||||||
|
- [x] Implement bytecode emission for try/catch and finally blocks.
|
||||||
|
- [x] Preserve existing error/stack semantics.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
|
||||||
|
## Remaining Migration (prioritized)
|
||||||
|
|
||||||
|
- [x] Step 4: Allow bytecode wrapping for supported declaration statements.
|
||||||
|
- [x] Enable `DestructuringVarDeclStatement` and `ExtensionPropertyDeclStatement` in `containsUnsupportedForBytecode`.
|
||||||
|
- [x] Keep JVM tests green before commit.
|
||||||
|
- [x] Step 5: Enable bytecode for delegated var declarations.
|
||||||
|
- [x] Revisit `containsDelegatedRefs` guard for `DelegatedVarDeclStatement`.
|
||||||
|
- [x] Ensure delegate binding uses explicit `Statement` objects (no inline suspend lambdas).
|
||||||
|
- [x] Keep JVM tests green before commit.
|
||||||
|
- [x] Step 6: Map literal spread in bytecode.
|
||||||
|
- [x] Replace `MapLiteralEntry.Spread` bytecode exception with runtime `putAll`/merge logic.
|
||||||
|
- [x] Step 7: Class-scope member refs in bytecode.
|
||||||
|
- [x] Support `ClassScopeMemberRef` without scope-map fallback.
|
||||||
|
- [x] Step 8: ObjDynamic member access in bytecode.
|
||||||
|
- [x] Allow dynamic receiver field/method lookup without falling back to interpreter.
|
||||||
|
- [x] Step 9: Module-level bytecode execution.
|
||||||
|
- [x] Compile `Script` bodies to bytecode instead of interpreting at module scope.
|
||||||
|
- [x] Keep import/module slot seeding in frame-only flow.
|
||||||
|
- [x] Step 10: Bytecode for declaration statements in module scripts.
|
||||||
|
- [x] Support `ClassDeclStatement`, `FunctionDeclStatement`, `EnumDeclStatement` in bytecode compilation.
|
||||||
|
- [x] Keep a mixed execution path for declarations (module bytecode calls statement bodies via `CALL_SLOT`).
|
||||||
|
- [x] Ensure module object member refs compile as instance access (not class-scope).
|
||||||
|
- [x] Step 11: Destructuring assignment bytecode.
|
||||||
|
- [x] Handle `[a, b] = expr` (AssignRef target `ListLiteralRef`) without interpreter fallback.
|
||||||
|
- [x] Step 12: Optional member assign-ops and inc/dec in bytecode.
|
||||||
|
- [x] Support `a?.b += 1` and `a?.b++` for `FieldRef` targets.
|
||||||
|
- [x] Fix post-inc return value for object slots stored in scope frames.
|
||||||
|
- [x] Handle optional receivers for member assign-ops and inc/dec without evaluating operands on null.
|
||||||
|
- [x] Support class-scope and index optional inc/dec paths in bytecode.
|
||||||
|
- [x] Step 13: Qualified `this` value refs in bytecode.
|
||||||
|
- [x] Compile `QualifiedThisRef` (`this@Type`) via `LOAD_THIS_VARIANT`.
|
||||||
|
- [x] Add a JVM test that evaluates `this@Type` as a value inside nested classes.
|
||||||
|
- [x] Step 14: Fast local ref reads in bytecode.
|
||||||
|
- [x] Support `FastLocalVarRef` reads with the same slot resolution as `LocalVarRef`.
|
||||||
|
- [x] If `BoundLocalVarRef` is still emitted, map it to a direct slot read instead of failing.
|
||||||
|
- [x] Add a JVM test that exercises fast-local reads in a bytecode-compiled function.
|
||||||
|
- [x] Step 15: Class-scope `?=` in bytecode.
|
||||||
|
- [x] Handle `C.x ?= v` and `C?.x ?= v` for class-scope members without falling back.
|
||||||
|
- [x] Add a JVM test for class-scope `?=` on static vars.
|
||||||
|
- [x] Step 16: Remove dead `ToBoolStatement`.
|
||||||
|
- [x] Confirm no parser/compiler paths construct `ToBoolStatement` and delete it plus interpreter hooks.
|
||||||
|
- [x] Keep JVM tests green after removal.
|
||||||
|
- [x] Step 17: Callable property calls in bytecode.
|
||||||
|
- [x] Support `CallRef` where the target is a `FieldRef` (e.g., `(obj.fn)()`), keeping compile-time resolution.
|
||||||
|
- [x] Add a JVM test for a callable property call compiled to bytecode.
|
||||||
|
- [x] Step 18: Delegated member access in bytecode.
|
||||||
|
- [x] Remove `containsDelegatedRefs` guard once bytecode emits delegated get/set/call correctly.
|
||||||
|
- [x] Add JVM coverage for delegated member get/set/call in bytecode.
|
||||||
|
- [x] Step 19: Unknown receiver member access in bytecode.
|
||||||
|
- [x] Reject Object/unknown receiver member calls without explicit cast or Dynamic.
|
||||||
|
- [x] Add union-member dispatch with ordered type checks and runtime mismatch error.
|
||||||
|
- [x] Add JVM tests for unknown receiver and union member access.
|
||||||
|
- [x] Step 20: Bytecode support for `NopStatement`.
|
||||||
|
- [x] Allow `NopStatement` in `containsUnsupportedForBytecode`.
|
||||||
|
- [x] Emit `ObjVoid` directly in bytecode for `NopStatement` in statement/value contexts.
|
||||||
|
- [x] Add a JVM test that exercises a code path returning `NopStatement` in bytecode (e.g., static class member decl in class body).
|
||||||
|
- [x] Step 21: Union mismatch path in bytecode.
|
||||||
|
- [x] Replace `UnionTypeMismatchStatement` branch with a bytecode-compilable throw path (no custom `StatementRef` that blocks bytecode).
|
||||||
|
- [x] Add a JVM test that forces the union mismatch at runtime and asserts the error message.
|
||||||
|
- [x] Step 22: Delegated local slots in bytecode.
|
||||||
|
- [x] Support reads/writes/assign-ops/inc/dec for delegated locals (`LocalSlotRef.isDelegated`) in `BytecodeCompiler`.
|
||||||
|
- [x] Remove `containsDelegatedRefs` guard once delegated locals are bytecode-safe.
|
||||||
|
- [x] Add JVM tests that use delegated locals inside bytecode-compiled functions.
|
||||||
|
- [x] Step 23: Refactor delegated locals to keep delegate objects in frame slots.
|
||||||
|
- [x] Add bytecode ops to bind/get/set delegated locals without scope storage.
|
||||||
|
- [x] Store delegated locals in frame slots and compile get/set/assign ops with new ops.
|
||||||
|
- [x] Preserve reflection facade by syncing delegated locals into scope only when needed.
|
||||||
|
- [x] Step 24: Remove `ASSIGN_SCOPE_SLOT` now that delegated locals are always frame-backed.
|
||||||
|
- [x] Force delegated locals into local slots (even module) and avoid scope-slot resolution.
|
||||||
|
- [x] Drop opcode/runtime support for `ASSIGN_SCOPE_SLOT`.
|
||||||
|
|
||||||
|
## Frame-Only Execution (new, before interpreter removal)
|
||||||
|
|
||||||
|
- [x] Step 24A: Bytecode capture tables for lambdas (compiler only).
|
||||||
|
- [x] Emit per-lambda capture tables containing (ownerFrameKind, ownerSlotId).
|
||||||
|
- [x] Create captures only when detected; do not allocate scope slots.
|
||||||
|
- [x] Add disassembler output for capture tables.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
- [x] Step 24B: Frame-slot captures in bytecode runtime.
|
||||||
|
- [x] Build lambdas from bytecode + capture table (no capture names).
|
||||||
|
- [x] Read captured values via `FrameSlotRef` only.
|
||||||
|
- [x] Forbid `resolveCaptureRecord` in bytecode paths; keep only in interpreter.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
- [x] Step 24C: Remove scope local mirroring in bytecode execution.
|
||||||
|
- [x] Remove/disable any bytecode runtime code that writes locals into Scope for execution.
|
||||||
|
- [x] Keep Scope creation only for reflection/Kotlin interop paths.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
- [x] Step 24D: Eliminate `ClosureScope` usage on bytecode execution paths.
|
||||||
|
- [x] Avoid `ClosureScope` in bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty).
|
||||||
|
- [x] Keep interpreter path using `ClosureScope` until interpreter removal.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
- [x] Step 24E: Isolate interpreter-only capture logic.
|
||||||
|
- [x] Mark `resolveCaptureRecord` paths as interpreter-only.
|
||||||
|
- [x] Guard or delete any bytecode path that tries to sync captures into scopes.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
|
||||||
|
## Interpreter Removal (next)
|
||||||
|
|
||||||
|
- [ ] Step 25: Replace Statement-based declaration calls in bytecode.
|
||||||
|
- [x] Add bytecode const/op for enum declarations (no `Statement` objects in constants).
|
||||||
|
- [x] Add bytecode const/op for class declarations (no `Statement` objects in constants).
|
||||||
|
- [x] Add bytecode const/op for function declarations (no `Statement` objects in constants).
|
||||||
|
- [x] Replace `emitStatementCall` usage for `EnumDeclStatement`.
|
||||||
|
- [x] Replace `emitStatementCall` usage for `ClassDeclStatement`.
|
||||||
|
- [x] Replace `emitStatementCall` usage for `FunctionDeclStatement`.
|
||||||
|
- [x] Add JVM disasm coverage to ensure module init has no `CALL_SLOT` to `Callable@...` for declarations.
|
||||||
|
- [ ] Step 26: Bytecode-backed lambdas (remove `ValueFnRef` runtime execution).
|
||||||
|
- [x] Compile lambda bodies to bytecode and emit an opcode to create a callable from bytecode + capture plan.
|
||||||
|
- [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.
|
||||||
|
- [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.
|
||||||
|
- [x] Remove `emitStatementCall`/`emitStatementEval` once unused.
|
||||||
|
- [ ] Step 28: Scope as facade only.
|
||||||
|
- [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.
|
||||||
|
|
||||||
|
## Interpreter Removal v2 (2026-02-12)
|
||||||
|
|
||||||
|
Goal: remove interpreter execution paths entirely without regressing semantics; all runtime execution must be bytecode + frame slots.
|
||||||
|
|
||||||
|
- [ ] Step A1: Interpreter path audit (map all remaining runtime `Statement.execute` and scope-lookup paths).
|
||||||
|
- [x] Audit results (current hotspots):
|
||||||
|
- `ClassDeclStatement.executeClassDecl`: executes class body/init via `spec.bodyInit?.execute` and `spec.initScope` (Statement list).
|
||||||
|
- `FunctionDeclStatement.executeFunctionDecl`: class-body delegated function init uses `Statement.execute` in initializer thunk.
|
||||||
|
- `EnumDeclStatement.execute`: direct execution path exists (bytecode also emits DECL_ENUM).
|
||||||
|
- `BlockStatement.execute`: creates child scope, applies slot plan/captures, then executes `Script` (which may interpret).
|
||||||
|
- `Script.execute`: interpreter loop when `moduleBytecode` is null or disabled.
|
||||||
|
- `ObjClass.addFn/addProperty` wrappers use `ObjNativeCallable` calling into `Statement.execute` for declared bodies.
|
||||||
|
- Object expressions: class body executed via `executeClassDecl` path (same as above).
|
||||||
|
- Extension wrappers: `ObjExtensionMethodCallable` uses callable that executes `Statement`.
|
||||||
|
|
||||||
|
- [ ] Step A2: Bytecode-backed class + init.
|
||||||
|
- Replace class-body and instance init execution with bytecode functions (per-class + per-instance).
|
||||||
|
- Remove all class init `Statement.execute` calls.
|
||||||
|
- [x] Introduce `ClassStaticFieldInitStatement` + bytecode ops `DECL_CLASS_FIELD`/`DECL_CLASS_DELEGATED` for static class field init.
|
||||||
|
|
||||||
|
- [ ] Step A3: Bytecode-safe delegated properties/functions and object expressions.
|
||||||
|
- Use explicit `object : Statement()` where needed.
|
||||||
|
- No inline suspend lambdas in hot paths.
|
||||||
|
- Remove interpreter fallbacks.
|
||||||
|
|
||||||
|
- [ ] Step A4: Bytecode for all blocks/lambdas (including class bodies).
|
||||||
|
- Compile non-module blocks/lambdas to bytecode; eliminate interpreter gate flags.
|
||||||
|
|
||||||
|
- [ ] Step A5: Delete interpreter execution path and dead code.
|
||||||
|
- Remove interpreter ops/constants and any runtime name-lookup fallbacks.
|
||||||
|
- Full test suite green.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Keep imports bound to module frame slots; no scope map writes for imports.
|
||||||
|
- Avoid inline suspend lambdas in compiler hot paths; use explicit `object : Statement()`.
|
||||||
|
- Do not reintroduce bytecode fallback opcodes; all symbol resolution remains compile-time only.
|
||||||
12
notes/archive/bytecode_migration_plan_completed.md
Normal file
12
notes/archive/bytecode_migration_plan_completed.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Bytecode Frame-First Migration Plan (Completed)
|
||||||
|
|
||||||
|
Status: completed (archived for historical reference)
|
||||||
|
|
||||||
|
Original goals
|
||||||
|
- Migrate bytecode to frame-first locals with lazy scope reflection; avoid eager scope slot plans in compiled functions.
|
||||||
|
- Use FrameSlotRef for captures and only materialize Scope for Kotlin interop.
|
||||||
|
- Avoid PUSH_SCOPE/POP_SCOPE in bytecode for loops/functions unless dynamic name access or Kotlin reflection is requested.
|
||||||
|
|
||||||
|
Notes
|
||||||
|
- This plan is now considered done at the architectural level; see current work items in other notes.
|
||||||
|
- Scope slots are still present for interop in the current codebase; removal is tracked separately.
|
||||||
94
notes/plan_scope_slots_elimination.md
Normal file
94
notes/plan_scope_slots_elimination.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Plan: Eliminate Scope Slots for Module + Extensions (Frame-Backed Only)
|
||||||
|
|
||||||
|
Goal (1 + 2)
|
||||||
|
- Module-level vars must be frame-backed just like locals; no special scope storage.
|
||||||
|
- Extension wrappers must use frame slots as well; no scope-slot storage.
|
||||||
|
- Scope remains only as a facade for Kotlin extern symbols (reflection/interop), not as primary storage.
|
||||||
|
|
||||||
|
Non-Goals (for now)
|
||||||
|
- Full reflection redesign (set aside).
|
||||||
|
- Removing scope slots entirely (interop/reflection may still need a facade).
|
||||||
|
|
||||||
|
Current Findings (high level)
|
||||||
|
- BytecodeCompiler still builds scope slots for module symbols and for extension wrappers via pendingScopeNameRefs.
|
||||||
|
- CmdRuntime reads/writes scope slots whenever slot < scopeSlotCount.
|
||||||
|
- RESOLVE_SCOPE_SLOT + addr slots are emitted for moving between scope and frame slots.
|
||||||
|
|
||||||
|
Design Direction
|
||||||
|
- Frame slots are the sole storage for module vars and extension wrappers.
|
||||||
|
- Scope slots are reserved for explicit extern/interop names only (not part of this change).
|
||||||
|
- Module symbols are captured like any other (closure capture via FrameSlotRef), no module slot plan or scope slot mapping.
|
||||||
|
- Persist the module frame inside ModuleScope so its lifetime matches the module.
|
||||||
|
|
||||||
|
Key Decisions
|
||||||
|
- Module scope is just another frame in the parent chain; do not seed or persist module vars through scope slots.
|
||||||
|
- Extension wrappers are treated like ordinary locals/globals in frame slots; no pending scope name refs.
|
||||||
|
- Compatibility: when `seedScope` is not a `ModuleScope`, module-level declarations still map to scope slots so `Scope.eval(...)` retains cross-call variables.
|
||||||
|
|
||||||
|
Work Plan
|
||||||
|
|
||||||
|
Phase A: Compiler changes (scope slot creation)
|
||||||
|
1) Stop adding module declarations to scope slots
|
||||||
|
- In `BytecodeCompiler.collectScopeSlots`, when `moduleScopeId` matches a declaration, treat it as a local slot, not a scope slot.
|
||||||
|
- Remove `isModuleDecl` special-casing that pushes into `scopeSlotMap`.
|
||||||
|
|
||||||
|
2) Remove synthetic scope slots for extension wrappers
|
||||||
|
- Gate `pendingScopeNameRefs` (extension callable/getter/setter queueing) behind explicit scope-slot usage.
|
||||||
|
- Ensure extension wrappers are resolved via local slot indices only (frame-backed).
|
||||||
|
|
||||||
|
3) Tighten scope-slot eligibility
|
||||||
|
- `isModuleSlot(...)` should no longer route module names to scope slots.
|
||||||
|
- Only names in `allowedScopeNames` / `scopeSlotNameSet` (extern/interop) should become scope slots.
|
||||||
|
|
||||||
|
4) ResolveSlot prefers frame slots for everything
|
||||||
|
- Update `resolveSlot` to pick local slots for module/extension names.
|
||||||
|
- Only return scopeSlotMap entries for explicit extern/interop names.
|
||||||
|
|
||||||
|
Phase B: Runtime changes (module frame)
|
||||||
|
1) Persist the module frame inside ModuleScope
|
||||||
|
- Add `ModuleScope.ensureModuleFrame(fn)` that creates/reuses a `BytecodeFrame` with `fn.localCount`.
|
||||||
|
- Store local slot metadata on ModuleScope for future interop (names/mutables/delegated).
|
||||||
|
|
||||||
|
2) Use the module frame when executing the module bytecode
|
||||||
|
- `CmdFrame` should reuse the module frame when `scope0 is ModuleScope` and args are empty.
|
||||||
|
- Avoid clobbering module locals with args.
|
||||||
|
|
||||||
|
3) Remove module seed path that depends on scope slots
|
||||||
|
- Remove `moduleSlotPlan` application in `Script.execute`.
|
||||||
|
- Keep `seedImportBindings` and `seedModuleLocals`, but write into the module frame (scopeSlotCount should be 0).
|
||||||
|
|
||||||
|
Phase C: Bytecode + ABI considerations
|
||||||
|
1) No new opcodes required for (1) + (2)
|
||||||
|
- If we later need persistence across module reloads, introduce explicit sync opcodes (requires approval).
|
||||||
|
|
||||||
|
2) Migration safety
|
||||||
|
- Keep existing scope-slot ops for interop only; do not delete opcodes yet.
|
||||||
|
|
||||||
|
Detailed Change List (by file)
|
||||||
|
- `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt`
|
||||||
|
- `collectScopeSlots`: treat module declarations as locals; do not map into `scopeSlotMap`.
|
||||||
|
- Remove/guard `pendingScopeNameRefs` queuing for extension wrappers.
|
||||||
|
- `isModuleSlot`: stop routing by moduleScopeId; only allow for explicit interop names.
|
||||||
|
- `resolveSlot`: prefer local slots; avoid scope slot fallback for module/extension names.
|
||||||
|
|
||||||
|
- `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`
|
||||||
|
- Remove module slot plan application that assumes scope slots.
|
||||||
|
- Ensure module vars survive via frame capture rather than scope seeding.
|
||||||
|
|
||||||
|
- `lynglib/src/commonMain/kotlin/net/sergeych/lyng/ModuleScope.kt`
|
||||||
|
- Add module frame storage + metadata and `ensureModuleFrame(fn)`.
|
||||||
|
|
||||||
|
- `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt`
|
||||||
|
- Use module frame when `scope0 is ModuleScope` in `CmdFrame`.
|
||||||
|
|
||||||
|
Testing / Validation
|
||||||
|
- Disassemble compiled modules: verify no `RESOLVE_SCOPE_SLOT` for module vars or extension wrappers.
|
||||||
|
- Run `:lynglib:jvmTest` and at least one module script execution that references module vars across calls.
|
||||||
|
- Verify extension call wrappers still resolve and invoke correctly without scope-slot backing.
|
||||||
|
|
||||||
|
Risks / Open Questions
|
||||||
|
- Module persistence: if module vars were previously “persisted” via scope slots, confirm frame-backed storage matches desired lifetime.
|
||||||
|
- Extension wrapper lookup: ensure name resolution does not rely on scope slot indexing anywhere else.
|
||||||
|
|
||||||
|
Approval Gates
|
||||||
|
- Any new opcode or runtime sync step must be proposed separately and approved before implementation.
|
||||||
Loading…
x
Reference in New Issue
Block a user