Skip to content

Commit

Permalink
vm: very basic virtual machine
Browse files Browse the repository at this point in the history
  • Loading branch information
azenla committed Nov 15, 2023
1 parent 8c48c93 commit 041848c
Show file tree
Hide file tree
Showing 92 changed files with 1,650 additions and 241 deletions.
42 changes: 42 additions & 0 deletions ast/src/main/kotlin/gay/pizza/pork/ast/FunctionLevelVisitor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package gay.pizza.pork.ast

import gay.pizza.pork.ast.gen.*

abstract class FunctionLevelVisitor<T> : NodeVisitor<T> {
override fun visitForInItem(node: ForInItem): T =
throw RuntimeException("Visiting ForInItem is not supported.")

override fun visitSymbol(node: Symbol): T =
throw RuntimeException("Visiting Symbol is not supported.")

override fun visitLetDefinition(node: LetDefinition): T {
topLevelUsedError("LetDefinition")
}

override fun visitArgumentSpec(node: ArgumentSpec): T =
throw RuntimeException("Visiting ArgumentSpec is not supported.")

override fun visitFunctionDefinition(node: FunctionDefinition): T {
topLevelUsedError("FunctionDefinition")
}

override fun visitImportDeclaration(node: ImportDeclaration): T {
topLevelUsedError("ImportDeclaration")
}

override fun visitImportPath(node: ImportPath): T {
topLevelUsedError("ImportPath")
}

override fun visitCompilationUnit(node: CompilationUnit): T {
topLevelUsedError("CompilationUnit")
}

override fun visitNativeFunctionDescriptor(node: NativeFunctionDescriptor): T {
topLevelUsedError("NativeFunctionDescriptor")
}

private fun topLevelUsedError(name: String): Nothing {
throw RuntimeException("$name cannot be visited in a FunctionVisitor.")
}
}
7 changes: 7 additions & 0 deletions bytecode/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
id("gay.pizza.pork.module")
}

dependencies {
implementation(project(":common"))
}
10 changes: 10 additions & 0 deletions bytecode/src/main/kotlin/gay/pizza/pork/bytecode/CompiledWorld.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gay.pizza.pork.bytecode

import kotlinx.serialization.Serializable

@Serializable
data class CompiledWorld(
val constantPool: ConstantPool,
val symbolTable: SymbolTable,
val code: List<Op>
)
3 changes: 3 additions & 0 deletions bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Constant.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package gay.pizza.pork.bytecode

class Constant(val id: UInt, val value: ByteArray)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gay.pizza.pork.bytecode

import kotlinx.serialization.Serializable

@Serializable
data class ConstantPool(val constants: List<ByteArray>)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gay.pizza.pork.bytecode

class MutableConstantPool {
private val pool = mutableListOf<Constant>()

fun assign(content: ByteArray): UInt {
for (constant in pool) {
if (constant.value.contentEquals(content)) {
return constant.id
}
}
val id = pool.size.toUInt()
pool.add(Constant(id, content))
return id
}

fun all(): List<Constant> = pool
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package gay.pizza.pork.bytecode

class MutableRel(var rel: UInt)
6 changes: 6 additions & 0 deletions bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Op.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package gay.pizza.pork.bytecode

import kotlinx.serialization.Serializable

@Serializable
data class Op(val code: Opcode, val args: List<UInt>)
43 changes: 43 additions & 0 deletions bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Opcode.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package gay.pizza.pork.bytecode

enum class Opcode(val id: UByte) {
Constant(1u),
None(2u),
False(3u),
True(4u),
Pop(5u),
Jump(6u),
JumpIf(7u),
Not(8u),
UnaryPlus(9u),
UnaryMinus(10u),
BinaryNot(11u),
And(20u),
Native(24u),
Return(10u),
StoreLocal(16u),
LoadLocal(17u),
Add(18u),
Subtract(19u),
Multiply(20u),
Divide(21u),
CompareEqual(22u),
CompareLesser(23u),
CompareGreater(24u),
CompareLesserEqual(25u),
CompareGreaterEqual(26u),
Or(27u),
BinaryAnd(28u),
BinaryOr(29u),
BinaryXor(30u),
List(31u),
Integer(32u),
Double(33u),
Call(34u),
EuclideanModulo(35u),
Remainder(36u),
Index(37u),
ScopeIn(38u),
ScopeOut(39u),
End(255u),
}
3 changes: 3 additions & 0 deletions bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Ops.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package gay.pizza.pork.bytecode

class Ops(val ops: List<Op>)
10 changes: 10 additions & 0 deletions bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package gay.pizza.pork.bytecode

import kotlinx.serialization.Serializable

@Serializable
data class SymbolInfo(
val id: String,
val offset: UInt,
val size: UInt
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package gay.pizza.pork.bytecode

import kotlinx.serialization.Serializable

@Serializable
data class SymbolTable(
val symbols: List<SymbolInfo>
)
40 changes: 40 additions & 0 deletions common/src/main/kotlin/gay/pizza/pork/common/ByteRepresentation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package gay.pizza.pork.common

object ByteRepresentation {
fun encode(value: Int): ByteArray {
val buffer = ByteArray(4)
encode(value, buffer, 0)
return buffer
}

fun encode(value: Long): ByteArray {
val buffer = ByteArray(8)
encode(value, buffer, 0)
return buffer
}

fun encode(value: Double): ByteArray =
encode(value.toRawBits())

fun encode(value: Int, buffer: ByteArray, offset: Int) {
buffer[offset + 0] = (value shr 0).toByte()
buffer[offset + 1] = (value shr 8).toByte()
buffer[offset + 2] = (value shr 16).toByte()
buffer[offset + 3] = (value shr 24).toByte()
}

fun encode(value: Long, buffer: ByteArray, offset: Int) {
buffer[offset + 0] = (value shr 0).toByte()
buffer[offset + 1] = (value shr 8).toByte()
buffer[offset + 2] = (value shr 16).toByte()
buffer[offset + 3] = (value shr 24).toByte()
buffer[offset + 4] = (value shr 32).toByte()
buffer[offset + 5] = (value shr 40).toByte()
buffer[offset + 6] = (value shr 48).toByte()
buffer[offset + 7] = (value shr 56).toByte()
}

fun encode(value: Double, buffer: ByteArray, offset: Int) {
encode(value.toRawBits(), buffer, offset)
}
}
11 changes: 11 additions & 0 deletions compiler/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id("gay.pizza.pork.module")
}

dependencies {
api(project(":ast"))
api(project(":bytecode"))
api(project(":parser"))
api(project(":frontend"))
implementation(project(":common"))
}
16 changes: 16 additions & 0 deletions compiler/src/main/kotlin/gay/pizza/pork/compiler/CompilableSlab.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gay.pizza.pork.compiler

import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.frontend.Slab

class CompilableSlab(val compiler: Compiler, val slab: Slab) {
val compilableSymbols: List<CompilableSymbol> by lazy {
slab.scope.internalSymbols.map { symbol ->
CompilableSymbol(this, symbol)
}
}

fun compilableSymbolOf(symbol: Symbol): CompilableSymbol? = compilableSymbols.firstOrNull {
it.scopeSymbol.symbol == symbol
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package gay.pizza.pork.compiler

import gay.pizza.pork.ast.gen.FunctionDefinition
import gay.pizza.pork.ast.gen.LetDefinition
import gay.pizza.pork.ast.gen.visit
import gay.pizza.pork.frontend.scope.ScopeSymbol

class CompilableSymbol(val compilableSlab: CompilableSlab, val scopeSymbol: ScopeSymbol) {
val compiledStubOps: List<StubOp> by lazy { compile() }

val usedSymbols: List<ScopeSymbol>
get() = scopeSymbol.scope.usedSymbols

private fun compile(): List<StubOp> {
val emitter = StubOpEmitter(compilableSlab.compiler, this)
emitter.enter()
val what = if (scopeSymbol.definition is FunctionDefinition) {
val functionDefinition = scopeSymbol.definition as FunctionDefinition
emitter.allocateOuterScope(functionDefinition)
functionDefinition.block ?: functionDefinition.nativeFunctionDescriptor!!
} else {
val letDefinition = scopeSymbol.definition as LetDefinition
letDefinition.value
}
emitter.visit(what)
emitter.exit()
return emitter.ops()
}

val id: String
get() = "${compilableSlab.slab.location.commonFriendlyName} ${scopeSymbol.symbol.id}"

override fun toString(): String = "${compilableSlab.slab.location.commonFriendlyName} ${scopeSymbol.symbol.id}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package gay.pizza.pork.compiler

import gay.pizza.pork.bytecode.*

class CompiledWorldLayout(val compiler: Compiler) : StubResolutionContext {
private val allStubOps = mutableListOf<StubOp>()
private val symbolTable = mutableMapOf<CompilableSymbol, SymbolInfo>()

fun add(symbol: CompilableSymbol) {
val start = allStubOps.size
val stubOps = symbol.compiledStubOps
symbolTable[symbol] = SymbolInfo(symbol.id, start.toUInt(), stubOps.size.toUInt())
allStubOps.addAll(stubOps)
}

private fun patch(): List<Op> {
val ops = mutableListOf<Op>()
for (stub in allStubOps) {
val actualArguments = stub.op.args.toMutableList()
stub.patch(this, actualArguments)
ops.add(Op(stub.op.code, actualArguments))
}
return ops
}

override fun resolveJumpTarget(symbol: CompilableSymbol): UInt {
return symbolTable[symbol]?.offset ?:
throw RuntimeException("Unable to resolve jump target: ${symbol.scopeSymbol.symbol.id}")
}

fun layoutCompiledWorld(): CompiledWorld {
val constantPool = mutableListOf<ByteArray>()
for (item in compiler.constantPool.all()) {
constantPool.add(item.value)
}
return CompiledWorld(ConstantPool(constantPool), SymbolTable(symbolTable.values.toList()), patch())
}
}
47 changes: 47 additions & 0 deletions compiler/src/main/kotlin/gay/pizza/pork/compiler/Compiler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package gay.pizza.pork.compiler

import gay.pizza.pork.bytecode.CompiledWorld
import gay.pizza.pork.bytecode.MutableConstantPool
import gay.pizza.pork.frontend.Slab
import gay.pizza.pork.frontend.scope.ScopeSymbol

class Compiler {
val constantPool: MutableConstantPool = MutableConstantPool()
val compilableSlabs: ComputableState<Slab, CompilableSlab> = ComputableState { slab ->
CompilableSlab(this, slab)
}

fun resolveOrNull(scopeSymbol: ScopeSymbol): CompilableSymbol? {
val compiledSlab = compilableSlabs.of(scopeSymbol.slabScope.slab)
return compiledSlab.compilableSymbolOf(scopeSymbol.symbol)
}

fun resolve(scopeSymbol: ScopeSymbol): CompilableSymbol = resolveOrNull(scopeSymbol) ?:
throw RuntimeException(
"Unable to resolve scope symbol: " +
"${scopeSymbol.slabScope.slab.location.commonFriendlyName} ${scopeSymbol.symbol.id}")

fun contributeCompiledSymbols(
into: MutableSet<CompilableSymbol>,
symbol: ScopeSymbol,
resolved: CompilableSymbol = resolve(symbol)
) {
if (!into.add(resolved)) {
return
}

for (used in resolved.usedSymbols) {
contributeCompiledSymbols(into, used)
}
}

fun compile(entryPointSymbol: CompilableSymbol): CompiledWorld {
val usedSymbolSet = mutableSetOf<CompilableSymbol>()
contributeCompiledSymbols(usedSymbolSet, entryPointSymbol.scopeSymbol, entryPointSymbol)
val layout = CompiledWorldLayout(this)
for (used in usedSymbolSet) {
layout.add(used)
}
return layout.layoutCompiledWorld()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package gay.pizza.pork.compiler

class ComputableState<X, T>(val computation: (X) -> T) {
private val state = StoredState<X, T>()

fun of(key: X): T = state.computeIfAbsent(key, computation)
}
11 changes: 11 additions & 0 deletions compiler/src/main/kotlin/gay/pizza/pork/compiler/LoopState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package gay.pizza.pork.compiler

import gay.pizza.pork.bytecode.MutableRel

class LoopState(
val startOfLoop: UInt,
val exitJumpTarget: MutableRel,
val body: MutableRel,
val scopeDepth: Int,
val enclosing: LoopState? = null
)
12 changes: 12 additions & 0 deletions compiler/src/main/kotlin/gay/pizza/pork/compiler/PatchRelOp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package gay.pizza.pork.compiler

import gay.pizza.pork.bytecode.MutableRel
import gay.pizza.pork.bytecode.Op

class PatchRelOp(op: Op, val index: Int, val symbol: CompilableSymbol, val rel: MutableRel) : StubOp(op) {
override fun patch(context: StubResolutionContext, arguments: MutableList<UInt>) {
arguments[index] = context.resolveJumpTarget(symbol) + rel.rel
}

override fun toString(): String = "PatchRelOp(${op}, ${index}, ${symbol}, ${rel})"
}
Loading

0 comments on commit 041848c

Please sign in to comment.