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",