diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt index a898bf3f3..8cbeb6f74 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AssemblyProgram.kt @@ -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 diff --git a/codeGenExperimental/src/prog8/codegen/experimental/ExperiCodeGen.kt b/codeGenExperimental/src/prog8/codegen/experimental/ExperiCodeGen.kt index 2640f3696..2fd82c918 100644 --- a/codeGenExperimental/src/prog8/codegen/experimental/ExperiCodeGen.kt +++ b/codeGenExperimental/src/prog8/codegen/experimental/ExperiCodeGen.kt @@ -34,6 +34,9 @@ class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend { private object EmptyProgram : IAssemblyProgram { override val name = "" + 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 **") diff --git a/codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt b/codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt index 625d22f56..0f9f7bfa0 100644 --- a/codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt +++ b/codeGenIntermediate/src/prog8/codegen/vm/VmCodeGen.kt @@ -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() diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 465ac80bf..b0f40e334 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -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) + val importedFiles: List, + 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 + 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) } diff --git a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt index 734f9e236..e0d5c8822 100644 --- a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt +++ b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt @@ -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) diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 097d4072a..28c66bf76 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -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) } } }) diff --git a/compilerAst/src/prog8/ast/statements/AstStatements.kt b/compilerAst/src/prog8/ast/statements/AstStatements.kt index 336b6f707..8862521df 100644 --- a/compilerAst/src/prog8/ast/statements/AstStatements.kt +++ b/compilerAst/src/prog8/ast/statements/AstStatements.kt @@ -203,7 +203,7 @@ class Return(val values: Array, override val position: Position) : S override fun referencesIdentifier(nameInSource: List): 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 } } diff --git a/intermediate/src/prog8/intermediate/IRProgram.kt b/intermediate/src/prog8/intermediate/IRProgram.kt index 9018d7609..0062ba56d 100644 --- a/intermediate/src/prog8/intermediate/IRProgram.kt +++ b/intermediate/src/prog8/intermediate/IRProgram.kt @@ -72,6 +72,23 @@ class IRProgram(val name: String, allAsmSubs().forEach { operation(it.asmChunk) } operation(globalInits) } + + fun countCodeElements(): Pair { + 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) { diff --git a/simpleAst/src/prog8/code/ICodeGeneratorBackend.kt b/simpleAst/src/prog8/code/ICodeGeneratorBackend.kt index 926c5b037..43f0acd50 100644 --- a/simpleAst/src/prog8/code/ICodeGeneratorBackend.kt +++ b/simpleAst/src/prog8/code/ICodeGeneratorBackend.kt @@ -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 }