IR example metrics now count instructions not p8ir lines

This commit is contained in:
Irmen de Jong committed 2026-03-29 22:46:51 +02:00
1 parent 331e2b8f2b
commit 10ce992b35
9 files changed
+108 -42

No files matched your search

@@ -17,6 +17,10 @@ internal class AssemblyProgram(
outputDir: Path,
private val compTarget: ICompilationTarget) : IAssemblyProgram {
override val irInstructionCount: Int = 0
override val irChunkCount: Int = 0
override val irRegisterCount: Int = 0
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
@@ -34,6 +34,9 @@ class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
private object EmptyProgram : IAssemblyProgram {
override val name = "<Empty Program>"
override val irInstructionCount: Int = 0
override val irChunkCount: Int = 0
override val irRegisterCount: Int = 0
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
if(!options.quiet)
println("** nothing assembled **")
@@ -32,6 +32,15 @@ internal class VmAssemblyProgram(
internal val irProgram: IRProgram
): IAssemblyProgram {
override val irInstructionCount: Int
get() = irProgram.countCodeElements().first
override val irChunkCount: Int
get() = irProgram.countCodeElements().second
override val irRegisterCount: Int
get() = irProgram.countUsedRegisters()
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
// the VM reads the IR file from disk.
IRFileWriter(irProgram, null).write()
+27 -8
View File
@@ -34,11 +34,17 @@ import kotlin.time.measureTime
import kotlin.time.measureTimedValue
private data class AssemblyResult(val success: Boolean, val irInstructionCount: Int, val irChunkCount: Int, val irRegisterCount: Int)
class CompilationResult(val compilerAst: Program, // deprecated, use codegenAst instead
val codegenAst: PtProgram?,
val codegenSymboltable: SymbolTable?,
val compilationOptions: CompilationOptions,
val importedFiles: List<Path>)
val importedFiles: List<Path>,
val irInstructionCount: Int = 0,
val irChunkCount: Int = 0,
val irRegisterCount: Int = 0)
class CompilerArguments(val filepath: Path,
val optimize: Boolean,
@@ -76,6 +82,9 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
var ast: PtProgram? = null
var resultingProgram: Program? = null
var importedFiles: List<Path>
var irInstructionCount = 0
var irChunkCount = 0
var irRegisterCount = 0
val targetConfigFile = expandTilde(Path(args.compilationTarget))
val compTarget = if(targetConfigFile.isRegularFile()) {
@@ -216,7 +225,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
if (compilationOptions.optimize) {
optimizeSimplifiedAst(intermediateAst, compilationOptions, symbolTable!!, args.errors)
args.errors.report()
symbolTable = stMaker.make() // need an updated ST because the optimization changes stuff
// symbolTable = stMaker.make() // need an updated ST because the optimization changes stuff
}
// Remove redundant pointer typecasts - must run AFTER optimization, BEFORE code generation
@@ -243,18 +252,23 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
simplifiedAstDuration = simplifiedAstDuration2
createAssemblyDuration = measureTime {
if (!createAssemblyAndAssemble(
val result = createAssemblyAndAssemble(
intermediateAst,
symbolTable!!,
args.errors,
compilationOptions,
program.generatedLabelSequenceNumber
)
) {
irInstructionCount = result.irInstructionCount
irChunkCount = result.irChunkCount
irRegisterCount = result.irRegisterCount
if (!result.success) {
System.err.println("Error in codegeneration or assembler")
return null
}
}
if (irInstructionCount < 0) {
return null
}
ast = intermediateAst
} else {
if (args.printAst1) {
@@ -286,7 +300,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
if(!args.quietAll) {
println("\nTotal compilation+assemble time: ${totalTime.toString(DurationUnit.SECONDS, 3)}.")
}
return CompilationResult(resultingProgram!!, ast, symbolTable, compilationOptions, importedFiles)
return CompilationResult(resultingProgram!!, ast, symbolTable, compilationOptions, importedFiles, irInstructionCount, irChunkCount, irRegisterCount)
} catch (px: ParseError) {
args.errors.printSingleError("ERROR ${px.position.toClickableStr()} parse error: ${px.message}".trim())
} catch (ac: ErrorsReportedException) {
@@ -617,7 +631,7 @@ private fun createAssemblyAndAssemble(program: PtProgram,
errors: IErrorReporter,
compilerOptions: CompilationOptions,
lastGeneratedLabelSequenceNr: Int
): Boolean {
): AssemblyResult {
val retainSSAforIR = true
@@ -633,9 +647,14 @@ private fun createAssemblyAndAssemble(program: PtProgram,
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
errors.report()
return if(assembly!=null && errors.noErrors()) {
val instructionCount = assembly?.irInstructionCount ?: 0
val chunkCount = assembly?.irChunkCount ?: 0
val registerCount = assembly?.irRegisterCount ?: 0
val success = if(assembly!=null && errors.noErrors()) {
assembly.assemble(compilerOptions, errors)
} else {
false
}
return AssemblyResult(success, instructionCount, chunkCount, registerCount)
}
@@ -616,7 +616,7 @@ _after:
assignIndex = null
conditionVar = null
} else {
conditionVar = VarDecl.createAuto(indexDt)
conditionVar = VarDecl.createAuto(indexDt, ongoto.index.position)
indexValue = IdentifierReference(listOf(conditionVar.name), conditionVar.position)
val varTarget = AssignTarget(indexValue, null, null, null, false, position=conditionVar.position)
assignIndex = Assignment(varTarget, ongoto.index, AssignmentOrigin.USERCODE, ongoto.position)
+41 -30
View File
@@ -37,8 +37,10 @@ private data class ExampleSizes(
val cx16SizeUnoptimized: Int = 0,
val pet32SizeOptimized: Int = 0,
val pet32SizeUnoptimized: Int = 0,
val virtualLineCountOptimized: Int = 0,
val virtualLineCountUnoptimized: Int = 0
val virtualInstrCountOptimized: Int = 0,
val virtualInstrCountUnoptimized: Int = 0,
val virtualRegCountOptimized: Int = 0,
val virtualRegCountUnoptimized: Int = 0
)
private val examplesDir = assumeDirectory(workingDir, "../examples")
@@ -112,16 +114,23 @@ private fun verifyOutputFileSize(result: CompilationResult, expectedSize: Int) {
}
/**
* Verifies the line count of the compiled .p8ir file against an expected value.
* This is used for the virtual target where file size can be unstable due to non-deterministic formatting.
* A small tolerance of ±5 lines is allowed for minor non-deterministic variations.
* Verifies the IR instruction count of the compiled .p8ir file against an expected value.
* This is a stable metric that doesn't change based on source position comments.
* A small tolerance of ±5 instructions is allowed for minor non-deterministic variations.
*/
private fun verifyOutputFileLineCount(result: CompilationResult, expectedLineCount: Int) {
val outputFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
val lineCount = Files.lines(outputFile).count().toInt()
val tolerance = 2 // Allow ±2 lines for minor non-deterministic variations
if (lineCount < expectedLineCount - tolerance || lineCount > expectedLineCount + tolerance) {
lineCount shouldBe expectedLineCount
private fun verifyIrInstructionCount(result: CompilationResult, expectedCount: Int) {
val actualCount = result.irInstructionCount
val tolerance = 5 // Allow ±5 instructions for minor non-deterministic variations
if (actualCount < expectedCount - tolerance || actualCount > expectedCount + tolerance) {
actualCount shouldBe expectedCount
}
}
private fun verifyIrRegisterCount(result: CompilationResult, expectedCount: Int) {
val actualCount = result.irRegisterCount
val tolerance = 5 // Allow ±5 registers for minor non-deterministic variations
if (actualCount < expectedCount - tolerance || actualCount > expectedCount + tolerance) {
actualCount shouldBe expectedCount
}
}
@@ -292,11 +301,11 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
ExampleSizes("swirl-float", c64SizeOptimized=708, c64SizeUnoptimized=735, cx16SizeOptimized=595, cx16SizeUnoptimized=906),
ExampleSizes("tehtriz", c64SizeOptimized=4339, c64SizeUnoptimized=4504, cx16SizeOptimized=6215, cx16SizeUnoptimized=6747),
ExampleSizes("textelite", c64SizeOptimized=11455, c64SizeUnoptimized=12778, cx16SizeOptimized=10789, cx16SizeUnoptimized=13505),
ExampleSizes("pointers/animalgame", c64SizeOptimized=1570, c64SizeUnoptimized=2208, cx16SizeOptimized=1605, cx16SizeUnoptimized=2492, virtualLineCountOptimized=774, virtualLineCountUnoptimized=7598),
ExampleSizes("pointers/binarytree", c64SizeOptimized=2562, c64SizeUnoptimized=2640, cx16SizeOptimized=2085, cx16SizeUnoptimized=2389, virtualLineCountOptimized=1705, virtualLineCountUnoptimized=4922),
ExampleSizes("pointers/hashtable", c64SizeOptimized=3294, c64SizeUnoptimized=4266, cx16SizeOptimized=2832, cx16SizeUnoptimized=3866, virtualLineCountOptimized=1928, virtualLineCountUnoptimized=8066),
ExampleSizes("pointers/sortedlist", c64SizeOptimized=1274, c64SizeUnoptimized=1319, cx16SizeOptimized=1223, cx16SizeUnoptimized=1532, virtualLineCountOptimized=953, virtualLineCountUnoptimized=5814),
ExampleSizes("pointers/sorting", c64SizeOptimized=1776, c64SizeUnoptimized=2802, cx16SizeOptimized=1788, cx16SizeUnoptimized=2739, virtualLineCountOptimized=1369, virtualLineCountUnoptimized=8337),
ExampleSizes("pointers/animalgame", c64SizeOptimized=1570, c64SizeUnoptimized=2208, cx16SizeOptimized=1605, cx16SizeUnoptimized=2492),
ExampleSizes("pointers/binarytree", c64SizeOptimized=2562, c64SizeUnoptimized=2640, cx16SizeOptimized=2085, cx16SizeUnoptimized=2389),
ExampleSizes("pointers/hashtable", c64SizeOptimized=3294, c64SizeUnoptimized=4266, cx16SizeOptimized=2832, cx16SizeUnoptimized=3866),
ExampleSizes("pointers/sortedlist", c64SizeOptimized=1274, c64SizeUnoptimized=1319, cx16SizeOptimized=1223, cx16SizeUnoptimized=1532),
ExampleSizes("pointers/sorting", c64SizeOptimized=1776, c64SizeUnoptimized=2802, cx16SizeOptimized=1788, cx16SizeUnoptimized=2739),
),
listOf(false, true),
listOf(C64Target(), Cx16Target())
@@ -332,17 +341,17 @@ class TestCompilerOnExamplesVirtual: FunSpec({
val onlyVirtual = cartesianProduct(
listOf(
ExampleSizes("bouncegfx", virtualLineCountOptimized=916, virtualLineCountUnoptimized=3604),
ExampleSizes("bsieve", virtualLineCountOptimized=828, virtualLineCountUnoptimized=4546),
ExampleSizes("fountain", virtualLineCountOptimized=746, virtualLineCountUnoptimized=3449),
ExampleSizes("pixelshader", virtualLineCountOptimized=390, virtualLineCountUnoptimized=1260),
ExampleSizes("sincos", virtualLineCountOptimized=1103, virtualLineCountUnoptimized=3654),
ExampleSizes("pointers/animalgame", virtualLineCountOptimized=894, virtualLineCountUnoptimized=8629),
ExampleSizes("pointers/binarytree", virtualLineCountOptimized=2023, virtualLineCountUnoptimized=5732),
ExampleSizes("pointers/hashtable", virtualLineCountOptimized=2267, virtualLineCountUnoptimized=9178),
ExampleSizes("pointers/sortedlist", virtualLineCountOptimized=1117, virtualLineCountUnoptimized=6713),
ExampleSizes("pointers/fountain-virtual", virtualLineCountOptimized=786, virtualLineCountUnoptimized=3491),
ExampleSizes("pointers/sorting", virtualLineCountOptimized=1548, virtualLineCountUnoptimized=9401)
ExampleSizes("bouncegfx", virtualInstrCountOptimized=208, virtualInstrCountUnoptimized=883, virtualRegCountOptimized=100, virtualRegCountUnoptimized=396),
ExampleSizes("bsieve", virtualInstrCountOptimized=158, virtualInstrCountUnoptimized=1141, virtualRegCountOptimized=66, virtualRegCountUnoptimized=468),
ExampleSizes("fountain", virtualInstrCountOptimized=152, virtualInstrCountUnoptimized=829, virtualRegCountOptimized=77, virtualRegCountUnoptimized=371),
ExampleSizes("pixelshader", virtualInstrCountOptimized=43, virtualInstrCountUnoptimized=291, virtualRegCountOptimized=15, virtualRegCountUnoptimized=109),
ExampleSizes("sincos", virtualInstrCountOptimized=276, virtualInstrCountUnoptimized=935, virtualRegCountOptimized=133, virtualRegCountUnoptimized=411),
ExampleSizes("pointers/animalgame", virtualInstrCountOptimized=221, virtualInstrCountUnoptimized=2167, virtualRegCountOptimized=103, virtualRegCountUnoptimized=943),
ExampleSizes("pointers/binarytree", virtualInstrCountOptimized=510, virtualInstrCountUnoptimized=1455, virtualRegCountOptimized=251, virtualRegCountUnoptimized=628),
ExampleSizes("pointers/hashtable", virtualInstrCountOptimized=578, virtualInstrCountUnoptimized=2349, virtualRegCountOptimized=293, virtualRegCountUnoptimized=1055),
ExampleSizes("pointers/sortedlist", virtualInstrCountOptimized=267, virtualInstrCountUnoptimized=1643, virtualRegCountOptimized=118, virtualRegCountUnoptimized=702),
ExampleSizes("pointers/fountain-virtual", virtualInstrCountOptimized=146, virtualInstrCountUnoptimized=824, virtualRegCountOptimized=71, virtualRegCountUnoptimized=365),
ExampleSizes("pointers/sorting", virtualInstrCountOptimized=369, virtualInstrCountUnoptimized=2336, virtualRegCountOptimized=173, virtualRegCountUnoptimized=1020)
),
listOf(false, true)
)
@@ -354,11 +363,13 @@ class TestCompilerOnExamplesVirtual: FunSpec({
) { (params, prep) ->
val filepath = prep.second
val optimize = params.second
val expectedLineCount = if (optimize) params.first.virtualLineCountOptimized else params.first.virtualLineCountUnoptimized
val expectedInstrCount = if (optimize) params.first.virtualInstrCountOptimized else params.first.virtualInstrCountUnoptimized
val expectedRegCount = if (optimize) params.first.virtualRegCountOptimized else params.first.virtualRegCountUnoptimized
val result = compileTheThing(filepath, optimize, target, outputDir)
result shouldNotBe null
if (expectedLineCount > 0) {
verifyOutputFileLineCount(result!!, expectedLineCount)
if (expectedInstrCount > 0) {
verifyIrInstructionCount(result!!, expectedInstrCount)
verifyIrRegisterCount(result, expectedRegCount)
}
}
})
@@ -203,7 +203,7 @@ class Return(val values: Array<Expression>, override val position: Position) : S
override fun referencesIdentifier(nameInSource: List<String>): Boolean = values.any{ it.referencesIdentifier(nameInSource) }
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString() = "Return($values, pos=$position)"
override fun toString() = "Return(${values.contentToString()}, pos=$position)"
}
class Break(override val position: Position) : Statement() {
@@ -321,11 +321,11 @@ class VarDecl(
sharedWithAsm = false, alignment = 0u, dirty = false, position = array.position)
}
fun createAuto(dt: DataType): VarDecl {
fun createAuto(dt: DataType, position: Position): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val vardecl = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, dt, ZeropageWish.NOT_IN_ZEROPAGE,
SplitWish.DONTCARE, null, autoVarName, emptyList(), null,
sharedWithAsm = false, alignment = 0u, dirty = false, position = Position.DUMMY)
sharedWithAsm = false, alignment = 0u, dirty = false, position = position)
return vardecl
}
}
@@ -72,6 +72,23 @@ class IRProgram(val name: String,
allAsmSubs().forEach { operation(it.asmChunk) }
operation(globalInits)
}
fun countCodeElements(): Pair<Int, Int> {
var numInstr = 0
var numChunks = 0
foreachCodeChunk { chunk ->
numChunks++
numInstr += chunk.instructions.size
}
return Pair(numInstr, numChunks)
}
fun countUsedRegisters(): Int {
val used = registersUsed()
return (used.readRegs.keys + used.writeRegs.keys).size +
(used.readFpRegs.keys + used.writeFpRegs.keys).size
}
fun getChunkWithLabel(label: String): IRCodeChunkBase {
for(sub in allSubs()) {
for(chunk in sub.chunks) {
@@ -15,5 +15,8 @@ interface ICodeGeneratorBackend {
interface IAssemblyProgram {
val name: String
val irInstructionCount: Int
val irChunkCount: Int
val irRegisterCount: Int
fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean
}