vm: very basic virtual machine

This commit is contained in:
2023-11-14 23:44:10 -08:00
parent 8c48c93663
commit 041848c14e
92 changed files with 1652 additions and 243 deletions

11
vm/build.gradle.kts Normal file
View File

@ -0,0 +1,11 @@
plugins {
id("gay.pizza.pork.module")
}
dependencies {
api(project(":execution"))
api(project(":bytecode"))
api(project(":compiler"))
implementation(project(":common"))
}

View 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
}

View 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)
}

View 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()
}
}

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.vm
class VirtualMachineException(message: String) : RuntimeException(message)

View File

@ -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)
}
}

View 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)
}
}

View 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()
}
}

View 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 CompareEqualOpHandler : OpHandler(Opcode.CompareEqual) {
override fun handle(machine: InternalMachine, op: Op) {
machine.push(machine.pop() == machine.pop())
}
}

View 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 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)
}
}

View 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 ConstantOpHandler : OpHandler(Opcode.Constant) {
override fun handle(machine: InternalMachine, op: Op) {
machine.loadConstant(op.args[0])
}
}

View 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)
}
}

View 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())
}
}

View 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])
}
}
}

View 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])
}
}

View 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())
}
}

View 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 LoadLocalOpHandler : OpHandler(Opcode.LoadLocal) {
override fun handle(machine: InternalMachine, op: Op) {
machine.loadLocal(op.args[0])
}
}

View 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))
}
}
}

View 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())
}
}

View 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) {}
}

View 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 ScopeOutOpHandler : OpHandler(Opcode.ScopeOut) {
override fun handle(machine: InternalMachine, op: Op) {}
}

View 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 StoreLocalOpHandler : OpHandler(Opcode.StoreLocal) {
override fun handle(machine: InternalMachine, op: Op) {
machine.storeLocal(op.args[0])
}
}

View 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)
}
}