Add bytecode opcode for statement eval

This commit is contained in:
Sergey Chernov 2026-01-28 22:17:08 +03:00
parent 9a15470cdb
commit 951ce989a6
6 changed files with 428 additions and 34 deletions

View File

@ -40,11 +40,14 @@ class BytecodeCompiler(
private var scopeSlotNames = emptyArray<String?>()
private val scopeSlotMap = LinkedHashMap<ScopeSlotKey, Int>()
private val scopeSlotNameMap = LinkedHashMap<ScopeSlotKey, String>()
private val scopeSlotIndexByName = LinkedHashMap<String, Int>()
private val pendingScopeNameRefs = LinkedHashSet<String>()
private val addrSlotByScopeSlot = LinkedHashMap<Int, Int>()
private data class LocalSlotInfo(val name: String, val isMutable: Boolean, val depth: Int)
private val localSlotInfoMap = LinkedHashMap<ScopeSlotKey, LocalSlotInfo>()
private val localSlotIndexByKey = LinkedHashMap<ScopeSlotKey, Int>()
private val localSlotIndexByName = LinkedHashMap<String, Int>()
private val loopSlotOverrides = LinkedHashMap<String, Int>()
private var localSlotNames = emptyArray<String?>()
private var localSlotMutables = BooleanArray(0)
private var localSlotDepths = IntArray(0)
@ -70,12 +73,54 @@ class BytecodeCompiler(
is net.sergeych.lyng.IfStatement -> compileIf(name, stmt)
is net.sergeych.lyng.ForInStatement -> compileForIn(name, stmt)
is net.sergeych.lyng.DoWhileStatement -> compileDoWhile(name, stmt)
is net.sergeych.lyng.WhileStatement -> compileWhile(name, stmt)
is BlockStatement -> compileBlock(name, stmt)
is VarDeclStatement -> compileVarDecl(name, stmt)
is net.sergeych.lyng.ThrowStatement -> compileThrowStatement(name, stmt)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> compileExtensionPropertyDecl(name, stmt)
else -> null
}
}
private fun compileThrowStatement(name: String, stmt: net.sergeych.lyng.ThrowStatement): CmdFunction? {
prepareCompilation(stmt)
compileThrow(stmt) ?: return null
return builder.build(
name,
localCount = nextSlot - scopeSlotCount,
addrCount = nextAddrSlot,
returnLabels = returnLabels,
scopeSlotDepths,
scopeSlotIndices,
scopeSlotNames,
localSlotNames,
localSlotMutables,
localSlotDepths
)
}
private fun compileExtensionPropertyDecl(
name: String,
stmt: net.sergeych.lyng.ExtensionPropertyDeclStatement,
): CmdFunction? {
prepareCompilation(stmt)
val value = emitExtensionPropertyDecl(stmt)
builder.emit(Opcode.RET, value.slot)
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
return builder.build(
name,
localCount,
addrCount = nextAddrSlot,
returnLabels = returnLabels,
scopeSlotDepths,
scopeSlotIndices,
scopeSlotNames,
localSlotNames,
localSlotMutables,
localSlotDepths
)
}
fun compileExpression(name: String, stmt: ExpressionStatement): CmdFunction? {
prepareCompilation(stmt)
val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null
@ -99,6 +144,14 @@ class BytecodeCompiler(
private fun allocSlot(): Int = nextSlot++
private fun compileNameLookup(name: String): CompiledValue {
val nameId = builder.addConst(BytecodeConst.StringVal(name))
val slot = allocSlot()
builder.emit(Opcode.GET_NAME, nameId, slot)
updateSlotType(slot, SlotType.OBJ)
return CompiledValue(slot, SlotType.OBJ)
}
private fun compileRef(ref: ObjRef): CompiledValue? {
return when (ref) {
is ConstRef -> compileConst(ref.constValue)
@ -106,7 +159,7 @@ class BytecodeCompiler(
if (!allowLocalSlots) return null
if (ref.isDelegated) return null
if (ref.name.isEmpty()) return null
val mapped = resolveSlot(ref) ?: return null
val mapped = resolveSlot(ref) ?: return compileNameLookup(ref.name)
var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
updateSlotType(mapped, SlotType.INT)
@ -121,16 +174,30 @@ class BytecodeCompiler(
}
CompiledValue(mapped, resolved)
}
is BinaryOpRef -> compileBinary(ref)
is LocalVarRef -> compileNameLookup(ref.name)
is ValueFnRef -> compileEvalRef(ref)
is ListLiteralRef -> compileEvalRef(ref)
is ThisMethodSlotCallRef -> compileEvalRef(ref)
is StatementRef -> {
val constId = builder.addConst(BytecodeConst.StatementVal(ref.statement))
val slot = allocSlot()
builder.emit(Opcode.EVAL_STMT, constId, slot)
updateSlotType(slot, SlotType.OBJ)
CompiledValue(slot, SlotType.OBJ)
}
is BinaryOpRef -> compileBinary(ref) ?: compileEvalRef(ref)
is UnaryOpRef -> compileUnary(ref)
is AssignRef -> compileAssign(ref)
is AssignOpRef -> compileAssignOp(ref)
is AssignRef -> compileAssign(ref) ?: compileEvalRef(ref)
is AssignOpRef -> compileAssignOp(ref) ?: compileEvalRef(ref)
is AssignIfNullRef -> compileAssignIfNull(ref)
is IncDecRef -> compileIncDec(ref, true)
is ConditionalRef -> compileConditional(ref)
is ElvisRef -> compileElvis(ref)
is CallRef -> compileCall(ref)
is MethodCallRef -> compileMethodCall(ref)
is FieldRef -> compileFieldRef(ref)
is ImplicitThisMemberRef -> compileEvalRef(ref)
is ImplicitThisMethodCallRef -> compileEvalRef(ref)
is IndexRef -> compileIndexRef(ref)
else -> null
}
@ -171,6 +238,14 @@ class BytecodeCompiler(
}
}
private fun compileEvalRef(ref: ObjRef): CompiledValue? {
val slot = allocSlot()
val id = builder.addConst(BytecodeConst.Ref(ref))
builder.emit(Opcode.EVAL_REF, id, slot)
updateSlotType(slot, SlotType.OBJ)
return CompiledValue(slot, SlotType.OBJ)
}
private fun compileUnary(ref: UnaryOpRef): CompiledValue? {
val a = compileRef(unaryOperand(ref)) ?: return null
val out = allocSlot()
@ -187,8 +262,21 @@ class BytecodeCompiler(
else -> null
}
UnaryOp.NOT -> {
if (a.type != SlotType.BOOL) return null
builder.emit(Opcode.NOT_BOOL, a.slot, out)
when (a.type) {
SlotType.BOOL -> builder.emit(Opcode.NOT_BOOL, a.slot, out)
SlotType.INT -> {
val tmp = allocSlot()
builder.emit(Opcode.INT_TO_BOOL, a.slot, tmp)
builder.emit(Opcode.NOT_BOOL, tmp, out)
}
SlotType.OBJ, SlotType.UNKNOWN -> {
val obj = ensureObjSlot(a)
val tmp = allocSlot()
builder.emit(Opcode.OBJ_TO_BOOL, obj.slot, tmp)
builder.emit(Opcode.NOT_BOOL, tmp, out)
}
else -> return null
}
CompiledValue(out, SlotType.BOOL)
}
UnaryOp.BITNOT -> {
@ -790,11 +878,12 @@ class BytecodeCompiler(
private fun compileAssignOp(ref: AssignOpRef): CompiledValue? {
val localTarget = ref.target as? LocalSlotRef
if (localTarget != null) {
if (!allowLocalSlots) return null
if (!localTarget.isMutable || localTarget.isDelegated) return null
if (!allowLocalSlots) return compileEvalRef(ref)
if (localTarget.isDelegated) return compileEvalRef(ref)
if (!localTarget.isMutable) return compileEvalRef(ref)
val slot = resolveSlot(localTarget) ?: return null
val targetType = slotTypes[slot] ?: SlotType.OBJ
var rhs = compileRef(ref.value) ?: return null
var rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
if (targetType == SlotType.OBJ && rhs.type != SlotType.OBJ) {
rhs = ensureObjSlot(rhs)
}
@ -826,6 +915,10 @@ class BytecodeCompiler(
updateSlotType(out, result.type)
return CompiledValue(out, result.type)
}
val varTarget = ref.target as? LocalVarRef
if (varTarget != null) {
return compileEvalRef(ref)
}
val objOp = when (ref.op) {
BinOp.PLUS -> Opcode.ADD_OBJ
BinOp.MINUS -> Opcode.SUB_OBJ
@ -833,16 +926,16 @@ class BytecodeCompiler(
BinOp.SLASH -> Opcode.DIV_OBJ
BinOp.PERCENT -> Opcode.MOD_OBJ
else -> null
} ?: return null
} ?: return compileEvalRef(ref)
val fieldTarget = ref.target as? FieldRef
if (fieldTarget != null) {
val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
if (nameId > 0xFFFF) return null
if (nameId > 0xFFFF) return compileEvalRef(ref)
val current = allocSlot()
val result = allocSlot()
if (!fieldTarget.isOptional) {
val rhs = compileRef(ref.value) ?: return null
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
builder.emit(Opcode.GET_FIELD, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, result)
@ -859,13 +952,13 @@ class BytecodeCompiler(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
val rhs = compileRef(ref.value) ?: return null
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
builder.emit(Opcode.GET_FIELD, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, result)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
val rhsNull = compileRef(ref.value) ?: return null
val rhsNull = compileRef(ref.value) ?: return compileEvalRef(ref)
builder.emit(Opcode.CONST_NULL, current)
builder.emit(objOp, current, rhsNull.slot, result)
builder.mark(endLabel)
@ -879,7 +972,7 @@ class BytecodeCompiler(
val result = allocSlot()
if (!indexTarget.optionalRef) {
val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
val rhs = compileRef(ref.value) ?: return null
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result)
@ -897,20 +990,107 @@ class BytecodeCompiler(
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
val rhs = compileRef(ref.value) ?: return null
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
val rhsNull = compileRef(ref.value) ?: return null
val rhsNull = compileRef(ref.value) ?: return compileEvalRef(ref)
builder.emit(Opcode.CONST_NULL, current)
builder.emit(objOp, current, rhsNull.slot, result)
builder.mark(endLabel)
updateSlotType(result, SlotType.OBJ)
return CompiledValue(result, SlotType.OBJ)
}
return null
return compileEvalRef(ref)
}
private fun compileAssignIfNull(ref: AssignIfNullRef): CompiledValue? {
val target = ref.target
val currentValue = compileRefWithFallback(target, null, Pos.builtIn) ?: return null
val currentObj = ensureObjSlot(currentValue)
val resultSlot = allocSlot()
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, currentObj.slot, nullSlot, cmpSlot)
val assignLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(assignLabel))
)
builder.emit(Opcode.MOVE_OBJ, currentObj.slot, resultSlot)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(assignLabel)
val newValue = compileRefWithFallback(ref.value, null, Pos.builtIn) ?: return null
when (target) {
is LocalSlotRef -> {
if (!allowLocalSlots || !target.isMutable || target.isDelegated) return null
val slot = resolveSlot(target) ?: return null
if (slot < scopeSlotCount) {
val addrSlot = ensureScopeAddr(slot)
val storeType = if (newValue.type == SlotType.UNKNOWN) SlotType.OBJ else newValue.type
emitStoreToAddr(newValue.slot, addrSlot, storeType)
} else {
when (newValue.type) {
SlotType.INT -> builder.emit(Opcode.MOVE_INT, newValue.slot, slot)
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, newValue.slot, slot)
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, newValue.slot, slot)
else -> builder.emit(Opcode.MOVE_OBJ, newValue.slot, slot)
}
}
updateSlotType(slot, newValue.type)
}
is FieldRef -> {
val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
if (nameId > 0xFFFF) return null
if (!target.isOptional) {
builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, newValue.slot)
} else {
val recvNull = allocSlot()
builder.emit(Opcode.CONST_NULL, recvNull)
val recvCmp = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
val skipLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
)
builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, newValue.slot)
builder.mark(skipLabel)
}
}
is IndexRef -> {
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
if (!target.optionalRef) {
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot)
} else {
val recvNull = allocSlot()
builder.emit(Opcode.CONST_NULL, recvNull)
val recvCmp = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, recvNull, recvCmp)
val skipLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
)
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot)
builder.mark(skipLabel)
}
}
else -> return null
}
val newObj = ensureObjSlot(newValue)
builder.emit(Opcode.MOVE_OBJ, newObj.slot, resultSlot)
builder.mark(endLabel)
updateSlotType(resultSlot, SlotType.OBJ)
return CompiledValue(resultSlot, SlotType.OBJ)
}
private fun compileFieldRef(ref: FieldRef): CompiledValue? {
@ -1347,7 +1527,12 @@ class BytecodeCompiler(
}
private fun compileIf(name: String, stmt: IfStatement): CmdFunction? {
val conditionStmt = stmt.condition as? ExpressionStatement ?: return null
val conditionTarget = if (stmt.condition is BytecodeStatement) {
stmt.condition.original
} else {
stmt.condition
}
val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
val condValue = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
if (condValue.type != SlotType.BOOL) return null
@ -1359,13 +1544,13 @@ class BytecodeCompiler(
Opcode.JMP_IF_FALSE,
listOf(CmdBuilder.Operand.IntVal(condValue.slot), CmdBuilder.Operand.LabelRef(elseLabel))
)
val thenValue = compileStatementValue(stmt.ifBody) ?: return null
val thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null
emitMove(thenValue, resultSlot)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(elseLabel)
if (stmt.elseBody != null) {
val elseValue = compileStatementValue(stmt.elseBody) ?: return null
val elseValue = compileStatementValueOrFallback(stmt.elseBody) ?: return null
emitMove(elseValue, resultSlot)
} else {
val id = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
@ -1524,6 +1709,7 @@ class BytecodeCompiler(
}
is BlockStatement -> emitBlock(target, true)
is VarDeclStatement -> emitVarDecl(target)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
is net.sergeych.lyng.BreakStatement -> compileBreak(target)
is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
@ -1563,6 +1749,7 @@ class BytecodeCompiler(
}
is BlockStatement -> emitBlock(target, false)
is VarDeclStatement -> emitVarDecl(target)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
is net.sergeych.lyng.BreakStatement -> compileBreak(target)
is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
@ -1583,7 +1770,16 @@ class BytecodeCompiler(
for ((index, statement) in statements.withIndex()) {
val isLast = index == statements.lastIndex
val wantResult = needResult && isLast
val value = compileStatementValueOrFallback(statement, wantResult) ?: return null
val value = compileStatementValueOrFallback(statement, wantResult)
?: run {
val original = (statement as? BytecodeStatement)?.original
val name = original?.let { "${statement::class.simpleName}(${it::class.simpleName})" }
?: statement::class.simpleName
throw BytecodeFallbackException(
"Bytecode fallback: failed to compile block statement ($name)",
statement.pos
)
}
if (wantResult) {
lastValue = value
}
@ -1705,9 +1901,26 @@ class BytecodeCompiler(
rangeRef = extractRangeFromLocal(stmt.source)
}
val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
val loopLocalIndex = localSlotIndexByName[stmt.loopVarName] ?: return null
val loopSlotId = scopeSlotCount + loopLocalIndex
val loopLocalIndex = localSlotIndexByName[stmt.loopVarName]
var usedOverride = false
val loopSlotId = when {
loopLocalIndex != null -> scopeSlotCount + loopLocalIndex
else -> {
val localKey = localSlotInfoMap.entries.firstOrNull { it.value.name == stmt.loopVarName }?.key
val localIndex = localKey?.let { localSlotIndexByKey[it] }
when {
localIndex != null -> scopeSlotCount + localIndex
else -> scopeSlotIndexByName[stmt.loopVarName]
}
}
} ?: run {
val slot = allocSlot()
loopSlotOverrides[stmt.loopVarName] = slot
usedOverride = true
slot
}
try {
if (range == null && rangeRef == null && typedRangeLocal == null) {
val sourceValue = compileStatementValueOrFallback(stmt.source) ?: return null
val sourceObj = ensureObjSlot(sourceValue)
@ -1919,6 +2132,11 @@ class BytecodeCompiler(
builder.mark(afterElse)
}
return resultSlot
} finally {
if (usedOverride) {
loopSlotOverrides.remove(stmt.loopVarName)
}
}
}
private fun emitWhile(stmt: net.sergeych.lyng.WhileStatement, wantResult: Boolean): Int? {
@ -2077,8 +2295,9 @@ class BytecodeCompiler(
}
private fun compileCondition(stmt: Statement, pos: Pos): CompiledValue? {
return when (stmt) {
is ExpressionStatement -> compileRefWithFallback(stmt.ref, SlotType.BOOL, stmt.pos)
val target = if (stmt is BytecodeStatement) stmt.original else stmt
return when (target) {
is ExpressionStatement -> compileRefWithFallback(target.ref, SlotType.BOOL, target.pos)
else -> {
throw BytecodeFallbackException(
"Bytecode fallback: unsupported condition",
@ -2150,6 +2369,23 @@ class BytecodeCompiler(
return objValue
}
private fun emitExtensionPropertyDecl(
stmt: net.sergeych.lyng.ExtensionPropertyDeclStatement
): CompiledValue {
val constId = builder.addConst(
BytecodeConst.ExtensionPropertyDecl(
stmt.extTypeName,
stmt.property,
stmt.visibility,
stmt.setterVisibility
)
)
val slot = allocSlot()
builder.emit(Opcode.DECL_EXT_PROPERTY, constId, slot)
updateSlotType(slot, SlotType.OBJ)
return CompiledValue(slot, SlotType.OBJ)
}
private fun resetAddrCache() {
addrSlotByScopeSlot.clear()
}
@ -2235,8 +2471,20 @@ class BytecodeCompiler(
compiled = null
}
}
val refInfo = when (ref) {
is LocalVarRef -> "LocalVarRef(${ref.name})"
is LocalSlotRef -> "LocalSlotRef(${ref.name})"
is FieldRef -> "FieldRef(${ref.name})"
else -> ref::class.simpleName ?: "UnknownRef"
}
val extra = if (ref is LocalVarRef) {
val names = scopeSlotNameMap.values.joinToString(prefix = "[", postfix = "]")
" scopeSlots=$names"
} else {
""
}
throw BytecodeFallbackException(
"Bytecode fallback: unsupported expression",
"Bytecode fallback: unsupported expression ($refInfo)$extra",
pos
)
}
@ -2254,6 +2502,7 @@ class BytecodeCompiler(
private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn
private fun resolveSlot(ref: LocalSlotRef): Int? {
loopSlotOverrides[ref.name]?.let { return it }
val localKey = ScopeSlotKey(refScopeDepth(ref), refSlot(ref))
val localIndex = localSlotIndexByKey[localKey]
if (localIndex != null) return scopeSlotCount + localIndex
@ -2277,9 +2526,13 @@ class BytecodeCompiler(
nextAddrSlot = 0
slotTypes.clear()
scopeSlotMap.clear()
scopeSlotNameMap.clear()
localSlotInfoMap.clear()
localSlotIndexByKey.clear()
localSlotIndexByName.clear()
loopSlotOverrides.clear()
scopeSlotIndexByName.clear()
pendingScopeNameRefs.clear()
localSlotNames = emptyArray()
localSlotMutables = BooleanArray(0)
localSlotDepths = IntArray(0)
@ -2291,10 +2544,23 @@ class BytecodeCompiler(
virtualScopeDepths.clear()
if (allowLocalSlots) {
collectLoopVarNames(stmt)
}
collectVirtualScopeDepths(stmt, 0)
collectScopeSlots(stmt)
if (allowLocalSlots) {
collectLoopSlotPlans(stmt, 0)
}
if (pendingScopeNameRefs.isNotEmpty()) {
val existingNames = HashSet<String>(scopeSlotNameMap.values)
var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1
for (name in pendingScopeNameRefs) {
if (!existingNames.add(name)) continue
maxSlotIndex += 1
val key = ScopeSlotKey(0, maxSlotIndex)
scopeSlotMap[key] = scopeSlotMap.size
scopeSlotNameMap[key] = name
}
}
scopeSlotCount = scopeSlotMap.size
scopeSlotDepths = IntArray(scopeSlotCount)
scopeSlotIndices = IntArray(scopeSlotCount)
@ -2324,6 +2590,14 @@ class BytecodeCompiler(
localSlotMutables = mutables
localSlotDepths = depths
}
if (scopeSlotCount > 0) {
for ((key, index) in scopeSlotMap) {
val name = scopeSlotNameMap[key] ?: continue
if (!scopeSlotIndexByName.containsKey(name)) {
scopeSlotIndexByName[name] = index
}
}
}
nextSlot = scopeSlotCount + localSlotNames.size
}
@ -2563,6 +2837,7 @@ class BytecodeCompiler(
scopeSlotNameMap[key] = ref.name
}
}
is LocalVarRef -> {}
is BinaryOpRef -> {
collectScopeSlotsRef(binaryLeft(ref))
collectScopeSlotsRef(binaryRight(ref))
@ -2594,6 +2869,10 @@ class BytecodeCompiler(
collectScopeSlotsRef(ref.target)
collectScopeSlotsRef(ref.value)
}
is AssignIfNullRef -> {
collectScopeSlotsRef(ref.target)
collectScopeSlotsRef(ref.value)
}
is IncDecRef -> collectScopeSlotsRef(ref.target)
is ConditionalRef -> {
collectScopeSlotsRef(ref.condition)

View File

@ -19,6 +19,7 @@ package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Visibility
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjProperty
sealed class BytecodeConst {
object Null : BytecodeConst()
@ -28,7 +29,15 @@ sealed class BytecodeConst {
data class StringVal(val value: String) : BytecodeConst()
data class PosVal(val pos: Pos) : BytecodeConst()
data class ObjRef(val value: Obj) : BytecodeConst()
data class Ref(val value: net.sergeych.lyng.obj.ObjRef) : BytecodeConst()
data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst()
data class SlotPlan(val plan: Map<String, Int>) : BytecodeConst()
data class ExtensionPropertyDecl(
val extTypeName: String,
val property: ObjProperty,
val visibility: Visibility,
val setterVisibility: Visibility?,
) : BytecodeConst()
data class LocalDecl(
val name: String,
val isMutable: Boolean,

View File

@ -142,7 +142,7 @@ class CmdBuilder {
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST)
Opcode.DECL_LOCAL ->
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
@ -176,11 +176,13 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_FIELD ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.GET_NAME ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.GET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.EVAL_FALLBACK ->
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT ->
listOf(OperandKind.ID, OperandKind.SLOT)
}
}
@ -359,15 +361,19 @@ class CmdBuilder {
Opcode.PUSH_SLOT_PLAN -> CmdPushSlotPlan(operands[0])
Opcode.POP_SLOT_PLAN -> CmdPopSlotPlan()
Opcode.DECL_LOCAL -> CmdDeclLocal(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_VIRTUAL -> CmdCallVirtual(operands[0], operands[1], operands[2], operands[3], operands[4])
Opcode.CALL_FALLBACK -> CmdCallFallback(operands[0], operands[1], operands[2], operands[3])
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2])
Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2])
Opcode.GET_NAME -> CmdGetName(operands[0], operands[1])
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
Opcode.EVAL_FALLBACK -> CmdEvalFallback(operands[0], operands[1])
Opcode.EVAL_REF -> CmdEvalRef(operands[0], operands[1])
Opcode.EVAL_STMT -> CmdEvalStmt(operands[0], operands[1])
}
}
}

View File

@ -173,15 +173,19 @@ object CmdDisassembler {
is CmdPushSlotPlan -> Opcode.PUSH_SLOT_PLAN to intArrayOf(cmd.planId)
is CmdPopSlotPlan -> Opcode.POP_SLOT_PLAN to intArrayOf()
is CmdDeclLocal -> Opcode.DECL_LOCAL 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 CmdCallVirtual -> Opcode.CALL_VIRTUAL to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallFallback -> Opcode.CALL_FALLBACK to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
is CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst)
is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot)
is CmdGetName -> Opcode.GET_NAME to intArrayOf(cmd.nameId, cmd.dst)
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
is CmdEvalFallback -> Opcode.EVAL_FALLBACK to intArrayOf(cmd.id, cmd.dst)
is CmdEvalRef -> Opcode.EVAL_REF to intArrayOf(cmd.id, cmd.dst)
is CmdEvalStmt -> Opcode.EVAL_STMT to intArrayOf(cmd.id, cmd.dst)
}
}
@ -221,7 +225,7 @@ object CmdDisassembler {
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST)
Opcode.DECL_LOCAL ->
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
@ -255,11 +259,13 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_FIELD ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.GET_NAME ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.GET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.EVAL_FALLBACK ->
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT ->
listOf(OperandKind.ID, OperandKind.SLOT)
}
}

View File

@ -1031,6 +1031,32 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
}
}
class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val decl = frame.fn.constants[constId] as? BytecodeConst.ExtensionPropertyDecl
?: error("DECL_EXT_PROPERTY expects ExtensionPropertyDecl at $constId")
val type = frame.scope[decl.extTypeName]?.value
?: frame.scope.raiseSymbolNotFound("class ${decl.extTypeName} not found")
if (type !is ObjClass) {
frame.scope.raiseClassCastError("${decl.extTypeName} is not the class instance")
}
frame.scope.addExtension(
type,
decl.property.name,
ObjRecord(
decl.property,
isMutable = false,
visibility = decl.visibility,
writeVisibility = decl.setterVisibility,
declaringClass = null,
type = ObjRecord.Type.Property
)
)
frame.setObj(slot, decl.property)
return
}
}
class CmdCallDirect(
internal val id: Int,
internal val argBase: Int,
@ -1120,6 +1146,17 @@ class CmdCallSlot(
frame.syncFrameToScope()
}
val callee = frame.slotToObj(calleeSlot)
if (callee === ObjUnset) {
val name = if (calleeSlot < frame.fn.scopeSlotCount) {
frame.fn.scopeSlotNames[calleeSlot]
} else {
val localIndex = calleeSlot - frame.fn.scopeSlotCount
frame.fn.localSlotNames.getOrNull(localIndex)
}
val message = name?.let { "property '$it' is unset (not initialized)" }
?: "property is unset (not initialized)"
frame.scope.raiseUnset(message)
}
val args = frame.buildArguments(argBase, argCount)
val canPool = PerfFlags.SCOPE_POOL && callee !is Statement
val result = if (canPool) {
@ -1170,6 +1207,22 @@ class CmdGetField(
}
}
class CmdGetName(
internal val nameId: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope()
}
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
?: error("GET_NAME expects StringVal at $nameId")
val result = frame.scope.get(nameConst.value)?.value ?: ObjUnset
frame.storeObjResult(dst, result)
return
}
}
class CmdSetField(
internal val recvSlot: Int,
internal val fieldId: Int,
@ -1238,6 +1291,38 @@ class CmdEvalFallback(internal val id: Int, internal val dst: Int) : Cmd() {
}
}
class CmdEvalRef(internal val id: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope()
}
val ref = frame.fn.constants[id] as? BytecodeConst.Ref
?: error("EVAL_REF expects Ref at $id")
val result = ref.value.evalValue(frame.scope)
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
frame.storeObjResult(dst, result)
return
}
}
class CmdEvalStmt(internal val id: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope()
}
val stmt = frame.fn.constants.getOrNull(id) as? BytecodeConst.StatementVal
?: error("EVAL_STMT expects StatementVal at $id")
val result = stmt.statement.execute(frame.scope)
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
frame.storeObjResult(dst, result)
return
}
}
class CmdFrame(
val vm: CmdVm,
val fn: CmdFunction,
@ -1659,9 +1744,14 @@ class CmdFrame(
}
private fun ensureScopeSlot(target: Scope, slot: Int): Int {
val name = fn.scopeSlotNames[slot]
if (name != null) {
val existing = target.getSlotIndexOf(name)
if (existing != null) return existing
}
val index = fn.scopeSlotIndices[slot]
if (index < target.slotCount) return index
val name = fn.scopeSlotNames[slot] ?: return index
if (name == null) return index
target.applySlotPlan(mapOf(name to index))
val existing = target.getLocalRecordDirect(name)
if (existing != null) {

View File

@ -118,6 +118,7 @@ enum class Opcode(val code: Int) {
PUSH_SLOT_PLAN(0x87),
POP_SLOT_PLAN(0x88),
DECL_LOCAL(0x89),
DECL_EXT_PROPERTY(0x8A),
CALL_DIRECT(0x90),
CALL_VIRTUAL(0x91),
@ -128,6 +129,7 @@ enum class Opcode(val code: Int) {
SET_FIELD(0xA1),
GET_INDEX(0xA2),
SET_INDEX(0xA3),
GET_NAME(0xA4),
EVAL_FALLBACK(0xB0),
RESOLVE_SCOPE_SLOT(0xB1),
@ -140,6 +142,8 @@ enum class Opcode(val code: Int) {
LOAD_BOOL_ADDR(0xB8),
STORE_BOOL_ADDR(0xB9),
THROW(0xBB),
EVAL_REF(0xBC),
EVAL_STMT(0xBD),
;
companion object {