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 { dependencies {
api(project(":ast")) api(project(":ast"))
api(project(":bir"))
api(project(":bytecode")) api(project(":bytecode"))
api(project(":parser")) api(project(":parser"))
api(project(":frontend")) 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) { class ScopeSymbol(val slabScope: SlabScope, val definition: Definition) {
val symbol: Symbol = definition.symbol val symbol: Symbol = definition.symbol
val scope: DefinitionScope by lazy { DefinitionScope(slabScope, definition) } 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", ":common",
":tokenizer", ":tokenizer",
":ast", ":ast",
":bir",
":bytecode", ":bytecode",
":parser", ":parser",
":frontend", ":frontend",

View File

@ -3,8 +3,14 @@ package gay.pizza.pork.tool
import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.arguments.argument import com.github.ajalt.clikt.parameters.arguments.argument
import gay.pizza.dough.fs.PlatformFsProvider 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.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.Compiler
import gay.pizza.pork.compiler.IrCodeEmitter
import gay.pizza.pork.compiler.IrSymbolWorld
import gay.pizza.pork.minimal.FileTool import gay.pizza.pork.minimal.FileTool
class CompileCommand : CliktCommand(help = "Compile Pork to Bytecode", name = "compile") { 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}") 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)
} }
} }