Step 25: emit DECL_EXEC for declarations

This commit is contained in:
Sergey Chernov 2026-02-09 22:00:46 +03:00
parent 8314127fdb
commit 0caa9849cf
12 changed files with 105 additions and 19 deletions

View File

@ -19,18 +19,18 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
class ClassDeclStatement( class ClassDeclStatement(
private val delegate: Statement, val executable: DeclExecutable,
private val startPos: Pos, private val startPos: Pos,
val declaredName: String?, val declaredName: String?,
) : Statement() { ) : Statement() {
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope) return executable.execute(scope)
} }
override suspend fun callOn(scope: Scope): Obj { override suspend fun callOn(scope: Scope): Obj {
val target = scope.parent ?: scope val target = scope.parent ?: scope
return delegate.execute(target) return executable.execute(target)
} }
} }

View File

@ -5784,7 +5784,7 @@ class Compiler(
return enumClass return enumClass
} }
} }
return EnumDeclStatement(enumDeclStatement, stmtPos) return EnumDeclStatement(StatementDeclExecutable(enumDeclStatement), stmtPos)
} }
private suspend fun parseObjectDeclaration(isExtern: Boolean = false): Statement { private suspend fun parseObjectDeclaration(isExtern: Boolean = false): Statement {
@ -5965,7 +5965,7 @@ class Compiler(
return instance return instance
} }
} }
return ClassDeclStatement(declStatement, startPos, className) return ClassDeclStatement(StatementDeclExecutable(declStatement), startPos, className)
} }
private suspend fun parseClassDeclaration(isAbstract: Boolean = false, isExtern: Boolean = false): Statement { private suspend fun parseClassDeclaration(isAbstract: Boolean = false, isExtern: Boolean = false): Statement {
@ -6372,7 +6372,7 @@ class Compiler(
return newClass return newClass
} }
} }
ClassDeclStatement(classDeclStatement, startPos, qualifiedName) ClassDeclStatement(StatementDeclExecutable(classDeclStatement), startPos, qualifiedName)
} }
} }
@ -7186,7 +7186,7 @@ class Compiler(
return annotatedFnBody return annotatedFnBody
} }
} }
val declaredFn = FunctionDeclStatement(fnCreateStatement, start) val declaredFn = FunctionDeclStatement(StatementDeclExecutable(fnCreateStatement), start)
if (isStatic) { if (isStatic) {
currentInitScope += declaredFn currentInitScope += declaredFn
NopStatement NopStatement

View File

@ -0,0 +1,27 @@
/*
* 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.obj.Obj
interface DeclExecutable {
suspend fun execute(scope: Scope): Obj
}
class StatementDeclExecutable(private val delegate: Statement) : DeclExecutable {
override suspend fun execute(scope: Scope): Obj = delegate.execute(scope)
}

View File

@ -19,17 +19,17 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
class EnumDeclStatement( class EnumDeclStatement(
private val delegate: Statement, val executable: DeclExecutable,
private val startPos: Pos, private val startPos: Pos,
) : Statement() { ) : Statement() {
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope) return executable.execute(scope)
} }
override suspend fun callOn(scope: Scope): Obj { override suspend fun callOn(scope: Scope): Obj {
val target = scope.parent ?: scope val target = scope.parent ?: scope
return delegate.execute(target) return executable.execute(target)
} }
} }

View File

@ -19,17 +19,17 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
class FunctionDeclStatement( class FunctionDeclStatement(
private val delegate: Statement, val executable: DeclExecutable,
private val startPos: Pos, private val startPos: Pos,
) : Statement() { ) : Statement() {
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
return delegate.execute(scope) return executable.execute(scope)
} }
override suspend fun callOn(scope: Scope): Obj { override suspend fun callOn(scope: Scope): Obj {
val target = scope.parent ?: scope val target = scope.parent ?: scope
return delegate.execute(target) return executable.execute(target)
} }
} }

View File

@ -4077,6 +4077,23 @@ class BytecodeCompiler(
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
private fun emitDeclExec(stmt: Statement): CompiledValue {
val executable = when (stmt) {
is net.sergeych.lyng.ClassDeclStatement -> stmt.executable
is net.sergeych.lyng.FunctionDeclStatement -> stmt.executable
is net.sergeych.lyng.EnumDeclStatement -> stmt.executable
else -> throw BytecodeCompileException(
"Bytecode compile error: unsupported declaration ${stmt::class.simpleName}",
stmt.pos
)
}
val constId = builder.addConst(BytecodeConst.DeclExec(executable))
val dst = allocSlot()
builder.emit(Opcode.DECL_EXEC, constId, dst)
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? { private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
val target = if (stmt is BytecodeStatement) stmt.original else stmt val target = if (stmt is BytecodeStatement) stmt.original else stmt
setPos(target.pos) setPos(target.pos)
@ -4110,9 +4127,9 @@ class BytecodeCompiler(
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target) is DestructuringVarDeclStatement -> emitDestructuringVarDecl(target)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target) is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target) is net.sergeych.lyng.ClassDeclStatement -> emitDeclExec(target)
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target) is net.sergeych.lyng.FunctionDeclStatement -> emitDeclExec(target)
is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target) is net.sergeych.lyng.EnumDeclStatement -> emitDeclExec(target)
is net.sergeych.lyng.TryStatement -> emitTry(target, true) is net.sergeych.lyng.TryStatement -> emitTry(target, true)
is net.sergeych.lyng.WhenStatement -> compileWhen(target, true) is net.sergeych.lyng.WhenStatement -> compileWhen(target, true)
is net.sergeych.lyng.BreakStatement -> compileBreak(target) is net.sergeych.lyng.BreakStatement -> compileBreak(target)
@ -4143,9 +4160,9 @@ class BytecodeCompiler(
is VarDeclStatement -> emitVarDecl(target) is VarDeclStatement -> emitVarDecl(target)
is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target) is DelegatedVarDeclStatement -> emitDelegatedVarDecl(target)
is IfStatement -> compileIfStatement(target) is IfStatement -> compileIfStatement(target)
is net.sergeych.lyng.ClassDeclStatement -> emitStatementCall(target) is net.sergeych.lyng.ClassDeclStatement -> emitDeclExec(target)
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementCall(target) is net.sergeych.lyng.FunctionDeclStatement -> emitDeclExec(target)
is net.sergeych.lyng.EnumDeclStatement -> emitStatementCall(target) is net.sergeych.lyng.EnumDeclStatement -> emitDeclExec(target)
is net.sergeych.lyng.ForInStatement -> { is net.sergeych.lyng.ForInStatement -> {
val resultSlot = emitForIn(target, false) ?: return null val resultSlot = emitForIn(target, false) ?: return null
CompiledValue(resultSlot, SlotType.OBJ) CompiledValue(resultSlot, SlotType.OBJ)

View File

@ -34,6 +34,7 @@ sealed class BytecodeConst {
data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst() data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst()
data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst() data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst()
data class ValueFn(val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord) : BytecodeConst() data class ValueFn(val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord) : BytecodeConst()
data class DeclExec(val executable: net.sergeych.lyng.DeclExecutable) : BytecodeConst()
data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst() data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst()
data class ExtensionPropertyDecl( data class ExtensionPropertyDecl(
val extTypeName: String, val extTypeName: String,

View File

@ -157,6 +157,7 @@ class CmdBuilder {
Opcode.PUSH_TRY -> Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
Opcode.DECL_EXEC,
Opcode.ASSIGN_DESTRUCTURE -> Opcode.ASSIGN_DESTRUCTURE ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
@ -409,6 +410,7 @@ class CmdBuilder {
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1]) Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1]) Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1]) Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
Opcode.DECL_EXEC -> CmdDeclExec(operands[0], operands[1])
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1]) Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3]) Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1]) Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1])

View File

@ -194,6 +194,7 @@ object CmdDisassembler {
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot) is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot) is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclExec -> Opcode.DECL_EXEC to intArrayOf(cmd.constId, cmd.slot)
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot) is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot) is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
@ -265,6 +266,7 @@ object CmdDisassembler {
Opcode.PUSH_TRY -> Opcode.PUSH_TRY ->
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP) listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE, Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
Opcode.DECL_EXEC,
Opcode.ASSIGN_DESTRUCTURE -> Opcode.ASSIGN_DESTRUCTURE ->
listOf(OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT, Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,

View File

@ -1314,6 +1314,22 @@ class CmdDeclDelegated(internal val constId: Int, internal val slot: Int) : Cmd(
} }
} }
class CmdDeclExec(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope(useRefs = true)
}
val decl = frame.fn.constants[constId] as? BytecodeConst.DeclExec
?: error("DECL_EXEC expects DeclExec at $constId")
val result = decl.executable.execute(frame.ensureScope())
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
frame.storeObjResult(slot, result)
return
}
}
class CmdDeclDestructure(internal val constId: Int, internal val slot: Int) : Cmd() { class CmdDeclDestructure(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.DestructureDecl val decl = frame.fn.constants[constId] as? BytecodeConst.DestructureDecl

View File

@ -158,6 +158,7 @@ enum class Opcode(val code: Int) {
STORE_BOOL_ADDR(0xB9), STORE_BOOL_ADDR(0xB9),
THROW(0xBB), THROW(0xBB),
RETHROW_PENDING(0xBC), RETHROW_PENDING(0xBC),
DECL_EXEC(0xBD),
ITER_PUSH(0xBF), ITER_PUSH(0xBF),
ITER_POP(0xC0), ITER_POP(0xC0),
ITER_CANCEL(0xC1), ITER_CANCEL(0xC1),

View File

@ -24,10 +24,13 @@ import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.Source import net.sergeych.lyng.Source
import net.sergeych.lyng.eval import net.sergeych.lyng.eval
import net.sergeych.lyng.toSource import net.sergeych.lyng.toSource
import net.sergeych.lyng.bytecode.CmdDisassembler
import net.sergeych.lyng.bytecode.CmdFunction
import net.sergeych.lyng.obj.toInt import net.sergeych.lyng.obj.toInt
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
class BytecodeRecentOpsTest { class BytecodeRecentOpsTest {
@ -243,6 +246,23 @@ class BytecodeRecentOpsTest {
assertTrue(disasm.contains("DECL_DELEGATED"), disasm) assertTrue(disasm.contains("DECL_DELEGATED"), disasm)
} }
@Test
fun moduleDeclsAvoidCallableCallSlots() = runTest {
val script = """
class A {}
fun f() { 1 }
enum E { one }
""".trimIndent()
val compiled = Compiler.compile(script.toSource(), Script.defaultImportManager)
val field = Script::class.java.getDeclaredField("moduleBytecode")
field.isAccessible = true
val moduleFn = field.get(compiled) as? CmdFunction
assertNotNull(moduleFn, "module bytecode missing")
val disasm = CmdDisassembler.disassemble(moduleFn)
assertTrue(!disasm.contains("CALL_SLOT"), disasm)
assertTrue(disasm.contains("DECL_EXEC"), disasm)
}
@Test @Test
fun unionMemberDispatchSubtype() = runTest { fun unionMemberDispatchSubtype() = runTest {
eval( eval(