Step 20: bytecode NopStatement

This commit is contained in:
Sergey Chernov 2026-02-09 12:04:51 +03:00
parent ae88898f58
commit b49f291bff
5 changed files with 60 additions and 0 deletions

View File

@ -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] 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 union-member dispatch with ordered type checks and runtime mismatch error.
- [x] Add JVM tests for unknown receiver and union member access. - [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 ## Notes

View File

@ -1934,6 +1934,7 @@ class Compiler(
is ContinueStatement -> false is ContinueStatement -> false
is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false
is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr) is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr)
is NopStatement -> false
is ExtensionPropertyDeclStatement -> false is ExtensionPropertyDeclStatement -> false
is ClassDeclStatement -> false is ClassDeclStatement -> false
is FunctionDeclStatement -> false is FunctionDeclStatement -> false

View File

@ -202,6 +202,23 @@ class BytecodeCompiler(
localSlotMutables 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 else -> null
} }
} }
@ -3920,6 +3937,12 @@ class BytecodeCompiler(
private fun compileStatementValue(stmt: Statement): CompiledValue? { private fun compileStatementValue(stmt: Statement): CompiledValue? {
return when (stmt) { return when (stmt) {
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos) 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 else -> null
} }
} }
@ -3990,6 +4013,12 @@ class BytecodeCompiler(
is net.sergeych.lyng.ReturnStatement -> compileReturn(target) is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
is net.sergeych.lyng.ThrowStatement -> compileThrow(target) is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
is net.sergeych.lyng.TryStatement -> emitTry(target, false) 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 -> { else -> {
emitFallbackStatement(target) emitFallbackStatement(target)
} }
@ -4037,6 +4066,12 @@ class BytecodeCompiler(
is net.sergeych.lyng.ThrowStatement -> compileThrow(target) is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
is net.sergeych.lyng.TryStatement -> emitTry(target, false) is net.sergeych.lyng.TryStatement -> emitTry(target, false)
is net.sergeych.lyng.WhenStatement -> compileWhen(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 -> { else -> {
emitFallbackStatement(target) emitFallbackStatement(target)
} }

View File

@ -132,6 +132,7 @@ class BytecodeStatement private constructor(
target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false
is net.sergeych.lyng.ThrowStatement -> is net.sergeych.lyng.ThrowStatement ->
containsUnsupportedStatement(target.throwExpr) containsUnsupportedStatement(target.throwExpr)
is net.sergeych.lyng.NopStatement -> false
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> false is net.sergeych.lyng.ExtensionPropertyDeclStatement -> false
is net.sergeych.lyng.ClassDeclStatement -> false is net.sergeych.lyng.ClassDeclStatement -> false
is net.sergeych.lyng.FunctionDeclStatement -> false is net.sergeych.lyng.FunctionDeclStatement -> false

View File

@ -209,6 +209,18 @@ class BytecodeRecentOpsTest {
) )
} }
@Test
fun staticMemberDeclNopStatement() = runTest {
eval(
"""
class C {
static fun ping() { 7 }
}
assertEquals(7, C.ping())
""".trimIndent()
)
}
@Test @Test
fun objectReceiverMemberError() = runTest { fun objectReceiverMemberError() = runTest {
val failed = try { val failed = try {