mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-08-03 13:11:32 +00:00
vm: very basic virtual machine
This commit is contained in:
11
vm/build.gradle.kts
Normal file
11
vm/build.gradle.kts
Normal file
@ -0,0 +1,11 @@
|
||||
plugins {
|
||||
id("gay.pizza.pork.module")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":execution"))
|
||||
api(project(":bytecode"))
|
||||
api(project(":compiler"))
|
||||
|
||||
implementation(project(":common"))
|
||||
}
|
80
vm/src/main/kotlin/gay/pizza/pork/vm/InternalMachine.kt
Normal file
80
vm/src/main/kotlin/gay/pizza/pork/vm/InternalMachine.kt
Normal file
@ -0,0 +1,80 @@
|
||||
package gay.pizza.pork.vm
|
||||
|
||||
import gay.pizza.pork.bytecode.CompiledWorld
|
||||
|
||||
class InternalMachine(val world: CompiledWorld, val handlers: List<OpHandler>) {
|
||||
private val inlined = world.code.map { op ->
|
||||
val handler = handlers.firstOrNull { it.code == op.code } ?:
|
||||
throw VirtualMachineException("Opcode ${op.code.name} does not have a handler.")
|
||||
op to handler
|
||||
}
|
||||
|
||||
private var inst: UInt = 0u
|
||||
private val stack = mutableListOf<Any>(EndOfCode)
|
||||
private val locals = mutableListOf<MutableMap<UInt, Any>>(
|
||||
mutableMapOf()
|
||||
)
|
||||
private var autoNextInst = true
|
||||
private var exitFlag = false
|
||||
|
||||
fun step(): Boolean {
|
||||
val (op, handler) = inlined[inst.toInt()]
|
||||
handler.handle(this, op)
|
||||
if (autoNextInst) {
|
||||
inst++
|
||||
}
|
||||
autoNextInst = true
|
||||
return !exitFlag
|
||||
}
|
||||
|
||||
fun pushScope() {
|
||||
locals.add(mutableMapOf())
|
||||
}
|
||||
|
||||
fun popScope() {
|
||||
locals.removeLast()
|
||||
}
|
||||
|
||||
fun loadConstant(id: UInt) {
|
||||
push(world.constantPool.constants[id.toInt()])
|
||||
}
|
||||
|
||||
fun loadLocal(id: UInt) {
|
||||
val localSet = locals.last()
|
||||
val local = localSet[id]
|
||||
?: throw VirtualMachineException("Attempted to load local $id but it was not stored.")
|
||||
push(local)
|
||||
}
|
||||
|
||||
fun storeLocal(id: UInt) {
|
||||
val localSet = locals.last()
|
||||
val value = pop()
|
||||
localSet[id] = value
|
||||
}
|
||||
|
||||
fun setNextInst(value: UInt) {
|
||||
inst = value
|
||||
autoNextInst = false
|
||||
}
|
||||
|
||||
fun push(item: Any) {
|
||||
stack.add(item)
|
||||
}
|
||||
|
||||
fun pop(): Any = stack.removeLast()
|
||||
fun exit() {
|
||||
exitFlag = true
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
stack.clear()
|
||||
stack.add(EndOfCode)
|
||||
locals.clear()
|
||||
locals.add(mutableMapOf())
|
||||
inst = 0u
|
||||
exitFlag = false
|
||||
autoNextInst = true
|
||||
}
|
||||
|
||||
data object EndOfCode
|
||||
}
|
8
vm/src/main/kotlin/gay/pizza/pork/vm/OpHandler.kt
Normal file
8
vm/src/main/kotlin/gay/pizza/pork/vm/OpHandler.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package gay.pizza.pork.vm
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
|
||||
abstract class OpHandler(val code: Opcode) {
|
||||
abstract fun handle(machine: InternalMachine, op: Op)
|
||||
}
|
45
vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachine.kt
Normal file
45
vm/src/main/kotlin/gay/pizza/pork/vm/VirtualMachine.kt
Normal file
@ -0,0 +1,45 @@
|
||||
package gay.pizza.pork.vm
|
||||
|
||||
import gay.pizza.pork.bytecode.CompiledWorld
|
||||
import gay.pizza.pork.execution.ExecutionContext
|
||||
import gay.pizza.pork.vm.ops.*
|
||||
|
||||
class VirtualMachine(world: CompiledWorld) : ExecutionContext {
|
||||
private val internal = InternalMachine(world, listOf(
|
||||
IntegerOpHandler,
|
||||
ConstantOpHandler,
|
||||
|
||||
TrueOpHandler,
|
||||
FalseOpHandler,
|
||||
|
||||
ListOpHandler,
|
||||
|
||||
CompareEqualOpHandler,
|
||||
CompareLesserEqualOpHandler,
|
||||
|
||||
AddOpHandler,
|
||||
|
||||
JumpOpHandler,
|
||||
JumpIfOpHandler,
|
||||
|
||||
LoadLocalOpHandler,
|
||||
StoreLocalOpHandler,
|
||||
|
||||
CallOpHandler,
|
||||
RetOpHandler,
|
||||
|
||||
NativeOpHandler,
|
||||
|
||||
ScopeInOpHandler,
|
||||
ScopeOutOpHandler
|
||||
))
|
||||
|
||||
override fun execute() {
|
||||
while (true) {
|
||||
if (!internal.step()) {
|
||||
break
|
||||
}
|
||||
}
|
||||
internal.reset()
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
package gay.pizza.pork.vm
|
||||
|
||||
class VirtualMachineException(message: String) : RuntimeException(message)
|
@ -0,0 +1,20 @@
|
||||
package gay.pizza.pork.vm
|
||||
|
||||
import gay.pizza.pork.ast.gen.Symbol
|
||||
import gay.pizza.pork.compiler.Compiler
|
||||
import gay.pizza.pork.execution.ExecutionContext
|
||||
import gay.pizza.pork.execution.ExecutionContextProvider
|
||||
import gay.pizza.pork.frontend.ImportLocator
|
||||
import gay.pizza.pork.frontend.World
|
||||
|
||||
class VirtualMachineProvider(val world: World) : ExecutionContextProvider {
|
||||
override fun prepare(importLocator: ImportLocator, entryPointSymbol: Symbol): ExecutionContext {
|
||||
val compiler = Compiler()
|
||||
val slab = world.load(importLocator)
|
||||
val compilableSlab = compiler.compilableSlabs.of(slab)
|
||||
val compilableSymbol = compilableSlab.compilableSymbolOf(entryPointSymbol) ?:
|
||||
throw RuntimeException("Unable to find compilable symbol for entry point '${entryPointSymbol.id}'")
|
||||
val compiledWorld = compiler.compile(compilableSymbol)
|
||||
return VirtualMachine(compiledWorld)
|
||||
}
|
||||
}
|
19
vm/src/main/kotlin/gay/pizza/pork/vm/ops/AddOpHandler.kt
Normal file
19
vm/src/main/kotlin/gay/pizza/pork/vm/ops/AddOpHandler.kt
Normal file
@ -0,0 +1,19 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
import gay.pizza.pork.vm.VirtualMachineException
|
||||
|
||||
object AddOpHandler : OpHandler(Opcode.Add) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
val left = machine.pop()
|
||||
val right = machine.pop()
|
||||
|
||||
if (left !is Int || right !is Int) {
|
||||
throw VirtualMachineException("Bad types.")
|
||||
}
|
||||
machine.push(left + right)
|
||||
}
|
||||
}
|
13
vm/src/main/kotlin/gay/pizza/pork/vm/ops/CallOpHandler.kt
Normal file
13
vm/src/main/kotlin/gay/pizza/pork/vm/ops/CallOpHandler.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object CallOpHandler : OpHandler(Opcode.Call) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
machine.setNextInst(op.args[0])
|
||||
machine.pushScope()
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object CompareEqualOpHandler : OpHandler(Opcode.CompareEqual) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
machine.push(machine.pop() == machine.pop())
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
import gay.pizza.pork.vm.VirtualMachineException
|
||||
|
||||
object CompareLesserEqualOpHandler : OpHandler(Opcode.CompareLesserEqual) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
val right = machine.pop()
|
||||
val left = machine.pop()
|
||||
|
||||
if (left !is Int || right !is Int) {
|
||||
throw VirtualMachineException("Bad types.")
|
||||
}
|
||||
machine.push(left <= right)
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object ConstantOpHandler : OpHandler(Opcode.Constant) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
machine.loadConstant(op.args[0])
|
||||
}
|
||||
}
|
12
vm/src/main/kotlin/gay/pizza/pork/vm/ops/FalseOpHandler.kt
Normal file
12
vm/src/main/kotlin/gay/pizza/pork/vm/ops/FalseOpHandler.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object FalseOpHandler : OpHandler(Opcode.False) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
machine.push(false)
|
||||
}
|
||||
}
|
12
vm/src/main/kotlin/gay/pizza/pork/vm/ops/IntegerOpHandler.kt
Normal file
12
vm/src/main/kotlin/gay/pizza/pork/vm/ops/IntegerOpHandler.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object IntegerOpHandler : OpHandler(Opcode.Integer) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
machine.push(op.args[0].toInt())
|
||||
}
|
||||
}
|
20
vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpIfOpHandler.kt
Normal file
20
vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpIfOpHandler.kt
Normal file
@ -0,0 +1,20 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
import gay.pizza.pork.vm.VirtualMachineException
|
||||
|
||||
object JumpIfOpHandler : OpHandler(Opcode.JumpIf) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
val value = machine.pop()
|
||||
if (value !is Boolean) {
|
||||
throw VirtualMachineException("JumpIf expects a boolean value on the stack.")
|
||||
}
|
||||
|
||||
if (value) {
|
||||
machine.setNextInst(op.args[0])
|
||||
}
|
||||
}
|
||||
}
|
12
vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpOpHandler.kt
Normal file
12
vm/src/main/kotlin/gay/pizza/pork/vm/ops/JumpOpHandler.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object JumpOpHandler : OpHandler(Opcode.Jump) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
machine.setNextInst(op.args[0])
|
||||
}
|
||||
}
|
18
vm/src/main/kotlin/gay/pizza/pork/vm/ops/ListOpHandler.kt
Normal file
18
vm/src/main/kotlin/gay/pizza/pork/vm/ops/ListOpHandler.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object ListOpHandler : OpHandler(Opcode.List) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
val count = op.args[0]
|
||||
val list = mutableListOf<Any>()
|
||||
for (i in 1u..count) {
|
||||
val item = machine.pop()
|
||||
list.add(item)
|
||||
}
|
||||
machine.push(list.reversed())
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object LoadLocalOpHandler : OpHandler(Opcode.LoadLocal) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
machine.loadLocal(op.args[0])
|
||||
}
|
||||
}
|
16
vm/src/main/kotlin/gay/pizza/pork/vm/ops/NativeOpHandler.kt
Normal file
16
vm/src/main/kotlin/gay/pizza/pork/vm/ops/NativeOpHandler.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object NativeOpHandler : OpHandler(Opcode.Native) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
val countOfNativeDefs = op.args[1].toInt()
|
||||
val defs = mutableListOf<Any>()
|
||||
for (i in 0 until countOfNativeDefs) {
|
||||
defs.add(String(machine.pop() as ByteArray))
|
||||
}
|
||||
}
|
||||
}
|
18
vm/src/main/kotlin/gay/pizza/pork/vm/ops/RetOpHandler.kt
Normal file
18
vm/src/main/kotlin/gay/pizza/pork/vm/ops/RetOpHandler.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object RetOpHandler : OpHandler(Opcode.Return) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
val last = machine.pop()
|
||||
if (last == InternalMachine.EndOfCode) {
|
||||
machine.exit()
|
||||
return
|
||||
}
|
||||
machine.popScope()
|
||||
machine.setNextInst((last as Int).toUInt())
|
||||
}
|
||||
}
|
10
vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeInOpHandler.kt
Normal file
10
vm/src/main/kotlin/gay/pizza/pork/vm/ops/ScopeInOpHandler.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object ScopeInOpHandler : OpHandler(Opcode.ScopeIn) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object ScopeOutOpHandler : OpHandler(Opcode.ScopeOut) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object StoreLocalOpHandler : OpHandler(Opcode.StoreLocal) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
machine.storeLocal(op.args[0])
|
||||
}
|
||||
}
|
12
vm/src/main/kotlin/gay/pizza/pork/vm/ops/TrueOpHandler.kt
Normal file
12
vm/src/main/kotlin/gay/pizza/pork/vm/ops/TrueOpHandler.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package gay.pizza.pork.vm.ops
|
||||
|
||||
import gay.pizza.pork.bytecode.Op
|
||||
import gay.pizza.pork.bytecode.Opcode
|
||||
import gay.pizza.pork.vm.InternalMachine
|
||||
import gay.pizza.pork.vm.OpHandler
|
||||
|
||||
object TrueOpHandler : OpHandler(Opcode.True) {
|
||||
override fun handle(machine: InternalMachine, op: Op) {
|
||||
machine.push(true)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user