mirror of
https://github.com/irmen/prog8.git
synced 2024-12-23 09:32:43 +00:00
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.
This commit is contained in:
parent
b4fa72c058
commit
eea3fb48a8
@ -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<IAstModification> {
|
||||
// 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,
|
||||
|
@ -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))
|
||||
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ private fun compileMain(args: Array<String>): 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<String>): 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<String>): 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
|
||||
|
@ -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<IAstModification> {
|
||||
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())) {
|
||||
|
@ -29,8 +29,10 @@ class CompilationResult(val success: Boolean,
|
||||
val importedFiles: List<Path>)
|
||||
|
||||
|
||||
// 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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -42,6 +42,7 @@ class TestCompilerOnExamples {
|
||||
compileProgram(
|
||||
filepath,
|
||||
optimize,
|
||||
optimizeFloatExpressions = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
|
@ -47,6 +47,7 @@ class TestCompilerOptionSourcedirs {
|
||||
compileProgram(
|
||||
filepath = filePath,
|
||||
optimize = false,
|
||||
optimizeFloatExpressions = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
|
@ -40,6 +40,7 @@ internal fun compileFile(
|
||||
return compileProgram(
|
||||
filepath,
|
||||
optimize,
|
||||
optimizeFloatExpressions = false,
|
||||
writeAssembly = writeAssembly,
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
|
@ -28,4 +28,5 @@ data class CompilationOptions(val output: OutputType,
|
||||
) {
|
||||
var slowCodegenWarnings = false
|
||||
var optimize = false
|
||||
var optimizeFloatExpressions = false
|
||||
}
|
||||
|
@ -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
|
||||
------------------------
|
||||
|
@ -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 })
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user