From eea3fb48a83e4f7d1910cec3fe07ee2d316a5c68 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 3 Nov 2021 22:52:08 +0100 Subject: [PATCH] add command line option 'optfloatx' to explicitly re-enable float expr optimization as this can increase code size significantly. The output size of the various example programs using floating point, when not using this optimization, has been reduced significantly. The resulting code runs a (tiny) bit slower though. --- .../src/prog8/optimizer/BinExprSplitter.kt | 9 ++++++++- .../prog8/optimizer/ExpressionSimplifier.kt | 13 +++++++++++- .../src/prog8/optimizer/Extensions.kt | 5 +++-- compiler/src/prog8/CompilerMain.kt | 7 +++++-- .../compiler/BeforeAsmGenerationAstChanger.kt | 14 +++++++------ compiler/src/prog8/compiler/Compiler.kt | 20 ++++++++++++------- .../compiler/astprocessing/AstChecker.kt | 9 ++++----- .../compiler/astprocessing/AstExtensions.kt | 9 ++++----- compiler/test/TestCompilerOnExamples.kt | 1 + compiler/test/TestCompilerOptionLibdirs.kt | 1 + compiler/test/helpers/compileXyz.kt | 1 + .../compilerinterface/CompilationOptions.kt | 1 + docs/source/building.rst | 2 ++ docs/source/todo.rst | 17 ++-------------- .../src/prog8/http/TestHttp.kt | 1 + 15 files changed, 66 insertions(+), 44 deletions(-) diff --git a/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt b/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt index 8faec57fa..ee1742e50 100644 --- a/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt +++ b/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt @@ -3,17 +3,19 @@ package prog8.optimizer import prog8.ast.IStatementContainer import prog8.ast.Node import prog8.ast.Program +import prog8.ast.base.DataType import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.augmentAssignmentOperators import prog8.ast.statements.AssignTarget import prog8.ast.statements.Assignment import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification +import prog8.compilerinterface.CompilationOptions import prog8.compilerinterface.ICompilationTarget import prog8.compilerinterface.isInRegularRAMof -class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() { +class BinExprSplitter(private val program: Program, private val options: CompilationOptions, private val compTarget: ICompilationTarget) : AstWalker() { // override fun after(decl: VarDecl, parent: Node): Iterable { // TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...: @@ -38,6 +40,11 @@ class BinExprSplitter(private val program: Program, private val compTarget: ICom val binExpr = assignment.value as? BinaryExpression if (binExpr != null) { + + if(binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions) + return noModifications + + /* Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack, diff --git a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt index 608a6398d..764704bf4 100644 --- a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt +++ b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt @@ -15,10 +15,21 @@ import kotlin.math.log2 import kotlin.math.pow /* - todo add more expression optimizations + todo add more peephole expression optimizations Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html +*(&X) => X +X % 1 => 0 +X / 1 => X +X ^ -1 => ~x +X >= 1 => X > 0 +X < 1 => X <= 0 +X + ะก1 == C2 => X == C2 - C1 +((X + C1) + C2) => (X + (C1 + C2)) +((X + C1) + (Y + C2)) => ((X + Y) + (C1 + C2)) + + */ diff --git a/codeOptimizers/src/prog8/optimizer/Extensions.kt b/codeOptimizers/src/prog8/optimizer/Extensions.kt index 430b7ab86..13bd57aec 100644 --- a/codeOptimizers/src/prog8/optimizer/Extensions.kt +++ b/codeOptimizers/src/prog8/optimizer/Extensions.kt @@ -2,6 +2,7 @@ package prog8.optimizer import prog8.ast.IBuiltinFunctions import prog8.ast.Program +import prog8.compilerinterface.CompilationOptions import prog8.compilerinterface.ICompilationTarget import prog8.compilerinterface.IErrorReporter @@ -59,8 +60,8 @@ fun Program.simplifyExpressions() : Int { return opti.applyModifications() } -fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int { - val opti = BinExprSplitter(this, compTarget) +fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICompilationTarget) : Int { + val opti = BinExprSplitter(this, options, compTarget) opti.visit(this) return opti.applyModifications() } diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index 48afb75f4..cc149a3c7 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -35,6 +35,7 @@ private fun compileMain(args: Array): Boolean { val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".") val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code") val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations") + val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)") val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed") val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation") val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results") @@ -75,7 +76,8 @@ private fun compileMain(args: Array): Boolean { for(filepathRaw in moduleFiles) { val filepath = pathFrom(filepathRaw).normalize() val compilationResult = compileProgram(filepath, - dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true, + dontOptimize!=true, optimizeFloatExpressions==true, + dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true, compilationTarget, srcdirs, outputPath) results.add(compilationResult) } @@ -114,7 +116,8 @@ private fun compileMain(args: Array): Boolean { val compilationResult: CompilationResult try { compilationResult = compileProgram(filepath, - dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true, + dontOptimize!=true, optimizeFloatExpressions==true, + dontWriteAssembly!=true, slowCodegenWarnings==true, quietAssembler==true, compilationTarget, srcdirs, outputPath) if(!compilationResult.success) return false diff --git a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt index 26deb6c3e..2009ad3bf 100644 --- a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt +++ b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt @@ -11,13 +11,11 @@ import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstVisitor import prog8.compiler.astprocessing.isSubroutineParameter -import prog8.compilerinterface.InternalCompilerException -import prog8.compilerinterface.ICompilationTarget -import prog8.compilerinterface.IErrorReporter -import prog8.compilerinterface.isInRegularRAMof +import prog8.compilerinterface.* -internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() { +internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions, + private val errors: IErrorReporter) : AstWalker() { override fun after(decl: VarDecl, parent: Node): Iterable { if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes) @@ -33,8 +31,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: I // But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF. if(!assignment.isAugmentable && assignment.target.identifier != null - && assignment.target.isInRegularRAMof(compTarget.machine)) { + && assignment.target.isInRegularRAMof(options.compTarget.machine)) { val binExpr = assignment.value as? BinaryExpression + + if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions) + return noModifications + if (binExpr != null && binExpr.operator !in comparisonOperators) { if (binExpr.left !is BinaryExpression) { if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) { diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 01cad3fbd..ce80637fb 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -29,8 +29,10 @@ class CompilationResult(val success: Boolean, val importedFiles: List) +// TODO refactor the gigantic list of parameters fun compileProgram(filepath: Path, optimize: Boolean, + optimizeFloatExpressions: Boolean, writeAssembly: Boolean, slowCodegenWarnings: Boolean, quietAssembler: Boolean, @@ -53,14 +55,18 @@ fun compileProgram(filepath: Path, val totalTime = measureTimeMillis { // import main module and everything it needs val (programresult, compilationOptions, imported) = parseImports(filepath, errors, compTarget, sourceDirs) - compilationOptions.slowCodegenWarnings = slowCodegenWarnings - compilationOptions.optimize = optimize + with(compilationOptions) { + this.slowCodegenWarnings = slowCodegenWarnings + this.optimize = optimize + this.optimizeFloatExpressions = optimizeFloatExpressions + } program = programresult importedFiles = imported processAst(program, errors, compilationOptions) if (compilationOptions.optimize) optimizeAst( program, + compilationOptions, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget @@ -262,13 +268,13 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions errors.report() program.variousCleanups(program, errors) errors.report() - program.checkValid(compilerOptions, errors, compilerOptions.compTarget) + program.checkValid(errors, compilerOptions) errors.report() program.checkIdentifiers(errors, compilerOptions) errors.report() } -private fun optimizeAst(program: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) { +private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) { // optimize the parse tree println("Optimizing...") @@ -279,7 +285,7 @@ private fun optimizeAst(program: Program, errors: IErrorReporter, functions: IBu while (true) { // keep optimizing expressions and statements until no more steps remain val optsDone1 = program.simplifyExpressions() - val optsDone2 = program.splitBinaryExpressions(compTarget) + val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget) val optsDone3 = program.optimizeStatements(errors, functions, compTarget) program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away errors.report() @@ -294,7 +300,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt program.addTypecasts(errors) errors.report() program.variousCleanups(program, errors) - program.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid + program.checkValid(errors, compilerOptions) // check if final tree is still valid errors.report() val callGraph = CallGraph(program) callGraph.checkRecursiveCalls(errors) @@ -315,7 +321,7 @@ private fun writeAssembly(program: Program, compilerOptions: CompilationOptions ): WriteAssemblyResult { // asm generation directly from the Ast - program.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget) + program.processAstBeforeAsmGeneration(compilerOptions, errors) errors.report() // printAst(program) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index df223aca4..840e6327b 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -15,9 +15,8 @@ import java.util.* import kotlin.io.path.Path internal class AstChecker(private val program: Program, - private val compilerOptions: CompilationOptions, private val errors: IErrorReporter, - private val compTarget: ICompilationTarget + private val compilerOptions: CompilationOptions ) : IAstVisitor { override fun visit(program: Program) { @@ -773,7 +772,7 @@ internal class AstChecker(private val program: Program, override fun visit(char: CharLiteral) { try { // just *try* if it can be encoded, don't actually do it - compTarget.encodeString(char.value.toString(), char.altEncoding) + compilerOptions.compTarget.encodeString(char.value.toString(), char.altEncoding) } catch (cx: CharConversionException) { errors.err(cx.message ?: "can't encode character", char.position) } @@ -785,7 +784,7 @@ internal class AstChecker(private val program: Program, checkValueTypeAndRangeString(DataType.STR, string) try { // just *try* if it can be encoded, don't actually do it - compTarget.encodeString(string.value, string.altEncoding) + compilerOptions.compTarget.encodeString(string.value, string.altEncoding) } catch (cx: CharConversionException) { errors.err(cx.message ?: "can't encode string", string.position) } @@ -1259,7 +1258,7 @@ internal class AstChecker(private val program: Program, // check if the floating point values are all within range val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray() - if(doubles.any { it < compTarget.machine.FLOAT_MAX_NEGATIVE || it > compTarget.machine.FLOAT_MAX_POSITIVE }) + if(doubles.any { it < compilerOptions.compTarget.machine.FLOAT_MAX_NEGATIVE || it > compilerOptions.compTarget.machine.FLOAT_MAX_POSITIVE }) return err("floating point value overflow") return true } diff --git a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt index 557b88af1..e8309bb6c 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt @@ -11,18 +11,17 @@ import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification import prog8.compiler.BeforeAsmGenerationAstChanger import prog8.compilerinterface.CompilationOptions -import prog8.compilerinterface.ICompilationTarget import prog8.compilerinterface.IErrorReporter import prog8.compilerinterface.IStringEncoding -internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) { - val checker = AstChecker(this, compilerOptions, errors, compTarget) +internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) { + val checker = AstChecker(this, errors, compilerOptions) checker.visit(this) } -internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) { - val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget) +internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) { + val fixer = BeforeAsmGenerationAstChanger(this, compilerOptions, errors) fixer.visit(this) while(errors.noErrors() && fixer.applyModifications()>0) { fixer.visit(this) diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 20ab55cc8..c8910cf7f 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -42,6 +42,7 @@ class TestCompilerOnExamples { compileProgram( filepath, optimize, + optimizeFloatExpressions = false, writeAssembly = true, slowCodegenWarnings = false, quietAssembler = true, diff --git a/compiler/test/TestCompilerOptionLibdirs.kt b/compiler/test/TestCompilerOptionLibdirs.kt index 83ae7207d..ba6f552f2 100644 --- a/compiler/test/TestCompilerOptionLibdirs.kt +++ b/compiler/test/TestCompilerOptionLibdirs.kt @@ -47,6 +47,7 @@ class TestCompilerOptionSourcedirs { compileProgram( filepath = filePath, optimize = false, + optimizeFloatExpressions = false, writeAssembly = true, slowCodegenWarnings = false, quietAssembler = true, diff --git a/compiler/test/helpers/compileXyz.kt b/compiler/test/helpers/compileXyz.kt index 4951d5298..e95039b3c 100644 --- a/compiler/test/helpers/compileXyz.kt +++ b/compiler/test/helpers/compileXyz.kt @@ -40,6 +40,7 @@ internal fun compileFile( return compileProgram( filepath, optimize, + optimizeFloatExpressions = false, writeAssembly = writeAssembly, slowCodegenWarnings = false, quietAssembler = true, diff --git a/compilerInterfaces/src/prog8/compilerinterface/CompilationOptions.kt b/compilerInterfaces/src/prog8/compilerinterface/CompilationOptions.kt index 5ffe26eea..e264a34e8 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/CompilationOptions.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/CompilationOptions.kt @@ -28,4 +28,5 @@ data class CompilationOptions(val output: OutputType, ) { var slowCodegenWarnings = false var optimize = false + var optimizeFloatExpressions = false } diff --git a/docs/source/building.rst b/docs/source/building.rst index c2fb00c55..b9704a0af 100644 --- a/docs/source/building.rst +++ b/docs/source/building.rst @@ -104,6 +104,8 @@ Also it is possible to specify more than one main module to compile: this can be useful to quickly recompile multiple separate programs quickly. (compiling in a batch like this is a lot faster than invoking the compiler again once per main file) +A short list and explanation of the options is printed if you launch the compiler with ``-h`` or ``-help``. + Module source code files ------------------------ diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 987b042c0..f3a68f2a0 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,24 +1,10 @@ TODO ==== - - For next compiler release (7.2) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* FIX CRASH BUG * -when compiling petaxian: (attack.p8) -Exception in thread "main" prog8.ast.base.FatalAstException: vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment VarDecl(name=attack_num, vartype=VAR, datatype=UBYTE, value=DirectMemoryRead([IdentifierRef([eRef]) + NumericLiteral(UWORD:11)]), pos=[attack.p8: line 72 col 5-45]) - at prog8.compiler.BeforeAsmGenerationAstChanger.after(BeforeAsmGenerationAstChanger.kt:24) - at prog8.ast.walk.AstWalker.visit(AstWalker.kt:239) - at prog8.ast.statements.VarDecl.accept(AstStatements.kt:248) - - - -- analyze (and fix?): TODO why are these bigger now than before the var-initializer optimization: - ; cube3d-float (THIS ONE IS A LOT BIGGER!!) - -- fix the asm-labels problem (github issue #62) - find a way to optimize asm-subroutine param passing where it now sometimes uses the evalstack? +- fix the asm-labels problem (github issue #62) @@ -30,6 +16,7 @@ Blocked by Commander-x16 v39 release Future ^^^^^^ +- document the various compiler command line options in more detail. See "Compiling program code" in the docs - get rid of all TODO's in the code - improve testability further, add more tests - replace certain uses of inferredType.getOr(DataType.UNDEFINED) by i.getOrElse({ errorhandler }) diff --git a/httpCompilerService/src/prog8/http/TestHttp.kt b/httpCompilerService/src/prog8/http/TestHttp.kt index e7363169f..56a4a3f56 100644 --- a/httpCompilerService/src/prog8/http/TestHttp.kt +++ b/httpCompilerService/src/prog8/http/TestHttp.kt @@ -31,6 +31,7 @@ class RequestParser : Take { val a = form.param("a").single() val compilationResult = compileProgram(Path.of(a), optimize = true, + optimizeFloatExpressions = false, writeAssembly = true, slowCodegenWarnings = true, compilationTarget = "c64",