WIP on IR

This commit is contained in:
Alex Zenla 2023-11-22 07:23:33 -08:00
parent 76290a401a
commit 2d88666f05
Signed by: alex
GPG Key ID: C0780728420EBFE5
35 changed files with 532 additions and 0 deletions

7
bir/build.gradle.kts Normal file
View File

@ -0,0 +1,7 @@
plugins {
id("gay.pizza.pork.module")
}
dependencies {
implementation(project(":common"))
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.bir
data class IrCodeBlock(val items: List<IrCodeElement>) : IrCodeElement

View File

@ -0,0 +1,3 @@
package gay.pizza.pork.bir
sealed interface IrCodeElement : IrElement

View File

@ -0,0 +1,13 @@
package gay.pizza.pork.bir
data class IrConditional(
val conditional: IrCodeElement,
val ifTrue: IrCodeElement,
val ifFalse: IrCodeElement
) : IrCodeElement {
override fun crawl(block: (IrElement) -> Unit) {
block(conditional)
block(ifTrue)
block(ifFalse)
}
}

View File

@ -0,0 +1,12 @@
package gay.pizza.pork.bir
sealed interface IrConstant : IrCodeElement {
override fun crawl(block: (IrElement) -> Unit) {}
}
data class IrIntegerConstant(val value: Int) : IrConstant
data class IrLongConstant(val value: Long) : IrConstant
data class IrDoubleConstant(val value: Double) : IrConstant
data class IrStringConstant(val value: String) : IrConstant
data class IrBooleanConstant(val value: Boolean) : IrConstant
data object IrNoneConstant : IrConstant

View File

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

View File

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

View File

@ -0,0 +1,6 @@
package gay.pizza.pork.bir
enum class IrDefinitionType {
Variable,
Function
}

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.bir
sealed interface IrElement {
fun crawl(block: (IrElement) -> Unit)
}

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.bir
data class IrInfix(val op: IrInfixOp, val left: IrCodeElement, val right: IrCodeElement) : IrCodeElement {
override fun crawl(block: (IrElement) -> Unit) {
block(left)
block(right)
}
}

View File

@ -0,0 +1,21 @@
package gay.pizza.pork.bir
enum class IrInfixOp {
Add,
Subtract,
Multiply,
Divide,
Equals,
NotEquals,
EuclideanModulo,
Remainder,
Lesser,
Greater,
LesserEqual,
GreaterEqual,
BooleanAnd,
BooleanOr,
BinaryAnd,
BinaryOr,
BinaryExclusiveOr
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.bir
data class IrPrefix(val op: IrPrefixOp, val value: IrCodeElement) : IrCodeElement {
override fun crawl(block: (IrElement) -> Unit) {
block(value)
}
}

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.bir
enum class IrPrefixOp {
BooleanNot,
UnaryPlus,
UnaryMinus,
BinaryNot
}

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.bir
data class IrReturn(val from: IrSymbol, val value: IrCodeElement) : IrCodeElement {
override fun crawl(block: (IrElement) -> Unit) {
block(from)
block(value)
}
}

View File

@ -0,0 +1,11 @@
package gay.pizza.pork.bir
data class IrSlab(
val location: IrSlabLocation,
val definitions: List<IrDefinition>
) : IrElement {
override fun crawl(block: (IrElement) -> Unit) {
block(location)
definitions.forEach(block)
}
}

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.bir
data class IrSlabLocation(
val form: String,
val path: String
) : IrElement {
override fun crawl(block: (IrElement) -> Unit) {}
}

View File

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

View File

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

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.bir
class IrSymbolAssignment {
private var index = 0u
private fun nextSymbolId(): UInt = index++
fun next(tag: IrSymbolTag): IrSymbol = IrSymbol(nextSymbolId(), tag)
}

View File

@ -0,0 +1,8 @@
package gay.pizza.pork.bir
enum class IrSymbolTag {
Function,
Variable,
Local,
Loop
}

View File

@ -4,6 +4,7 @@ plugins {
dependencies {
api(project(":ast"))
api(project(":bir"))
api(project(":bytecode"))
api(project(":parser"))
api(project(":frontend"))

View File

@ -0,0 +1,5 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.gen.Node
class CompileError(message: String, val node: Node? = null) : RuntimeException(message)

View File

@ -0,0 +1,258 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.FunctionLevelVisitor
import gay.pizza.pork.ast.gen.*
import gay.pizza.pork.bir.*
import gay.pizza.pork.frontend.scope.ScopeSymbol
import gay.pizza.pork.frontend.scope.SlabScope
class IrCodeEmitter(
val self: IrSymbol,
val irSymbolWorld: IrSymbolWorld,
val irSymbolAssignment: IrSymbolAssignment,
val scope: SlabScope
) : FunctionLevelVisitor<IrCodeElement>() {
private val loopSymbols = mutableListOf<IrSymbol>()
private val localVariables = mutableListOf<MutableList<LocalVariable>>()
private fun startLoop(): IrSymbol {
val symbol = irSymbolAssignment.next(IrSymbolTag.Loop)
loopSymbols.add(symbol)
return symbol
}
private fun endLoop() {
loopSymbols.removeLast()
}
private fun <T> loop(block: (IrSymbol) -> T): T {
val symbol = startLoop()
return try {
block(symbol)
} finally {
endLoop()
}
}
private fun enterBlockScope() {
val locals = mutableListOf<LocalVariable>()
localVariables.add(locals)
}
private fun exitBlockScope() {
localVariables.removeLast()
}
private fun createLocalVariable(name: Symbol): IrSymbol {
val symbol = irSymbolAssignment.next(IrSymbolTag.Local)
val variable = LocalVariable(symbol, name)
localVariables.last().add(variable)
return symbol
}
private fun scopeSymbolToTag(scopeSymbol: ScopeSymbol): IrSymbolTag =
if (scopeSymbol.definition is FunctionDefinition) {
IrSymbolTag.Function
} else {
IrSymbolTag.Variable
}
private fun lookupLocalVariable(name: Symbol): IrSymbol? {
for (i in 0..localVariables.size) {
val b = localVariables.size - i - 1
val scope = localVariables[b]
val found = scope.firstOrNull { it.name == name }
if (found != null) {
return found.symbol
}
}
return null
}
private fun lookup(name: Symbol): IrSymbol? {
val local = lookupLocalVariable(name)
if (local != null) {
return local
}
val scoped = scope.resolve(name)
if (scoped != null) {
return irSymbolWorld.lookup(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))
}
override fun visitBlock(node: Block): IrCodeBlock {
enterBlockScope()
val block = IrCodeBlock(node.expressions.map { it.visit(this) })
exitBlockScope()
return block
}
override fun visitBooleanLiteral(node: BooleanLiteral): IrCodeElement =
IrBooleanConstant(node.value)
override fun visitBreak(node: Break): IrCodeElement {
val currentLoopSymbol = loopSymbols.lastOrNull() ?:
throw CompileError("break does not have a target loop", node)
return IrBreak(currentLoopSymbol)
}
override fun visitContinue(node: Continue): IrCodeElement {
val currentLoopSymbol = loopSymbols.lastOrNull() ?:
throw CompileError("continue does not have a target loop", node)
return IrBreak(currentLoopSymbol)
}
override fun visitDoubleLiteral(node: DoubleLiteral): IrCodeElement =
IrDoubleConstant(node.value)
override fun visitForIn(node: ForIn): IrCodeElement {
return IrNoneConstant
}
override fun visitFunctionCall(node: FunctionCall): IrCodeElement {
val (scopeSymbol, symbol) = lookupFunction(node.symbol) ?:
throw CompileError("Failed to resolve function call target '${node.symbol.id}'", node)
if (symbol.tag != IrSymbolTag.Function) {
throw CompileError("Failed to resolve function call target '${node.symbol.id}', it is not a function", node)
}
val functionDefinition = scopeSymbol.definition as FunctionDefinition
val arguments = mutableListOf<IrCodeElement>()
var variableArguments: List<IrCodeElement>? = null
val inputs = node.arguments
for ((index, spec) in functionDefinition.arguments.withIndex()) {
if (variableArguments != null) {
throw CompileError(
"Failed to build function call, '${node.symbol.id}', illegal function definition",
node
)
}
if (spec.multiple) {
variableArguments = inputs.drop(index).map { it.visit(this) }
} else {
if (index > inputs.size - 1) {
throw CompileError(
"Failed to build function call, '${node.symbol.id}', no matching argument for '${spec.symbol.id}'",
node
)
}
arguments.add(inputs[index].visit(this))
}
}
if (functionDefinition.arguments.any { it.multiple }) {
variableArguments = mutableListOf()
}
return IrCall(symbol, arguments, variableArguments)
}
override fun visitIf(node: If): IrCodeElement =
IrConditional(
node.condition.visit(this),
node.thenBlock.visit(this),
node.elseBlock?.visit(this) ?: IrNoneConstant
)
override fun visitIndexedBy(node: IndexedBy): IrCodeElement {
TODO("Not yet implemented")
}
override fun visitInfixOperation(node: InfixOperation): IrCodeElement {
val op = when (node.op) {
InfixOperator.Plus -> IrInfixOp.Add
InfixOperator.Minus -> IrInfixOp.Subtract
InfixOperator.Multiply -> IrInfixOp.Multiply
InfixOperator.Divide -> IrInfixOp.Divide
InfixOperator.Equals -> IrInfixOp.Equals
InfixOperator.NotEquals -> IrInfixOp.NotEquals
InfixOperator.EuclideanModulo -> IrInfixOp.EuclideanModulo
InfixOperator.Remainder -> IrInfixOp.Remainder
InfixOperator.Lesser -> IrInfixOp.Lesser
InfixOperator.Greater -> IrInfixOp.Greater
InfixOperator.GreaterEqual -> IrInfixOp.GreaterEqual
InfixOperator.LesserEqual -> IrInfixOp.LesserEqual
InfixOperator.BooleanAnd -> IrInfixOp.BooleanAnd
InfixOperator.BooleanOr -> IrInfixOp.BooleanOr
InfixOperator.BinaryAnd -> IrInfixOp.BinaryAnd
InfixOperator.BinaryOr -> IrInfixOp.BinaryOr
InfixOperator.BinaryExclusiveOr -> IrInfixOp.BinaryExclusiveOr
}
return IrInfix(op, node.left.visit(this), node.right.visit(this))
}
override fun visitIntegerLiteral(node: IntegerLiteral): IrCodeElement =
IrIntegerConstant(node.value)
override fun visitLetAssignment(node: LetAssignment): IrCodeElement {
val symbol = createLocalVariable(node.symbol)
return IrStore(symbol, node.value.visit(this))
}
override fun visitListLiteral(node: ListLiteral): IrCodeElement =
IrList(node.items.map { it.visit(this) })
override fun visitLongLiteral(node: LongLiteral): IrCodeElement =
IrLongConstant(node.value)
override fun visitNoneLiteral(node: NoneLiteral): IrCodeElement =
IrNoneConstant
override fun visitParentheses(node: Parentheses): IrCodeElement =
node.expression.visit(this)
override fun visitPrefixOperation(node: PrefixOperation): IrCodeElement {
val op = when (node.op) {
PrefixOperator.BooleanNot -> IrPrefixOp.BooleanNot
PrefixOperator.UnaryPlus -> IrPrefixOp.UnaryPlus
PrefixOperator.UnaryMinus -> IrPrefixOp.UnaryMinus
PrefixOperator.BinaryNot -> IrPrefixOp.BinaryNot
}
return IrPrefix(op, node.expression.visit(this))
}
override fun visitReturn(node: Return): IrCodeElement =
IrReturn(from = self, value = node.value.visit(this))
override fun visitSetAssignment(node: SetAssignment): IrCodeElement {
val symbol = lookupLocalVariable(node.symbol) ?:
throw CompileError("Unable to find local variable target '${node.symbol.id}'", node)
return IrStore(symbol, node.value.visit(this))
}
override fun visitStringLiteral(node: StringLiteral): IrCodeElement =
IrStringConstant(node.text)
override fun visitSuffixOperation(node: SuffixOperation): IrCodeElement {
TODO("Not yet implemented")
}
override fun visitSymbolReference(node: SymbolReference): IrCodeElement {
val symbol = lookup(node.symbol) ?:
throw CompileError("Unable to resolve symbol reference '${node.symbol.id}'", node)
return IrLoad(symbol)
}
override fun visitVarAssignment(node: VarAssignment): IrCodeElement {
val local = createLocalVariable(node.symbol)
return IrStore(local, node.value.visit(this))
}
override fun visitWhile(node: While): IrCodeElement = loop { symbol ->
IrLoop(
symbol = symbol,
condition = node.condition.visit(this),
inner = node.block.visit(this)
)
}
}

View File

@ -0,0 +1,13 @@
package gay.pizza.pork.compiler
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>()
fun lookup(value: Any, tag: IrSymbolTag): IrSymbol = symbols.getOrPut(value) {
irSymbolAssignment.next(tag)
}
}

View File

@ -0,0 +1,6 @@
package gay.pizza.pork.compiler
import gay.pizza.pork.ast.gen.Symbol
import gay.pizza.pork.bir.IrSymbol
data class LocalVariable(val symbol: IrSymbol, val name: Symbol)

View File

@ -6,4 +6,16 @@ import gay.pizza.pork.ast.gen.Symbol
class ScopeSymbol(val slabScope: SlabScope, val definition: Definition) {
val symbol: Symbol = definition.symbol
val scope: DefinitionScope by lazy { DefinitionScope(slabScope, definition) }
override fun equals(other: Any?): Boolean {
if (other !is ScopeSymbol) return false
return other.slabScope.slab == slabScope.slab && other.symbol == symbol
}
override fun hashCode(): Int {
var result = slabScope.hashCode()
result = 31 * result + definition.hashCode()
result = 31 * result + symbol.hashCode()
return result
}
}

View File

@ -0,0 +1,6 @@
package gay.pizza.pork.frontend.scope
enum class SymbolType {
Variable,
Function
}

View File

@ -6,6 +6,7 @@ include(
":common",
":tokenizer",
":ast",
":bir",
":bytecode",
":parser",
":frontend",

View File

@ -3,8 +3,14 @@ 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") {
@ -31,5 +37,12 @@ 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)
}
}