diff --git a/CHANGELOG.md b/CHANGELOG.md index cbdb909b..9990eb02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current version +* Added optimization hints. + * Added `utf32be`, `utf32le`, `cp1253`, `cp1254`, `cp1257`, `geos_de` encodings. * Fixed escape sequences in many encodings. diff --git a/docs/api/command-line.md b/docs/api/command-line.md index 21c9ccc2..997ee267 100644 --- a/docs/api/command-line.md +++ b/docs/api/command-line.md @@ -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. diff --git a/docs/doc_index.md b/docs/doc_index.md index 3c8d9ee0..36d555cf 100644 --- a/docs/doc_index.md +++ b/docs/doc_index.md @@ -44,6 +44,8 @@ * [Important guidelines regarding reentrancy](lang/reentrancy.md) +* [Optimization hints](lang/hints.md) + * [List of keywords](lang/keywords.md) ## Library reference diff --git a/docs/lang/functions.md b/docs/lang/functions.md index 3a695c72..dd76490c 100644 --- a/docs/lang/functions.md +++ b/docs/lang/functions.md @@ -4,17 +4,17 @@ Syntax: -`[segment ()] [] ( ) [align ( )] [@
] { }` +`[segment ()] [] ( ) [align ( )] [] [@
] { }` -`[segment ()] [] ( ) [align ( )] [@
] = ` +`[segment ()] [] ( ) [align ( )] [] [@
] = ` -`[segment ()] asm ( ) @
extern` +`[segment ()] asm ( ) [] @
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 +* `` is a list of [optimization hints](./hints.md), separated by spaces + * `
` 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, diff --git a/docs/lang/hints.md b/docs/lang/hints.md new file mode 100644 index 00000000..58498e5b --- /dev/null +++ b/docs/lang/hints.md @@ -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 + + + + + diff --git a/docs/lang/syntax.md b/docs/lang/syntax.md index 8ea06fbc..226d7c57 100644 --- a/docs/lang/syntax.md +++ b/docs/lang/syntax.md @@ -50,7 +50,7 @@ or a top level of a function (*local* variables). Syntax: -`[segment()] [volatile] [] [@
] [= ]` +`[segment()] [volatile] [] [] [@
] [= ]` Examples: @@ -69,6 +69,8 @@ Volatile variables cannot be declared as `register` or `stack`. * `` 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). + +* `` is a list of [optimization hints](./hints.md), separated by spaces * `
` 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. diff --git a/include/c128_kernal.mfk b/include/c128_kernal.mfk index 9fd33920..400957fc 100644 --- a/include/c128_kernal.mfk +++ b/include/c128_kernal.mfk @@ -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 diff --git a/include/c264_kernal.mfk b/include/c264_kernal.mfk index db55d492..1c7f92ff 100644 --- a/include/c264_kernal.mfk +++ b/include/c264_kernal.mfk @@ -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 diff --git a/include/c64_kernal.mfk b/include/c64_kernal.mfk index 40c1c1ac..5ee723d3 100644 --- a/include/c64_kernal.mfk +++ b/include/c64_kernal.mfk @@ -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. diff --git a/include/pet_kernal.mfk b/include/pet_kernal.mfk index e401ea61..bb7b7ca7 100644 --- a/include/pet_kernal.mfk +++ b/include/pet_kernal.mfk @@ -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) diff --git a/include/vic20_kernal.mfk b/include/vic20_kernal.mfk index 689c5aa6..d6627306 100644 --- a/include/vic20_kernal.mfk +++ b/include/vic20_kernal.mfk @@ -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 diff --git a/include/zxspectrum.mfk b/include/zxspectrum.mfk index 502ff185..d6115e03 100644 --- a/include/zxspectrum.mfk +++ b/include/zxspectrum.mfk @@ -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 } diff --git a/mkdocs.yml b/mkdocs.yml index 7b42fad9..f2c7f720 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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 diff --git a/src/main/scala/millfork/CompilationOptions.scala b/src/main/scala/millfork/CompilationOptions.scala index 3c67cbfb..4d4d39f0 100644 --- a/src/main/scala/millfork/CompilationOptions.scala +++ b/src/main/scala/millfork/CompilationOptions.scala @@ -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( diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 266d6b1e..50fe3b9d 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -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") diff --git a/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala b/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala index 8f2305e3..20ee650a 100644 --- a/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala +++ b/src/main/scala/millfork/assembly/z80/opt/CoarseFlowAnalyzer.scala @@ -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 ) } diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index ee160711..bc4761ca 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -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 diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index 4e46f614..a6ca137d 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -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) } diff --git a/src/main/scala/millfork/output/M6809Assembler.scala b/src/main/scala/millfork/output/M6809Assembler.scala index 7d43825e..7c57f8a0 100644 --- a/src/main/scala/millfork/output/M6809Assembler.scala +++ b/src/main/scala/millfork/output/M6809Assembler.scala @@ -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} diff --git a/src/main/scala/millfork/output/Z80Assembler.scala b/src/main/scala/millfork/output/Z80Assembler.scala index c4f4dfff..d869e932 100644 --- a/src/main/scala/millfork/output/Z80Assembler.scala +++ b/src/main/scala/millfork/output/Z80Assembler.scala @@ -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 } diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index 0a42e07e..f0efee16 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -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) diff --git a/src/test/scala/millfork/test/OptimizationHintsSuite.scala b/src/test/scala/millfork/test/OptimizationHintsSuite.scala index 39159330..7e51ef87 100644 --- a/src/test/scala/millfork/test/OptimizationHintsSuite.scala +++ b/src/test/scala/millfork/test/OptimizationHintsSuite.scala @@ -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