Skip to content

Commit

Permalink
vm: a functional virtual machine, mostly
Browse files Browse the repository at this point in the history
  • Loading branch information
azenla committed Nov 21, 2023
1 parent 4c50d48 commit 5540918
Show file tree
Hide file tree
Showing 39 changed files with 323 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import kotlinx.serialization.Serializable
data class CompiledWorld(
val constantPool: ConstantPool,
val symbolTable: SymbolTable,
val code: List<Op>
val code: List<Op>,
val annotations: List<OpAnnotation>
)
20 changes: 19 additions & 1 deletion bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Constant.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
package gay.pizza.pork.bytecode

class Constant(val id: UInt, val value: ByteArray)
import kotlinx.serialization.Serializable

@Serializable
data class Constant(val id: UInt, val tag: ConstantTag, val value: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
other as Constant

if (id != other.id) return false
if (!value.contentEquals(other.value)) return false
return true
}

override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + value.contentHashCode()
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ package gay.pizza.pork.bytecode
import kotlinx.serialization.Serializable

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

import kotlinx.serialization.Serializable

@Serializable
enum class ConstantTag {
String
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ package gay.pizza.pork.bytecode
class MutableConstantPool {
private val pool = mutableListOf<Constant>()

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

fun all(): List<Constant> = pool
fun build(): ConstantPool = ConstantPool(pool)
}
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 OpAnnotation(val inst: UInt, val text: String)
20 changes: 11 additions & 9 deletions bytecode/src/main/kotlin/gay/pizza/pork/bytecode/Opcode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ enum class Opcode(val id: UByte) {
BinaryAnd(28u),
BinaryOr(29u),
BinaryXor(30u),
List(31u),
Integer(32u),
Double(33u),
Call(34u),
EuclideanModulo(35u),
Remainder(36u),
Index(37u),
ScopeIn(38u),
ScopeOut(39u),
ListMake(31u),
ListSize(32u),
Integer(33u),
Double(34u),
Call(35u),
EuclideanModulo(36u),
Remainder(37u),
Index(38u),
ScopeIn(39u),
ScopeOut(40u),
ReturnAddress(41u),
End(255u),
}
11 changes: 10 additions & 1 deletion bytecode/src/main/kotlin/gay/pizza/pork/bytecode/SymbolTable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,13 @@ import kotlinx.serialization.Serializable
@Serializable
data class SymbolTable(
val symbols: List<SymbolInfo>
)
) {
fun lookup(inst: UInt): Pair<SymbolInfo, UInt>? {
val symbol = symbols.firstOrNull {
(inst >= it.offset) && inst < (it.offset + it.size)
} ?: return null

val rel = inst - symbol.offset
return symbol to rel
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import gay.pizza.pork.bytecode.Opcode

class CodeBuilder(val symbol: CompilableSymbol) {
private val ops = mutableListOf<StubOp>()
private val annotations = mutableListOf<StubOpAnnotation>()

val localState: LocalState = LocalState(symbol)

Expand Down Expand Up @@ -39,5 +40,9 @@ class CodeBuilder(val symbol: CompilableSymbol) {
ops.add(PatchSymOp(Op(code, arguments), patches))
}

fun build(): List<StubOp> = ops.toList()
fun annotate(text: String) {
annotations.add(StubOpAnnotation(symbol, nextOpInst(), text))
}

fun build(): CompiledSymbolResult = CompiledSymbolResult(ops.toList(), annotations.toList())
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ 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 compiledStubOps: CompiledSymbolResult by lazy { compile() }

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

private fun compile(): List<StubOp> {
private fun compile(): CompiledSymbolResult {
val emitter = StubOpEmitter(compilableSlab.compiler, this)
emitter.enter()
val what = if (scopeSymbol.definition is FunctionDefinition) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package gay.pizza.pork.compiler

class CompiledSymbolResult(val ops: List<StubOp>, val annotations: List<StubOpAnnotation>)
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import gay.pizza.pork.bytecode.*

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

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

private fun patch(): List<Op> {
Expand All @@ -23,16 +26,24 @@ class CompiledWorldLayout(val compiler: Compiler) : StubResolutionContext {
return ops
}

private fun patchAnnotations(): List<OpAnnotation> {
val annotations = mutableListOf<OpAnnotation>()
for (stub in allStubAnnotations) {
val actual = symbolTable[stub.symbol]!!.offset + stub.rel
annotations.add(OpAnnotation(actual, stub.text))
}
return annotations
}

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())
}
fun build(): CompiledWorld = CompiledWorld(
constantPool = compiler.constantPool.build(),
symbolTable = SymbolTable(symbolTable.values.toList()),
code = patch(),
annotations = patchAnnotations()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ class Compiler {
for (used in usedSymbolSet) {
layout.add(used)
}
return layout.layoutCompiledWorld()
return layout.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ class LocalState(val symbol: CompilableSymbol) {
return variable
}

fun createAnonymousLocal(): StubVar {
val scope = variables.last()
val variable = StubVar(localVarIndex++)
scope.add(variable)
return variable
}

fun pushScope() {
variables.add(mutableListOf())
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package gay.pizza.pork.compiler

data class StubOpAnnotation(val symbol: CompilableSymbol, val rel: UInt, val text: String)
52 changes: 42 additions & 10 deletions compiler/src/main/kotlin/gay/pizza/pork/compiler/StubOpEmitter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package gay.pizza.pork.compiler

import gay.pizza.pork.ast.FunctionLevelVisitor
import gay.pizza.pork.ast.gen.*
import gay.pizza.pork.bytecode.ConstantTag
import gay.pizza.pork.bytecode.MutableRel
import gay.pizza.pork.bytecode.Opcode

class StubOpEmitter(val compiler: Compiler, val symbol: CompilableSymbol) : FunctionLevelVisitor<Unit>() {
val code = CodeBuilder(symbol)
val code: CodeBuilder = CodeBuilder(symbol)

fun allocateOuterScope(definition: FunctionDefinition) {
val allNormalArguments = definition.arguments.takeWhile { !it.multiple }
Expand Down Expand Up @@ -55,7 +56,35 @@ class StubOpEmitter(val compiler: Compiler, val symbol: CompilableSymbol) : Func
}

override fun visitForIn(node: ForIn) {
TODO("ForIn is currently unsupported")
val listLocalVar = code.localState.createAnonymousLocal()
val sizeLocalVar = code.localState.createAnonymousLocal()
val currentIndexVar = code.localState.createAnonymousLocal()
val currentValueVar = code.localState.createLocal(node.item.symbol)
node.expression.visit(this)
code.emit(Opcode.StoreLocal, listOf(listLocalVar.index))
load(Loadable(stubVar = listLocalVar))
code.emit(Opcode.ListSize)
code.emit(Opcode.StoreLocal, listOf(sizeLocalVar.index))
code.emit(Opcode.Integer, listOf(0u))
code.emit(Opcode.StoreLocal, listOf(currentIndexVar.index))
val endOfLoop = MutableRel(0u)
val startOfLoop = code.nextOpInst()
code.localState.startLoop(startOfLoop, endOfLoop)
load(Loadable(stubVar = currentIndexVar))
load(Loadable(stubVar = sizeLocalVar))
code.emit(Opcode.CompareGreaterEqual)
code.patch(Opcode.JumpIf, listOf(0u), 0, symbol, endOfLoop)
load(Loadable(stubVar = currentIndexVar))
load(Loadable(stubVar = listLocalVar))
code.emit(Opcode.Index)
code.emit(Opcode.StoreLocal, listOf(currentValueVar.index))
node.block.visit(this)
code.emit(Opcode.LoadLocal, listOf(currentIndexVar.index))
code.emit(Opcode.Integer, listOf(1u))
code.emit(Opcode.Add)
code.emit(Opcode.StoreLocal, listOf(currentIndexVar.index))
code.patch(Opcode.Jump, listOf(0u), 0, symbol, startOfLoop)
endOfLoop.rel = code.nextOpInst()
}

override fun visitFunctionCall(node: FunctionCall) {
Expand All @@ -64,10 +93,12 @@ class StubOpEmitter(val compiler: Compiler, val symbol: CompilableSymbol) : Func
val targetSymbol = compiler.resolve(targetScopeSymbol)
val functionDefinition = targetSymbol.scopeSymbol.definition as FunctionDefinition
val retRel = MutableRel(0u)
code.patch(Opcode.Integer, listOf(0u), 0, symbol, retRel)

val normalArguments = mutableListOf<Expression>()
var variableArguments: List<Expression>? = null
if (functionDefinition.arguments.any { it.multiple }) {
variableArguments = emptyList()
}

for ((index, item) in functionDefinition.arguments.zip(node.arguments).withIndex()) {
val (spec, value) = item
if (spec.multiple) {
Expand All @@ -83,14 +114,15 @@ class StubOpEmitter(val compiler: Compiler, val symbol: CompilableSymbol) : Func
for (item in variableArguments.reversed()) {
item.visit(this)
}
code.emit(Opcode.List, listOf(variableArguments.size.toUInt()))
code.emit(Opcode.ListMake, listOf(variableArguments.size.toUInt()))
}

for (item in normalArguments.reversed()) {
visit(item)
}

retRel.rel = code.nextOpInst() + 1u
retRel.rel = code.nextOpInst() + 2u
code.patch(Opcode.ReturnAddress, listOf(0u), 0, symbol, retRel)
code.patch(Opcode.Call, listOf(0u), mapOf(0 to targetSymbol))
}

Expand Down Expand Up @@ -154,7 +186,7 @@ class StubOpEmitter(val compiler: Compiler, val symbol: CompilableSymbol) : Func
for (item in node.items) {
item.visit(this)
}
code.emit(Opcode.List, listOf(count.toUInt()))
code.emit(Opcode.ListMake, listOf(count.toUInt()))
}

override fun visitLongLiteral(node: LongLiteral) {
Expand Down Expand Up @@ -190,7 +222,7 @@ class StubOpEmitter(val compiler: Compiler, val symbol: CompilableSymbol) : Func

override fun visitStringLiteral(node: StringLiteral) {
val bytes = node.text.toByteArray()
val constant = compiler.constantPool.assign(bytes)
val constant = compiler.constantPool.assign(ConstantTag.String, bytes)
code.emit(Opcode.Constant, listOf(constant))
}

Expand Down Expand Up @@ -243,10 +275,10 @@ class StubOpEmitter(val compiler: Compiler, val symbol: CompilableSymbol) : Func

override fun visitNativeFunctionDescriptor(node: NativeFunctionDescriptor) {
for (def in node.definitions) {
val defConstant = compiler.constantPool.assign(def.text.toByteArray())
val defConstant = compiler.constantPool.assign(ConstantTag.String, def.text.toByteArray())
code.emit(Opcode.Constant, listOf(defConstant))
}
val formConstant = compiler.constantPool.assign(node.form.id.toByteArray())
val formConstant = compiler.constantPool.assign(ConstantTag.String, node.form.id.toByteArray())
code.emit(Opcode.Native, listOf(formConstant, node.definitions.size.toUInt()))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import gay.pizza.pork.ast.gen.Symbol

class StubVar(
val index: UInt,
val symbol: Symbol
val symbol: Symbol? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.intellij.navigation.ItemPresentation
import gay.pizza.pork.idea.psi.PorkElementHelpers
import javax.swing.Icon

class NativeElement(node: ASTNode) : PorkElement(node) {
class NativeFunctionDescriptorElement(node: ASTNode) : PorkElement(node) {
override fun getIcon(flags: Int): Icon? =
PorkElementHelpers.iconOf(this)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ object PorkElementFactory {
NodeType.Break -> BreakElement(node)
NodeType.Continue -> ContinueElement(node)
NodeType.NoneLiteral -> NoneLiteralElement(node)
NodeType.Native -> NativeElement(node)
NodeType.NativeFunctionDescriptor -> NativeFunctionDescriptorElement(node)
NodeType.IndexedBy -> IndexedByElement(node)
else -> ASTWrapperPsiElement(node)
}
Expand Down
7 changes: 6 additions & 1 deletion tool/src/main/kotlin/gay/pizza/pork/tool/CompileCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ class CompileCommand : CliktCommand(help = "Compile Pork to Bytecode", name = "c
val code = compiledWorld.code.subList(symbol.offset.toInt(), (symbol.offset + symbol.size).toInt())
println(symbol.id)
for ((index, op) in code.withIndex()) {
println(" ${symbol.offset + index.toUInt()} ${op.code.name} ${op.args.joinToString(" ")}")
var annotation = ""
val annotations = compiledWorld.annotations.filter { it.inst == (symbol.offset + index.toUInt()) }
if (annotations.isNotEmpty()) {
annotation = " ; ${annotations.joinToString(", ") { it.text}}"
}
println(" ${symbol.offset + index.toUInt()} ${op.code.name} ${op.args.joinToString(" ")}${annotation}")
}
}
val vm = VirtualMachine(compiledWorld)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ScopeAnalysisCommand : CliktCommand(help = "Run Scope Analysis", name = "s
"symbol ${visibleScopeSymbol.scopeSymbol.symbol.id} " +
"type=${visibleScopeSymbol.scopeSymbol.definition.type.name} " +
"internal=${visibleScopeSymbol.isInternalSymbol} " +
"slab=${visibleScopeSymbol.scopeSymbol.slab.location.commonFriendlyName}"
"slab=${visibleScopeSymbol.scopeSymbol.slabScope.slab.location.commonFriendlyName}"
)
}
}
Expand Down
Loading

0 comments on commit 5540918

Please sign in to comment.