From d164f1b7659541f3c225b043147e316169ff13f8 Mon Sep 17 00:00:00 2001 From: Sascha Lisson Date: Mon, 22 Jul 2024 10:32:19 +0200 Subject: [PATCH] fix(interpreter): introduced operand stack --- commitlint.config.js | 35 +++++----- .../interpreter/vm/core/Instructions.kt | 64 +++++++++++++------ .../interpreter/vm/core/InterpreterVM.kt | 20 +++++- .../interpreter/vm/core/ProgramBuilder.kt | 3 +- .../interpreter.vm.core/InterpreterTest.kt | 46 +++++++------ 5 files changed, 104 insertions(+), 64 deletions(-) diff --git a/commitlint.config.js b/commitlint.config.js index 7ec4c504..703a214d 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -1,20 +1,21 @@ module.exports = { - extends: ["@commitlint/config-conventional"], - rules: { - "scope-enum": [ - 2, - "always", - [ - "deps", - "projectional-editor", - "mps-plugin", - ], + extends: ["@commitlint/config-conventional"], + rules: { + "scope-enum": [ + 2, + "always", + [ + "deps", + "projectional-editor", + "mps-plugin", + "interpreter" + ], + ], + "subject-case": [0, 'never'], + "body-max-line-length": [0, 'always'], + "footer-max-line-length": [0, 'always'] + }, + ignores: [ + (message) => message.includes('skip-lint') ], - "subject-case": [0, 'never'], - "body-max-line-length": [0, 'always'], - "footer-max-line-length": [0, 'always'] - }, - ignores: [ - (message) => message.includes('skip-lint') - ], }; diff --git a/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/Instructions.kt b/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/Instructions.kt index 2da55c7f..baf4e11b 100644 --- a/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/Instructions.kt +++ b/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/Instructions.kt @@ -1,53 +1,77 @@ package org.modelix.interpreter.vm.core -class CallInstruction(val entryPoint: Instruction, val parameterSourceKey: List>, val resultTargetKeys: List>) : Instruction() { +class CallInstruction(val entryPoint: Instruction, val parameterCount: Int) : Instruction() { override fun execute(state: VMState): VMState { - val newFrame = StackFrame(returnTo = next, resultTargetKeys = resultTargetKeys) - var newState = state.copy(callStack = state.callStack.pushFrame(newFrame), nextInstruction = entryPoint) - parameterSourceKey.forEachIndexed { index, key -> - newState = newState.writeMemory(ParameterKey(index), state.readMemory(key)) + var newFrame = StackFrame(returnTo = next) + var newState = state + for (i in 0 until parameterCount) { + newState.popOperand().let { + newState = it.second + newFrame = newFrame.writeLocalMemory(ParameterKey(i), it.first) + } } + newState = newState.copy(callStack = newState.callStack.pushFrame(newFrame), nextInstruction = entryPoint) return newState } } -class ReturnInstruction(val returnValues: List>) : Instruction() { +class ReturnInstruction() : Instruction() { override fun execute(state: VMState): VMState { val (newCallStack, currentFrame) = state.callStack.popFrame() - check(currentFrame.resultTargetKeys.size == returnValues.size) { - "Caller expected ${currentFrame.resultTargetKeys.size} return values, but function returns ${returnValues.size} values" - } - var newState = state.copy(nextInstruction = currentFrame.returnTo, callStack = newCallStack) - returnValues.forEachIndexed { index, key -> - newState = newState.writeMemory(currentFrame.resultTargetKeys[index] as MemoryKey, state.readMemory(key)) - } - return newState + check(currentFrame.operandStack.size == 1) { "Operand stack should contain a single value, but was: " + currentFrame.operandStack } + return state + .copy(nextInstruction = currentFrame.returnTo, callStack = newCallStack) + .pushOperand(currentFrame.operandStack.single()) } } -class LoadConstantInstruction(val value: E, val target: MemoryKey) : Instruction() { +class PushConstantInstruction(val value: E) : Instruction() { override fun execute(state: VMState): VMState { - return state.writeMemory(target, value) + return state.pushOperand(value) } } -abstract class BinaryOperationInstruction(val arg1: MemoryKey, val arg2: MemoryKey, val result: MemoryKey) : Instruction() { +class StoreInstruction(val target: MemoryKey) : Instruction() { + override fun execute(state: VMState): VMState { + return state.popOperand().let { (value, newState) -> newState.writeMemory(target, value as E) } + } +} + +class LoadInstruction(val source: MemoryKey) : Instruction() { + override fun execute(state: VMState): VMState { + return state.pushOperand(state.readMemory(source)) + } +} + +abstract class BinaryOperationInstruction() : Instruction() { abstract fun apply(arg1: Arg1, arg2: Arg2): Result override fun execute(state: VMState): VMState { - return state.writeMemory(result, apply(state.readMemory(arg1), state.readMemory(arg2))) + var newState: VMState = state + var arg1: Arg1 + var arg2: Arg2 + newState.popOperand().let { + arg2 = it.first as Arg2 + newState = it.second + } + newState.popOperand().let { + arg1 = it.first as Arg1 + newState = it.second + } + newState = newState.pushOperand(apply(arg1, arg2)) + return newState } } -class AddIntegersInstruction(arg1: MemoryKey, arg2: MemoryKey, result: MemoryKey) : BinaryOperationInstruction(arg1, arg2, result) { +class AddIntegersInstruction() : BinaryOperationInstruction() { override fun apply(arg1: Int, arg2: Int): Int { return arg1 + arg2 } } -class MultiplyIntegersInstruction(arg1: MemoryKey, arg2: MemoryKey, result: MemoryKey) : BinaryOperationInstruction(arg1, arg2, result) { +class MultiplyIntegersInstruction() : BinaryOperationInstruction() { override fun apply(arg1: Int, arg2: Int): Int { return arg1 * arg2 } diff --git a/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/InterpreterVM.kt b/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/InterpreterVM.kt index 2e0094dc..a76a504e 100644 --- a/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/InterpreterVM.kt +++ b/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/InterpreterVM.kt @@ -33,12 +33,22 @@ class InterpreterVM(entryPoint: Instruction) { data class VMState( val nextInstruction: Instruction?, val globalMemory: Memory = Memory(), - val callStack: CallStack = CallStack().pushFrame(StackFrame(returnTo = null, resultTargetKeys = listOf())), + val callStack: CallStack = CallStack().pushFrame(StackFrame(returnTo = null)), ) { fun readMemory(key: MemoryKey): T = key.memoryType.getMemory(this).read(key) fun writeMemory(key: MemoryKey, value: T): VMState { return key.memoryType.setMemory(this, key.memoryType.getMemory(this).write(key, value)) } + fun updateCurrentFrame(body: (StackFrame) -> StackFrame): VMState { + return replaceCurrentFrame(body(callStack.currentFrame())) + } + fun replaceCurrentFrame(newFrame: StackFrame): VMState { + return copy(callStack = callStack.updateCurrentFrame(newFrame)) + } + fun pushOperand(value: Any?): VMState = updateCurrentFrame { it.pushOperand(value) } + fun popOperand(): Pair { + return callStack.currentFrame().popOperand().let { (value, newFrame) -> value to replaceCurrentFrame(newFrame) } + } } data class Memory(private val data: PersistentMap, Any?> = persistentHashMapOf()) { @@ -91,8 +101,14 @@ data class CallStack(val frames: PersistentList = persistentListOf() fun size() = frames.size } -data class StackFrame(val returnTo: Instruction?, val localMemory: Memory = Memory(), val resultTargetKeys: List>) { +data class StackFrame( + val returnTo: Instruction?, + val localMemory: Memory = Memory(), + val operandStack: PersistentList = persistentListOf(), +) { fun writeLocalMemory(key: MemoryKey, value: T): StackFrame = copy(localMemory = localMemory.write(key, value)) + fun pushOperand(value: Any?): StackFrame = copy(operandStack = operandStack.add(value)) + fun popOperand(): Pair = operandStack.last() to copy(operandStack = operandStack.removeAt(operandStack.lastIndex)) } abstract class Instruction { diff --git a/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/ProgramBuilder.kt b/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/ProgramBuilder.kt index 03f1eff0..971d0337 100644 --- a/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/ProgramBuilder.kt +++ b/interpreter-vm/src/commonMain/kotlin/org/modelix/interpreter/vm/core/ProgramBuilder.kt @@ -49,6 +49,7 @@ class FunctionBuilder { } fun load(value: T, variable: MemoryKey) { - addInstruction(LoadConstantInstruction(value, variable)) + addInstruction(PushConstantInstruction(value)) + addInstruction(StoreInstruction(variable)) } } diff --git a/interpreter-vm/src/commonTest/kotlin/org/modelix/interpreter.vm.core/InterpreterTest.kt b/interpreter-vm/src/commonTest/kotlin/org/modelix/interpreter.vm.core/InterpreterTest.kt index 4a860bd1..78ac22d6 100644 --- a/interpreter-vm/src/commonTest/kotlin/org/modelix/interpreter.vm.core/InterpreterTest.kt +++ b/interpreter-vm/src/commonTest/kotlin/org/modelix/interpreter.vm.core/InterpreterTest.kt @@ -7,14 +7,13 @@ class InterpreterTest { @Test fun addition() { - val a = MemoryKey(MemoryType.LOCAL, "a") - val b = MemoryKey(MemoryType.LOCAL, "b") val c = MemoryKey(MemoryType.GLOBAL, "c") val entryPoint = ProgramBuilder().buildFunction("main") { - addInstruction(LoadConstantInstruction(10, a)) - addInstruction(LoadConstantInstruction(20, b)) - addInstruction(AddIntegersInstruction(a, b, c)) + addInstruction(PushConstantInstruction(10)) + addInstruction(PushConstantInstruction(20)) + addInstruction(AddIntegersInstruction()) + addInstruction(StoreInstruction(c)) }.getEntryPoint() val finalState = InterpreterVM(entryPoint).run() @@ -30,7 +29,10 @@ class InterpreterTest { val c = MemoryKey(MemoryType.GLOBAL, "c") val entryPoint = ProgramBuilder().buildFunction("main") { - addInstruction(AddIntegersInstruction(a, b, c)) + addInstruction(LoadInstruction(ParameterKey(1))) + addInstruction(LoadInstruction(ParameterKey(0))) + addInstruction(AddIntegersInstruction()) + addInstruction(StoreInstruction(c)) }.getEntryPoint() val vm = InterpreterVM(entryPoint) @@ -46,28 +48,24 @@ class InterpreterTest { fun functionCall() { val entryPoint = ProgramBuilder().run { val plusFunction = buildFunction("plus") { - val r = NamedLocalVarKey("result") - addInstruction(AddIntegersInstruction(ParameterKey(0), ParameterKey(1), r)) - addInstruction(ReturnInstruction(listOf(r))) + addInstruction(LoadInstruction(ParameterKey(0))) + addInstruction(LoadInstruction(ParameterKey(1))) + addInstruction(AddIntegersInstruction()) + addInstruction(ReturnInstruction()) } val mulFunction = buildFunction("mul") { - val r = NamedLocalVarKey("result") - addInstruction(MultiplyIntegersInstruction(ParameterKey(0), ParameterKey(1), r)) - addInstruction(ReturnInstruction(listOf(r))) + addInstruction(LoadInstruction(ParameterKey(0))) + addInstruction(LoadInstruction(ParameterKey(1))) + addInstruction(MultiplyIntegersInstruction()) + addInstruction(ReturnInstruction()) } buildFunction("main") { - val a by variable() - val b by variable() - val c by variable() - val product by variable() - val sum by variable() - - load(7, a) - load(13, b) - load(31, c) - addInstruction(CallInstruction(plusFunction.getEntryPoint(), listOf(a, b), listOf(sum))) - addInstruction(CallInstruction(mulFunction.getEntryPoint(), listOf(sum, c), listOf(product))) - addInstruction(MoveInstruction(product, NamedGlobalVarKey("finalResult"))) + addInstruction(PushConstantInstruction(31)) + addInstruction(PushConstantInstruction(13)) + addInstruction(PushConstantInstruction(7)) + addInstruction(CallInstruction(plusFunction.getEntryPoint(), 2)) + addInstruction(CallInstruction(mulFunction.getEntryPoint(), 2)) + addInstruction(StoreInstruction(NamedGlobalVarKey("finalResult"))) }.getEntryPoint() }