Optimization hints

This commit is contained in:
Karol Stasiak 2021-03-15 00:44:14 +01:00
parent 2468d8cca5
commit 1e4a193741
22 changed files with 171 additions and 34 deletions

View File

@ -2,6 +2,8 @@
## Current version
* Added optimization hints.
* Added `utf32be`, `utf32le`, `cp1253`, `cp1254`, `cp1257`, `geos_de` encodings.
* Fixed escape sequences in many encodings.

View File

@ -206,6 +206,10 @@ The compiler doesn't support accessing the stack variables via the S stack point
* `-O9` Optimize code using superoptimizer (experimental). Computationally very expensive, decent results.
* `-fhints`, `-fno-hints`
Whether optimization hints should be used.
Default: yes.
* `-finline`, `-fno-inline` Whether should inline functions automatically.
See the [documentation about inlining](../abi/inlining.md). Computationally easy, can give decent gains.
`.ini` equivalent: `inline`.
@ -312,3 +316,7 @@ You can also enable or disable warnings individually:
* `-Wuseless`, `-Wno-useless`
Whether should warn about code that does nothing.
Default: enabled.
* `-Whints`, `-Wno-hints`
Whether should warn about unsupported optimization hints.
Default: enabled.

View File

@ -44,6 +44,8 @@
* [Important guidelines regarding reentrancy](lang/reentrancy.md)
* [Optimization hints](lang/hints.md)
* [List of keywords](lang/keywords.md)
## Library reference

View File

@ -4,17 +4,17 @@
Syntax:
`[segment (<segment>)] [<modifiers>] <return_type> <name> ( <params> ) [align ( <alignment> )] [@ <address>] { <body> }`
`[segment (<segment>)] [<modifiers>] <return_type> <name> ( <params> ) [align ( <alignment> )] [<optimization hints>] [@ <address>] { <body> }`
`[segment (<segment>)] [<modifiers>] <return_type> <name> ( <params> ) [align ( <alignment> )] [@ <address>] = <expression>`
`[segment (<segment>)] [<modifiers>] <return_type> <name> ( <params> ) [align ( <alignment> )] [<optimization hints>] [@ <address>] = <expression>`
`[segment (<segment>)] asm <return_type> <name> ( <params> ) @ <address> extern`
`[segment (<segment>)] asm <return_type> <name> ( <params> ) [<optimization hints>] @ <address> extern`
Examples:
void do_nothing() { }
inline byte two() = 2
asm void chkout(byte register(a) char) @ $FFD2 extern
asm void chkout(byte register(a) char) !preserves_x !preserves_y @ $FFD2 extern
segment(prgrom0) void main_loop(word w, byte x) align(fast) { // body omitted
@ -68,6 +68,8 @@ For assembly functions, certain parameter names are interpreted as CPU registers
* on 6502, it means that the function will not cross a page boundary if possible
* on Z80, it is ignored
* `<optimization hints>` is a list of [optimization hints](./hints.md), separated by spaces
* `<address>` is a constant expression that defines where in the memory the function is or will be located.
* `extern` is a keyword than marks functions that are not defined in the current program,

86
docs/lang/hints.md Normal file
View File

@ -0,0 +1,86 @@
[< back to index](../doc_index.md)
# Optimization hints
Optimization hints are optional annotations for functions and variables
that allow the compiler to make extra assumptions that help with code optimization.
The general idea is that removing or disabling optimization hints will not break the code,
but adding invalid optimization hints may break the code.
Every optimization hint's name starts with an exclamation mark.
Optimization hints marked with **(X)** currently do nothing and are planned to be implemented in the future.
## Hints for functions
* `!preserves_memory` the function does not write to memory
* `!idempotent` calling the function multiple times in succession has no effect
* `!hot` the function is hot and should be optimized for speed at the cost of increased size
* `!cold` **(X)** the function is cold and should be optimized for size at the cost of increased run time
* `!odd` the function returns an odd value
* `!even` the function returns an even value
## Hints for 6502 assembly functions
These hints have only effect when used on an assembly function on a 6502 target:
* `!preserves_a` the function preserves the contents of the A register
* `!preserves_x` the function preserves the contents of the X register
* `!preserves_y` the function preserves the contents of the Y register
* `!preserves_c` the function preserves the contents of the carry flag
## Hints for 8080/Z80/LR35902 assembly functions
These hints have only effect when used on an assembly function on a 8080-like target:
* `!preserves_a` the function preserves the contents of the A register
* `!preserves_bc` the function preserves the contents of the B and C registers
* `!preserves_de` the function preserves the contents of the D and E registers
* `!preserves_hl` the function preserves the contents of the H and L registers
* `!preserves_hl` the function preserves the contents of the H and L registers
* `!preserves_cf` the function preserves the contents of the carry flag
## Hints for 6809 assembly functions
These hints have only effect when used on an assembly function on a 6809 target:
* `!preserves_a` the function preserves the contents of the A register
* `!preserves_b` the function preserves the contents of the B register
* `!preserves_d` the function preserves the contents of the A and B register
* `!preserves_dp` **(X)** the function preserves the contents of the DP register; exceptionally this can also be used on non-assembly functions
* `!preserves_x` the function preserves the contents of the X register
* `!preserves_y` the function preserves the contents of the Y register
* `!preserves_u` the function preserves the contents of the U register
* `!preserves_c` the function preserves the contents of the carry flag
## Hints for variables
* `!odd` **(X)** the variable can only contain odd values
* `!even` **(X)** the variable can only contain even values

View File

@ -50,7 +50,7 @@ or a top level of a function (*local* variables).
Syntax:
`[segment(<segment>)] [volatile] [<storage>] <type> <name> [@<address>] [= <initial_value>]`
`[segment(<segment>)] [volatile] [<storage>] <type> <name> [<optimization hints>] [@<address>] [= <initial_value>]`
Examples:
@ -69,6 +69,8 @@ Volatile variables cannot be declared as `register` or `stack`.
* `<storage>` can be only specified for local variables. It can be either `stack`, `static`, `register` or nothing.
`register` is only a hint for the optimizer.
See [the description of variable storage](../abi/variable-storage.md).
* `<optimization hints>` is a list of [optimization hints](./hints.md), separated by spaces
* `<address>` is a constant expression that defines where in the memory the variable will be located.
If not specified, it will be located according to the usual allocation rules.

View File

@ -6,7 +6,7 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void chrout(byte register(a) char) @$FFD2 extern
asm void chrout(byte register(a) char) @$FFD2 !preserves_a !preserves_x !preserves_y extern
asm void putchar(byte register(a) char) {
JSR chrout

View File

@ -5,7 +5,7 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void chrout(byte register(a) char) @$FFD2 extern
asm void chrout(byte register(a) char) @$FFD2 !preserves_a !preserves_x !preserves_y extern
asm void putchar(byte register(a) char) {
JSR chrout

View File

@ -2,7 +2,7 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void chrout(byte register(a) char) @$FFD2 extern
asm void chrout(byte register(a) char) @$FFD2 !preserves_a !preserves_x !preserves_y extern
// CHRIN. Read byte from default input (for keyboard, read a line from the screen). (If not keyboard, must call OPEN and CHKIN beforehands.)
// Output: A = Byte read.

View File

@ -6,7 +6,7 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void putchar(byte register(a) char) @$FFD2 extern
asm void putchar(byte register(a) char) @$FFD2 !preserves_a !preserves_x !preserves_y extern
inline void new_line() {
putchar(13)

View File

@ -6,7 +6,7 @@
// CHROUT. Write byte to default output. (If not screen, must call OPEN and CHKOUT beforehands.)
// Input: A = Byte to write.
asm void chrout(byte register(a) char) @$FFD2 extern
asm void chrout(byte register(a) char) @$FFD2 !preserves_a !preserves_x !preserves_y extern
asm void putchar(byte register(a) char) {
JSR chrout

View File

@ -5,16 +5,16 @@
#pragma zilog_syntax
inline asm void putchar(byte register(a) char) {
inline asm void putchar(byte register(a) char) !preserves_bc !preserves_de !preserves_hl {
rst $10
? ret
}
inline void new_line() {
inline void new_line() !preserves_bc !preserves_de !preserves_hl {
putchar(13)
}
inline asm void set_border(byte register(a) colour) {
inline asm void set_border(byte register(a) colour) !preserves_bc !preserves_de !preserves_hl {
out (254),a
? ret
}

View File

@ -35,6 +35,7 @@ nav:
- Inline 8080/LR35902/Z80 assembly: lang/assemblyz80.md
- Inline 6809 assembly: lang/assembly6809.md
- Reentrancy guidelines: lang/reentrancy.md
- Optimization hints: lang/hints.md
- List of keywords: lang/keywords.md
- Library reference:
- stdlib module: stdlib/stdlib.md

View File

@ -419,6 +419,7 @@ object Cpu extends Enumeration {
RegisterVariables,
FunctionDeduplication,
EnableBreakpoints,
UseOptimizationHints,
GenericWarnings,
UselessCodeWarning,
BuggyCodeWarning,
@ -427,6 +428,7 @@ object Cpu extends Enumeration {
NonZeroTerminatedLiteralWarning,
CallToOverlappingBankWarning,
DataMissingInOutputWarning,
UnsupportedOptimizationHintWarning,
)
private val mosAlwaysDefaultFlags = alwaysDefaultFlags
@ -591,6 +593,7 @@ object CompilationFlag extends Enumeration {
NonZeroTerminatedLiteralWarning,
CallToOverlappingBankWarning,
DataMissingInOutputWarning,
UnsupportedOptimizationHintWarning,
FatalWarnings,
// special options for internal compiler use
EnableInternalTestSyntax,
@ -606,6 +609,7 @@ object CompilationFlag extends Enumeration {
NonZeroTerminatedLiteralWarning,
CallToOverlappingBankWarning,
DataMissingInOutputWarning,
UnsupportedOptimizationHintWarning,
)
val fromString: Map[String, CompilationFlag.Value] = Map(

View File

@ -712,6 +712,9 @@ object Main {
}.description("Optimize code even more.")
if (i == 1 || i > 4) f.hidden()
}
boolean("-fhints", "-fnohints").action{ (c,v) =>
c.changeFlag(CompilationFlag.UseOptimizationHints, v)
}.description("Whether optimization hints should be used.")
flag("--inline").repeatable().action { c =>
c.changeFlag(CompilationFlag.InlineFunctions, true)
}.description("Inline functions automatically.").hidden()
@ -815,6 +818,10 @@ object Main {
c.changeFlag(CompilationFlag.UselessCodeWarning, v)
}.description("Whether should warn about code that does nothing. Default: enabled.")
boolean("-Whints", "-Wno-hints").repeatable().action { (c, v) =>
c.changeFlag(CompilationFlag.UnsupportedOptimizationHintWarning, v)
}.description("Whether should warn about unsupported optimization hints. Default: enabled.")
fluff("", "Other options:", "")
expansion("-Xd")("-O1", "-s", "-fsource-in-asm", "-g").description("Do a debug build. Equivalent to -O1 -s -fsource-in-asm -g")

View File

@ -4,7 +4,7 @@ import millfork.assembly.OptimizationContext
import millfork.assembly.opt.{AnyStatus, FlowCache, SingleStatus, Status}
import millfork.assembly.z80._
import millfork.env._
import millfork.node.Z80NiceFunctionProperty.{DoesntChangeBC, DoesntChangeDE, DoesntChangeHL, SetsATo}
import millfork.node.Z80NiceFunctionProperty.{DoesntChangeA, DoesntChangeBC, DoesntChangeCF, DoesntChangeDE, DoesntChangeHL, SetsATo}
import millfork.node.{NiceFunctionProperty, ZRegister}
import millfork.CompilationFlag
@ -93,10 +93,11 @@ object CoarseFlowAnalyzer {
h = if (preservesH(n) || niceFunctionProperties(DoesntChangeHL -> n)) currentStatus.h else result.h,
l = if (preservesL(n) || niceFunctionProperties(DoesntChangeHL -> n)) currentStatus.l else result.l,
hl = if (preservesH(n) && preservesL(n) || niceFunctionProperties(DoesntChangeHL -> n)) currentStatus.hl else result.hl,
a = extractNiceConstant(n){
a = if (niceFunctionProperties(DoesntChangeA -> n)) currentStatus.a else extractNiceConstant(n){
case SetsATo(a) => Some(a)
case _ => None
},
cf = if (niceFunctionProperties(DoesntChangeCF -> n)) currentStatus.cf else AnyStatus
)
}

View File

@ -1807,13 +1807,18 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def prepareFunctionOptimizationHints(options: CompilationOptions, stmt: FunctionDeclarationStatement): Set[String] = {
if (!options.flag(CompilationFlag.UseOptimizationHints)) return Set.empty
def warn(msg: String): Unit = {
if (options.flag(CompilationFlag.UnsupportedOptimizationHintWarning)){
log.warn(msg, stmt.position)
}
}
val filteredFlags = stmt.optimizationHints.flatMap{
case f@("hot" | "cold" | "idempotent" | "preserves_memory" | "inline" | "odd" | "even") =>
Seq(f)
case f@("preserves_a" | "preserves_x" | "preserves_y" | "preserves_c")
if options.platform.cpuFamily == CpuFamily.M6502 =>
if (stmt.statements.isDefined && !stmt.assembly) {
log.warn(s"Cannot use the $f optimization flags on non-assembly functions", stmt.position)
warn(s"Cannot use the $f optimization hint on non-assembly functions")
Nil
} else {
Seq(f)
@ -1821,7 +1826,15 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
case f@("preserves_a" | "preserves_b" | "preserves_d" | "preserves_c" | "preserves_x" | "preserves_y" | "preserves_u")
if options.platform.cpuFamily == CpuFamily.M6809 =>
if (stmt.statements.isDefined && !stmt.assembly) {
log.warn(s"Cannot use the $f optimization flags on non-assembly functions", stmt.position)
warn(s"Cannot use the $f optimization hints on non-assembly functions")
Nil
} else {
Seq(f)
}
case f@("preserves_a" | "preserves_bc" | "preserves_de" | "preserves_hl" | "preserves_cf")
if options.platform.cpuFamily == CpuFamily.I80 =>
if (stmt.statements.isDefined && !stmt.assembly) {
warn(s"Cannot use the $f optimization hints on non-assembly functions")
Nil
} else {
Seq(f)
@ -1830,21 +1843,21 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
if options.platform.cpuFamily == CpuFamily.M6809 =>
Seq(f)
case f =>
log.warn(s"Unsupported function optimization flag: $f", stmt.position)
warn(s"Unsupported function optimization hint: $f")
Nil
}
if (filteredFlags("hot") && filteredFlags("cold")) {
log.warn(s"Conflicting optimization flags used: `hot` and `cold`", stmt.position)
warn(s"Conflicting optimization hints used: `hot` and `cold`")
}
if (filteredFlags("even") && filteredFlags("odd")) {
log.warn(s"Conflicting optimization flags used: `even` and `odd`", stmt.position)
warn(s"Conflicting optimization hints used: `even` and `odd`")
}
if (filteredFlags("even") || filteredFlags("odd")) {
maybeGet[Type](stmt.resultType) match {
case Some(t) if t.size < 1 =>
log.warn(s"Cannot use `even` or `odd` flags with an empty return type", stmt.position)
warn(s"Cannot use `even` or `odd` hints with an empty return type")
case Some(t: CompoundVariableType) =>
log.warn(s"Cannot use `even` or `odd` flags with a compound return type", stmt.position)
warn(s"Cannot use `even` or `odd` hints with a compound return type")
case _ =>
}
}
@ -1853,15 +1866,20 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def prepareVariableOptimizationHints(options: CompilationOptions, stmt: VariableDeclarationStatement): Set[String] = {
if (!options.flag(CompilationFlag.UseOptimizationHints)) return Set.empty
def warn(msg: String): Unit = {
if (options.flag(CompilationFlag.UnsupportedOptimizationHintWarning)){
log.warn(msg, stmt.position)
}
}
val filteredFlags = stmt.optimizationHints.flatMap{
case f@("odd" | "even") =>
Seq(f)
case f =>
log.warn(s"Unsupported variable optimization flag: $f", stmt.position)
warn(s"Unsupported variable optimization hint: $f")
Nil
}
if (filteredFlags("even") && filteredFlags("odd")) {
log.warn(s"Conflicting optimization flags used: `even` and `odd`", stmt.position)
warn(s"Conflicting optimization hints used: `even` and `odd`")
}
filteredFlags
}
@ -1871,7 +1889,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
if (!options.flag(CompilationFlag.UseOptimizationHints)) return Set.empty
val filteredFlags: Set[String] = stmt.optimizationHints.flatMap{
case f =>
log.warn(s"Unsupported array optimization flag: $f", stmt.position)
log.warn(s"Unsupported array optimization hint: $f", stmt.position)
Nil
}
filteredFlags

View File

@ -233,9 +233,11 @@ object MosNiceFunctionProperty {
}
object Z80NiceFunctionProperty {
case object DoesntChangeA extends NiceFunctionProperty("A")
case object DoesntChangeBC extends NiceFunctionProperty("BC")
case object DoesntChangeDE extends NiceFunctionProperty("DE")
case object DoesntChangeHL extends NiceFunctionProperty("HL")
case object DoesntChangeCF extends NiceFunctionProperty("CF")
case object DoesntChangeIY extends NiceFunctionProperty("IY")
case class SetsATo(value: Int) extends NiceFunctionProperty("A=" + value)
}

View File

@ -2,7 +2,7 @@ package millfork.output
import millfork.assembly.SourceLine
import millfork.assembly.m6809.opt.JumpFixing
import millfork.{CompilationOptions, Platform}
import millfork.{CompilationFlag, CompilationOptions, Platform}
import millfork.assembly.m6809.{MOpcode, _}
import millfork.compiler.m6809.M6809Compiler
import millfork.env.{Environment, FunctionInMemory, Label, MemoryAddressConstant, NormalFunction, NumericConstant}

View File

@ -1,5 +1,6 @@
package millfork.output
import millfork.CompilationFlag.EmitIllegals
import millfork.assembly.OptimizationContext
import millfork.assembly.opt.{SingleStatus, Status}
import millfork.{CompilationFlag, CompilationOptions, Cpu, Platform}
@ -8,7 +9,7 @@ import millfork.assembly.z80.opt.{CoarseFlowAnalyzer, ConditionalInstructions, C
import millfork.compiler.z80.Z80Compiler
import millfork.env._
import millfork.node.NiceFunctionProperty.DoesntWriteMemory
import millfork.node.Z80NiceFunctionProperty.{DoesntChangeBC, DoesntChangeDE, DoesntChangeHL, DoesntChangeIY, SetsATo}
import millfork.node.Z80NiceFunctionProperty.{DoesntChangeA, DoesntChangeBC, DoesntChangeCF, DoesntChangeDE, DoesntChangeHL, DoesntChangeIY, SetsATo}
import millfork.node.{NiceFunctionProperty, Position, Program, ZRegister}
import scala.annotation.tailrec
@ -831,6 +832,7 @@ class Z80Assembler(program: Program,
niceFunctionProperties += (niceFunctionProperty -> functionName)
}
}
genericPropertyScan(DoesntChangeA)(l => !l.changesRegister(ZRegister.A))
genericPropertyScan(DoesntChangeHL)(l => !l.changesRegister(ZRegister.HL))
genericPropertyScan(DoesntChangeDE)(l => !l.changesRegister(ZRegister.DE))
genericPropertyScan(DoesntChangeBC)(l => !l.changesRegister(ZRegister.BC))
@ -841,6 +843,11 @@ class Z80Assembler(program: Program,
override def gatherFunctionOptimizationHints(options: CompilationOptions, niceFunctionProperties: mutable.Set[(NiceFunctionProperty, String)], function: FunctionInMemory): Unit = {
import NiceFunctionProperty._
val functionName = function.name
if (function.optimizationHints("preserves_a")) niceFunctionProperties += DoesntChangeA -> functionName
if (function.optimizationHints("preserves_bc")) niceFunctionProperties += DoesntChangeBC -> functionName
if (function.optimizationHints("preserves_de")) niceFunctionProperties += DoesntChangeDE -> functionName
if (function.optimizationHints("preserves_hl")) niceFunctionProperties += DoesntChangeHL -> functionName
if (function.optimizationHints("preserves_cf")) niceFunctionProperties += DoesntChangeCF -> functionName
if (function.optimizationHints("preserves_memory")) niceFunctionProperties += DoesntWriteMemory -> functionName
if (function.optimizationHints("idempotent")) niceFunctionProperties += Idempotent -> functionName
}

View File

@ -210,12 +210,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
map{case (name, params) => Seq(ImportStatement(name.mkString("/"), params.getOrElse(Nil).toList))}
val optimizationHintsDeclaration: P[Set[String]] =
if (options.flag(CompilationFlag.EnableInternalTestSyntax)) {
("¥" ~/ HWS ~ "(" ~/ HWS ~/ identifier.rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ HWS ~ ")" ~/ "").?.map {
case None => Set()
case Some(list) => list.toSet
}
} else P("").map(_ => Set.empty)
("!" ~/ HWS ~/ identifier ~/ "").rep(min = 0, sep = AWS).map { _.toSet }
val globalVariableDefinition: P[Seq[BankedDeclarationStatement]] = variableDefinition(true)
val localVariableDefinition: P[Seq[DeclarationStatement]] = variableDefinition(false)

View File

@ -11,7 +11,7 @@ class OptimizationHintsSuite extends FunSuite with Matchers {
test("Optimization hints test 1") {
EmuBenchmarkRun("""
| asm void putchar(byte register(a) character) ¥( preserves_a, preserves_x, preserves_y ) @$ffd2 extern
| asm void putchar(byte register(a) character) !preserves_a !preserves_x !preserves_y @$ffd2 extern
| noinline bool should_print(byte a) = a == 5
| void main() {
| byte i