compiler: full support for IR based compilation

This commit is contained in:
Alex Zenla 2023-11-23 21:48:10 -08:00
parent 2d88666f05
commit 8951c3cd60
Signed by: alex
GPG Key ID: C0780728420EBFE5
32 changed files with 554 additions and 400 deletions

View File

@ -1,10 +1,10 @@
package gay.pizza.pork.bir
data class IrCall(
val target: IrSymbol,
override val target: IrSymbol,
val arguments: List<IrCodeElement>,
val variableArguments: List<IrCodeElement>?
) : IrCodeElement {
) : IrCodeElement, IrSymbolUser {
override fun crawl(block: (IrElement) -> Unit) {
block(target)
arguments.forEach(block)

View File

@ -1,3 +1,7 @@
package gay.pizza.pork.bir
data class IrCodeBlock(val items: List<IrCodeElement>) : IrCodeElement
data class IrCodeBlock(val items: List<IrCodeElement>) : IrCodeElement {
override fun crawl(block: (IrElement) -> Unit) {
items.forEach(block)
}
}

View File

@ -1,6 +1,6 @@
package gay.pizza.pork.bir
data class IrContinue(val target: IrSymbol) : IrCodeElement {
data class IrContinue(override val target: IrSymbol) : IrCodeElement, IrSymbolUser {
override fun crawl(block: (IrElement) -> Unit) {
block(target)
}

View File

@ -1,12 +1,14 @@
package gay.pizza.pork.bir
data class IrDefinition(
val symbol: IrSymbol,
override val symbol: IrSymbol,
val type: IrDefinitionType,
val arguments: List<IrFunctionArgument>,
val code: IrCodeBlock
) : IrElement {
) : IrElement, IrSymbolOwner {
override fun crawl(block: (IrElement) -> Unit) {
block(symbol)
arguments.forEach(block)
block(code)
}
}

View File

@ -2,5 +2,6 @@ package gay.pizza.pork.bir
enum class IrDefinitionType {
Variable,
Function
CodeFunction,
NativeFunction
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.bir
data class IrFunctionArgument(override val symbol: IrSymbol) : IrSymbolOwner {
override fun crawl(block: (IrElement) -> Unit) {}
}

View File

@ -1,7 +1,7 @@
package gay.pizza.pork.bir
data class IrLoad(val symbol: IrSymbol) : IrCodeElement {
data class IrLoad(override val target: IrSymbol) : IrCodeElement, IrSymbolUser {
override fun crawl(block: (IrElement) -> Unit) {
block(symbol)
block(target)
}
}

View File

@ -1,6 +1,10 @@
package gay.pizza.pork.bir
data class IrLoop(val symbol: IrSymbol, val condition: IrCodeElement, val inner: IrCodeElement) : IrCodeElement {
data class IrLoop(
override val symbol: IrSymbol,
val condition: IrCodeElement,
val inner: IrCodeElement
) : IrCodeElement, IrSymbolOwner {
override fun crawl(block: (IrElement) -> Unit) {
block(symbol)
block(condition)

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.bir
data class IrNativeDefinition(val form: String, val definitions: List<String>) : IrCodeElement {
override fun crawl(block: (IrElement) -> Unit) {}
}

View File

@ -1,6 +1,6 @@
package gay.pizza.pork.bir
data class IrStore(val symbol: IrSymbol, val value: IrCodeElement) : IrCodeElement {
data class IrStore(override val target: IrSymbol, val value: IrCodeElement) : IrCodeElement, IrSymbolUser {
override fun crawl(block: (IrElement) -> Unit) {
value.crawl(block)
}

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.bir
data class IrSuffix(val op: IrSuffixOp, override val target: IrSymbol) : IrCodeElement, IrSymbolUser {
override fun crawl(block: (IrElement) -> Unit) {
block(target)
}
}

View File

@ -0,0 +1,6 @@
package gay.pizza.pork.bir
enum class IrSuffixOp {
Increment,
Decrement
}

View File

@ -0,0 +1,40 @@
package gay.pizza.pork.bir
class IrSymbolGraph {
private val edges = mutableSetOf<Pair<IrSymbolUser, IrSymbolOwner>>()
private fun crawlForKnown(known: MutableMap<IrSymbol, IrSymbolOwner>, root: IrElement) {
if (root is IrSymbolOwner) {
known[root.symbol] = root
}
root.crawl { item ->
crawlForKnown(known, item)
}
}
private fun crawlForAssociations(known: Map<IrSymbol, IrSymbolOwner>, root: IrElement) {
if (root is IrSymbolUser) {
val what = known[root.target]
if (what != null) {
edges.add(root to what)
}
}
root.crawl { item ->
crawlForAssociations(known, item)
}
}
fun crawl(root: IrElement) {
val known = mutableMapOf<IrSymbol, IrSymbolOwner>()
crawlForKnown(known, root)
crawlForAssociations(known, root)
}
fun forEachEdge(block: (IrSymbolUser, IrSymbolOwner) -> Unit) {
for ((from, to) in edges) {
block(from, to)
}
}
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.bir
sealed interface IrSymbolOwner : IrElement {
val symbol: IrSymbol
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.bir
sealed interface IrSymbolUser : IrElement {
val target: IrSymbol
}

View File

@ -0,0 +1,59 @@
package gay.pizza.pork.bir
interface IrVisitor<T> {
fun visitIrSlab(ir: IrSlab): T
fun visitIrSlabLocation(ir: IrSlabLocation): T
fun visitIrDefinition(ir: IrDefinition): T
fun visitIrSymbol(ir: IrSymbol): T
fun visitIrBeak(ir: IrBreak): T
fun visitIrCall(ir: IrCall): T
fun visitIrCodeBlock(ir: IrCodeBlock): T
fun visitIrConditional(ir: IrConditional): T
fun visitIrBooleanConstant(ir: IrBooleanConstant): T
fun visitIrIntegerConstant(ir: IrIntegerConstant): T
fun visitIrLongConstant(ir: IrLongConstant): T
fun visitIrDoubleConstant(ir: IrDoubleConstant): T
fun visitIrStringConstant(ir: IrStringConstant): T
fun visitIrNoneConstant(ir: IrNoneConstant): T
fun visitIrContinue(ir: IrContinue): T
fun visitIrInfix(ir: IrInfix): T
fun visitIrList(ir: IrList): T
fun visitIrLoad(ir: IrLoad): T
fun visitIrLoop(ir: IrLoop): T
fun visitIrPrefix(ir: IrPrefix): T
fun visitIrReturn(ir: IrReturn): T
fun visitIrStore(ir: IrStore): T
fun visitIrSuffix(ir: IrSuffix): T
fun visitIrWorld(ir: IrWorld): T
fun visitIrNativeDefinition(ir: IrNativeDefinition): T
fun visitIrFunctionArgument(ir: IrFunctionArgument): T
fun visit(ir: IrElement): T = when (ir) {
is IrBreak -> visitIrBeak(ir)
is IrCall -> visitIrCall(ir)
is IrCodeBlock -> visitIrCodeBlock(ir)
is IrConditional -> visitIrConditional(ir)
is IrBooleanConstant -> visitIrBooleanConstant(ir)
is IrDoubleConstant -> visitIrDoubleConstant(ir)
is IrIntegerConstant -> visitIrIntegerConstant(ir)
is IrLongConstant -> visitIrLongConstant(ir)
is IrNoneConstant -> visitIrNoneConstant(ir)
is IrStringConstant -> visitIrStringConstant(ir)
is IrContinue -> visitIrContinue(ir)
is IrInfix -> visitIrInfix(ir)
is IrList -> visitIrList(ir)
is IrLoad -> visitIrLoad(ir)
is IrLoop -> visitIrLoop(ir)
is IrPrefix -> visitIrPrefix(ir)
is IrReturn -> visitIrReturn(ir)
is IrStore -> visitIrStore(ir)
is IrSuffix -> visitIrSuffix(ir)
is IrDefinition -> visitIrDefinition(ir)
is IrSlab -> visitIrSlab(ir)
is IrSlabLocation -> visitIrSlabLocation(ir)
is IrSymbol -> visitIrSymbol(ir)
is IrWorld -> visitIrWorld(ir)
is IrNativeDefinition -> visitIrNativeDefinition(ir)
is IrFunctionArgument -> visitIrFunctionArgument(ir)
}
}

View File

@ -1,7 +1,7 @@
package gay.pizza.pork.bir
data class IrAccess(val target: IrSymbol) : IrCodeElement {
data class IrWorld(val slabs: List<IrSlab>) : IrElement {
override fun crawl(block: (IrElement) -> Unit) {
block(target)
slabs.forEach(block)
}
}

View File

@ -1,3 +1,3 @@
package gay.pizza.pork.bytecode
class MutableRel(var rel: UInt)
data class MutableRel(var rel: UInt)

View File

@ -1,3 +0,0 @@
package gay.pizza.pork.bytecode
class Ops(val ops: List<Op>)

View File

@ -1,9 +1,14 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.bir.IrDefinition
import gay.pizza.pork.bir.IrSlab
import gay.pizza.pork.bir.IrSlabLocation
import gay.pizza.pork.frontend.Slab
class CompilableSlab(val compiler: Compiler, val slab: Slab) {
val compiledIrSlab: IrSlab by lazy { compileIrSlab() }
val compilableSymbols: List<CompilableSymbol> by lazy {
slab.scope.internalSymbols.map { symbol ->
CompilableSymbol(this, symbol)
@ -18,4 +23,13 @@ class CompilableSlab(val compiler: Compiler, val slab: Slab) {
val scopeSymbol = slab.scope.resolve(symbol) ?: return null
return compiler.resolveOrNull(scopeSymbol)
}
private fun compileIrSlab(): IrSlab {
val definitions = mutableListOf<IrDefinition>()
for (compilableSymbol in compilableSymbols) {
definitions.add(compilableSymbol.compiledIrDefinition)
}
val irSlabLocation = IrSlabLocation(slab.location.form, slab.location.filePath)
return IrSlab(irSlabLocation, definitions)
}
}

View File

@ -2,29 +2,62 @@ 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.NativeFunctionDescriptor
import gay.pizza.pork.ast.gen.visit
import gay.pizza.pork.bir.IrCodeBlock
import gay.pizza.pork.bir.IrDefinition
import gay.pizza.pork.bir.IrDefinitionType
import gay.pizza.pork.bir.IrSymbolTag
import gay.pizza.pork.frontend.scope.ScopeSymbol
class CompilableSymbol(val compilableSlab: CompilableSlab, val scopeSymbol: ScopeSymbol) {
val compiledIrDefinition: IrDefinition by lazy { compileIrDefinition() }
val compiledStubOps: CompiledSymbolResult by lazy { compile() }
val usedSymbols: List<ScopeSymbol>
get() = scopeSymbol.scope.usedSymbols
private fun compile(): CompiledSymbolResult {
val emitter = StubOpEmitter(compilableSlab.compiler, this)
emitter.enter()
val code = CodeBuilder(this)
val ir = compiledIrDefinition
val emitter = IrStubOpEmitter(ir, code)
emitter.visit(ir.code)
emitter.final()
return emitter.code.build()
}
private fun compileIrDefinition(): IrDefinition {
val compiler = compilableSlab.compiler
val functionSymbol = compiler.irSymbolWorld.create(scopeSymbol, IrSymbolTag.Function)
val irCodeEmitter = IrCodeEmitter(
self = functionSymbol,
irSymbolWorld = compiler.irSymbolWorld,
irSymbolAssignment = compiler.irSymbolAssignment,
scope = compilableSlab.slab.scope
)
irCodeEmitter.enterLocalScope()
val what = if (scopeSymbol.definition is FunctionDefinition) {
val functionDefinition = scopeSymbol.definition as FunctionDefinition
emitter.allocateOuterScope(functionDefinition)
irCodeEmitter.createFunctionArguments(functionDefinition)
functionDefinition.block ?: functionDefinition.nativeFunctionDescriptor!!
} else {
val letDefinition = scopeSymbol.definition as LetDefinition
letDefinition.value
}
emitter.visit(what)
emitter.exit()
return emitter.code.build()
val type = if (what is NativeFunctionDescriptor) {
IrDefinitionType.NativeFunction
} else IrDefinitionType.CodeFunction
val irCodeElement = irCodeEmitter.visit(what)
val irCodeBlock = if (irCodeElement is IrCodeBlock) {
irCodeElement
} else IrCodeBlock(listOf(irCodeElement))
irCodeEmitter.exitLocalScope()
return IrDefinition(
symbol = functionSymbol,
type = type,
arguments = irCodeEmitter.functionArguments,
code = irCodeBlock
)
}
val id: String

View File

@ -1,5 +1,6 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bir.IrSymbolAssignment
import gay.pizza.pork.bytecode.CompiledWorld
import gay.pizza.pork.bytecode.MutableConstantPool
import gay.pizza.pork.frontend.Slab
@ -11,6 +12,9 @@ class Compiler {
CompilableSlab(this, slab)
}
val irSymbolAssignment: IrSymbolAssignment = IrSymbolAssignment()
val irSymbolWorld: IrSymbolWorld<Any> = IrSymbolWorld(irSymbolAssignment)
fun resolveOrNull(scopeSymbol: ScopeSymbol): CompilableSymbol? {
val compiledSlab = compilableSlabs.of(scopeSymbol.slabScope.slab)
return compiledSlab.resolve(scopeSymbol.symbol)

View File

@ -8,12 +8,23 @@ import gay.pizza.pork.frontend.scope.SlabScope
class IrCodeEmitter(
val self: IrSymbol,
val irSymbolWorld: IrSymbolWorld,
val irSymbolWorld: IrSymbolWorld<Any>,
val irSymbolAssignment: IrSymbolAssignment,
val scope: SlabScope
) : FunctionLevelVisitor<IrCodeElement>() {
private val loopSymbols = mutableListOf<IrSymbol>()
private val localVariables = mutableListOf<MutableList<LocalVariable>>()
private val localVariables = mutableListOf<MutableMap<String, LocalVariable>>()
var functionArguments: List<IrFunctionArgument> = emptyList()
fun createFunctionArguments(functionDefinition: FunctionDefinition) {
val functionSymbols = mutableListOf<IrFunctionArgument>()
for (arg in functionDefinition.arguments) {
val symbol = createLocalVariable(arg.symbol)
functionSymbols.add(IrFunctionArgument(symbol))
}
functionArguments = functionSymbols
}
private fun startLoop(): IrSymbol {
val symbol = irSymbolAssignment.next(IrSymbolTag.Loop)
@ -34,19 +45,24 @@ class IrCodeEmitter(
}
}
private fun enterBlockScope() {
val locals = mutableListOf<LocalVariable>()
fun enterLocalScope() {
val locals = mutableMapOf<String, LocalVariable>()
localVariables.add(locals)
}
private fun exitBlockScope() {
fun exitLocalScope() {
localVariables.removeLast()
}
private fun createLocalVariable(name: Symbol): IrSymbol {
val symbol = irSymbolAssignment.next(IrSymbolTag.Local)
val variable = LocalVariable(symbol, name)
localVariables.last().add(variable)
val variables = localVariables.last()
val existing = variables[name.id]
if (existing != null) {
throw CompileError("Unable to define local variable '${name.id}' within this scope, it already exists", name)
}
variables[name.id] = variable
return symbol
}
@ -58,10 +74,10 @@ class IrCodeEmitter(
}
private fun lookupLocalVariable(name: Symbol): IrSymbol? {
for (i in 0..localVariables.size) {
val b = localVariables.size - i - 1
for (i in 1..localVariables.size) {
val b = localVariables.size - i
val scope = localVariables[b]
val found = scope.firstOrNull { it.name == name }
val found = scope[name.id]
if (found != null) {
return found.symbol
}
@ -76,20 +92,20 @@ class IrCodeEmitter(
}
val scoped = scope.resolve(name)
if (scoped != null) {
return irSymbolWorld.lookup(scoped, scopeSymbolToTag(scoped))
return irSymbolWorld.create(scoped, scopeSymbolToTag(scoped))
}
return null
}
private fun lookupFunction(name: Symbol): Pair<ScopeSymbol, IrSymbol>? {
val scoped = scope.resolve(name) ?: return null
return scoped to irSymbolWorld.lookup(scoped, scopeSymbolToTag(scoped))
return scoped to irSymbolWorld.create(scoped, scopeSymbolToTag(scoped))
}
override fun visitBlock(node: Block): IrCodeBlock {
enterBlockScope()
enterLocalScope()
val block = IrCodeBlock(node.expressions.map { it.visit(this) })
exitBlockScope()
exitLocalScope()
return block
}
@ -149,7 +165,7 @@ class IrCodeEmitter(
}
}
if (functionDefinition.arguments.any { it.multiple }) {
if (variableArguments == null && functionDefinition.arguments.any { it.multiple }) {
variableArguments = mutableListOf()
}
@ -234,7 +250,13 @@ class IrCodeEmitter(
IrStringConstant(node.text)
override fun visitSuffixOperation(node: SuffixOperation): IrCodeElement {
TODO("Not yet implemented")
val op = when (node.op) {
SuffixOperator.Increment -> IrSuffixOp.Increment
SuffixOperator.Decrement -> IrSuffixOp.Decrement
}
val symbol = lookup(node.reference.symbol) ?: throw CompileError(
"Unable to find symbol for suffix operation '${node.reference.symbol.id}'", node)
return IrSuffix(op, symbol)
}
override fun visitSymbolReference(node: SymbolReference): IrCodeElement {
@ -255,4 +277,9 @@ class IrCodeEmitter(
inner = node.block.visit(this)
)
}
override fun visitNativeFunctionDescriptor(node: NativeFunctionDescriptor): IrCodeElement = IrNativeDefinition(
form = node.form.id,
definitions = node.definitions.map { it.text }
)
}

View File

@ -0,0 +1,33 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bir.*
interface IrCodeVisitor<T> : IrVisitor<T> {
override fun visitIrDefinition(ir: IrDefinition): T {
codeOnlyError("IrDefinition")
}
override fun visitIrSlab(ir: IrSlab): T {
codeOnlyError("IrSlab")
}
override fun visitIrSlabLocation(ir: IrSlabLocation): T {
codeOnlyError("IrSlabLocation")
}
override fun visitIrWorld(ir: IrWorld): T {
codeOnlyError("IrWorld")
}
override fun visitIrSymbol(ir: IrSymbol): T {
codeOnlyError("IrSymbol")
}
override fun visitIrFunctionArgument(ir: IrFunctionArgument): T {
codeOnlyError("IrFunctionArgument")
}
private fun codeOnlyError(type: String): Nothing {
throw RuntimeException("This visitor targets only code, and $type is not a code element.")
}
}

View File

@ -0,0 +1,222 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.bir.*
import gay.pizza.pork.bytecode.ConstantTag
import gay.pizza.pork.bytecode.MutableRel
import gay.pizza.pork.bytecode.Opcode
class IrStubOpEmitter(val irDefinition: IrDefinition, val code: CodeBuilder) : IrCodeVisitor<Unit> {
private val symbol = code.symbol
private val functionArgumentCount = irDefinition.arguments.size
init {
for (argument in irDefinition.arguments.reversed()) {
val stubVar = code.localState.createOrFindLocal(argument.symbol)
code.emit(Opcode.StoreLocal, listOf(stubVar.index))
}
}
fun final() {
if (irDefinition.type == IrDefinitionType.CodeFunction) {
code.emit(Opcode.None)
}
code.emit(Opcode.Return)
code.emit(Opcode.End)
}
private fun resolve(symbol: IrSymbol): Loadable = code.localState.resolve(symbol)
private fun load(callOrStubVar: Loadable) {
if (callOrStubVar.stubVar != null) {
code.emit(Opcode.LoadLocal, listOf(callOrStubVar.stubVar.index))
} else {
code.emit(Opcode.Integer, listOf(code.nextOpInst() + 2u))
code.patch(Opcode.Call, listOf(0u), mapOf(0 to callOrStubVar.call!!))
}
}
private fun store(stubVar: StubVar) {
code.emit(Opcode.StoreLocal, listOf(stubVar.index))
}
override fun visitIrBeak(ir: IrBreak) {
val loop = code.localState.findLoopState(ir.target)
code.patch(Opcode.Jump, listOf(0u), 0, symbol, loop.exitJumpTarget)
}
override fun visitIrCall(ir: IrCall) {
val target = resolve(ir.target)
val targetSymbol = target.call!!
val retRel = MutableRel(0u)
for (argument in ir.arguments) {
visit(argument)
}
val variableArguments = ir.variableArguments
if (variableArguments != null) {
for (argument in variableArguments) {
visit(argument)
}
code.emit(Opcode.ListMake, listOf(variableArguments.size.toUInt()))
}
retRel.rel = code.nextOpInst() + 2u
code.patch(Opcode.ReturnAddress, listOf(0u), 0, symbol, retRel)
code.patch(Opcode.Call, listOf(0u), mapOf(0 to targetSymbol))
}
override fun visitIrCodeBlock(ir: IrCodeBlock) {
for (item in ir.items) {
visit(item)
}
}
override fun visitIrConditional(ir: IrConditional) {
val thenRel = MutableRel(0u)
val endRel = MutableRel(0u)
visit(ir.conditional)
code.patch(Opcode.JumpIf, listOf(0u), 0, symbol, thenRel)
visit(ir.ifTrue)
code.patch(Opcode.Jump, listOf(0u), 0, symbol, endRel)
thenRel.rel = code.nextOpInst()
visit(ir.ifFalse)
endRel.rel = code.nextOpInst()
}
override fun visitIrBooleanConstant(ir: IrBooleanConstant) {
code.emit(if (ir.value) Opcode.True else Opcode.False)
}
override fun visitIrIntegerConstant(ir: IrIntegerConstant) {
code.emit(Opcode.Integer, listOf(ir.value.toUInt()))
}
override fun visitIrLongConstant(ir: IrLongConstant) {
code.emit(Opcode.Integer, listOf(ir.value.toUInt()))
}
override fun visitIrDoubleConstant(ir: IrDoubleConstant) {
code.emit(Opcode.Integer, listOf(ir.value.toUInt()))
}
override fun visitIrStringConstant(ir: IrStringConstant) {
val bytes = ir.value.toByteArray()
val constant = symbol.compilableSlab.compiler.constantPool.assign(ConstantTag.String, bytes)
code.emit(Opcode.Constant, listOf(constant))
}
override fun visitIrNoneConstant(ir: IrNoneConstant) {
code.emit(Opcode.None)
}
override fun visitIrContinue(ir: IrContinue) {
val loop = code.localState.findLoopState(ir.target)
code.patch(Opcode.Jump, listOf(0u), 0, symbol, loop.exitJumpTarget)
}
override fun visitIrInfix(ir: IrInfix) {
visit(ir.left)
visit(ir.right)
when (ir.op) {
IrInfixOp.Add -> code.emit(Opcode.Add)
IrInfixOp.Subtract -> code.emit(Opcode.Subtract)
IrInfixOp.Multiply -> code.emit(Opcode.Multiply)
IrInfixOp.Divide -> code.emit(Opcode.Divide)
IrInfixOp.Equals -> code.emit(Opcode.CompareEqual)
IrInfixOp.NotEquals -> {
code.emit(Opcode.CompareEqual)
code.emit(Opcode.Not)
}
IrInfixOp.EuclideanModulo -> code.emit(Opcode.EuclideanModulo)
IrInfixOp.Remainder -> code.emit(Opcode.Remainder)
IrInfixOp.Lesser -> code.emit(Opcode.CompareLesser)
IrInfixOp.Greater -> code.emit(Opcode.CompareGreater)
IrInfixOp.GreaterEqual -> code.emit(Opcode.CompareGreaterEqual)
IrInfixOp.LesserEqual -> code.emit(Opcode.CompareLesserEqual)
IrInfixOp.BooleanAnd -> code.emit(Opcode.And)
IrInfixOp.BooleanOr -> code.emit(Opcode.Or)
IrInfixOp.BinaryAnd -> code.emit(Opcode.BinaryAnd)
IrInfixOp.BinaryOr -> code.emit(Opcode.BinaryOr)
IrInfixOp.BinaryExclusiveOr -> code.emit(Opcode.BinaryXor)
}
}
override fun visitIrList(ir: IrList) {
val count = ir.items.size
for (item in ir.items) {
visit(item)
}
code.emit(Opcode.ListMake, listOf(count.toUInt()))
}
override fun visitIrLoad(ir: IrLoad) {
val loadable = resolve(ir.target)
load(loadable)
}
override fun visitIrLoop(ir: IrLoop) {
val startOfBody = MutableRel(0u)
val startOfLoop = MutableRel(0u)
val endOfLoop = MutableRel(0u)
code.localState.startLoop(ir.symbol, code.nextOpInst(), endOfLoop)
startOfLoop.rel = code.nextOpInst()
visit(ir.condition)
code.patch(Opcode.JumpIf, listOf(0u), 0, symbol, startOfBody)
code.patch(Opcode.Jump, listOf(0u), 0, symbol, endOfLoop)
startOfBody.rel = code.nextOpInst()
visit(ir.inner)
code.patch(Opcode.Jump, listOf(0u), 0, symbol, startOfLoop)
endOfLoop.rel = code.nextOpInst()
code.localState.endLoop(ir.symbol)
}
override fun visitIrPrefix(ir: IrPrefix) {
visit(ir.value)
when (ir.op) {
IrPrefixOp.BooleanNot -> code.emit(Opcode.Not)
IrPrefixOp.UnaryPlus -> code.emit(Opcode.UnaryPlus)
IrPrefixOp.UnaryMinus -> code.emit(Opcode.UnaryMinus)
IrPrefixOp.BinaryNot -> code.emit(Opcode.BinaryNot)
}
}
override fun visitIrReturn(ir: IrReturn) {
visit(ir.value)
code.emit(Opcode.Return)
}
override fun visitIrStore(ir: IrStore) {
visit(ir.value)
val variable = code.localState.createOrFindLocal(ir.target)
store(variable)
}
override fun visitIrSuffix(ir: IrSuffix) {
val loadable = code.localState.resolve(ir.target)
load(loadable)
when (ir.op) {
IrSuffixOp.Increment -> {
code.emit(Opcode.Integer, listOf(1u))
code.emit(Opcode.Add, emptyList())
}
IrSuffixOp.Decrement-> {
code.emit(Opcode.Integer, listOf(1u))
code.emit(Opcode.Subtract, emptyList())
}
}
store(loadable.stubVar!!)
}
override fun visitIrNativeDefinition(ir: IrNativeDefinition) {
for (def in ir.definitions.reversed()) {
val defConstant = symbol.compilableSlab.compiler.constantPool.assign(
ConstantTag.String,
def.encodeToByteArray()
)
code.emit(Opcode.Constant, listOf(defConstant))
}
val formConstant = symbol.compilableSlab.compiler.constantPool.assign(
ConstantTag.String,
ir.form.encodeToByteArray()
)
code.emit(Opcode.Native, listOf(formConstant, ir.definitions.size.toUInt(), functionArgumentCount.toUInt()))
}
}

View File

@ -4,10 +4,13 @@ import gay.pizza.pork.bir.IrSymbol
import gay.pizza.pork.bir.IrSymbolAssignment
import gay.pizza.pork.bir.IrSymbolTag
class IrSymbolWorld(val irSymbolAssignment: IrSymbolAssignment) {
private val symbols = mutableMapOf<Any, IrSymbol>()
class IrSymbolWorld<T>(val irSymbolAssignment: IrSymbolAssignment) {
private val symbols = mutableMapOf<T, IrSymbol>()
fun lookup(value: Any, tag: IrSymbolTag): IrSymbol = symbols.getOrPut(value) {
fun create(value: T, tag: IrSymbolTag): IrSymbol = symbols.getOrPut(value) {
irSymbolAssignment.next(tag)
}
fun resolve(value: T): IrSymbol? = symbols[value]
fun resolve(symbol: IrSymbol): T? = symbols.entries.firstOrNull { it.value == symbol }?.key
}

View File

@ -1,62 +1,55 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.bir.IrSymbol
import gay.pizza.pork.bytecode.MutableRel
import gay.pizza.pork.frontend.scope.ScopeSymbol
class LocalState(val symbol: CompilableSymbol) {
private var internalLoopState: LoopState? = null
val loopState: LoopState?
get() = internalLoopState
private var localVarIndex: UInt = 0u
private val variables = mutableListOf<MutableList<StubVar>>()
private val stubVariables = mutableMapOf<IrSymbol, StubVar>()
private val loops = mutableMapOf<IrSymbol, LoopState>()
fun startLoop(startOfLoop: UInt, exitJumpTarget: MutableRel) {
internalLoopState = LoopState(
fun startLoop(symbol: IrSymbol, startOfLoop: UInt, exitJumpTarget: MutableRel) {
val existing = loops[symbol]
if (existing != null) {
throw CompileError("Starting loop that already is started")
}
val loopState = LoopState(
startOfLoop = startOfLoop,
exitJumpTarget = exitJumpTarget,
scopeDepth = (internalLoopState?.scopeDepth ?: 0u) + 1u,
enclosing = internalLoopState
scopeDepth = (internalLoopState?.scopeDepth ?: 0u) + 1u
)
loops[symbol] = loopState
}
fun endLoop() {
internalLoopState = internalLoopState?.enclosing
fun findLoopState(symbol: IrSymbol): LoopState =
loops[symbol] ?: throw CompileError("Unable to find target loop")
fun endLoop(symbol: IrSymbol) {
loops.remove(symbol) ?: throw CompileError("End of loop target not found")
}
fun createLocal(symbol: Symbol): StubVar {
val scope = variables.last()
val variable = StubVar(localVarIndex++, symbol)
scope.add(variable)
fun createOrFindLocal(symbol: IrSymbol): StubVar {
val existing = stubVariables[symbol]
if (existing != null) {
return existing
}
val variable = StubVar(localVarIndex++, symbol.id)
stubVariables[symbol] = variable
return variable
}
fun createAnonymousLocal(): StubVar {
val scope = variables.last()
val variable = StubVar(localVarIndex++)
scope.add(variable)
return variable
}
fun pushScope() {
variables.add(mutableListOf())
}
fun popScope() {
variables.removeLast()
}
fun resolve(symbol: Symbol): Loadable {
for (scope in variables.reversed()) {
val found = scope.firstOrNull { it.symbol == symbol }
if (found != null) {
return Loadable(stubVar = found)
}
fun resolve(symbol: IrSymbol): Loadable {
val localStubVar = stubVariables[symbol]
if (localStubVar != null) {
return Loadable(stubVar = localStubVar)
}
val found = this.symbol.compilableSlab.resolveVisible(symbol)
if (found != null) {
return Loadable(call = found)
}
throw RuntimeException("Unable to resolve symbol: ${symbol.id}")
val value = this.symbol.compilableSlab.compiler.irSymbolWorld.resolve(symbol) ?:
throw RuntimeException("Unable to resolve symbol: ${symbol.id} ${symbol.tag}")
val scopeSymbol = value as ScopeSymbol
val call = this.symbol.compilableSlab.compiler.resolve(scopeSymbol)
return Loadable(call = call)
}
}

View File

@ -5,6 +5,5 @@ import gay.pizza.pork.bytecode.MutableRel
class LoopState(
val startOfLoop: UInt,
val exitJumpTarget: MutableRel,
val scopeDepth: UInt,
val enclosing: LoopState? = null
val scopeDepth: UInt
)

View File

@ -1,301 +0,0 @@
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 = CodeBuilder(symbol)
fun allocateOuterScope(definition: FunctionDefinition) {
val allNormalArguments = definition.arguments.takeWhile { !it.multiple }
val varArgument = definition.arguments.firstOrNull { it.multiple }
for (arg in allNormalArguments.reversed()) {
val functionLocal = code.localState.createLocal(arg.symbol)
code.emit(Opcode.StoreLocal, listOf(functionLocal.index))
}
if (varArgument != null) {
val functionLocal = code.localState.createLocal(varArgument.symbol)
code.emit(Opcode.StoreLocal, listOf(functionLocal.index))
}
}
fun enter() {
code.localState.pushScope()
}
fun exit() {
code.localState.popScope()
code.emit(Opcode.None)
code.emit(Opcode.Return)
}
override fun visitBlock(node: Block) {
code.localState.pushScope()
node.visitChildren(this)
code.localState.popScope()
}
override fun visitBooleanLiteral(node: BooleanLiteral) {
code.emit(if (node.value) Opcode.True else Opcode.False)
}
override fun visitBreak(node: Break) {
code.patch(Opcode.Jump, listOf(0u), 0, symbol, code.localState.loopState!!.exitJumpTarget)
}
override fun visitContinue(node: Continue) {
code.patch(Opcode.Jump, listOf(0u), 0, symbol, code.localState.loopState!!.startOfLoop)
}
override fun visitDoubleLiteral(node: DoubleLiteral) {
code.emit(Opcode.Integer, listOf(node.value.toUInt()))
}
override fun visitForIn(node: ForIn) {
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) {
val targetScopeSymbol = symbol.scopeSymbol.scope.resolve(node.symbol) ?:
throw RuntimeException("Unable to resolve symbol: ${node.symbol.id}")
val targetSymbol = compiler.resolve(targetScopeSymbol)
val functionDefinition = targetSymbol.scopeSymbol.definition as FunctionDefinition
val retRel = MutableRel(0u)
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) {
val remaining = node.arguments.drop(index)
variableArguments = remaining
break
} else {
normalArguments.add(value)
}
}
if (variableArguments != null) {
for (item in variableArguments.reversed()) {
item.visit(this)
}
code.emit(Opcode.ListMake, listOf(variableArguments.size.toUInt()))
}
for (item in normalArguments.reversed()) {
visit(item)
}
retRel.rel = code.nextOpInst() + 2u
code.patch(Opcode.ReturnAddress, listOf(0u), 0, symbol, retRel)
code.patch(Opcode.Call, listOf(0u), mapOf(0 to targetSymbol))
}
override fun visitIf(node: If) {
val thenRel = MutableRel(0u)
val endRel = MutableRel(0u)
node.condition.visit(this)
code.patch(Opcode.JumpIf, listOf(0u), 0, symbol, thenRel)
node.elseBlock?.visit(this)
code.patch(Opcode.Jump, listOf(0u), 0, symbol, endRel)
thenRel.rel = code.nextOpInst()
node.thenBlock.visit(this)
endRel.rel = code.nextOpInst()
}
override fun visitIndexedBy(node: IndexedBy) {
node.expression.visit(this)
node.index.visit(this)
code.emit(Opcode.Index)
}
override fun visitInfixOperation(node: InfixOperation) {
node.left.visit(this)
node.right.visit(this)
when (node.op) {
InfixOperator.Plus -> code.emit(Opcode.Add)
InfixOperator.Minus -> code.emit(Opcode.Subtract)
InfixOperator.Multiply -> code.emit(Opcode.Multiply)
InfixOperator.Divide -> code.emit(Opcode.Divide)
InfixOperator.Equals -> code.emit(Opcode.CompareEqual)
InfixOperator.NotEquals -> {
code.emit(Opcode.CompareEqual)
code.emit(Opcode.Not)
}
InfixOperator.EuclideanModulo -> code.emit(Opcode.EuclideanModulo)
InfixOperator.Remainder -> code.emit(Opcode.Remainder)
InfixOperator.Lesser -> code.emit(Opcode.CompareLesser)
InfixOperator.Greater -> code.emit(Opcode.CompareGreater)
InfixOperator.GreaterEqual -> code.emit(Opcode.CompareGreaterEqual)
InfixOperator.LesserEqual -> code.emit(Opcode.CompareLesserEqual)
InfixOperator.BooleanAnd -> code.emit(Opcode.And)
InfixOperator.BooleanOr -> code.emit(Opcode.Or)
InfixOperator.BinaryAnd -> code.emit(Opcode.BinaryAnd)
InfixOperator.BinaryOr -> code.emit(Opcode.BinaryOr)
InfixOperator.BinaryExclusiveOr -> code.emit(Opcode.BinaryXor)
}
}
override fun visitIntegerLiteral(node: IntegerLiteral) {
code.emit(Opcode.Integer, listOf(node.value.toUInt()))
}
override fun visitLetAssignment(node: LetAssignment) {
val variable = code.localState.createLocal(node.symbol)
node.value.visit(this)
code.emit(Opcode.StoreLocal, listOf(variable.index))
}
override fun visitListLiteral(node: ListLiteral) {
val count = node.items.size
for (item in node.items) {
item.visit(this)
}
code.emit(Opcode.ListMake, listOf(count.toUInt()))
}
override fun visitLongLiteral(node: LongLiteral) {
code.emit(Opcode.Integer, listOf(node.value.toUInt()))
}
override fun visitNoneLiteral(node: NoneLiteral) {
code.emit(Opcode.None)
}
override fun visitParentheses(node: Parentheses) {
node.expression.visit(this)
}
override fun visitPrefixOperation(node: PrefixOperation) {
node.expression.visit(this)
when (node.op) {
PrefixOperator.BooleanNot -> code.emit(Opcode.Not)
PrefixOperator.UnaryPlus -> code.emit(Opcode.UnaryPlus)
PrefixOperator.UnaryMinus -> code.emit(Opcode.UnaryMinus)
PrefixOperator.BinaryNot -> code.emit(Opcode.BinaryNot)
}
}
override fun visitReturn(node: Return) {
node.value.visit(this)
code.emit(Opcode.Return)
}
override fun visitSetAssignment(node: SetAssignment) {
val stubVarOrCall = code.localState.resolve(node.symbol)
if (stubVarOrCall.stubVar == null) {
throw RuntimeException("Invalid set assignment.")
}
node.value.visit(this)
code.emit(Opcode.StoreLocal, listOf(stubVarOrCall.stubVar.index))
}
override fun visitStringLiteral(node: StringLiteral) {
val bytes = node.text.toByteArray()
val constant = compiler.constantPool.assign(ConstantTag.String, bytes)
code.emit(Opcode.Constant, listOf(constant))
}
override fun visitSuffixOperation(node: SuffixOperation) {
val stubVarOrCall = code.localState.resolve(node.reference.symbol)
if (stubVarOrCall.stubVar == null) {
throw RuntimeException("Invalid suffix operation.")
}
load(stubVarOrCall)
when (node.op) {
SuffixOperator.Increment -> {
code.emit(Opcode.Integer, listOf(1u))
code.emit(Opcode.Add, emptyList())
code.emit(Opcode.StoreLocal, listOf(stubVarOrCall.stubVar.index))
}
SuffixOperator.Decrement -> {
code.emit(Opcode.Integer, listOf(1u))
code.emit(Opcode.Subtract, emptyList())
code.emit(Opcode.StoreLocal, listOf(stubVarOrCall.stubVar.index))
}
}
}
override fun visitSymbolReference(node: SymbolReference) {
val variable = code.localState.resolve(node.symbol)
load(variable)
}
override fun visitVarAssignment(node: VarAssignment) {
val variable = code.localState.createLocal(node.symbol)
node.value.visit(this)
code.emit(Opcode.StoreLocal, listOf(variable.index))
}
override fun visitWhile(node: While) {
val startOfBody = MutableRel(0u)
val startOfLoop = MutableRel(0u)
val endOfLoop = MutableRel(0u)
code.localState.startLoop(code.nextOpInst(), endOfLoop)
startOfLoop.rel = code.nextOpInst()
node.condition.visit(this)
code.patch(Opcode.JumpIf, listOf(0u), 0, symbol, startOfBody)
code.patch(Opcode.Jump, listOf(0u), 0, symbol, endOfLoop)
startOfBody.rel = code.nextOpInst()
node.block.visit(this)
code.patch(Opcode.Jump, listOf(0u), 0, symbol, startOfLoop)
endOfLoop.rel = code.nextOpInst()
code.localState.endLoop()
}
override fun visitNativeFunctionDescriptor(node: NativeFunctionDescriptor) {
for (def in node.definitions) {
val defConstant = compiler.constantPool.assign(ConstantTag.String, def.text.toByteArray())
code.emit(Opcode.Constant, listOf(defConstant))
}
val formConstant = compiler.constantPool.assign(ConstantTag.String, node.form.id.toByteArray())
val functionDefinition = symbol.scopeSymbol.definition as FunctionDefinition
val functionArgumentCount = functionDefinition.arguments.size
code.emit(Opcode.Native, listOf(formConstant, node.definitions.size.toUInt(), functionArgumentCount.toUInt()))
}
private fun load(callOrStubVar: Loadable) {
if (callOrStubVar.stubVar != null) {
code.emit(Opcode.LoadLocal, listOf(callOrStubVar.stubVar.index))
} else {
code.emit(Opcode.Integer, listOf(code.nextOpInst() + 2u))
code.patch(Opcode.Call, listOf(0u), mapOf(0 to callOrStubVar.call!!))
}
}
}

View File

@ -1,8 +1,6 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.gen.Symbol
class StubVar(
val index: UInt,
val symbol: Symbol? = null
val id: UInt
)

View File

@ -18,4 +18,6 @@ class ScopeSymbol(val slabScope: SlabScope, val definition: Definition) {
result = 31 * result + symbol.hashCode()
return result
}
override fun toString(): String = "ScopeSymbol(${symbol.id})"
}

View File

@ -3,14 +3,8 @@ package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument
import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.ast.gen.FunctionDefinition
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.ast.gen.visit
import gay.pizza.pork.bir.IrSymbolAssignment
import gay.pizza.pork.bir.IrSymbolTag
import gay.pizza.pork.compiler.Compiler
import gay.pizza.pork.compiler.IrCodeEmitter
import gay.pizza.pork.compiler.IrSymbolWorld
import gay.pizza.pork.minimal.FileTool
class CompileCommand : CliktCommand(help = "Compile Pork to Bytecode", name = "compile") {
@ -37,12 +31,5 @@ class CompileCommand : CliktCommand(help = "Compile Pork to Bytecode", name = "c
println(" ${symbol.offset + index.toUInt()} ${op}${annotation}")
}
}
val irSymbolAssignment = IrSymbolAssignment()
val irSymbolWorld = IrSymbolWorld(irSymbolAssignment)
val self = irSymbolAssignment.next(IrSymbolTag.Function)
val irCodeEmitter = IrCodeEmitter(self, irSymbolWorld, irSymbolAssignment, compiledSlab.slab.scope)
val ir = irCodeEmitter.visit((compiledMain.scopeSymbol.definition as FunctionDefinition).block!!)
println(ir)
}
}