From b49f291bffb560b7ca32d0457989eeebd9e6e8c1 Mon Sep 17 00:00:00 2001 From: sergeych Date: Mon, 9 Feb 2026 12:04:51 +0300 Subject: [PATCH] Step 20: bytecode NopStatement --- bytecode_migration_plan.md | 11 ++++++ .../kotlin/net/sergeych/lyng/Compiler.kt | 1 + .../lyng/bytecode/BytecodeCompiler.kt | 35 +++++++++++++++++++ .../lyng/bytecode/BytecodeStatement.kt | 1 + .../kotlin/BytecodeRecentOpsTest.kt | 12 +++++++ 5 files changed, 60 insertions(+) diff --git a/bytecode_migration_plan.md b/bytecode_migration_plan.md index 12b0415..b6375fd 100644 --- a/bytecode_migration_plan.md +++ b/bytecode_migration_plan.md @@ -66,6 +66,17 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te - [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). +- [ ] Step 21: Union mismatch path in bytecode. + - [ ] Replace `UnionTypeMismatchStatement` branch with a bytecode-compilable throw path (no custom `StatementRef` that blocks bytecode). + - [ ] Add a JVM test that forces the union mismatch at runtime and asserts the error message. +- [ ] Step 22: Delegated local slots in bytecode. + - [ ] Support reads/writes/assign-ops/inc/dec for delegated locals (`LocalSlotRef.isDelegated`) in `BytecodeCompiler`. + - [ ] Remove `containsDelegatedRefs` guard once delegated locals are bytecode-safe. + - [ ] Add JVM tests that use delegated locals inside bytecode-compiled functions. ## Notes diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 1cc1a0a..24b54f6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -1934,6 +1934,7 @@ class Compiler( is ContinueStatement -> false is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr) + is NopStatement -> false is ExtensionPropertyDeclStatement -> false is ClassDeclStatement -> false is FunctionDeclStatement -> false diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index ae0a09a..d779a5a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -202,6 +202,23 @@ class BytecodeCompiler( localSlotMutables ) } + is net.sergeych.lyng.NopStatement -> { + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + val slot = allocSlot() + builder.emit(Opcode.CONST_OBJ, voidId, slot) + builder.emit(Opcode.RET, slot) + builder.build( + name, + localCount = maxOf(nextSlot, slot + 1) - scopeSlotCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotIndices, + scopeSlotNames, + scopeSlotIsModule, + localSlotNames, + localSlotMutables + ) + } else -> null } } @@ -3920,6 +3937,12 @@ class BytecodeCompiler( private fun compileStatementValue(stmt: Statement): CompiledValue? { return when (stmt) { is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos) + is net.sergeych.lyng.NopStatement -> { + val slot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, slot) + CompiledValue(slot, SlotType.OBJ) + } else -> null } } @@ -3990,6 +4013,12 @@ class BytecodeCompiler( is net.sergeych.lyng.ReturnStatement -> compileReturn(target) is net.sergeych.lyng.ThrowStatement -> compileThrow(target) is net.sergeych.lyng.TryStatement -> emitTry(target, false) + is net.sergeych.lyng.NopStatement -> { + val slot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, slot) + CompiledValue(slot, SlotType.OBJ) + } else -> { emitFallbackStatement(target) } @@ -4037,6 +4066,12 @@ class BytecodeCompiler( is net.sergeych.lyng.ThrowStatement -> compileThrow(target) is net.sergeych.lyng.TryStatement -> emitTry(target, false) is net.sergeych.lyng.WhenStatement -> compileWhen(target, false) + is net.sergeych.lyng.NopStatement -> { + val slot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, slot) + CompiledValue(slot, SlotType.OBJ) + } else -> { emitFallbackStatement(target) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt index 05eb0d6..6bf1bf9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt @@ -132,6 +132,7 @@ class BytecodeStatement private constructor( target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false is net.sergeych.lyng.ThrowStatement -> containsUnsupportedStatement(target.throwExpr) + is net.sergeych.lyng.NopStatement -> false is net.sergeych.lyng.ExtensionPropertyDeclStatement -> false is net.sergeych.lyng.ClassDeclStatement -> false is net.sergeych.lyng.FunctionDeclStatement -> false diff --git a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt index afb4be4..89e9bf6 100644 --- a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt +++ b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt @@ -209,6 +209,18 @@ class BytecodeRecentOpsTest { ) } + @Test + fun staticMemberDeclNopStatement() = runTest { + eval( + """ + class C { + static fun ping() { 7 } + } + assertEquals(7, C.ping()) + """.trimIndent() + ) + } + @Test fun objectReceiverMemberError() = runTest { val failed = try {