From 8d59af2378097a7d73cd74ac14b59e0a4d3c2594 Mon Sep 17 00:00:00 2001 From: SrGaabriel Date: Sat, 7 Sep 2024 13:52:58 -0300 Subject: [PATCH] feat(ryujin): virtual tables, analyzers, more statements, etc... --- compiler/build.gradle.kts | 8 -- libs.versions.toml | 2 + ryujin/build.gradle.kts | 8 ++ ryujin/src/commonMain/kotlin/DragonModule.kt | 16 ++++ .../kotlin/analyzer/RyujinAnalysisError.kt | 8 ++ .../kotlin/analyzer/RyujinAnalyzer.kt | 13 +++ .../analyzer/impl/BalancedRyujinAnalyzer.kt | 40 ++++++++++ .../commonMain/kotlin/dsl/FunctionScopeDsl.kt | 35 +++++++- .../commonMain/kotlin/dsl/ModuleScopeDsl.kt | 38 +++++++-- .../kotlin/function/DragonFunction.kt | 3 +- .../kotlin/statement/DragonStatement.kt | 2 +- .../src/commonMain/kotlin/statement/Math.kt | 21 +++++ .../commonMain/kotlin/statement/Pointers.kt | 10 +++ .../commonMain/kotlin/struct/Dependency.kt | 3 +- ryujin/src/commonMain/kotlin/struct/Type.kt | 6 +- ryujin/src/commonMain/kotlin/struct/Value.kt | 45 ++++++++--- .../transcript/DefaultDragonIrTranscriber.kt | 8 +- ryujin/src/commonTest/kotlin/General.kt | 79 +++++++++++++++++++ 18 files changed, 307 insertions(+), 38 deletions(-) create mode 100644 ryujin/src/commonMain/kotlin/analyzer/RyujinAnalysisError.kt create mode 100644 ryujin/src/commonMain/kotlin/analyzer/RyujinAnalyzer.kt create mode 100644 ryujin/src/commonMain/kotlin/analyzer/impl/BalancedRyujinAnalyzer.kt create mode 100644 ryujin/src/commonMain/kotlin/statement/Math.kt create mode 100644 ryujin/src/commonTest/kotlin/General.kt diff --git a/compiler/build.gradle.kts b/compiler/build.gradle.kts index c195846..99c3828 100644 --- a/compiler/build.gradle.kts +++ b/compiler/build.gradle.kts @@ -24,11 +24,6 @@ kotlin { executable() } } - watchosX64("watchosX64") { - binaries { - executable() - } - } iosArm64("iosArm64") { binaries { executable() @@ -66,9 +61,6 @@ kotlin { val windowsX64Main by getting { dependsOn(nativeMain) } - val watchosX64Main by getting { - dependsOn(nativeMain) - } } } diff --git a/libs.versions.toml b/libs.versions.toml index 0ead8e6..a17db14 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -17,6 +17,8 @@ kotlinx-html-js = { module = "org.jetbrains.kotlinx:kotlinx-html-js", version.re kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization-json" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization-json" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } + [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/ryujin/build.gradle.kts b/ryujin/build.gradle.kts index 42d3f9d..bcaf7ac 100644 --- a/ryujin/build.gradle.kts +++ b/ryujin/build.gradle.kts @@ -13,4 +13,12 @@ kotlin { iosArm64() macosX64() js().browser() + + sourceSets { + val commonTest by getting { + dependencies { + implementation(libs.kotlin.test) + } + } + } } \ No newline at end of file diff --git a/ryujin/src/commonMain/kotlin/DragonModule.kt b/ryujin/src/commonMain/kotlin/DragonModule.kt index 57addc4..3f5d9f3 100644 --- a/ryujin/src/commonMain/kotlin/DragonModule.kt +++ b/ryujin/src/commonMain/kotlin/DragonModule.kt @@ -24,4 +24,20 @@ interface DragonModule { fun removeDependencies(vararg dependencies: Dependency) { this.dependencies.removeAll(dependencies) } + + fun addFunction(function: DragonFunction) { + functions.add(function) + } + + fun addFunctions(vararg functions: DragonFunction) { + this.functions.addAll(functions) + } + + fun removeFunction(function: DragonFunction) { + functions.remove(function) + } + + fun removeFunctions(vararg functions: DragonFunction) { + this.functions.removeAll(functions) + } } \ No newline at end of file diff --git a/ryujin/src/commonMain/kotlin/analyzer/RyujinAnalysisError.kt b/ryujin/src/commonMain/kotlin/analyzer/RyujinAnalysisError.kt new file mode 100644 index 0000000..2b03e1b --- /dev/null +++ b/ryujin/src/commonMain/kotlin/analyzer/RyujinAnalysisError.kt @@ -0,0 +1,8 @@ +package me.gabriel.ryujin.analyzer + +import me.gabriel.ryujin.statement.DragonStatement + +data class RyujinAnalysisError( + val statement: DragonStatement, + val message: String +) \ No newline at end of file diff --git a/ryujin/src/commonMain/kotlin/analyzer/RyujinAnalyzer.kt b/ryujin/src/commonMain/kotlin/analyzer/RyujinAnalyzer.kt new file mode 100644 index 0000000..2d93ae4 --- /dev/null +++ b/ryujin/src/commonMain/kotlin/analyzer/RyujinAnalyzer.kt @@ -0,0 +1,13 @@ +package me.gabriel.ryujin.analyzer + +import me.gabriel.ryujin.DragonModule +import me.gabriel.ryujin.statement.DragonStatement + +/** + * This analyzer is responsible for catching obvious flaws in LLVM IR. + * It should be used to catch errors that are either not caught by the parser, or way too obvious to go unnoticed. + */ +interface RyujinAnalyzer { + fun analyze(module: DragonModule): List +} + diff --git a/ryujin/src/commonMain/kotlin/analyzer/impl/BalancedRyujinAnalyzer.kt b/ryujin/src/commonMain/kotlin/analyzer/impl/BalancedRyujinAnalyzer.kt new file mode 100644 index 0000000..834349d --- /dev/null +++ b/ryujin/src/commonMain/kotlin/analyzer/impl/BalancedRyujinAnalyzer.kt @@ -0,0 +1,40 @@ +package me.gabriel.ryujin.analyzer.impl + +import me.gabriel.ryujin.DragonModule +import me.gabriel.ryujin.analyzer.RyujinAnalysisError +import me.gabriel.ryujin.analyzer.RyujinAnalyzer +import me.gabriel.ryujin.statement.DragonStatement +import me.gabriel.ryujin.statement.StoreStatement +import me.gabriel.ryujin.struct.DragonType + +/** + * This is a balanced analyzer: neither too thorough nor too shallow, as to + * provide a good balance between performance and error detection. + */ +class BalancedRyujinAnalyzer: RyujinAnalyzer { + override fun analyze(module: DragonModule): List { + val errors = mutableListOf() + module.functions.asSequence().flatMap { it.statements }.forEach { + errors.addAll(analyzeStatement(it)) + } + return errors + } + + private fun analyzeStatement(statement: DragonStatement): List = when (statement) { + is StoreStatement -> analyzeStoreStatement(statement) + else -> emptyList() + } + + private fun analyzeStoreStatement(statement: StoreStatement): List { + val errors = mutableListOf() + if (statement.target.type !is DragonType.Pointer) { + errors.add( + RyujinAnalysisError( + statement = statement, + message = "Target is not a pointer: ${statement.target.type}" + ) + ) + } + return errors + } +} \ No newline at end of file diff --git a/ryujin/src/commonMain/kotlin/dsl/FunctionScopeDsl.kt b/ryujin/src/commonMain/kotlin/dsl/FunctionScopeDsl.kt index c66f26b..d99ccf2 100644 --- a/ryujin/src/commonMain/kotlin/dsl/FunctionScopeDsl.kt +++ b/ryujin/src/commonMain/kotlin/dsl/FunctionScopeDsl.kt @@ -1,13 +1,46 @@ package me.gabriel.ryujin.dsl import me.gabriel.ryujin.function.DragonFunction -import me.gabriel.ryujin.statement.DragonStatement +import me.gabriel.ryujin.statement.* +import me.gabriel.ryujin.struct.Memory +import me.gabriel.ryujin.struct.Value class FunctionScopeDsl( val module: ModuleScopeDsl, val function: DragonFunction ) { + var register = function.parameters.size + fun statement(statement: DragonStatement) { + if (!statement.isValid()) { + throw IllegalArgumentException("Invalid statement: $statement") + } function.statements.add(statement) } + + fun load(target: Memory): LoadStatement = + LoadStatement(target) + + fun add(left: Value, right: Value): AddStatement = + AddStatement(left, right) + + fun assignment(target: Memory, value: TypedDragonStatement) { + statement(AssignStatement(target, value)) + } + + fun assign(value: (FunctionScopeDsl) -> TypedDragonStatement): Memory = + value(this).assign() + + fun TypedDragonStatement.ignore() = + statement(this) + + fun TypedDragonStatement.assign(): Memory { + val memory = Memory.Sized( + type = type, + size = type.size, + register = register++ + ) + statement(AssignStatement(memory, this)) + return memory + } } \ No newline at end of file diff --git a/ryujin/src/commonMain/kotlin/dsl/ModuleScopeDsl.kt b/ryujin/src/commonMain/kotlin/dsl/ModuleScopeDsl.kt index d5b25d2..02e28da 100644 --- a/ryujin/src/commonMain/kotlin/dsl/ModuleScopeDsl.kt +++ b/ryujin/src/commonMain/kotlin/dsl/ModuleScopeDsl.kt @@ -2,8 +2,7 @@ package me.gabriel.ryujin.dsl import me.gabriel.ryujin.DragonModule import me.gabriel.ryujin.function.DragonFunction -import me.gabriel.ryujin.struct.Dependency -import me.gabriel.ryujin.struct.DragonType +import me.gabriel.ryujin.struct.* class ModuleScopeDsl: DragonModule { override val functions: MutableSet = mutableSetOf() @@ -11,20 +10,45 @@ class ModuleScopeDsl: DragonModule { fun function( name: String, - parameters: Map, returnType: DragonType, - block: FunctionScopeDsl.() -> Unit + parameters: Collection = emptyList(), + block: FunctionScopeDsl.(Collection) -> Unit ) { + val parameterMemoryUnits = parameters.mapIndexed { index, dragonType -> + Memory.Sized( + type = dragonType, + size = dragonType.size, + register = index + ) + } + val function = DragonFunction( module = this, name = name, - parameters = parameters, + parameters = parameterMemoryUnits, returnType = returnType ) - FunctionScopeDsl( + functions.add(function) + val dsl = FunctionScopeDsl( module = this, function = function - ).apply(block) + ) + dsl.block(parameterMemoryUnits) + } + + fun virtualTable( + name: String, + values: Collection + ) { + dependencies.add(Dependency.Constant( + name = name, + value = Constant.VirtualTable(values) + )) } } +fun ryujinModule(block: ModuleScopeDsl.() -> Unit): ModuleScopeDsl { + val module = ModuleScopeDsl() + module.apply(block) + return module +} \ No newline at end of file diff --git a/ryujin/src/commonMain/kotlin/function/DragonFunction.kt b/ryujin/src/commonMain/kotlin/function/DragonFunction.kt index 67941ac..8cedb56 100644 --- a/ryujin/src/commonMain/kotlin/function/DragonFunction.kt +++ b/ryujin/src/commonMain/kotlin/function/DragonFunction.kt @@ -3,11 +3,12 @@ package me.gabriel.ryujin.function import me.gabriel.ryujin.DragonModule import me.gabriel.ryujin.statement.DragonStatement import me.gabriel.ryujin.struct.DragonType +import me.gabriel.ryujin.struct.Memory class DragonFunction( val module: DragonModule, val name: String, - val parameters: Map, + val parameters: Collection, val returnType: DragonType ) { val statements = mutableListOf() diff --git a/ryujin/src/commonMain/kotlin/statement/DragonStatement.kt b/ryujin/src/commonMain/kotlin/statement/DragonStatement.kt index be8da05..4bb8f1e 100644 --- a/ryujin/src/commonMain/kotlin/statement/DragonStatement.kt +++ b/ryujin/src/commonMain/kotlin/statement/DragonStatement.kt @@ -3,7 +3,7 @@ package me.gabriel.ryujin.statement import me.gabriel.ryujin.struct.DragonType import me.gabriel.ryujin.struct.Value -interface DragonStatement { +sealed interface DragonStatement { val memoryDependencies: Set fun isValid(): Boolean = true diff --git a/ryujin/src/commonMain/kotlin/statement/Math.kt b/ryujin/src/commonMain/kotlin/statement/Math.kt new file mode 100644 index 0000000..942d414 --- /dev/null +++ b/ryujin/src/commonMain/kotlin/statement/Math.kt @@ -0,0 +1,21 @@ +package me.gabriel.ryujin.statement + +import me.gabriel.ryujin.struct.DragonType +import me.gabriel.ryujin.struct.Value + +class AddStatement( + val left: Value, + val right: Value +) : TypedDragonStatement { + override val memoryDependencies: Set + get() = setOf(left, right) + + override fun isValid(): Boolean { + return left.type == right.type + } + + override val type: DragonType get() = left.type + + override fun llvm(): String = + "add ${type.llvm} ${left.llvm()}, ${right.llvm()}" +} diff --git a/ryujin/src/commonMain/kotlin/statement/Pointers.kt b/ryujin/src/commonMain/kotlin/statement/Pointers.kt index 795d3de..7eb711e 100644 --- a/ryujin/src/commonMain/kotlin/statement/Pointers.kt +++ b/ryujin/src/commonMain/kotlin/statement/Pointers.kt @@ -5,6 +5,16 @@ import me.gabriel.ryujin.struct.Memory import me.gabriel.ryujin.struct.Value import me.gabriel.ryujin.struct.descendOneLevel +class AssignStatement( + val memory: Memory, + val value: TypedDragonStatement +): DragonStatement { + override val memoryDependencies: Set = setOf(memory) + value.memoryDependencies + + override fun llvm(): String = + "%${memory.register} = ${value.llvm()}" +} + class StoreStatement( val value: Value, val target: Memory diff --git a/ryujin/src/commonMain/kotlin/struct/Dependency.kt b/ryujin/src/commonMain/kotlin/struct/Dependency.kt index 950baf9..1403331 100644 --- a/ryujin/src/commonMain/kotlin/struct/Dependency.kt +++ b/ryujin/src/commonMain/kotlin/struct/Dependency.kt @@ -18,10 +18,9 @@ sealed interface Dependency { data class Constant( val name: String, - val type: DragonType, val value: Value ): Dependency { - override fun asType(): DragonType = type + override fun asType(): DragonType = value.type } fun asType(): DragonType diff --git a/ryujin/src/commonMain/kotlin/struct/Type.kt b/ryujin/src/commonMain/kotlin/struct/Type.kt index 74dc723..c4cdf53 100644 --- a/ryujin/src/commonMain/kotlin/struct/Type.kt +++ b/ryujin/src/commonMain/kotlin/struct/Type.kt @@ -30,11 +30,11 @@ sealed class DragonType( val types: Collection ) : DragonType("%$name", 8, 0) - data class Dynamic( + data class VirtualTable( val types: List ): DragonType(types.joinToString( - prefix = "<{", - postfix = "}>", + prefix = "<{ ", + postfix = " }>", separator = ", " ) { it.llvm }, 8, types.sumOf { it.size }) diff --git a/ryujin/src/commonMain/kotlin/struct/Value.kt b/ryujin/src/commonMain/kotlin/struct/Value.kt index ddb82af..a11a0bf 100644 --- a/ryujin/src/commonMain/kotlin/struct/Value.kt +++ b/ryujin/src/commonMain/kotlin/struct/Value.kt @@ -12,20 +12,43 @@ data object Void : Value { override fun llvm(): String = "void" } -data class Constant(val value: T, override val type: DragonType): Value { - override fun llvm(): String = value.toString() -} - -data class VirtualTable( - val values: List -): Value { - override val type: DragonType = DragonType.Dynamic(listOf()) - - override fun llvm(): String = """ +abstract class Constant(override val type: DragonType): Value { + abstract override fun llvm(): kotlin.String + + class Number( + val value: Long, + type: DragonType + ): Constant(type) { + override fun llvm(): kotlin.String = "$value" + } + + class FunctionPtr( + val name: kotlin.String, + ): Constant( + type = DragonType.Ptr + ) { + override fun llvm(): kotlin.String = "@$name" + } + + data class String( + val value: kotlin.String + ): Constant( + type = DragonType.Array(DragonType.Int8, value.length + 1) + ) { + override fun llvm(): kotlin.String = "c\"$value\\00\"" + } + + data class VirtualTable( + val values: Collection + ): Constant( + type = DragonType.VirtualTable(values.map { it.type }) + ) { + override fun llvm(): kotlin.String = """ |<{ - ${values.joinToString(",\n") { "|${it.type.llvm} ${it.llvm()}" }} + ${values.joinToString(",\n") { "| ${it.type.llvm} ${it.llvm()}" }} |}> """.trimMargin() + } } sealed class Memory( diff --git a/ryujin/src/commonMain/kotlin/transcript/DefaultDragonIrTranscriber.kt b/ryujin/src/commonMain/kotlin/transcript/DefaultDragonIrTranscriber.kt index c9f764a..fbba277 100644 --- a/ryujin/src/commonMain/kotlin/transcript/DefaultDragonIrTranscriber.kt +++ b/ryujin/src/commonMain/kotlin/transcript/DefaultDragonIrTranscriber.kt @@ -26,17 +26,17 @@ class DefaultDragonIrTranscriber: DragonIrTranscriber { override fun transcribeFunction(function: DragonFunction): String { return buildString { - append("define ${function.returnType} @${function.name}(") - append(function.parameters.entries.joinToString(", ") { "${it.value} %${it.key}" }) + append("define ${function.returnType.llvm} @${function.name}(") + append(function.parameters.joinToString(", ") { "${it.type.llvm} %${it.register}" }) append(") {\n") - function.statements.forEach { append(" $it\n") } + function.statements.forEach { append(" ${it.llvm()}\n") } append("}") } } private fun transcribeConstantDependency(dependency: Dependency.Constant): String { return """ - |@${dependency.name} = unnamed_addr constant ${dependency.type} ${dependency.value.llvm()} + |@${dependency.name} = unnamed_addr constant ${dependency.value.type.llvm} ${dependency.value.llvm()} """.trimMargin(marginPrefix = "|") } diff --git a/ryujin/src/commonTest/kotlin/General.kt b/ryujin/src/commonTest/kotlin/General.kt new file mode 100644 index 0000000..b223b88 --- /dev/null +++ b/ryujin/src/commonTest/kotlin/General.kt @@ -0,0 +1,79 @@ +import me.gabriel.ryujin.dsl.ryujinModule +import me.gabriel.ryujin.struct.Constant +import me.gabriel.ryujin.struct.DragonType +import me.gabriel.ryujin.transcript.DefaultDragonIrTranscriber +import kotlin.test.Test +import kotlin.test.assertEquals + +class GeneralTest { + + val transcriber = DefaultDragonIrTranscriber() + + @Test + fun functionSignature() { + val module = ryujinModule { + function("main", DragonType.Int32, listOf( + DragonType.Int32, + DragonType.Pointer(DragonType.Pointer(DragonType.Int8)) + )) {} + } + val transcribed = transcriber.transcribe(module) + assertModuleEquals( + """ + |define i32 @main(i32 %0, i8** %1) { + |} + """, + transcribed + ) + } + + @Test + fun basicMemory() { + val module = ryujinModule { + function("main", DragonType.Int32) { arguments -> + val three = assign { + add(Constant.Number(2, DragonType.Int32), Constant.Number(1, DragonType.Int32)) + } + } + } + val transcribed = transcriber.transcribe(module) + assertModuleEquals( + """ + |define i32 @main() { + | %0 = add i32 2, 1 + |} + """, + transcribed + ) + } + + @Test + fun virtualTable() { + val module = ryujinModule { + virtualTable( + "my_vtable", listOf( + Constant.FunctionPtr("test"), + Constant.FunctionPtr("test2") + ) + ) + } + val transcribed = transcriber.transcribe(module) + assertModuleEquals( + """ + |@my_vtable = unnamed_addr constant <{ ptr, ptr }> <{ + | ptr @test, + | ptr @test2 + |}> + """, + transcribed + ) + } + + private inline fun assertModuleEquals(expected: String, actual: String) { + val formattedExpected = expected.trimMargin().trim() + val formattedActual = actual.trim() + if (formattedExpected != formattedActual) { + error("Expected:\n$formattedExpected\nActual:\n$formattedActual") + } + } +} \ No newline at end of file