wip: fuzzer
diff --git a/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/Fuzzer.kt b/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/Fuzzer.kt
new file mode 100644
index 0000000..1c7f919
--- /dev/null
+++ b/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/Fuzzer.kt
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.konan.test.gcfuzzing
+
+import kotlin.random.*
+import org.jetbrains.kotlin.konan.test.gcfuzzing.dsl.*
+import kotlin.math.roundToInt
+
+interface Distribution<T> {
+ fun next(random: Random): T
+
+ companion object {
+ fun <T> uniformal(vararg discreteValues: T): Distribution<T> = object : Distribution<T> {
+ override fun next(random: Random): T {
+ val idx = random.nextInt(0, discreteValues.size)
+ return discreteValues[idx]
+ }
+ }
+ inline fun <reified T: Enum<T>> uniformal(): Distribution<T> = uniformal(*enumValues<T>())
+
+ inline fun <reified T> weighted(vararg variants: Pair<T, Int>): Distribution<T> = uniformal(*(variants.map { it.first }.toTypedArray()))
+
+ fun uniformalBetween(from: Int, until: Int): Distribution<Int> = CumulativeFun.uniformal(
+ 0.0 to from,
+ 1.0 to until
+ )
+ }
+}
+
+fun interface CumulativeFun<T> : Distribution<T> {
+ fun cdf(p: Double): T
+
+ override fun next(random: Random): T {
+ val p = random.nextDouble(0.0, 1.0)
+ return cdf(p)
+ }
+
+ companion object {
+ fun uniformal(vararg anchors: Pair<Double, Int>): CumulativeFun<Int> = object : CumulativeFun<Int> {
+ override fun cdf(p: Double): Int {
+ val sortedAnchors = anchors.sortedBy { it.first }
+ val greaterIndex = sortedAnchors.indexOfFirst { it.first > p }
+ val from = sortedAnchors[greaterIndex - 1].second
+ val to = sortedAnchors[greaterIndex].second
+ return (from + p * (to - from)).roundToInt()
+ }
+ }
+ }
+}
+
+enum class DefinitionKind {
+ CLASS, FUNCTION, GLOBAL
+}
+
+enum class StatementKind {
+ ALLOC, LOAD, STORE, CALL, SPAWN_THREAD
+}
+
+enum class AccessKind {
+ LOCAL, GLOBAL
+}
+
+class Fuzzer(seed: Int = 0, val distributions: Distributions = Distributions()) {
+ val random = Random(seed)
+
+ // TODO first generate all symbols only then bodies
+
+ class Distributions {
+ val numDefinitions = Distribution.uniformalBetween(0, 20) // FIXME make normal?
+ val language = Distribution.uniformal(TargetLanguage.Kotlin) // TODO other languages
+ val definition = Distribution.uniformal<DefinitionKind>()
+ val numFields = CumulativeFun.uniformal(
+ 0.0 to 0,
+ 0.1 to 0,
+ 0.5 to 2,
+ 0.9 to 10,
+ 0.999 to 1000,
+ 1.0 to Int.MAX_VALUE
+ )
+ val numParameters = CumulativeFun.uniformal(
+ 0.0 to 0,
+ 0.9 to 10,
+ 1.0 to 256
+ )
+ // FIXME completely arbitrary
+ val bodySize = Distribution.uniformalBetween(0, 100)
+ val statement = Distribution.weighted(
+ StatementKind.ALLOC to 1,
+ StatementKind.LOAD to 1,
+ StatementKind.STORE to 1,
+ StatementKind.CALL to 1,
+ StatementKind.SPAWN_THREAD to 1,
+ )
+ val pathLength = CumulativeFun.uniformal(
+ 0.0 to 0,
+ 0.1 to 0,
+ 0.9 to 3,
+ 1.0 to 100
+ )
+ val access = Distribution.uniformal<AccessKind>() // FIXME should be more locals?
+ }
+
+ private val numDefinitions: Map<DefinitionKind, Int> = (0 until distributions.numDefinitions.next(random)).map {
+ distributions.definition.next(random)
+ }.groupBy{ it }.mapValues { it.value.size }
+
+ private fun genDefinitions(): List<Definition> {
+ val globals = List(numDefinitions[DefinitionKind.GLOBAL]!!) { genGlobal() }
+ val classes = List(numDefinitions[DefinitionKind.CLASS]!!) { genClass() }
+ val functions = List(numDefinitions[DefinitionKind.FUNCTION]!!) { genFunction() }
+ return globals + classes + functions
+ }
+
+ fun genProgram(): Program = Program(
+ definitions = genDefinitions(),
+ mainBody = genBody()
+ )
+
+ fun genClass(): Definition.Class {
+ val lang = distributions.language.next(random)
+ val numFields = distributions.numFields.next(random)
+ return Definition.Class(
+ lang,
+ List(numFields) { Field }
+ )
+ }
+
+ fun genFunction(): Definition.Function {
+ val lang = distributions.language.next(random)
+ val numParameters = distributions.numParameters.next(random)
+ return Definition.Function(
+ lang,
+ List(numParameters) { Parameter },
+ genBodyWithReturn(),
+ )
+ }
+
+ fun genGlobal(): Definition.Global {
+ val lang = distributions.language.next(random)
+ return Definition.Global(lang, Field)
+ }
+
+ fun genBodyWithReturn() = BodyWithReturn(genBody(), LoadExpression.Default) // FIXME non-default load?
+
+ fun genBody(): Body {
+ return Body(List(distributions.bodySize.next(random)) { genStatement() })
+ }
+
+ fun genStatement(): BodyStatement = when (distributions.statement.next(random)) {
+ StatementKind.ALLOC -> genAlloc()
+ StatementKind.LOAD -> genLoad()
+ StatementKind.STORE -> genStore()
+ StatementKind.CALL -> genCall()
+ StatementKind.SPAWN_THREAD -> genSpawnThread()
+ }
+
+ private fun chooseClass(): Int = random.nextInt(0, numDefinitions[DefinitionKind.CLASS]!!)
+ private fun chooseFunction(): Int = random.nextInt(0, numDefinitions[DefinitionKind.FUNCTION]!!)
+ private fun chooseGlobal(): Int = random.nextInt(0, numDefinitions[DefinitionKind.GLOBAL]!!)
+ private fun chooseLocal(): Int = random.nextInt(0, Int.MAX_VALUE)
+
+ fun genAlloc(): BodyStatement.Alloc {
+ return BodyStatement.Alloc(chooseClass(), genArgs())
+ }
+
+ fun genLoad(): BodyStatement.Load {
+ return BodyStatement.Load(genLoadExpression())
+ }
+
+ fun genStore(): BodyStatement.Store {
+ return BodyStatement.Store(genStoreExpression(), genLoadExpression())
+ }
+
+ fun genCall(): BodyStatement.Call {
+ return BodyStatement.Call(chooseFunction(), genArgs())
+ }
+
+ fun genSpawnThread(): BodyStatement.SpawnThread {
+ return BodyStatement.SpawnThread(chooseFunction(), genArgs())
+ }
+
+ // TODO respect symbol's parameters size?
+ fun genArgs(): List<LoadExpression> = List(distributions.numParameters.next(random)) { genLoadExpression() }
+
+ fun genLoadExpression(): LoadExpression = when (distributions.access.next(random)) {
+ AccessKind.LOCAL -> LoadExpression.Local(chooseLocal(), genPath())
+ AccessKind.GLOBAL -> LoadExpression.Global(chooseGlobal(), genPath())
+ }
+
+ fun genStoreExpression(): StoreExpression = when (distributions.access.next(random)) {
+ AccessKind.LOCAL -> StoreExpression.Local(chooseLocal(), genPath())
+ AccessKind.GLOBAL -> StoreExpression.Global(chooseGlobal(), genPath())
+ }
+
+ fun genPath(): Path = Path(List(distributions.pathLength.next(random)) { random.nextInt(0, Int.MAX_VALUE) })
+
+}
\ No newline at end of file
diff --git a/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/GCFuzzingDSLTest.kt b/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/GCFuzzingDSLTest.kt
index fd3afd6..170bb26 100644
--- a/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/GCFuzzingDSLTest.kt
+++ b/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/GCFuzzingDSLTest.kt
@@ -153,4 +153,22 @@
)
)
}
-}
\ No newline at end of file
+}
+
+class GCFuzzingTest : AbstractNativeSimpleTest() {
+ private val testDataDir = java.io.File(System.getProperty("kotlin.internal.native.test.testDataDir")).resolve("gcFuzzingTest")
+
+ private fun runTest(name: String, program: Program) {
+ val output = program.translate()
+ output.save(dslGeneratedDir)
+ runDSL(name, output, testRunSettings.get<Timeouts>().executionTimeout)
+ }
+
+ private inline fun runTest(testInfo: TestInfo, block: () -> Program) = runTest(testInfo.testMethod.get().name, block())
+
+ @Test
+ fun test(testInfo: TestInfo) = runTest(testInfo) {
+ val fuzzer = Fuzzer(0)
+ fuzzer.genProgram()
+ }
+}
diff --git a/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/dsl/Translator.kt b/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/dsl/Translator.kt
index ab44ee4..9a0727f 100644
--- a/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/dsl/Translator.kt
+++ b/native/native.tests/gc-fuzzing-tests/tests/org/jetbrains/kotlin/konan/test/gcfuzzing/dsl/Translator.kt
@@ -324,14 +324,16 @@
}
}
-private fun BodyTranslateContext.appendNewLocal() {
+private fun BodyTranslateContext.appendNewLocal(init : BodyTranslateContext.() -> Unit) {
appendIndent()
- val name = "l${localsCount++}"
+ val name = "l$localsCount"
when (targetLanguage) {
is TargetLanguage.Kotlin -> contents.append("var $name: Any?")
is TargetLanguage.ObjC -> contents.append("id $name")
}
contents.append(" = ")
+ init()
+ localsCount++
}
private class Global(val name: String, val definition: Definition.Global)
@@ -401,15 +403,17 @@
}
private fun BodyTranslateContext.translateAllocStatement(statement: BodyStatement.Alloc) {
- appendNewLocal()
- translateConstructorCallExpression(statement.classId, statement.args)
- appendStatementEnd()
+ appendNewLocal {
+ translateConstructorCallExpression(statement.classId, statement.args)
+ appendStatementEnd()
+ }
}
private fun BodyTranslateContext.translateLoadStatement(statement: BodyStatement.Load) {
- appendNewLocal()
- translateLoadExpression(statement.from)
- appendStatementEnd()
+ appendNewLocal {
+ translateLoadExpression(statement.from)
+ appendStatementEnd()
+ }
}
private fun BodyTranslateContext.translateStoreStatement(statement: BodyStatement.Store) {
@@ -429,9 +433,10 @@
}
private fun BodyTranslateContext.translateCallStatement(statement: BodyStatement.Call) {
- appendNewLocal()
- translateFunctionCallExpression(statement.functionId, statement.args)
- appendStatementEnd()
+ appendNewLocal {
+ translateFunctionCallExpression(statement.functionId, statement.args)
+ appendStatementEnd()
+ }
}
private fun BodyTranslateContext.translateSpawnThreadStatement(statement: BodyStatement.SpawnThread) {