From 21ffbfc46677a6cecc9814db3aa889c49678c4e4 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Thu, 27 Feb 2020 01:33:34 +0100 Subject: [PATCH] Extra optimization pass over non-inlined functions (implements #43) --- examples/crossplatform/hello_world.mfk | 2 +- src/main/scala/millfork/Main.scala | 12 ++-- .../VeryLateM6809AssemblyOptimizations.scala | 18 ++++++ .../VeryLateMosAssemblyOptimizations.scala | 59 +++++++++++++++++++ .../VeryLateI80AssemblyOptimizations.scala | 19 ++++++ .../millfork/output/AbstractAssembler.scala | 15 ++++- .../scala/millfork/test/emu/EmuI86Run.scala | 3 +- .../scala/millfork/test/emu/EmuM6809Run.scala | 3 +- src/test/scala/millfork/test/emu/EmuRun.scala | 3 +- .../scala/millfork/test/emu/EmuZ80Run.scala | 3 +- .../millfork/test/emu/ShouldNotCompile.scala | 6 +- 11 files changed, 127 insertions(+), 16 deletions(-) create mode 100644 src/main/scala/millfork/assembly/m6809/opt/VeryLateM6809AssemblyOptimizations.scala create mode 100644 src/main/scala/millfork/assembly/mos/opt/VeryLateMosAssemblyOptimizations.scala create mode 100644 src/main/scala/millfork/assembly/z80/opt/VeryLateI80AssemblyOptimizations.scala diff --git a/examples/crossplatform/hello_world.mfk b/examples/crossplatform/hello_world.mfk index c4deec52..a9430943 100644 --- a/examples/crossplatform/hello_world.mfk +++ b/examples/crossplatform/hello_world.mfk @@ -9,7 +9,7 @@ void main() { ensure_mixedcase() #if CBM_64 || CBM_264 - set_bg_color(green) + set_bg_color(white) #endif #if CBM_64 || CBM_264 || ZX_SPECTRUM set_border(red) diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 55f7acac..3eee9423 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -5,10 +5,10 @@ import java.nio.charset.StandardCharsets import java.nio.file.{Files, Paths} import java.util.Locale -import millfork.assembly.m6809.opt.M6809OptimizationPresets +import millfork.assembly.m6809.opt.{M6809OptimizationPresets, VeryLateM6809AssemblyOptimizations} import millfork.assembly.mos.AssemblyLine import millfork.assembly.mos.opt._ -import millfork.assembly.z80.opt.Z80OptimizationPresets +import millfork.assembly.z80.opt.{VeryLateI80AssemblyOptimizations, Z80OptimizationPresets} import millfork.buildinfo.BuildInfo import millfork.cli.{CliParser, CliStatus} import millfork.compiler.LabelGenerator @@ -273,7 +273,7 @@ object Main { // compile val assembler = new MosAssembler(program, env, platform) - val result = assembler.assemble(callGraph, assemblyOptimizations, options) + val result = assembler.assemble(callGraph, assemblyOptimizations, options, if (optLevel <= 1) (_,_) => Nil else VeryLateMosAssemblyOptimizations.All) options.log.assertNoErrors("Codegen failed") options.log.debug(f"Unoptimized code size: ${assembler.unoptimizedCodeSize}%5d B") options.log.debug(f"Optimized code size: ${assembler.optimizedCodeSize}%5d B") @@ -313,7 +313,7 @@ object Main { // compile val assembler = new Z80Assembler(program, env, platform) - val result = assembler.assemble(callGraph, assemblyOptimizations, options) + val result = assembler.assemble(callGraph, assemblyOptimizations, options, if (optLevel <= 1) (_,_) => Nil else VeryLateI80AssemblyOptimizations.All) options.log.assertNoErrors("Codegen failed") options.log.debug(f"Unoptimized code size: ${assembler.unoptimizedCodeSize}%5d B") options.log.debug(f"Optimized code size: ${assembler.optimizedCodeSize}%5d B") @@ -343,7 +343,7 @@ object Main { // compile val assembler = new M6809Assembler(program, env, platform) - val result = assembler.assemble(callGraph, assemblyOptimizations, options) + val result = assembler.assemble(callGraph, assemblyOptimizations, options, if (optLevel <= 1) (_,_) => Nil else VeryLateM6809AssemblyOptimizations.All) options.log.assertNoErrors("Codegen failed") options.log.debug(f"Unoptimized code size: ${assembler.unoptimizedCodeSize}%5d B") options.log.debug(f"Optimized code size: ${assembler.optimizedCodeSize}%5d B") @@ -376,7 +376,7 @@ object Main { // compile val assembler = new Z80ToX86Crossassembler(program, env, platform) - val result = assembler.assemble(callGraph, assemblyOptimizations, options) + val result = assembler.assemble(callGraph, assemblyOptimizations, options, if (optLevel <= 1) (_,_) => Nil else VeryLateI80AssemblyOptimizations.All) options.log.assertNoErrors("Codegen failed") options.log.debug(f"Unoptimized code size: ${assembler.unoptimizedCodeSize}%5d B") options.log.debug(f"Optimized code size: ${assembler.optimizedCodeSize}%5d B") diff --git a/src/main/scala/millfork/assembly/m6809/opt/VeryLateM6809AssemblyOptimizations.scala b/src/main/scala/millfork/assembly/m6809/opt/VeryLateM6809AssemblyOptimizations.scala new file mode 100644 index 00000000..af555b09 --- /dev/null +++ b/src/main/scala/millfork/assembly/m6809/opt/VeryLateM6809AssemblyOptimizations.scala @@ -0,0 +1,18 @@ +package millfork.assembly.m6809.opt + +import millfork.CompilationOptions +import millfork.assembly.AssemblyOptimization +import millfork.assembly.m6809.MLine +import millfork.node.NiceFunctionProperty + +/** + * @author Karol Stasiak + */ +object VeryLateM6809AssemblyOptimizations { + + def All(nice: Set[NiceFunctionProperty], options: CompilationOptions): Seq[AssemblyOptimization[MLine]] = { + val result = Seq.newBuilder[AssemblyOptimization[MLine]] + // TODO: add stuff here + result.result() + } +} diff --git a/src/main/scala/millfork/assembly/mos/opt/VeryLateMosAssemblyOptimizations.scala b/src/main/scala/millfork/assembly/mos/opt/VeryLateMosAssemblyOptimizations.scala new file mode 100644 index 00000000..8162a2d8 --- /dev/null +++ b/src/main/scala/millfork/assembly/mos/opt/VeryLateMosAssemblyOptimizations.scala @@ -0,0 +1,59 @@ +package millfork.assembly.mos.opt + +import millfork.CompilationOptions +import millfork.assembly.AssemblyOptimization +import millfork.assembly.mos.{AssemblyLine, State} +import millfork.compiler.mos.MosReturnDispatch +import millfork.node.{MosNiceFunctionProperty, NiceFunctionProperty} +import millfork.assembly.mos.Opcode._ +import millfork.assembly.mos.AddrMode._ + +/** + * @author Karol Stasiak + */ +//noinspection ZeroIndexToHead +object VeryLateMosAssemblyOptimizations { + + val StoresOfImmediatesDifferingByOneViaX = new RuleBasedAssemblyOptimization("", + needsFlowInfo = FlowInfoRequirement.BothFlows, + (Elidable & HasOpcode(LDA) & HasAddrMode(Immediate) & MatchNumericImmediate(0) & HasIndex8) ~ + (Elidable & HasOpcode(STA) & HasAddrModeIn(ZeroPage, Absolute) & DoesntMatterWhatItDoesWith(State.A, State.X)) ~ + (Linear & Not(HasOpcode(LDA) & HasAddrMode(Immediate))).*.capture(10) ~ + (Elidable & HasOpcode(LDA) & HasAddrMode(Immediate) & MatchNumericImmediate(1) & HasIndex8) ~ + Where(ctx => ctx.get[Int](0).+(1).&(0xff) == ctx.get[Int](1)) ~ + (Elidable & HasOpcode(STA) & HasAddrModeIn(ZeroPage, Absolute) & DoesntMatterWhatItDoesWith(State.A, State.X)) ~~> { code => + code(0).copy(opcode = LDX) :: + code(1).copy(opcode = STX) :: ( + code.drop(2).dropRight(2) ++ List( + AssemblyLine.implied(INX).pos(code(code.size - 2).source), + code.last.copy(opcode = STX))) + } + ) + + val StoresOfImmediatesDifferingByOneViaY = new RuleBasedAssemblyOptimization("", + needsFlowInfo = FlowInfoRequirement.BothFlows, + (Elidable & HasOpcode(LDA) & HasAddrMode(Immediate) & MatchNumericImmediate(0) & HasIndex8) ~ + (Elidable & HasOpcode(STA) & HasAddrModeIn(ZeroPage, Absolute) & DoesntMatterWhatItDoesWith(State.A, State.Y)) ~ + (Linear & Not(HasOpcode(LDA) & HasAddrMode(Immediate))).*.capture(10) ~ + (Elidable & HasOpcode(LDA) & HasAddrMode(Immediate) & MatchNumericImmediate(1) & HasIndex8) ~ + Where(ctx => ctx.get[Int](0).+(1).&(0xff) == ctx.get[Int](1)) ~ + (Elidable & HasOpcode(STA) & HasAddrModeIn(ZeroPage, Absolute) & DoesntMatterWhatItDoesWith(State.A, State.Y)) ~~> { code => + code(0).copy(opcode = LDY) :: + code(1).copy(opcode = STY) :: ( + code.drop(2).dropRight(2) ++ List( + AssemblyLine.implied(INY).pos(code(code.size - 2).source), + code.last.copy(opcode = STY))) + } + ) + + def All(nice: Set[NiceFunctionProperty], options: CompilationOptions): Seq[AssemblyOptimization[AssemblyLine]] = { + val result = Seq.newBuilder[AssemblyOptimization[AssemblyLine]] + if (!nice(MosNiceFunctionProperty.DoesntChangeX)){ + result += StoresOfImmediatesDifferingByOneViaX + } + if (!nice(MosNiceFunctionProperty.DoesntChangeY)){ + result += StoresOfImmediatesDifferingByOneViaY + } + result.result() + } +} diff --git a/src/main/scala/millfork/assembly/z80/opt/VeryLateI80AssemblyOptimizations.scala b/src/main/scala/millfork/assembly/z80/opt/VeryLateI80AssemblyOptimizations.scala new file mode 100644 index 00000000..c11e635f --- /dev/null +++ b/src/main/scala/millfork/assembly/z80/opt/VeryLateI80AssemblyOptimizations.scala @@ -0,0 +1,19 @@ +package millfork.assembly.z80.opt + +import millfork.CompilationOptions +import millfork.assembly.AssemblyOptimization +import millfork.assembly.z80.ZLine +import millfork.node.NiceFunctionProperty + +/** + * @author Karol Stasiak + */ +object VeryLateI80AssemblyOptimizations { + + def All(nice: Set[NiceFunctionProperty], options: CompilationOptions): Seq[AssemblyOptimization[ZLine]] = { + val result = Seq.newBuilder[AssemblyOptimization[ZLine]] + // TODO: add stuff here + result.result() + } + +} diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index 01725dda..461faba0 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -195,7 +195,11 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program protected def subbyte(c: Constant, index: Int, totalSize: Int): Constant = if (platform.isBigEndian) c.subbyteBe(index, totalSize) else c.subbyte(index) - def assemble(callGraph: CallGraph, unfilteredOptimizations: Seq[AssemblyOptimization[T]], options: CompilationOptions): AssemblerOutput = { + def assemble( + callGraph: CallGraph, + unfilteredOptimizations: Seq[AssemblyOptimization[T]], + options: CompilationOptions, + veryLateOptimizations: (Set[NiceFunctionProperty], CompilationOptions) => Seq[AssemblyOptimization[T]]): AssemblerOutput = { mem.programName = options.outputFileName.getOrElse("MILLFORK") val platform = options.platform val variableAllocators = platform.variableAllocators @@ -277,7 +281,14 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program case None => log.trace("Not inlining " + f, function.position) functionsThatCanBeCalledFromInlinedFunctions += function.name - compiledFunctions(f) = NormalCompiledFunction(function.declaredBank.getOrElse(platform.defaultCodeBank), code, function.address.isDefined, function.alignment) + val thisFunctionNiceProperties = niceFunctionProperties.filter(_._2.==(f)).map(_._1).toSet + val labelMapImm = labelMap.toMap + val niceFunctionPropertiesImm = niceFunctionProperties.toSet + val extraOptimizedCode = veryLateOptimizations(thisFunctionNiceProperties, options).foldLeft(code) { (c, opt) => + val code = opt.optimize(function, c, OptimizationContext(options, labelMapImm, env.maybeGet[ThingInMemory]("__reg"), niceFunctionPropertiesImm)) + if (code eq c) code else quickSimplify(code) + } + compiledFunctions(f) = NormalCompiledFunction(function.declaredBank.getOrElse(platform.defaultCodeBank), extraOptimizedCode, function.address.isDefined, function.alignment) optimizedCodeSize += code.map(_.sizeInBytes).sum if (options.flag(CompilationFlag.InterproceduralOptimization)) { gatherNiceFunctionProperties(options, niceFunctionProperties, function, code) diff --git a/src/test/scala/millfork/test/emu/EmuI86Run.scala b/src/test/scala/millfork/test/emu/EmuI86Run.scala index 09641e7c..dfc53dae 100644 --- a/src/test/scala/millfork/test/emu/EmuI86Run.scala +++ b/src/test/scala/millfork/test/emu/EmuI86Run.scala @@ -10,6 +10,7 @@ import javax.swing.SwingUtilities import millfork._ import millfork.assembly.AssemblyOptimization import millfork.assembly.z80.ZLine +import millfork.assembly.z80.opt.VeryLateI80AssemblyOptimizations import millfork.compiler.z80.Z80Compiler import millfork.compiler.{CompilationContext, LabelGenerator} import millfork.env.{Environment, InitializedArray, InitializedMemoryVariable, NormalFunction} @@ -135,7 +136,7 @@ class EmuI86Run(nodeOptimizations: List[NodeOptimization], assemblyOptimizations val env2 = new Environment(None, "", CpuFamily.I80, options) env2.collectDeclarations(program, options) val assembler = new Z80ToX86Crossassembler(program, env2, platform) - val output = assembler.assemble(callGraph, assemblyOptimizations, options) + val output = assembler.assemble(callGraph, assemblyOptimizations, options, VeryLateI80AssemblyOptimizations.All) println(";;; compiled: -----------------") output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("////; DISCARD_")).foreach(println) println(";;; ---------------------------") diff --git a/src/test/scala/millfork/test/emu/EmuM6809Run.scala b/src/test/scala/millfork/test/emu/EmuM6809Run.scala index c5686d08..0a8f00b8 100644 --- a/src/test/scala/millfork/test/emu/EmuM6809Run.scala +++ b/src/test/scala/millfork/test/emu/EmuM6809Run.scala @@ -7,6 +7,7 @@ import fastparse.core.Parsed.{Failure, Success} import millfork._ import millfork.assembly.AssemblyOptimization import millfork.assembly.m6809.MLine +import millfork.assembly.m6809.opt.VeryLateM6809AssemblyOptimizations import millfork.compiler.m6809.M6809Compiler import millfork.compiler.{CompilationContext, LabelGenerator} import millfork.env.{Environment, InitializedArray, InitializedMemoryVariable, NormalFunction} @@ -144,7 +145,7 @@ class EmuM6809Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizat val env2 = new Environment(None, "", CpuFamily.M6502, options) env2.collectDeclarations(program, options) val assembler = new M6809Assembler(program, env2, platform) - val output = assembler.assemble(callGraph, assemblyOptimizations, options) + val output = assembler.assemble(callGraph, assemblyOptimizations, options, VeryLateM6809AssemblyOptimizations.All) println(";;; compiled: -----------------") output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("; DISCARD_")).foreach(println) println(";;; ---------------------------") diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index a882fb01..8b98d9e8 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -9,6 +9,7 @@ import com.loomcom.symon.{Bus, Cpu, CpuState} import fastparse.core.Parsed.{Failure, Success} import millfork.assembly.AssemblyOptimization import millfork.assembly.mos.AssemblyLine +import millfork.assembly.mos.opt.VeryLateMosAssemblyOptimizations import millfork.compiler.{CompilationContext, LabelGenerator} import millfork.compiler.mos.MosCompiler import millfork.env.{Environment, InitializedArray, InitializedMemoryVariable, NormalFunction} @@ -221,7 +222,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], val env2 = new Environment(None, "", CpuFamily.M6502, options) env2.collectDeclarations(program, options) val assembler = new MosAssembler(program, env2, platform) - val output = assembler.assemble(callGraph, assemblyOptimizations, options) + val output = assembler.assemble(callGraph, assemblyOptimizations, options, VeryLateMosAssemblyOptimizations.All) println(";;; compiled: -----------------") output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("; DISCARD_")).foreach(println) println(";;; ---------------------------") diff --git a/src/test/scala/millfork/test/emu/EmuZ80Run.scala b/src/test/scala/millfork/test/emu/EmuZ80Run.scala index 55675867..7e22c29f 100644 --- a/src/test/scala/millfork/test/emu/EmuZ80Run.scala +++ b/src/test/scala/millfork/test/emu/EmuZ80Run.scala @@ -18,6 +18,7 @@ import millfork.node.opt.NodeOptimization import millfork.output.{MemoryBank, Z80Assembler} import millfork.parser.{MosParser, PreprocessingResult, Preprocessor, Z80Parser} import millfork._ +import millfork.assembly.z80.opt.VeryLateI80AssemblyOptimizations import millfork.compiler.z80.Z80Compiler import org.scalatest.Matchers @@ -143,7 +144,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio val env2 = new Environment(None, "", CpuFamily.I80, options) env2.collectDeclarations(program, options) val assembler = new Z80Assembler(program, env2, platform) - val output = assembler.assemble(callGraph, assemblyOptimizations, options) + val output = assembler.assemble(callGraph, assemblyOptimizations, options, VeryLateI80AssemblyOptimizations.All) println(";;; compiled: -----------------") output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("////; DISCARD_")).foreach(println) println(";;; ---------------------------") diff --git a/src/test/scala/millfork/test/emu/ShouldNotCompile.scala b/src/test/scala/millfork/test/emu/ShouldNotCompile.scala index 53040acd..03e585a6 100644 --- a/src/test/scala/millfork/test/emu/ShouldNotCompile.scala +++ b/src/test/scala/millfork/test/emu/ShouldNotCompile.scala @@ -95,17 +95,17 @@ object ShouldNotCompile extends Matchers { cpuFamily match { case CpuFamily.M6502 => val assembler = new MosAssembler(program, env2, platform) - val output = assembler.assemble(callGraph, Nil, options) + val output = assembler.assemble(callGraph, Nil, options, (_, _) => Nil) output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("; DISCARD_")).foreach(println) fail("Failed: Compilation succeeded for 6502") case CpuFamily.I80 => val assembler = new Z80Assembler(program, env2, platform) - val output = assembler.assemble(callGraph, Nil, options) + val output = assembler.assemble(callGraph, Nil, options, (_, _) => Nil) output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("; DISCARD_")).foreach(println) fail("Failed: Compilation succeeded for Z80") case CpuFamily.M6809 => val assembler = new M6809Assembler(program, env2, platform) - val output = assembler.assemble(callGraph, Nil, options) + val output = assembler.assemble(callGraph, Nil, options, (_, _) => Nil) output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("; DISCARD_")).foreach(println) fail("Failed: Compilation succeeded for 6809") case _ =>