Compare commits

...

3 Commits

8 changed files with 266 additions and 21 deletions

View File

@ -27,11 +27,11 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
- [x] Replace `MapLiteralEntry.Spread` bytecode exception with runtime `putAll`/merge logic. - [x] Replace `MapLiteralEntry.Spread` bytecode exception with runtime `putAll`/merge logic.
- [x] Step 7: Class-scope member refs in bytecode. - [x] Step 7: Class-scope member refs in bytecode.
- [x] Support `ClassScopeMemberRef` without scope-map fallback. - [x] Support `ClassScopeMemberRef` without scope-map fallback.
- [ ] Step 8: ObjDynamic member access in bytecode. - [x] Step 8: ObjDynamic member access in bytecode.
- [ ] Allow dynamic receiver field/method lookup without falling back to interpreter. - [x] Allow dynamic receiver field/method lookup without falling back to interpreter.
- [ ] Step 9: Module-level bytecode execution. - [x] Step 9: Module-level bytecode execution.
- [ ] Compile `Script` bodies to bytecode instead of interpreting at module scope. - [x] Compile `Script` bodies to bytecode instead of interpreting at module scope.
- [ ] Keep import/module slot seeding in frame-only flow. - [x] Keep import/module slot seeding in frame-only flow.
## Notes ## Notes

View File

@ -1526,25 +1526,24 @@ class Compiler(
resolutionScriptDepth == 1 && resolutionScriptDepth == 1 &&
statements.none { containsUnsupportedForBytecode(it) } && statements.none { containsUnsupportedForBytecode(it) } &&
statements.none { containsDelegatedRefs(it) } statements.none { containsDelegatedRefs(it) }
val finalStatements = if (wrapScriptBytecode) { val (finalStatements, moduleBytecode) = if (wrapScriptBytecode) {
val unwrapped = statements.map { unwrapBytecodeDeep(it) } val unwrapped = statements.map { unwrapBytecodeDeep(it) }
val block = InlineBlockStatement(unwrapped, start) val block = InlineBlockStatement(unwrapped, start)
listOf( val bytecodeStmt = BytecodeStatement.wrap(
BytecodeStatement.wrap( block,
block, "<script>",
"<script>", allowLocalSlots = true,
allowLocalSlots = true, allowedScopeNames = modulePlan.keys,
allowedScopeNames = modulePlan.keys, moduleScopeId = moduleSlotPlan()?.id,
moduleScopeId = moduleSlotPlan()?.id, slotTypeByScopeId = slotTypeByScopeId,
slotTypeByScopeId = slotTypeByScopeId, knownNameObjClass = knownClassMapForBytecode()
knownNameObjClass = knownClassMapForBytecode() ) as BytecodeStatement
) unwrapped to bytecodeStmt.bytecodeFunction()
)
} else { } else {
statements statements to null
} }
val moduleRefs = importedModules.map { ImportBindingSource.Module(it.scope.packageName, it.pos) } val moduleRefs = importedModules.map { ImportBindingSource.Module(it.scope.packageName, it.pos) }
Script(start, finalStatements, modulePlan, importBindings.toMap(), moduleRefs) Script(start, finalStatements, modulePlan, importBindings.toMap(), moduleRefs, moduleBytecode)
}.also { }.also {
// Best-effort script end notification (use current position) // Best-effort script end notification (use current position)
miniSink?.onScriptEnd( miniSink?.onScriptEnd(
@ -1944,7 +1943,6 @@ class Compiler(
is ElvisRef -> containsUnsupportedRef(ref.left) || containsUnsupportedRef(ref.right) is ElvisRef -> containsUnsupportedRef(ref.left) || containsUnsupportedRef(ref.right)
is FieldRef -> { is FieldRef -> {
val receiverClass = resolveReceiverClassForMember(ref.target) ?: return true val receiverClass = resolveReceiverClassForMember(ref.target) ?: return true
if (receiverClass == ObjDynamic.type) return true
val hasMember = receiverClass.instanceFieldIdMap()[ref.name] != null || val hasMember = receiverClass.instanceFieldIdMap()[ref.name] != null ||
receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null
if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true
@ -1975,7 +1973,6 @@ class Compiler(
is MethodCallRef -> { is MethodCallRef -> {
if (ref.name == "delay") return true if (ref.name == "delay") return true
val receiverClass = resolveReceiverClassForMember(ref.receiver) ?: return true val receiverClass = resolveReceiverClassForMember(ref.receiver) ?: return true
if (receiverClass == ObjDynamic.type) return true
val hasMember = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null val hasMember = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] != null
if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true if (!hasMember && !hasExtensionFor(receiverClass.className, ref.name)) return true
containsUnsupportedRef(ref.receiver) || ref.args.any { containsUnsupportedForBytecode(it.value) } containsUnsupportedRef(ref.receiver) || ref.args.any { containsUnsupportedForBytecode(it.value) }

View File

@ -20,6 +20,8 @@ package net.sergeych.lyng
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import net.sergeych.lyng.Script.Companion.defaultImportManager import net.sergeych.lyng.Script.Companion.defaultImportManager
import net.sergeych.lyng.bytecode.CmdFunction
import net.sergeych.lyng.bytecode.CmdVm
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
@ -35,10 +37,12 @@ class Script(
private val moduleSlotPlan: Map<String, Int> = emptyMap(), private val moduleSlotPlan: Map<String, Int> = emptyMap(),
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 catchReturn: Boolean = false, // private val catchReturn: Boolean = false,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
scope.pos = pos
val isModuleScope = scope is ModuleScope val isModuleScope = scope is ModuleScope
val shouldSeedModule = isModuleScope || scope.thisObj === ObjVoid val shouldSeedModule = isModuleScope || scope.thisObj === ObjVoid
val moduleTarget = scope val moduleTarget = scope
@ -79,6 +83,9 @@ class Script(
if (shouldSeedModule) { if (shouldSeedModule) {
seedModuleSlots(moduleTarget) seedModuleSlots(moduleTarget)
} }
moduleBytecode?.let { fn ->
return CmdVm().execute(fn, scope, scope.args.list)
}
var lastResult: Obj = ObjVoid var lastResult: Obj = ObjVoid
for (s in statements) { for (s in statements) {
lastResult = s.execute(scope) lastResult = s.execute(scope)

View File

@ -1670,6 +1670,25 @@ class BytecodeCompiler(
Pos.builtIn Pos.builtIn
) )
val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
if (receiverClass == ObjDynamic.type) {
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
if (!target.isOptional) {
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, value.slot)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, value.slot)
builder.mark(endLabel)
}
return value
}
val fieldId = receiverClass.instanceFieldIdMap()[target.name] val fieldId = receiverClass.instanceFieldIdMap()[target.name]
val methodId = if (fieldId == null) { val methodId = if (fieldId == null) {
receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
@ -1868,6 +1887,24 @@ class BytecodeCompiler(
} ?: return compileEvalRef(ref) } ?: return compileEvalRef(ref)
val fieldTarget = ref.target as? FieldRef val fieldTarget = ref.target as? FieldRef
if (fieldTarget != null) { if (fieldTarget != null) {
if (fieldTarget.isOptional) return compileEvalRef(ref)
val receiverClass = resolveReceiverClass(fieldTarget.target)
?: throw BytecodeCompileException(
"Member assignment requires compile-time receiver type: ${fieldTarget.name}",
Pos.builtIn
)
if (receiverClass == ObjDynamic.type) {
val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
val current = allocSlot()
val result = allocSlot()
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
builder.emit(objOp, current, rhs.slot, result)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
updateSlotType(result, SlotType.OBJ)
return CompiledValue(result, SlotType.OBJ)
}
throw BytecodeCompileException( throw BytecodeCompileException(
"Member assignment requires compile-time receiver type: ${fieldTarget.name}", "Member assignment requires compile-time receiver type: ${fieldTarget.name}",
Pos.builtIn Pos.builtIn
@ -2008,6 +2045,29 @@ class BytecodeCompiler(
Pos.builtIn Pos.builtIn
) )
val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null
if (receiverClass == ObjDynamic.type) {
val nameId = builder.addConst(BytecodeConst.StringVal(target.name))
if (!target.isOptional) {
builder.emit(Opcode.SET_DYNAMIC_MEMBER, 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_DYNAMIC_MEMBER, receiver.slot, nameId, newValue.slot)
builder.mark(skipLabel)
}
val newObj = ensureObjSlot(newValue)
builder.emit(Opcode.MOVE_OBJ, newObj.slot, resultSlot)
builder.mark(endLabel)
updateSlotType(resultSlot, SlotType.OBJ)
return CompiledValue(resultSlot, SlotType.OBJ)
}
val fieldId = receiverClass.instanceFieldIdMap()[target.name] val fieldId = receiverClass.instanceFieldIdMap()[target.name]
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name] val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[target.name]
if (fieldId != null || methodId != null) { if (fieldId != null || methodId != null) {
@ -2098,6 +2158,32 @@ class BytecodeCompiler(
Pos.builtIn Pos.builtIn
) )
} }
if (receiverClass == ObjDynamic.type) {
val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
val dst = allocSlot()
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
if (!ref.isOptional) {
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
} else {
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst)
builder.mark(endLabel)
}
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
val fieldId = receiverClass.instanceFieldIdMap()[ref.name] val fieldId = receiverClass.instanceFieldIdMap()[ref.name]
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
val encodedFieldId = encodeMemberId(receiverClass, fieldId) val encodedFieldId = encodeMemberId(receiverClass, fieldId)
@ -2613,6 +2699,29 @@ class BytecodeCompiler(
"Member access requires compile-time receiver type: ${fieldTarget.name}", "Member access requires compile-time receiver type: ${fieldTarget.name}",
Pos.builtIn Pos.builtIn
) )
if (receiverClass == ObjDynamic.type) {
val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
val current = allocSlot()
builder.emit(Opcode.GET_DYNAMIC_MEMBER, receiver.slot, nameId, current)
updateSlotType(current, SlotType.OBJ)
val oneSlot = allocSlot()
val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
updateSlotType(oneSlot, SlotType.OBJ)
val result = allocSlot()
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
if (wantResult && ref.isPost) {
val old = allocSlot()
builder.emit(Opcode.MOVE_OBJ, current, old)
builder.emit(op, current, oneSlot, result)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
return CompiledValue(old, SlotType.OBJ)
}
builder.emit(op, current, oneSlot, result)
builder.emit(Opcode.SET_DYNAMIC_MEMBER, receiver.slot, nameId, result)
return CompiledValue(result, SlotType.OBJ)
}
val fieldId = receiverClass.instanceFieldIdMap()[fieldTarget.name] val fieldId = receiverClass.instanceFieldIdMap()[fieldTarget.name]
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name] val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[fieldTarget.name]
if (fieldId == null && methodId == null) return null if (fieldId == null && methodId == null) return null
@ -2926,6 +3035,33 @@ class BytecodeCompiler(
} }
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
val dst = allocSlot() val dst = allocSlot()
if (receiverClass == ObjDynamic.type) {
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
val encodedCount = encodeCallArgCount(args) ?: return null
val nameId = builder.addConst(BytecodeConst.StringVal(ref.name))
if (!ref.isOptional) {
setPos(callPos)
builder.emit(Opcode.CALL_DYNAMIC_MEMBER, receiver.slot, nameId, args.base, encodedCount, dst)
return CompiledValue(dst, SlotType.OBJ)
}
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
setPos(callPos)
builder.emit(Opcode.CALL_DYNAMIC_MEMBER, receiver.slot, nameId, args.base, encodedCount, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst)
builder.mark(endLabel)
return CompiledValue(dst, SlotType.OBJ)
}
val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name] val methodId = receiverClass.instanceMethodIdMap(includeAbstract = true)[ref.name]
if (methodId != null) { if (methodId != null) {
val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId val encodedMethodId = encodeMemberId(receiverClass, methodId) ?: methodId

View File

@ -181,6 +181,8 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_SLOT -> Opcode.CALL_SLOT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_DYNAMIC_MEMBER ->
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_INDEX -> Opcode.GET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX -> Opcode.SET_INDEX ->
@ -197,6 +199,10 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
Opcode.SET_CLASS_SCOPE -> Opcode.SET_CLASS_SCOPE ->
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
Opcode.GET_DYNAMIC_MEMBER ->
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
Opcode.SET_DYNAMIC_MEMBER ->
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
Opcode.ITER_PUSH -> Opcode.ITER_PUSH ->
listOf(OperandKind.SLOT) listOf(OperandKind.SLOT)
Opcode.ITER_POP, Opcode.ITER_CANCEL -> Opcode.ITER_POP, Opcode.ITER_CANCEL ->
@ -394,6 +400,7 @@ class CmdBuilder {
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.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4]) Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4])
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.CALL_DYNAMIC_MEMBER -> CmdCallDynamicMember(operands[0], operands[1], operands[2], operands[3], operands[4])
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2]) Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2]) Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3]) Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
@ -401,6 +408,8 @@ class CmdBuilder {
Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.GET_CLASS_SCOPE -> CmdGetClassScope(operands[0], operands[1], operands[2]) Opcode.GET_CLASS_SCOPE -> CmdGetClassScope(operands[0], operands[1], operands[2])
Opcode.SET_CLASS_SCOPE -> CmdSetClassScope(operands[0], operands[1], operands[2]) Opcode.SET_CLASS_SCOPE -> CmdSetClassScope(operands[0], operands[1], operands[2])
Opcode.GET_DYNAMIC_MEMBER -> CmdGetDynamicMember(operands[0], operands[1], operands[2])
Opcode.SET_DYNAMIC_MEMBER -> CmdSetDynamicMember(operands[0], operands[1], operands[2])
Opcode.ITER_PUSH -> CmdIterPush(operands[0]) Opcode.ITER_PUSH -> CmdIterPush(operands[0])
Opcode.ITER_POP -> CmdIterPop() Opcode.ITER_POP -> CmdIterPop()
Opcode.ITER_CANCEL -> CmdIterCancel() Opcode.ITER_CANCEL -> CmdIterCancel()

View File

@ -195,6 +195,7 @@ object CmdDisassembler {
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 CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallDynamicMember -> Opcode.CALL_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.argBase, cmd.argCount, cmd.dst)
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, 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 CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst) is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
@ -202,6 +203,8 @@ object CmdDisassembler {
is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot) is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
is CmdGetClassScope -> Opcode.GET_CLASS_SCOPE to intArrayOf(cmd.classSlot, cmd.nameId, cmd.dst) is CmdGetClassScope -> Opcode.GET_CLASS_SCOPE to intArrayOf(cmd.classSlot, cmd.nameId, cmd.dst)
is CmdSetClassScope -> Opcode.SET_CLASS_SCOPE to intArrayOf(cmd.classSlot, cmd.nameId, cmd.valueSlot) is CmdSetClassScope -> Opcode.SET_CLASS_SCOPE to intArrayOf(cmd.classSlot, cmd.nameId, cmd.valueSlot)
is CmdGetDynamicMember -> Opcode.GET_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.dst)
is CmdSetDynamicMember -> Opcode.SET_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.valueSlot)
is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot) is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot)
is CmdIterPop -> Opcode.ITER_POP to intArrayOf() is CmdIterPop -> Opcode.ITER_POP to intArrayOf()
is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf() is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf()
@ -285,6 +288,8 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_MEMBER_SLOT -> Opcode.CALL_MEMBER_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_DYNAMIC_MEMBER ->
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_INDEX -> Opcode.GET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX -> Opcode.SET_INDEX ->
@ -299,6 +304,10 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
Opcode.SET_CLASS_SCOPE -> Opcode.SET_CLASS_SCOPE ->
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
Opcode.GET_DYNAMIC_MEMBER ->
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
Opcode.SET_DYNAMIC_MEMBER ->
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
} }
} }
} }

View File

@ -1404,6 +1404,21 @@ private fun decodeMemberId(id: Int): Pair<Int, Boolean> {
} }
} }
private suspend fun resolveDynamicFieldValue(scope: Scope, receiver: Obj, name: String, rec: ObjRecord): Obj {
if (rec.type == ObjRecord.Type.Delegated || rec.value is ObjProperty || rec.type == ObjRecord.Type.Property) {
val recv = rec.receiver ?: receiver
return recv.resolveRecord(scope, rec, name, rec.declaringClass).value
}
if (rec.receiver != null && rec.declaringClass != null) {
return rec.receiver!!.resolveRecord(scope, rec, name, rec.declaringClass).value
}
if (rec.type == ObjRecord.Type.Fun && !rec.isAbstract) {
val recv = rec.receiver ?: receiver
return rec.value.invoke(scope, recv, Arguments.EMPTY, rec.declaringClass)
}
return rec.value
}
class CmdGetMemberSlot( class CmdGetMemberSlot(
internal val recvSlot: Int, internal val recvSlot: Int,
internal val fieldId: Int, internal val fieldId: Int,
@ -1527,6 +1542,75 @@ class CmdSetClassScope(
} }
} }
class CmdGetDynamicMember(
internal val recvSlot: Int,
internal val nameId: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope(useRefs = true)
}
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
?: error("GET_DYNAMIC_MEMBER expects StringVal at $nameId")
val scope = frame.ensureScope()
val receiver = frame.slotToObj(recvSlot)
val rec = receiver.readField(scope, nameConst.value)
val value = resolveDynamicFieldValue(scope, receiver, nameConst.value, rec)
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
frame.storeObjResult(dst, value)
return
}
}
class CmdSetDynamicMember(
internal val recvSlot: Int,
internal val nameId: Int,
internal val valueSlot: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope(useRefs = true)
}
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
?: error("SET_DYNAMIC_MEMBER expects StringVal at $nameId")
val scope = frame.ensureScope()
val receiver = frame.slotToObj(recvSlot)
receiver.writeField(scope, nameConst.value, frame.slotToObj(valueSlot))
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
return
}
}
class CmdCallDynamicMember(
internal val recvSlot: Int,
internal val nameId: Int,
internal val argBase: Int,
internal val argCount: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope(useRefs = true)
}
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
?: error("CALL_DYNAMIC_MEMBER expects StringVal at $nameId")
val scope = frame.ensureScope()
val receiver = frame.slotToObj(recvSlot)
val callArgs = frame.buildArguments(argBase, argCount)
val result = receiver.invokeInstanceMethod(scope, nameConst.value, callArgs)
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
frame.storeObjResult(dst, result)
return
}
}
class CmdCallMemberSlot( class CmdCallMemberSlot(
internal val recvSlot: Int, internal val recvSlot: Int,
internal val methodId: Int, internal val methodId: Int,

View File

@ -142,6 +142,9 @@ enum class Opcode(val code: Int) {
SET_MEMBER_SLOT(0xA9), SET_MEMBER_SLOT(0xA9),
GET_CLASS_SCOPE(0xAA), GET_CLASS_SCOPE(0xAA),
SET_CLASS_SCOPE(0xAB), SET_CLASS_SCOPE(0xAB),
GET_DYNAMIC_MEMBER(0xAC),
SET_DYNAMIC_MEMBER(0xAD),
CALL_DYNAMIC_MEMBER(0xAE),
RESOLVE_SCOPE_SLOT(0xB1), RESOLVE_SCOPE_SLOT(0xB1),
LOAD_OBJ_ADDR(0xB2), LOAD_OBJ_ADDR(0xB2),