Compare commits

...

84 Commits

Author SHA1 Message Date
zbyti 579895caef
Merge 1980a72317 into d8c11a9c50 2023-09-03 00:21:59 -06:00
Karol Stasiak d8c11a9c50 Minor optimizations 2023-02-03 14:46:01 +01:00
Karol Stasiak ee47ccef5a fix z80 indexing in hardwritten assembly (#137) 2023-01-27 18:27:53 +01:00
Karol Stasiak 24de2f7530 enable tests of identity page 2023-01-27 18:19:19 +01:00
Karol Stasiak 1beb695151 more tests 2023-01-27 18:17:41 +01:00
Karol Stasiak 9229092309 random minor stuff 2023-01-27 18:16:25 +01:00
Karol Stasiak ca7166d1ae oops 2023-01-27 18:16:02 +01:00
Karol Stasiak 1594d63a9a Add a command line flag for identity page 2023-01-27 18:15:40 +01:00
Karol Stasiak 75cc34663c Z80: fix an optimization 2023-01-27 18:15:10 +01:00
Karol Stasiak f4d2fdd370 6502: use identity page for maths 2023-01-27 18:14:50 +01:00
Karol Stasiak 29c1e3f2a6 6502 and Z80: Optimize optimizations 2023-01-27 18:13:21 +01:00
Karol Stasiak e9cfec54b5 Various 6502 optimization fixes 2023-01-27 17:34:09 +01:00
Karol Stasiak 1bc1ab3539 Fix some codegen bugs on 6502 2023-01-27 17:00:29 +01:00
Karol Stasiak 6f294d6dec Fix -fbounds-checking 2022-02-12 02:13:30 +01:00
Karol Stasiak 9866c974ad Don't apply Constant index offset propagation in decimal mode 2022-02-12 02:12:55 +01:00
Karol Stasiak ef2f5b5918 Add test 2022-02-11 21:48:24 +01:00
Karol Stasiak a70a1c0e6b Consider kernal_interrupt functions as entry points 2022-02-11 21:48:13 +01:00
Karol Stasiak 790c836771 Fix handling of non-top-level return dispatch statements 2022-02-11 21:47:39 +01:00
Karol Stasiak 6af84d1628 Optimize the optimizer 2022-02-08 14:42:30 +01:00
Karol Stasiak 7b205a2754 Allow multiple -D options 2022-02-08 13:21:26 +01:00
Karol Stasiak d23fe1756e Switch to snapshot versioning 2022-02-08 13:20:15 +01:00
Karol Stasiak 3b3ee1bc55 Version 0.3.30 2021-12-15 16:01:46 +01:00
Karol Stasiak fb30075ea5 Update CHANGELOG 2021-12-15 15:54:00 +01:00
Karol Stasiak b5084cd180 More tests 2021-12-15 15:53:49 +01:00
Karol Stasiak b1a2be5574 6809: Fix constant condition compilation 2021-11-22 01:58:09 +01:00
Karol Stasiak a260d0a806 CBM BASIC loader should use the actual address of main (fixes #111) 2021-11-12 02:45:53 +01:00
Karol Stasiak 38ad919ed9 Show the positions of unevaluated constants 2021-11-12 02:44:58 +01:00
Karol Stasiak 34ef8b8de9 Better evaluation of the if function in constants 2021-11-12 02:44:20 +01:00
Karol Stasiak f676e74e38 Macro improvements:
– allow local constants in macros
– allow untyped macro parameters with void
– treat the name of a function as a pointer to it
– add this.function local alias (#118)
2021-11-12 02:10:07 +01:00
Karol Stasiak c9313e5dbe Add support for ld65 label file format (#128) 2021-11-12 00:47:12 +01:00
Karol Stasiak 73a223b9b6 Add support for Mesen label file format by tracking file-relative positions of segments (#128) 2021-11-03 21:48:45 +01:00
Karol Stasiak b168818bab Report every hole in the output 4 bytes or larger; count total free bytes in the entire bank 2021-09-21 00:43:29 +02:00
Karol Stasiak effa723863 Never commit .labels files again 2021-09-21 00:21:21 +02:00
Karol Stasiak b2ab3dbeab Clean accidentally commited labels file 2021-09-21 00:20:54 +02:00
Karol Stasiak c9ef5e636b Add raw label file format 2021-09-21 00:09:59 +02:00
Karol Stasiak fc2f0782c5 Update Notepad++ syntax 2021-09-20 00:56:04 +02:00
Karol Stasiak f0e22f02a6
Merge pull request #126 from mookiexl/master
Update x16_experimental.ini
2021-09-18 14:23:02 +02:00
Karol Stasiak b2291d1cb2
Merge pull request #122 from retrac0/master
gb library code updates to current syntax
2021-09-18 13:21:15 +02:00
Karol Stasiak 166acf2b18 R800 support 2021-09-18 00:36:16 +02:00
Karol Stasiak 7530b382a8 Fix array fields in certain contexts 2021-09-17 22:19:39 +02:00
Karol Stasiak b66435d06b Fix EasyFlash switch_hirom (fixes #121) 2021-09-13 15:10:11 +02:00
Karol Stasiak 0c8951d015 Fix unused variable elimination in for-array statements (fixes #125) 2021-09-13 09:27:34 +02:00
Karol Stasiak 84dde8589c Allow spaces after the array keyword (fixes #120) 2021-09-13 09:26:54 +02:00
Karol Stasiak 3b0aa9a425 Fixes for flag boolean types (fixes #123) 2021-09-13 09:26:27 +02:00
mookiexl 1b8de990a5 Update x16_experimental.ini 2021-08-19 14:37:20 +02:00
mookiexl b060fc599b Update x16_experimental.ini
$00-$01 are used for bank switching.
Unintentional write to those will crash the machine.
2021-08-16 16:06:28 +02:00
Karol Stasiak 90e5360bfd Related to #119:
– Detection of simple byte overflow cases.
– Optimization of 8×8→16 multiplication on 6809.
– Multiplication optimizations on Z80.
2021-08-06 21:01:03 +02:00
Joel Heikkila bbf430a1c7 update gb lib code to current syntax for example/ 2021-07-11 18:15:39 -04:00
Karol Stasiak 7f6a0c6b0d Fix documentation of .loword and .hiword 2021-06-29 02:39:23 +02:00
Karol Stasiak 7f0def54bc Improvements for 65CE02 assembly (fixes #116) 2021-06-29 02:29:30 +02:00
Karol Stasiak faf97cee1f Don't call valid control characters invalid 2021-06-29 02:29:12 +02:00
Karol Stasiak da862069a7 Fix for volatile fields 2021-06-29 02:28:32 +02:00
Karol Stasiak 431a25d325 Internal support for pointers to volatile objects; add volatile fields (closes #112) 2021-06-21 14:20:24 +02:00
Karol Stasiak 73beafd65e Support for expressions in file() (fixes #114) 2021-06-21 14:18:17 +02:00
Karol Stasiak 307ad90ecf 6809: Improve flow analysis and add few more optimizations 2021-06-21 14:15:40 +02:00
Karol Stasiak 2e592a2331 Correct address for sid_v1_sr (fixing #115) 2021-06-21 14:12:33 +02:00
Karol Stasiak 91c9b42f3d Switch to snapshot versioning 2021-06-21 14:11:56 +02:00
Karol Stasiak 144da70594 Version 0.3.28 2021-05-24 00:17:51 +02:00
Karol Stasiak 4f6eefab79 Officially deprecate old decimal operators 2021-05-23 22:31:11 +02:00
Karol Stasiak ca35367974 Tease floating-point numbers 2021-05-16 23:32:33 +02:00
Karol Stasiak 2065c3b4ac Fix 6809 under native-image 2021-05-16 23:32:18 +02:00
Karol Stasiak 9028d55a7e Various optimization improvements and fixes, mostly for 6809 2021-05-16 23:31:52 +02:00
Karol Stasiak 21d4d3252f Remove debugging statements 2021-05-16 00:40:12 +02:00
Karol Stasiak 45ad049a51 Update CHANGELOG 2021-05-08 00:46:37 +02:00
Karol Stasiak 0172e29bb2 Underscores in numeric literals. Fix parsing of Intel hex constants starting with 0B. 2021-05-08 00:42:06 +02:00
Karol Stasiak ffb46c4250 Optimize some bitmask operations 2021-05-05 02:58:57 +02:00
Karol Stasiak c51c08ad56 6809: Fix and improve optimizations 2021-05-05 02:58:28 +02:00
Karol Stasiak fcdad413b0 #110 Add a warning for comparisons between bytes and pointers. 2021-05-05 00:59:54 +02:00
Karol Stasiak 63edce28c4 Update CHANGELOG 2021-04-24 01:21:03 +02:00
Karol Stasiak 1f318a2a0e 6502: Optimize sign extension 2021-04-24 01:18:34 +02:00
Karol Stasiak 1bcb6d5010 Fix sign extension when using pointers 2021-04-24 01:18:21 +02:00
Karol Stasiak 510f85960c Fix optimizations of unused labels 2021-04-24 01:18:01 +02:00
Karol Stasiak 8412075175 Fix pointer type compatibility checks 2021-04-24 01:13:59 +02:00
Karol Stasiak e25df3d1b3 Parser optimizations again 2021-04-19 01:06:51 +02:00
Karol Stasiak b71d058c6a Parser optimizations 2021-04-18 23:48:23 +02:00
Karol Stasiak bf273456a3 Fix #107 2021-03-20 01:25:27 +01:00
Karol Stasiak 062483971a Fix #107 (syntax errors in stdlib, overpanicky statement preprocessor) 2021-03-20 01:23:51 +01:00
Karol Stasiak 1e4a193741 Optimization hints 2021-03-15 00:44:14 +01:00
Karol Stasiak 2468d8cca5 Update changelog 2021-03-13 21:42:52 +01:00
Karol Stasiak 58b5b6ff28 Fix #106: the current working directory should be always included in the include path 2021-03-13 21:42:11 +01:00
Karol Stasiak 8aac3bc329 Allow character literals in preprocessor 2021-03-13 21:40:38 +01:00
Karol Stasiak 24eac6708b Fix escape sequences in many encodings 2021-03-13 21:40:18 +01:00
Karol Stasiak 66fc1d3984 Add several more encodings 2021-03-13 21:39:48 +01:00
Karol Stasiak 0bbdc348e7 Switch to snapshot versioning 2021-03-13 02:19:29 +01:00
212 changed files with 5096 additions and 1324 deletions

3
.gitignore vendored
View File

@ -31,9 +31,12 @@ issue*.mfk
*.seq
*.asm
*.lbl
*.labels
*.nl
*.fns
*.sym
*.mlb
*.dbg
*.deb
*.xex
*.nes

View File

@ -1,5 +1,69 @@
# Change log
## 0.3.30 (2021-12-15)
* Added volatile structure fields (#112).
* Added `this.function` as the alias for the current function (#118).
* Added support for constant evaluation in `file` expressions (#114).
* Allowed declaring local constants and passing untyped parameters for macros.
* Allowed treating bare function name as a pointer to it.
* Added Mesen, ld65 and "raw" label file formats (#128).
* Commodore: the address used by SYS is now determined automatically instead of hardcoded (#111).
* C64: Fixed address for `sid_v1_sr` (#115).
* EasyFlash: Fixed address for `switch_hirom` (#121).
* GB: Fixed standard library (thanks to @retrac0).
* Commander X16: Updated platform definition file (thanks to @mookiexl).
* 65CE02: Full assembly support.
* R800: Full assembly support.
* Various miscompilation fixes (#123, #125) and parser fixes (e.g. #120).
* 6809: Various optimizations.
* Improvements related to constant evaluation.
## 0.3.28 (2021-05-24)
* Officially deprecated decimal operators with apostrophes.
* Added optimization hints.
* Added `utf32be`, `utf32le`, `cp1253`, `cp1254`, `cp1257`, `geos_de` encodings.
* Allowed for underscores in numeric literals for readability purposes, similar to several other programming languages.
* Added a warning for comparisons between bytes and pointers (#110).
* Fixed escape sequences in many encodings.
* Fixed and documented absolute module imports (#106)
* Fixed and optimized sign extension.
* Fixed optimizations involving unused labels.
* Fixed pointer types to type aliases.
* Fixed parsing of Intel hex literals of the form `0BH`, `0B0H` etc.
* 6809: Fixed flow analysis in optimization.
* Optimization of certain bitmask operations.
* Parsing optimizations.
## 0.3.26 (2021-03-01)
* Array fields in structs.

View File

@ -5,7 +5,7 @@
A middle-level programming language targeting 6502-based, 8080-based, Z80-based and 6809-based microcomputers.
For binary releases, see: [https://github.com/KarolS/millfork/releases](https://github.com/KarolS/millfork/releases)
(latest: 0.3.24).
(latest: 0.3.30).
For build instructions, see [Build instructions](./COMPILING.md).
## Features

View File

@ -1,6 +1,6 @@
name := "millfork"
version := "0.3.26"
version := "0.3.31-SNAPSHOT"
// keep it at 2.12.11 for GraalVM native image compatibility!
scalaVersion := "2.12.11"

View File

@ -14,6 +14,8 @@ It implies the following:
* cannot contain variable or array declarations
* but can contain scalar constant declarations; the constants are scoped to the particular macro invocation
* can be `asm` - in this case, they should **not** end with a return instruction
* do not have an address
@ -35,7 +37,13 @@ It implies the following:
* `call` parameters exceptionally can have their type declared as `void`;
such parameters accept expressions of any type, including `void`, however, you cannot assign from those expressions
* macros do not have their own scope (they reuse the scope from their invocations) exceptions: the parameters and the local labels defined in assembly
* macros do not have their own scope (they reuse the scope from their invocations) exceptions:
* the parameters
* the local labels defined in assembly
* the local constants
* control-flow statements (`break`, `continue`, `return`, `goto`, `label`) are run as if places in the caller function

View File

@ -47,6 +47,12 @@ The extension and the file format are platform-dependent.
* `-G sym` format used by the WLA DX assembler. The extension is `.sym`.
* `-G fceux` multi-file format used by the FCEUX emulator. The extension is `.nl`.
* `-G mesen` format used by the Mesen emulator. The extension is `.mlb`.
* `-G ld65` a simplified version of the format used by the `ld65` linker (used by CC65 and CA65). The extension is `.dbg`.
* `-G raw` Millfork-specific format. The extension is '.labels'. Each row contains bank number, start address, end address (if known), object type, and Millfork-specific object identifier.
* `-fbreakpoints`, `-fno-breakpoints`
Whether the compiler should use the `breakpoint` macro.
@ -206,6 +212,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`.
@ -289,6 +299,10 @@ You can also enable or disable warnings individually:
Whether should warn about deprecated aliases.
Default: enabled.
* `-Wcomparisons`, `-Wno-comparisons`
Whether should warn about comparisons between bytes and pointers.
Default: enabled.
* `-Wextra-comparisons`, `-Wno-extra-comparisons`
Whether should warn about simplifiable unsigned integer comparisons.
Default: disabled.
@ -312,3 +326,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

@ -2,6 +2,11 @@
### A note about Commodore 64
#### Unusual main function location
If you're creating a prg file and your `main` function may be located at address $2710 hex (10000 decimal) or higher,
you might need to define the `DISPLACED_MAIN=1` preprocessor feature.
#### Multifile programs
A multifile program is a program stored on a disk that consists of the main program file that is executed first

View File

@ -37,6 +37,8 @@ if a line ends with a backslash character, the value continues to the next line.
* `z80` (Zilog Z80)
* `strictz80` (Z80 without illegal instructions)
* `r800` (R800)
* `z80next` (Z80 core from ZX Spectrum Next)
Note: Millfork version 0.3.18 and earlier uses the name `zx80next` for this architecture.
@ -80,6 +82,8 @@ This list cannot contain module template instantiations.
* `emit_x80` whether the compiler should emit instructions present on Sharp LR35902 and Z80, but absent on Intel 8080, default is `true` on compatible processors and `false` elsewhere
* `emit_z80` whether the compiler should emit Zilog Z80 instructions not covered by `emit_x80`, default is `true` on compatible processors and `false` elsewhere
* `emit_r800` whether the compiler should emit R800 instructions, default is `true` on compatible processors and `false` elsewhere
* `prevent_jmp_indirect_bug` whether the compiler should try to avoid the indirect JMP bug,
default is `false` on 65C02-compatible or non-6502 processors and `true` elsewhere
@ -263,3 +267,7 @@ Default: `main,*`
* `sym` format used by the WLA/DX assembler. The extension is `.sym`.
* `fceux` multi-file format used by the FCEUX emulator. The extension is `.nl`.
* `mesen` format used by the Mesen emulator. The extension is `.mlb`.
* `ld65` format used by the `ld65` linker. The extension is `.dbg`.

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

@ -16,7 +16,7 @@ No other lines are allowed in the file.
* `NAME=<name>` defines the name for this encoding. Required.
* `BUILTIN=<internal name>` defines this encoding to be a UTF-based encoding.
`<internal name>` may be one of `UTF-8`, `UTF-16LE`, `UTF-16BE`.
`<internal name>` may be one of `UTF-8`, `UTF-16LE`, `UTF-16BE`, `UTF-32LE`, `UTF-32BE`.
If this directive is present, the only other allowed directive in the file is the `NAME` directive.
* `EOT=<xx>` where `<xx>` are two hex digits, defines the string terminator byte.

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,

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

@ -0,0 +1,84 @@
[< 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_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

@ -14,7 +14,14 @@ Octal: `0o172`
Hexadecimal: `$D323`, `0x2a2`
When using Intel syntax for inline assembly, another hexadecimal syntax is available: `0D323H`, `2a2h`.
Digits can be separated by underscores for readability. Underscores are also allowed between the radix prefix and the digits:
123_456
0x01_ff
0b_0101_1111__1100_0001
$___________FF
When using Intel syntax for inline assembly, another hexadecimal syntax is available: `0D323H`, `2a2h`, `3e_h`.
It is not allowed in any other places.
The type of a literal is the smallest type of undefined signedness
@ -65,7 +72,7 @@ and the byte constant `nullchar_scr` is defined to be equal to the string termin
You can override the values for `nullchar` and `nullchar_scr`
by defining preprocessor features `NULLCHAR` and `NULLCHAR_SCR` respectively.
Warning: If you define UTF-16 to be you default or screen encoding, you will encounter several problems:
Warning: If you define UTF-16 or UTF-32 to be you default or screen encoding, you will encounter several problems:
* `nullchar` and `nullchar_scr` will still be bytes, equal to zero.
* the `string` module in the Millfork standard library will not work correctly
@ -75,21 +82,26 @@ Warning: If you define UTF-16 to be you default or screen encoding, you will enc
You can also prepend `p` to the name of the encoding to make the string length-prefixed.
The length is measured in bytes and doesn't include the zero terminator, if present.
In all encodings except for UTF-16 the prefix takes one byte,
In all encodings except for UTF-16 and UTF-32 the prefix takes one byte,
which means that length-prefixed strings cannot be longer than 255 bytes.
In case of UTF-16, the length prefix contains the number of code units,
so the number of bytes divided by two,
which allows for strings of practically unlimited length.
The length is stores as two bytes and is always little endian,
The length is stored as two bytes and is always little endian,
even in case of the `utf16be` encoding or a big-endian processor.
In case of UTF-32, the length prefix contains the number of Unicode codepoints,
so the number of bytes divided by four.
The length is stored as four bytes and is always little endian,
even in case of the `utf32be` encoding or a big-endian processor.
"this is a Pascal string" pascii
"this is also a Pascal string"p
"this is a zero-terminated Pascal string"pz
Note: A string that's both length-prefixed and zero-terminated does not count as a normal zero-terminated string!
To pass it to a function that expects a zero-terminated string, add 1 (or, in case of UTF-16, 2):
To pass it to a function that expects a zero-terminated string, add 1 (or, in case of UTF-16, 2, or UTF-32, 4):
pointer p
p = "test"pz

View File

@ -11,7 +11,8 @@ Each module has a name, which is its unique identifier.
A module name is a sequence of slash-separated valid Millfork identifiers.
The name also defines where the module is located:
a module named `a/b` is presumed to exist in `a/b.mfk`
and it's looked up first in the current working directory,
and it's looked up first in the directory that contains the current source file,
then in the current working directory,
and then in the include directories.
A module can import other modules, using the `import` statement.

View File

@ -18,9 +18,9 @@ Millfork has different operator precedence compared to most other languages. Fro
* `->` and `[]`
* `*`, `*'`, `/`, `%%`
* `*`, `$*`, `/`, `%%`
* `+`, `+'`, `-`, `-'`, `|`, `&`, `^`, `>>`, `>>'`, `<<`, `<<'`, `>>>>`
* `+`, `$+`, `-`, `$-`, `|`, `&`, `^`, `>>`, `$>>`, `<<`, `$<<`, `>>>>`
* `:`
@ -34,16 +34,16 @@ Millfork has different operator precedence compared to most other languages. Fro
You cannot use two different operators at the same precedence levels without using parentheses to disambiguate.
It is to prevent confusion about whether `a + b & c << d` means `(a + b) & (c << d)` `((a + b) & c) << d` or something else.
The only exceptions are `+` and `-`, and `+'` and `-'`.
They are interpreted as expected: `5 - 3 + 2 == 4` and `5 -' 3 +' 2 == 4`.
Note that you cannot mix `+'` and `-'` with `+` and `-`.
The only exceptions are `+` and `-`, and `$+` and `$-`.
They are interpreted as expected: `5 - 3 + 2 == 4` and `5 $- 3 $+ 2 == 4`.
Note that you cannot mix `$+` and `$-` with `+` and `-`.
Certain operators (`/`, `%%`, `<<`, `>>`, `<<'`, `>>'`, `>>>>`, `:`, `!=`) cannot have more than 2 parameters,
Certain operators (`/`, `%%`, `<<`, `>>`, `$<<`, `$>>`, `>>>>`, `:`, `!=`) cannot have more than 2 parameters,
i.e. `x / y / z` will not compile.
The decimal operators have two different forms:
* apostrophe form (e.g. `+'`) the original one, to be deprecated in the future, may be removed in Millfork 0.4
* apostrophe form (e.g. `+'`) the original one, deprecated, will be removed in Millfork 0.4
* dollar form (e.g. `$+`) available since Millfork 0.3.22
@ -135,20 +135,20 @@ These operators work using the decimal arithmetic (packed BCD).
On Ricoh-based targets (e.g. Famicom) they require the zeropage register to have size at least 4
* `+'`, `-'`: decimal addition/subtraction
`$+`, `$-`: (since Millfork 0.3.22)
`byte +' byte`
`constant word +' constant word`
`constant long +' constant long`
`word +' word` (zpreg)
* `$+`, `$-`: decimal addition/subtraction
`+'`, `-'`: (deprecated form)
`byte $+ byte`
`constant word $+ constant word`
`constant long $+ constant long`
`word $+ word` (zpreg)
* `*'`: decimal multiplication
`$*`: (since Millfork 0.3.22)
`constant *' constant`
* `$*`: decimal multiplication
`*'`: (deprecated form)
`constant $* constant`
* `<<'`, `>>'`: decimal multiplication/division by power of two
`$<<`, `$>>`: (since Millfork 0.3.22)
`byte <<' constant byte`
* `$<<`, `$>>`: decimal multiplication/division by power of two
`<<'`, `>>'`: (deprecated form)
`byte $<< constant byte`
## Comparison operators
@ -201,8 +201,8 @@ An expression of form `a[f()] += b` may call `f` an undefined number of times.
`mutable word = word`
`mutable long = long`
* `+=`, `+'=`, `|=`, `^=`, `&=`: modification in place
`$+=` (since Millfork 0.3.22)
* `+=`, `$+=`, `|=`, `^=`, `&=`: modification in place
`+'=` (deprecated form)
`mutable byte += byte`
`mutable word += word`
`mutable trivial long += long`
@ -212,14 +212,14 @@ An expression of form `a[f()] += b` may call `f` an undefined number of times.
`mutable word <<= byte`
`mutable trivial long <<= byte`
* `<<'=`, `>>'=`: decimal shift in place
`$<<=`, `$>>=` (since Millfork 0.3.22)
`mutable byte <<'= constant byte`
`mutable word <<'= constant byte`
`mutable trivial long <<'= constant byte`
* `$<<=`, `$>>=`: decimal shift in place
`<<'=`, `>>'=` (deprecated form)
`mutable byte $<<= constant byte`
`mutable word $<<= constant byte`
`mutable trivial long $<<= constant byte`
* `-=`, `-'=`: subtraction in place
`$-=` (since Millfork 0.3.22)
* `-=`, `$-=`: subtraction in place
`-'=` (deprecated form)
`mutable byte -= byte`
`mutable word -= simple word`
`mutable trivial long -= simple long`
@ -230,9 +230,9 @@ An expression of form `a[f()] += b` may call `f` an undefined number of times.
`mutable word *= unsigned byte` (zpreg)
`mutable word *= word` (zpreg)
* `*'=`: decimal multiplication in place
`$*=` (since Millfork 0.3.22)
`mutable byte *'= constant byte`
* `$*=`: decimal multiplication in place
`*'=` (deprecated form)
`mutable byte $*= constant byte`
* `/=`, `%%=`: unsigned division and modulo in place
`mutable unsigned byte /= unsigned byte` (zpreg)
@ -291,9 +291,9 @@ but you can access its fields or take its pointer:
* `nonet`: expansion of an 8-bit operation to a 9-bit operation
`nonet(byte + byte)`
`nonet(byte +' byte)`
`nonet(byte $+ byte)`
`nonet(byte << constant byte)`
`nonet(byte <<' constant byte)`
`nonet(byte $<< constant byte)`
Other kinds of expressions than the above (even `nonet(byte + byte + byte)`) will not work as expected.
* `hi`, `lo`: most/least significant byte of a word

View File

@ -25,3 +25,5 @@
* `byte segment.N.bank` the value of `segment_N_bank` from the platform definition
* `byte segment.N.fill` the value of `segment_N_fill` from the platform definition
* `this.function` the alias of the current function (in macros, it resolves to the actual non-macro function that called the macro)

View File

@ -103,6 +103,8 @@ Some libraries may require that some of these be defined.
* `KEYBOARD` 1 if the target has a keyboard, 0 otherwise
* `DISPLACED_MAIN` set this to 1 if the `main` function is in a very unusual location for the target
* `USE_MOUSE_MBM` set this to 1 if you want to enable middle button support for the mouse.
* `JOYSTICKS` the maximum number of joysticks using standard hardware configurations, may be 0
@ -151,11 +153,40 @@ The `if` function returns its second parameter if the first parameter is defined
// prints 500:
#infoeval if(0, 400, 500)
TODO
`not`, `lo`, `hi`, `min`, `max` `+`, `-`, `*`, `|`, `&`, `^`, `||`, `&&`, `<<`, `>>`,`==`, `!=`, `>`, `>=`, `<`, `<=`
The `min` and `max` functions return the smallest or largest parameter respectively. They support any number of arguments:
// prints 400:
#infoeval min(400, 500, 600)
// prints 500:
#infoeval max(400, 500)
The following Millfork operators and functions are also available in the preprocessor:
`not`, `lo`, `hi`, `+`, `-`, `*`, `|`, `&`, `^`, `||`, `&&`, `<<`, `>>`,`==`, `!=`, `>`, `>=`, `<`, `<=`
The following Millfork operators and functions are not available in the preprocessor:
`+'`, `-'`, `*'`, `<<'`, `>>'`, `:`, `>>>>`, `nonet`, all the assignment operators
`$+`, `$-`, `$*`, `$<<`, `$>>`, `:`, `>>>>`, `nonet`, all the assignment operators
### Character literals
Preprocessor supports character literals. By default, they are interpreted in the default encoding,
but you can suffix them with other encodings.
// usually prints 97:
#infoeval 'a'
// prints 97:
#infoeval 'a'ascii
Exceptionally, you can suffix the character literal with `utf32`.
This gives the literal the value of the Unicode codepoint of the character:
// may print 94, 96, 112, 173, 176, 184, 185, 222, 227, 234, 240, something else, or even fail to compile:
#infoeval 'π'
// prints 960:
#infoeval 'π'utf32
Escape sequences are supported, as per encoding. `utf32` pseudoencoding supports the same escape sequences as `utf8`.
### `#template`

View File

@ -10,9 +10,9 @@ These suffixes can be only applied to arithmetic or pointer variables:
* `.hi` the most significant byte of a two-byte variable (word, pointer) (use `hi(_)` for arbitrary expressions)
* `.loword` the least significant byte of a three- or four-byte variable
* `.loword` the least significant word of a three- or four-byte variable
* `.hiword` the most significant byte of a three- or four-byte variable
* `.hiword` the most significant word of a three- or four-byte variable
* `.b0`, `.b1` etc. the given byte of the multi-byte arithmetic variable, with `.b0` being the least significant byte

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

@ -26,6 +26,8 @@ TODO: document the file format.
* `oldpetscii` or `oldpet` old PETSCII (Commodore PET with newer ROMs)
* `geos_de` text encoding used by the German version of GEOS for C64
* `cbmscr` or `petscr` Commodore screencodes
* `cbmscrjp` or `petscrjp` Commodore screencodes as used on Japanese versions of Commodore 64
@ -89,7 +91,7 @@ DOS codepages 437, 850, 851, 852, 855, 858, 866
* `kamenicky` Kamenický encoding
* `cp1250`, `cp1251`, `cp1252` Windows codepages 1250, 1251, 1252
* `cp1250`, `cp1251`, `cp1252`, `cp1253`, `cp1254`, `cp1257` Windows codepages 1250, 1251, 1252, 1253, 1254, 1257
* `msx_intl`, `msx_jp`, `msx_ru`, `msx_br` MSX character encoding, International, Japanese, Russian and Brazilian respectively
@ -132,6 +134,8 @@ English, Japanese, Spanish/Italian and French/German respectively
* `utf16be`, `utf16le` UTF-16BE and UTF-16LE
* `utf32be`, `utf32le` UTF-32BE and UTF-32LE
When programming for Commodore,
use `petscii` for strings you're printing using standard I/O routines
and `petsciiscr` for strings you're copying to screen memory directly.
@ -163,10 +167,12 @@ The exact value of `{nullchar}` is encoding-dependent:
* in the `zx80` encoding it's `{x01}`,
* in the `zx81` encoding it's `{x0b}`,
* in the `petscr` and `petscrjp` encodings it's `{xe0}`,
* in the `apple2e` encoding it's `{x7f}`,
* in the `atasciiscr` encoding it's `{xdb}`,
* in the `pokemon1*` encodings it's `{x50}`,
* in the `cocoscr` encoding it's exceptionally two bytes: `{xd0}`
* in the `utf16be` and `utf16le` encodings it's exceptionally two bytes: `{x00}{x00}`
* in the `utf32be` and `utf32le` encodings it's exceptionally four bytes: `{x00}{x00}{x00}{x00}`
* in other encodings it's `{x00}` (this may be a subject to change in future versions).
##### Available only in some encodings
@ -211,6 +217,7 @@ Encoding | lowercase letters | backslash | currencies | intl | card suits
`petscr` | yes¹ | no | £ | none | yes¹
`petjp` | no | no | ¥ | katakana³ | yes³
`petscrjp` | no | no | ¥ | katakana³ | yes³
`geos_de` | yes | no | | | no
`sinclair`, `bbc` | yes | yes | £ | none | no
`zx80`, `zx81` | no | no | £ | none | no
`apple2` | no | yes | | none | no
@ -273,6 +280,7 @@ Encoding | new line | braces | backspace | cursor movement | text colour | rever
`origpet` | yes | no | no | yes | no | yes | no
`oldpet` | yes | no | no | yes | no | yes | no
`petscr`, `petscrjp`| no | no | no | no | no | no | no
`geos_de` | no | no | no | no | no | yes | no
`sinclair` | yes | yes | no | yes | yes | yes | yes
`zx80`,`zx81` | yes | no | yes | yes | no | no | no
`ascii`, `iso_*` | yes | yes | yes | no | no | no | no

View File

@ -3,6 +3,12 @@
The examples showcased here are designed to compile with a compiler built from the newest sources.
If you are using a release version of the compiler, consider browsing the older versions of the examples:
* [for version 0.3.28](https://github.com/KarolS/millfork/tree/v0.3.28/examples)
* [for version 0.3.26](https://github.com/KarolS/millfork/tree/v0.3.26/examples)
* [for version 0.3.24](https://github.com/KarolS/millfork/tree/v0.3.24/examples)
* [for version 0.3.22](https://github.com/KarolS/millfork/tree/v0.3.22/examples)
* [for version 0.3.18](https://github.com/KarolS/millfork/tree/v0.3.18/examples)

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) !preserves_a !preserves_x !preserves_y @$FFD2 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) !preserves_a !preserves_x !preserves_y @$FFD2 extern
asm void putchar(byte register(a) char) {
JSR chrout

View File

@ -2,7 +2,7 @@ import err
inline asm void switch_hirom(byte register(a) bank) {
? and #$3F
! sta $DE01
! sta $DE00
? rts
}

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) !preserves_a !preserves_x !preserves_y @$FFD2 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

@ -10,7 +10,7 @@ word sid_v1_freq @$D400
word sid_v1_pulse @$D402
byte sid_v1_cr @$D404
byte sid_v1_ad @$D405
byte sid_v1_sr @$D409
byte sid_v1_sr @$D406
word sid_v2_freq @$D407
word sid_v2_pulse @$D409

View File

@ -0,0 +1,31 @@
#template $ADDR$
#if not(CBM)
#warn cbm/basic_loader module should be only used on Commodore targets
#endif
const array _basic_loader @ $ADDR$ = [
#if DISPLACED_MAIN
@word_le [
$ADDR$ + 0xB
],
#else
@word_le [
$ADDR$ + if(main.addr >= 10000, 1/0, 0xA) // use -D DISPLACED_MAIN=1 if you get an error here
],
#endif
10,
0,
$9e,
#if DISPLACED_MAIN
$30 + (main.addr/10000)%%10,
#endif
$30 + (main.addr/1000)%%10,
$30 + (main.addr/100)%%10,
$30 + (main.addr/10)%%10,
$30 + (main.addr/1)%%10,
0,
0,
0
]

View File

@ -7,7 +7,7 @@ C0-DF=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
a-z=C1
{q}=02
{apos}=07
{q}=A2
{apos}=A7
{nbsp}=40
{n}=8D

View File

@ -13,3 +13,4 @@ EOT=00
{apos}=27
{lbrace}=7b
{rbrace}=7d
{pound}=5c

View File

@ -7,8 +7,8 @@ EOT=7F
20-3f=@abcdefghijklmnopqrstuvwxyz[\]^_
60-7e=πABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~
{q}=22
{apos}=27
{q}=02
{apos}=07
{lbrace}=7b
{rbrace}=7d
{pi}=60

View File

@ -9,4 +9,4 @@ E0-FE=`abcdefghijklmnopqrstuvwxyz{\}~
{q}=A2
{apos}=A7
{lbrace}=FB
{rbrace}=FC
{rbrace}=FD

View File

@ -26,6 +26,7 @@ f0-ff=đńňóôőö÷řůúűüýţ˙
{apos}=27
{lbrace}=7b
{rbrace}=7d
{euro}=80
{copy}=a9
{ss}=df
{nbsp}=A0

View File

@ -23,6 +23,7 @@ f0-ff=рстуфхцчшщъыьэюя
{apos}=27
{lbrace}=7b
{rbrace}=7d
{euro}=88
{copy}=a9
{nbsp}=A0
{shy}=AD

View File

@ -25,6 +25,7 @@ f0-ff=ðñòóôõö÷øùúûüýþÿ
{apos}=27
{lbrace}=7b
{rbrace}=7d
{euro}=80
{cent}=a2
{pound}=a3
{yen}=a5

View File

@ -0,0 +1,39 @@
NAME=CP1253
EOT=00
20=U+0020
21-3f=!"#$%&'()*+,-./0123456789:;<=>?
40-5f=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
60-7e=`abcdefghijklmnopqrstuvwxyz{|}~
80=€
82-87=‚ƒ„…†‡
89=‰
8b=
91-97=‘’“”•–—
99=™
9b=
a1-ac=΅Ά£¤¥¦§¨©ͺ«¬
ae-af=®―
b0-bf=°±²³΄µ¶·ΈΉΊ»Ό½ΎΏ
c0-cf=ΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟ
d0-d1=ΠΡ
d3-df=ΣΤΥΦΧΨΩΪΫάέήί
e0-ef=ΰαβγδεζηθικλμνξο
f0-fe=πρςστυφχψωϊϋόύώ
{b}=08
{t}=09
{n}=0d0a
{q}=22
{apos}=27
{lbrace}=7b
{rbrace}=7d
{euro}=80
{pound}=a3
{yen}=a5
{copy}=a9
{pi}=f0
{nbsp}=A0
{shy}=AD

View File

@ -0,0 +1,34 @@
NAME=CP1254
EOT=00
20=U+0020
21-3f=!"#$%&'()*+,-./0123456789:;<=>?
40-5f=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
60-7e=`abcdefghijklmnopqrstuvwxyz{|}~
80=€
82-8c=‚ƒ„…†‡ˆ‰Š‹Œ
91-9c=‘’“”•–—˜™š›œ
9f=Ÿ
a1-ac=¡¢£¤¥¦§¨©ª«¬
ae-af=®¯
b0-bf=°±²³´µ¶·¸¹º»¼½¾¿
c0-cf=ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ
d0-df=ĞÑÒÓÔÕÖ×ØÙÚÛÜİŞß
e0-ef=àáâãäåæçèéêëìíîï
f0-ff=ğñòóôõö÷øùúûüışÿ
{b}=08
{t}=09
{n}=0d0a
{q}=22
{apos}=27
{lbrace}=7b
{rbrace}=7d
{euro}=80
{cent}=a2
{pound}=a3
{yen}=a5
{copy}=a9
{ss}=df
{nbsp}=A0
{shy}=AD

View File

@ -0,0 +1,36 @@
NAME=CP1257
EOT=00
20=U+0020
21-3f=!"#$%&'()*+,-./0123456789:;<=>?
40-5f=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
60-7e=`abcdefghijklmnopqrstuvwxyz{|}~
80=€
82-8c=‚ƒ„…†‡ˆ‰Š‹Œ
8d-8f=¨ˇ¸
91-9c=‘’“”•–—˜™š›œ
9e-9f=¯˛
a1-ac=¡¢£¤¥¦§Ø©Ŗ«¬
ae-af=®Æ
b0-bf=°±²³´µ¶·ø¹ŗ»¼½¾æ
c0-cf=ĄĮĀĆÄÅĘĒČÉŹĖĢĶĪĻ
d0-df=ŠŃŅÓŌÕÖ×ŲŁŚŪÜŻŽß
e0-ef=ąįāćäåęēčéźėģķīļ
f0-ff=šńņóōõö÷ųłśūüżž˙
{b}=08
{t}=09
{n}=0d0a
{q}=22
{apos}=27
{lbrace}=7b
{rbrace}=7d
{euro}=80
{cent}=a2
{pound}=a3
{yen}=a5
{copy}=a9
{ss}=df
{nbsp}=A0
{shy}=AD

View File

@ -27,4 +27,5 @@ F0-FE=≡±≥≤⌠⌡÷≈°∙·√ⁿ²■
{pound}=9c
{yen}=9d
{ss}=e1
{pi}=e3
{nbsp}=FF

View File

@ -23,6 +23,7 @@ F1-FE=±‗¾¶§÷¸°¨·¹³²■
{apos}=27
{lbrace}=7b
{rbrace}=7d
{copy}=b8
{cent}=BD
{pound}=9c
{yen}=BE

View File

@ -24,6 +24,7 @@ F1-FE=±υφχ§ψ¸°¨ωϋΰώ■
{apos}=27
{lbrace}=7b
{rbrace}=7d
{pound}=9c
{ss}=D7
{nbsp}=FF
{shy}=F0

View File

@ -25,6 +25,7 @@ F1-FE=±‗¾¶§÷¸°¨·¹³²■
{rbrace}=7d
{cent}=BD
{pound}=9c
{copy}=b8
{yen}=BE
{euro}=D5
{ss}=e1

View File

@ -19,6 +19,9 @@ fe-ff=↕↔
{apos}=27
{lbrace}=7b
{rbrace}=7d
{pound}=a3
{copy}=a4
{pi}=b8
{AE}=5b
{OE}=5c
{AA}=5d

View File

@ -19,3 +19,6 @@ fe-ff=↕↔
{apos}=27
{lbrace}=7b
{rbrace}=7d
{pound}=a3
{copy}=a4
{pi}=b8

View File

@ -19,3 +19,5 @@ fe-ff=↕↔
{apos}=27
{lbrace}=7b
{rbrace}=7d
{copy}=a4
{pi}=b8

View File

@ -20,3 +20,6 @@ fe-ff=↕↔
{apos}=27
{lbrace}=7b
{rbrace}=7d
{pound}=a3
{copy}=a4
{pi}=b8

View File

@ -24,3 +24,4 @@ F0-F9=0123456789
{q}=7F
{lbrace}=C0
{rbrace}=D0
{cent}=4A

View File

@ -0,0 +1,20 @@
NAME=GEOS-DE
EOT=00
20=U+0020
21-3f=!"#$%&'()*+,-./0123456789:;<=>?
40-5f=§ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜ^_
60-7e=`abcdefghijklmnopqrstuvwxyzäöüß
{b}=08
{t}=09
{n}=0d0a
{q}=22
{apos}=27
{AE}=5b
{OE}=5c
{UE}=5d
{ae}=7b
{oe}=7c
{ue}=7d
{ss}=7e

View File

@ -27,3 +27,4 @@ f1-ff=ñòóôġö÷ĝùúûüŭŝ˙
{ss}=df
{nbsp}=A0
{shy}=AD
{pound}=A3

View File

@ -24,4 +24,5 @@ F0-FE=≡±≥≤⌠⌡÷≈°∙·√ⁿ²■
{lbrace}=7b
{rbrace}=7d
{ss}=e1
{pi}=e3
{nbsp}=FF

View File

@ -14,6 +14,7 @@ EOT=00
{lbrace}=7b
{rbrace}=7d
{nbsp}=a0
{copy}=98
•=95
b0=№

View File

@ -14,6 +14,7 @@ EOT=00
{lbrace}=7b
{rbrace}=7d
{nbsp}=9a
{copy}=bf
80-8f=─│┌┐└┘├┤┬┴┼▀▄█▌▐
90-99=░▒▓⌠■∙√≈≤≥

View File

@ -14,6 +14,7 @@ EOT=00
{lbrace}=7b
{rbrace}=7d
{nbsp}=9a
{copy}=bf
80-8f=─│┌┐└┘├┤┬┴┼▀▄█▌▐
90-99=░▒▓“■∙”—№™

View File

@ -14,6 +14,7 @@ EOT=00
{lbrace}=7b
{rbrace}=7d
{shy}=8d
{copy}=bf

View File

@ -14,6 +14,7 @@ EOT=00
{lbrace}=7b
{rbrace}=7d
{nbsp}=9a
{copy}=bf
80-8f=─│┌┐└┘├┤┬┴┼▀▄█▌▐
90-99=░▒▓⌠■∙√≈≤≥

View File

@ -29,5 +29,6 @@ f1-ff=ÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ
{ss}=a7
{nbsp}=CA
{euro}=DB
{pi}=b9
€=DB
U+F8FF=F0

View File

@ -26,4 +26,5 @@ F0-FE=≡±≥≤⌠⌡÷≈°∙·√ⁿ²■
{cent}=9b
{yen}=9d
{ss}=e1
{pi}=e3
{nbsp}=FF

View File

@ -55,3 +55,8 @@ f0-ff=äëïöüçæåøñãõ⇒⇐⇔≡
{lbrace}=7b
{rbrace}=7d
{pi}=1b18
{pound}=a3
{copy}=a4
{yen}=bd
{cent}=b1
{ss}=ba

View File

@ -23,6 +23,7 @@ F0-FF=₽×./,♀0123456789
{ot}=BE
{'v}=BF
{hav}=BF
{apos}=E0
{'r}=E4
{ar}=E4
{'m}=E5

View File

@ -22,6 +22,7 @@ F0-FF=₽×./,♀0123456789
{'t}=DD
{'v}=DE
{apos}=E0
{PK}=E1
{pk}=E1
{MN}=E2

View File

@ -13,6 +13,7 @@ E6-E8=?!.
EC-EF=▷▶▼♂
F0-FF=₽×./,♀0123456789
{ss}=be
{c'}=d4
{ce}=d4
{d'}=d5
@ -37,6 +38,7 @@ F0-FF=₽×./,♀0123456789
{u'}=de
{ue}=de
{y'}=df
{apos}=e0
{PK}=E1
{pk}=E1

View File

@ -0,0 +1,2 @@
NAME=UTF-32BE
BUILTIN=UTF-32BE

View File

@ -0,0 +1,2 @@
NAME=UTF-32LE
BUILTIN=UTF-32LE

View File

@ -4,7 +4,7 @@
#pragma zilog_syntax
array __header @ $100 = [
const array __header @ $100 = [
$00, $C3, $50, $01, $CE, $ED, $66, $66, $CC, $0D, $00, $0B, $03, $73, $00, $83,
$00, $0C, $00, $0D, $00, $08, $11, $1F, $88, $89, $00, $0E, $DC, $CC, $6E, $E6,
$DD, $DD, $D9, $99, $BB, $BB, $67, $63, $6E, $0E, $EC, $CC, $DD, $DC, $99, $9F,

View File

@ -38,9 +38,9 @@ void read_joy() {
ld a,(reg_joypad)
ld a,(reg_joypad)
}
byte tmp
tmp = reg_joypad ^ $ff
input_a = (tmp & 1) >> 0
input_a = (tmp & 1) // >> 0
input_b = (tmp & 2) >> 1
input_select = (tmp & 4) >> 2
input_start = (tmp & 8) >> 3

View File

@ -11,6 +11,12 @@ inline asm byte __mul_u8u8u8() {
? LD A, E
? RET
}
#elseif CPUFEATURE_R800
inline asm byte __mul_u8u8u8() {
? MULUB A,D
? LD A,L
? RET
}
#elseif CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
//A = A * D
noinline asm byte __mul_u8u8u8() {
@ -89,6 +95,16 @@ __divmod_u16u8u16u8_skip:
? RET
}
#if CPUFEATURE_R800
inline asm word __mul_u16u8u16() {
? LD L,A
? LD H,0
? MULUW HL,DE
? RET
}
#else
// HL=A*DE
noinline asm word __mul_u16u8u16() {
? LD HL,0
? LD B,8
@ -113,8 +129,17 @@ __mul_u16u8u16_skip:
#endif
? RET
}
#endif
#if CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
#if CPUFEATURE_R800
inline asm word __mul_u16u16u16() {
? EX DE,HL
? MULUW HL,BC
? RET
}
#elseif CPUFEATURE_Z80 || CPUFEATURE_GAMEBOY
// HL=BC*DE
noinline asm word __mul_u16u16u16() {
LD HL,0
LD A,16

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_0401 module should be only used on Commodore targets
#endif
const array _basic_loader @$401 = [
$0b,
4,
10,
0,
$9e,
$31,
$30,
$33,
$37,
0,
0,
0
]
import cbm/basic_loader<$401>

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_0801 module should be only used on Commodore targets
#endif
const array _basic_loader @$801 = [
$0b,
$08,
10,
0,
$9e,
$32,
$30,
$36,
$31,
0,
0,
0
]
import cbm/basic_loader<$801>

View File

@ -22,4 +22,5 @@ asm void __init_16bit() @$80D {
clc
xce
sep #$30
}
jmp main
}

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_1001 module should be only used on Commodore targets
#endif
const array _basic_loader @$1001 = [
$0b,
$10,
10,
0,
$9e,
$34,
$31,
$30,
$39,
0,
0,
0
]
import cbm/basic_loader<$1001>

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_1201 module should be only used on Commodore targets
#endif
const array _basic_loader @$1201 = [
$0b,
$12,
10,
0,
$9e,
$34,
$36,
$32,
$31,
0,
0,
0
]
import cbm/basic_loader<$1201>

View File

@ -1,19 +1 @@
#if not(CBM)
#warn loader_1c01 module should be only used on Commodore targets
#endif
const array _basic_loader @$1C01 = [
$0b,
$1C,
10,
0,
$9e,
$37,
$31,
$38,
$31,
0,
0,
0
]
import cbm/basic_loader<$1c01>

View File

@ -3,7 +3,7 @@
#warn m6809_math module should be used only on 6809-like targets
#endif
noinline asm word __mul_u16u16u16(word register(x) x, word register(d) d) {
noinline asm word __mul_u16u16u16(word register(x) x, word register(d) d) !preserves_y !preserves_u {
pshs d,x
exg d,x
lda ,s
@ -26,7 +26,7 @@ noinline asm word __mul_u16u16u16(word register(x) x, word register(d) d) {
}
// returns p/q: quotient in A, remainder in B
noinline asm word __divmod_u8u8u8u8(word register(a) p, word register(b) q) {
noinline asm word __divmod_u8u8u8u8(word register(a) p, word register(b) q) !preserves_x !preserves_y !preserves_u {
pshs b,cc
ldb #8
stb ,-s
@ -47,7 +47,7 @@ noinline asm word __divmod_u8u8u8u8(word register(a) p, word register(b) q) {
}
// returns p/q: quotient in X, remainder in D
noinline asm word __divmod_u16u16u16u16(word register(x) p, word register(d) q) {
noinline asm word __divmod_u16u16u16u16(word register(x) p, word register(d) q) !preserves_y !preserves_u {
pshs x,d,cc
ldb #16
pshs b

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) !preserves_a !preserves_x !preserves_y @$FFD2 extern
inline void new_line() {
putchar(13)

View File

@ -11,14 +11,21 @@ modules=loader_0801,x16_kernal,x16_hardware,c64_panic,stdlib
[allocation]
; Let's not use the BASIC:
zp_pointers=0-$7F
segments=default,himem_00,himem_ff
; $00-$01 are used for bank switching
zp_pointers=$02-$7F
segments=default,user,himem_00,himem_ff
default_code_segment=default
segment_default_start=$80D
segment_default_codeend=$9eff
segment_default_datastart=after_code
segment_default_end=$9eff
;1KB user space
segment_user_start=$0400
segment_user_codeend=$07ff
segment_user_datastart=after_code
segment_user_end=$07ff
segment_himem_00_start=$a000
segment_himem_00_codeend=$bfff
segment_himem_00_datastart=after_code

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) !preserves_a !preserves_x !preserves_y @$FFD2 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

@ -19,11 +19,15 @@
+= -= *= &gt;&gt;= &lt;&lt;=
+&apos; -&apos; *&apos; &lt;&lt;&apos; &gt;&gt;&apos;
+&apos;= -&apos;= *&apos;= &lt;&lt;&apos;= &gt;&gt;&apos;=
$+ $- $* $&lt;&lt; $&gt;&gt;
$+= $-= $*= $&lt;&lt;= $&gt;&gt;=
= ?
/ %% /= %%=
&gt; &gt;= &lt; &lt;= != ==
| || |= ^ ^= &amp; &amp;&amp; &amp;=
( ) { } @ [ ] : # </Keywords>
( ) { } @ [ ] : #
; .
</Keywords>
<Keywords name="Operators2"></Keywords>
<Keywords name="Folders in code1, open">{</Keywords>
<Keywords name="Folders in code1, middle"></Keywords>
@ -38,10 +42,14 @@
void bool byte sbyte ubyte word farword pointer farpointer long word_be word_le long_be long_le file
int8 int16 int24 int32 int40 int48 int56 int64
int72 int80 int88 int96 int104 int112 int120 int128
signed8
signed8 signed16 signed24 signed32 signed40 signed48 signed56 signed64
signed72 signed80 signed88 signed96 signed104 signed112 signed120 signed128
unsigned8 unsigned16 unsigned24 unsigned32 unsigned40 unsigned48 unsigned56 unsigned64
unsigned72 unsigned80 unsigned88 unsigned96 unsigned104 unsigned112 unsigned120 unsigned128
array addr fast</Keywords>
array addr fast
clear_carry clear_zero clear_overflow clear_negative
set_carry set_zero set_overflow set_negative
</Keywords>
<Keywords name="Keywords2">
import segment
if else for return while do asm extern break continue default goto label
@ -54,9 +62,11 @@
utf8 utf16le utf16be latin0 latin9 iso8859_15 zx80 zx81 vectrex koi7n2 short_koi msx_intl msx_us msx_uk msx_de msx_fr msx_es msx_ru msx_jp msx_br
utf8z utf16lez utf16bez latin0z latin9z iso8859_15z zx80z zx81z vectrexz koi7n2z short_koiz msx_intlz msx_usz msx_ukz msx_dez msx_frz msx_esz msx_ruz msx_jpz msx_brz
until to downto parallelto paralleluntil
function
static stack ref const volatile inline noinline macro register kernal_interrupt interrupt align reentrant
hi lo sin cos tan call nonet
false true nullptr</Keywords>
false true nullptr nullchar nullchar_scr
</Keywords>
<Keywords name="Keywords4">&quot;sta &quot; &quot;lda &quot; &quot;jmp &quot; &quot;bit &quot; &quot;eor &quot; &quot;adc &quot; &quot;sbc &quot; &quot;ora &quot; &quot;and &quot; &quot;ldx &quot; &quot;ldy &quot; &quot;stx &quot; &quot;sty &quot; &quot;tax&quot; &quot;tay&quot; &quot;tya&quot; &quot;txa&quot; &quot;txs&quot; &quot;tsx&quot; &quot;sei&quot; &quot;cli&quot; &quot;clv&quot; &quot;clc&quot; &quot;cld&quot; &quot;sed&quot; &quot;sec&quot; &quot;bra &quot; &quot;beq &quot; &quot;bne &quot; &quot;bmi &quot; &quot;bpl &quot; &quot;bcc &quot; &quot;bcs &quot; &quot;bvs &quot; bvc &quot; &quot;jsr &quot; rts&quot; &quot;rti&quot; &quot;brk&quot; &quot;rol&quot; &quot;ror&quot; &quot;asl&quot; &quot;lsr&quot; &quot;inc &quot; &quot;dec &quot; &quot;cmp &quot; &quot;cpx &quot; &quot;cpy &quot; inx iny dex dey pla pha plp hp phx plx phy ply &quot;stz &quot; &quot;ldz &quot; tza taz &quot;tsb &quot; &quot;trb &quot; ra txy tyx pld plb phb phd phk xce&#x000D;&#x000A;&#x000D;&#x000A;&quot;STA &quot; &quot;LDA &quot; &quot;JMP &quot; &quot;BIT &quot; &quot;EOR &quot; &quot;ADC &quot; &quot;SBC &quot; &quot;ORA &quot; &quot;AND &quot; &quot;LDX &quot; &quot;LDY &quot; &quot;STX &quot; &quot;STY &quot; &quot;TAX&quot; &quot;TAY&quot; &quot;TYA&quot; &quot;TXA&quot; &quot;TXS&quot; &quot;TSX&quot; &quot;SEI&quot; &quot;CLI&quot; &quot;CLV&quot; &quot;CLC&quot; &quot;CLD&quot; &quot;SED&quot; &quot;SEC&quot; &quot;BEQ &quot; &quot;BRA &quot; &quot;BNE &quot; &quot;BMI &quot; &quot;BPL &quot; &quot;BCC &quot; &quot;BCS &quot; &quot;BVS &quot; BVC &quot; &quot;JSR &quot; RTS&quot; &quot;RTI&quot; &quot;BRK&quot; &quot;ROL&quot; &quot;ROR&quot; &quot;ASL&quot; &quot;LSR&quot; &quot;INC &quot; &quot;DEC &quot; &quot;CMP &quot; &quot;CPX &quot; &quot;CPY &quot; INX INY DEX DEY PLA PHA PLP HP PHX PLX PHY PLY &quot;STZ &quot; &quot;LDZ &quot; TZA TAZ &quot;TSB &quot; &quot;TRB &quot; RA TXY TYX PLD PLB PHB PHD PHK XCE</Keywords>
<Keywords name="Keywords5">&quot;sbx &quot; &quot;isc &quot; &quot;dcp &quot; &quot;lax &quot; &quot;sax &quot; &quot;anc &quot; &quot;alr &quot; &quot;arr &quot; &quot;rra &quot; &quot;rla &quot; &quot;lxa &quot; &quot;ane &quot; &quot;xaa &quot;&#x000D;&#x000A;&quot;SBX &quot; &quot;ISC &quot; &quot;DCP &quot; &quot;LAX &quot; &quot;SAX &quot; &quot;ANC &quot; &quot;ALR &quot; &quot;ARR &quot; &quot;RRA &quot; &quot;RLA &quot; &quot;LXA &quot; &quot;ANE &quot; &quot;XAA &quot;</Keywords>
<Keywords name="Keywords6"></Keywords>

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

@ -17,6 +17,11 @@
"allDeclaredFields":true,
"allPublicMethods":true
},
{
"name":"millfork.assembly.m6809.MOpcode$",
"allDeclaredFields":true,
"allPublicMethods":true
},
{
"name":"org.apache.commons.logging.LogFactory"
},

View File

@ -45,7 +45,7 @@ case class CompilationOptions(platform: Platform,
EmitIntel8085Opcodes, EmitIntel8080Opcodes, UseIxForStack, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
if (CpuFamily.forType(platform.cpu) != CpuFamily.I80) invalids ++= Set(
EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitEZ80Opcodes, EmitZ80NextOpcodes,
EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitEZ80Opcodes, EmitZ80NextOpcodes, EmitR800Opcodes,
UseIyForStack, UseIxForScratch, UseIyForScratch, UseShadowRegistersForInterrupts)
if (CpuFamily.forType(platform.cpu) != CpuFamily.M6809) invalids ++= Set(
@ -172,6 +172,11 @@ case class CompilationOptions(platform: Platform,
log.error("Extended 8080-like opcodes enabled for architecture that doesn't support them")
}
}
if (flags(EmitR800Opcodes)) {
if (platform.cpu != R800) {
log.error("R800 opcodes enabled for architecture that doesn't support them")
}
}
if (flags(EmitIntel8080Opcodes)) {
if (!Intel8080Compatible(platform.cpu)) {
log.error("Intel 8080 opcodes enabled for architecture that doesn't support them")
@ -220,6 +225,7 @@ case class CompilationOptions(platform: Platform,
"OPTIMIZE_IPO" -> toLong(flag(CompilationFlag.InterproceduralOptimization)),
"CPUFEATURE_DECIMAL_MODE" -> toLong(flag(CompilationFlag.DecimalMode)),
"CPUFEATURE_Z80" -> toLong(flag(CompilationFlag.EmitZ80Opcodes)),
"CPUFEATURE_R800" -> toLong(flag(CompilationFlag.EmitR800Opcodes)),
"CPUFEATURE_EZ80" -> toLong(flag(CompilationFlag.EmitEZ80Opcodes)),
"CPUFEATURE_8080" -> toLong(flag(CompilationFlag.EmitIntel8080Opcodes)),
"CPUFEATURE_8085" -> toLong(flag(CompilationFlag.EmitIntel8085Opcodes)),
@ -290,7 +296,7 @@ object CpuFamily extends Enumeration {
import Cpu._
cpu match {
case Mos | StrictMos | Ricoh | StrictRicoh | Cmos | SC02 | Rockwell | Wdc | HuC6280 | CE02 | Sixteen => M6502
case Intel8080 | Intel8085 | StrictIntel8085 | Sharp | Z80 | StrictZ80 | EZ80 | Z80Next => I80
case Intel8080 | Intel8085 | StrictIntel8085 | Sharp | Z80 | StrictZ80 | R800 | EZ80 | Z80Next => I80
case Intel8086 | Intel80186 => I86
case Cpu.Motorola6809 => M6809
}
@ -368,6 +374,10 @@ object Cpu extends Enumeration {
* The Zilog Z80 processor, without illegal instructions
*/
val StrictZ80: Cpu.Value = Value
/**
* The R800 CPU (used in MSX Turbo-R)
*/
val R800: Cpu.Value = Value
/**
* The Zilog eZ80 processor
*/
@ -400,11 +410,11 @@ object Cpu extends Enumeration {
/**
* Processors that can run code for Zilog Z80
*/
val Z80Compatible: Set[Cpu.Value] = Set(Z80, StrictZ80, EZ80, Z80Next)
val Z80Compatible: Set[Cpu.Value] = Set(Z80, StrictZ80, R800, EZ80, Z80Next)
/**
* Processors that can run code for Intel 8080
*/
val Intel8080Compatible: Set[Cpu.Value] = Set(Intel8080, Intel8085, StrictIntel8085, Z80, StrictZ80, EZ80, Z80Next)
val Intel8080Compatible: Set[Cpu.Value] = Set(Intel8080, Intel8085, StrictIntel8085, Z80, StrictZ80, R800, EZ80, Z80Next)
/**
* Processors that can run code for Intel 8085
*/
@ -419,14 +429,18 @@ object Cpu extends Enumeration {
RegisterVariables,
FunctionDeduplication,
EnableBreakpoints,
UseOptimizationHints,
GenericWarnings,
ByteOverflowWarning,
UselessCodeWarning,
BuggyCodeWarning,
FallbackValueUseWarning,
BytePointerComparisonWarning,
DeprecationWarning,
NonZeroTerminatedLiteralWarning,
CallToOverlappingBankWarning,
DataMissingInOutputWarning,
UnsupportedOptimizationHintWarning,
)
private val mosAlwaysDefaultFlags = alwaysDefaultFlags
@ -464,6 +478,8 @@ object Cpu extends Enumeration {
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitIntel8085Opcodes, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
case StrictZ80 | Z80 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts)
case R800 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts, EmitR800Opcodes)
case Z80Next =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts, EmitIllegals, EmitZ80NextOpcodes)
case EZ80 =>
@ -510,6 +526,7 @@ object Cpu extends Enumeration {
case "strict2a07" => StrictRicoh
case "z80" => Z80
case "strictz80" => Z80
case "r800" => R800
case "zx80next" => Z80Next
case "z80next" => Z80Next
// disabled for now:
@ -558,9 +575,9 @@ object CompilationFlag extends Enumeration {
EmitIllegals, DecimalMode, LenientTextEncoding, LineNumbersInAssembly, SourceInAssembly, EnableBreakpoints,
// compilation options for MOS:
EmitCmosOpcodes, EmitCmosNopOpcodes, EmitSC02Opcodes, EmitRockwellOpcodes, EmitWdcOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack, IdentityPage,
// compilation options for I80
EmitIntel8080Opcodes, EmitIntel8085Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, EmitEZ80Opcodes, EmitSharpOpcodes, EmitZ80NextOpcodes,
EmitIntel8080Opcodes, EmitIntel8085Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, EmitR800Opcodes, EmitEZ80Opcodes, EmitSharpOpcodes, EmitZ80NextOpcodes,
UseShadowRegistersForInterrupts,
UseIxForStack, UseIyForStack,
UseIxForScratch, UseIyForScratch,
@ -582,15 +599,18 @@ object CompilationFlag extends Enumeration {
SingleThreaded,
// warning options
GenericWarnings,
ByteOverflowWarning,
UselessCodeWarning,
BuggyCodeWarning,
DeprecationWarning,
FallbackValueUseWarning,
BytePointerComparisonWarning,
ExtraComparisonWarnings,
RorWarning,
NonZeroTerminatedLiteralWarning,
CallToOverlappingBankWarning,
DataMissingInOutputWarning,
UnsupportedOptimizationHintWarning,
FatalWarnings,
// special options for internal compiler use
EnableInternalTestSyntax,
@ -598,14 +618,17 @@ object CompilationFlag extends Enumeration {
val allWarnings: Set[CompilationFlag.Value] = Set(
GenericWarnings,
ByteOverflowWarning,
UselessCodeWarning,
BuggyCodeWarning,
DeprecationWarning,
FallbackValueUseWarning,
BytePointerComparisonWarning,
ExtraComparisonWarnings,
NonZeroTerminatedLiteralWarning,
CallToOverlappingBankWarning,
DataMissingInOutputWarning,
UnsupportedOptimizationHintWarning,
)
val fromString: Map[String, CompilationFlag.Value] = Map(
@ -618,6 +641,7 @@ object CompilationFlag extends Enumeration {
"emit_65ce02" -> Emit65CE02Opcodes,
"emit_huc6280" -> EmitHudsonOpcodes,
"emit_z80" -> EmitZ80Opcodes,
"emit_r800" -> EmitR800Opcodes,
"emit_ez80" -> EmitEZ80Opcodes,
"emit_x80" -> EmitExtended80Opcodes,
"emit_8080" -> EmitIntel8080Opcodes,
@ -630,6 +654,7 @@ object CompilationFlag extends Enumeration {
"u_stack" -> UseUForStack,
"y_stack" -> UseYForStack,
"software_stack" -> SoftwareStack,
"identity_page" -> IdentityPage,
"use_shadow_registers_for_irq" -> UseShadowRegistersForInterrupts,
"output_intel_syntax" -> UseIntelSyntaxForOutput,
"input_intel_syntax" -> UseIntelSyntaxForInput,

View File

@ -46,10 +46,24 @@ case class Context(errorReporting: Logger,
if (isFlagSet(CompilationFlag.EmitEZ80Opcodes)) {
addons += CompilationFlag.EmitZ80Opcodes -> true
}
if (isFlagSet(CompilationFlag.EmitZ80Opcodes) || isFlagSet(CompilationFlag.EmitSharpOpcodes)) {
if (isFlagSet(CompilationFlag.EmitZ80NextOpcodes)) {
addons += CompilationFlag.EmitZ80Opcodes -> true
}
if (isFlagSet(CompilationFlag.EmitR800Opcodes)) {
addons += CompilationFlag.EmitZ80Opcodes -> true
}
if (isFlagSet(CompilationFlag.EmitEZ80Opcodes)
|| isFlagSet(CompilationFlag.EmitZ80NextOpcodes)
|| isFlagSet(CompilationFlag.EmitR800Opcodes)
|| isFlagSet(CompilationFlag.EmitZ80Opcodes)
|| isFlagSet(CompilationFlag.EmitSharpOpcodes)) {
addons += CompilationFlag.EmitExtended80Opcodes -> true
}
if (isFlagSet(CompilationFlag.EmitZ80Opcodes) || isFlagSet(CompilationFlag.EmitIntel8085Opcodes)) {
if (isFlagSet(CompilationFlag.EmitEZ80Opcodes)
|| isFlagSet(CompilationFlag.EmitZ80NextOpcodes)
|| isFlagSet(CompilationFlag.EmitR800Opcodes)
|| isFlagSet(CompilationFlag.EmitZ80Opcodes)
|| isFlagSet(CompilationFlag.EmitIntel8085Opcodes)) {
addons += CompilationFlag.EmitIntel8080Opcodes -> true
}
if (isFlagSet(CompilationFlag.OptimizeForSpeed)) {

View File

@ -1,23 +1,37 @@
package millfork
import millfork.output.{BankLayoutInFile, FormattableLabel}
import java.util.regex.Pattern
import scala.collection.mutable
import scala.util.control.Breaks.{break, breakable}
/**
* @author Karol Stasiak
*/
object DebugOutputFormat {
val map: Map[String, DebugOutputFormat] = Map(
"raw" -> RawDebugOutputFormat,
"vice" -> ViceDebugOutputFormat,
"nesasm" -> NesasmDebugOutputFormat,
"fns" -> NesasmDebugOutputFormat,
"fceux" -> FceuxDebugOutputFormat,
"nl" -> FceuxDebugOutputFormat,
"mlb" -> MesenOutputFormat,
"mesen" -> MesenOutputFormat,
"asm6f" -> MesenOutputFormat,
"ld65" -> Ld65OutputFormat,
"ca65" -> Ld65OutputFormat,
"cc65" -> Ld65OutputFormat,
"dbg" -> Ld65OutputFormat,
"sym" -> SymDebugOutputFormat)
}
sealed trait DebugOutputFormat {
def formatAll(labels: Seq[(String, (Int, Int))], breakpoints: Seq[(Int, Int)]): String = {
val labelPart = labelsHeader + labels.map(formatLineTupled).mkString("\n") + "\n"
def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
val labelPart = labelsHeader + labels.map(formatLine).mkString("\n") + "\n"
if (breakpoints.isEmpty) {
labelPart
} else {
@ -25,9 +39,7 @@ sealed trait DebugOutputFormat {
}
}
final def formatLineTupled(labelAndValue: (String, (Int, Int))): String = formatLine(labelAndValue._1, labelAndValue._2._1, labelAndValue._2._2)
def formatLine(label: String, bank: Int, value: Int): String
def formatLine(label: FormattableLabel): String
final def formatBreakpointTupled(value: (Int, Int)): Seq[String] = formatBreakpoint(value._1, value._2).toSeq
@ -45,10 +57,25 @@ sealed trait DebugOutputFormat {
def breakpointsHeader: String = ""
}
object RawDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: FormattableLabel): String = {
f"${label.bankNumber}%02X:${label.startValue}%04X:${label.endValue.fold("")(_.formatted("%04X"))}%s:${label.category}%s:$label%s"
}
override def fileExtension(bank: Int): String = ".labels"
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = false
override def formatBreakpoint(bank: Int, value: Int): Option[String] =
Some(f"$bank%02X:$value%04X::b:<breakpoint@$value%04X>")
}
object ViceDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, value: Int): String = {
val normalized = label.replace('$', '_').replace('.', '_')
s"al ${value.toHexString} .$normalized"
override def formatLine(label: FormattableLabel): String = {
val normalized = label.labelName.replace('$', '_').replace('.', '_')
s"al ${label.startValue.toHexString} .$normalized"
}
override def fileExtension(bank: Int): String = ".lbl"
@ -61,8 +88,8 @@ object ViceDebugOutputFormat extends DebugOutputFormat {
}
object NesasmDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, value: Int): String = {
label + " = $" + value.toHexString
override def formatLine(label: FormattableLabel): String = {
label.labelName + " = $" + label.startValue.toHexString
}
override def fileExtension(bank: Int): String = ".fns"
@ -75,8 +102,8 @@ object NesasmDebugOutputFormat extends DebugOutputFormat {
}
object SymDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, value: Int): String = {
f"$bank%02x:$value%04x $label%s"
override def formatLine(label: FormattableLabel): String = {
f"${label.bankNumber}%02x:${label.startValue}%04x ${label.labelName}%s"
}
override def fileExtension(bank: Int): String = ".sym"
@ -93,8 +120,8 @@ object SymDebugOutputFormat extends DebugOutputFormat {
}
object FceuxDebugOutputFormat extends DebugOutputFormat {
override def formatLine(label: String, bank: Int, value: Int): String = {
f"$$$value%04x#$label%s#"
override def formatLine(label: FormattableLabel): String = {
f"$$${label.startValue}%04x#${label.labelName}%s#"
}
override def fileExtension(bank: Int): String = if (bank == 0xff) ".ram.nl" else s".$bank.nl"
@ -105,3 +132,97 @@ object FceuxDebugOutputFormat extends DebugOutputFormat {
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
}
object MesenOutputFormat extends DebugOutputFormat {
override def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
val allStarts = labels.groupBy(_.bankName).mapValues(_.map(_.startValue).toSet)
labels.flatMap{ l =>
val mesenShift = b.getMesenShift(l.bankName)
val shiftedStart = l.startValue + mesenShift
var shiftedEnd = l.endValue.map(_ + mesenShift).filter(_ != shiftedStart)
l.endValue match {
case None =>
case Some(e) =>
// Mesen does not like labels of form XXX-XXX, where both ends are equal
breakable {
for (i <- l.startValue.+(1) to e) {
if (allStarts.getOrElse(l.bankName, Set.empty).contains(i)) {
shiftedEnd = None
break
}
}
}
}
if (shiftedStart >= 0 && shiftedEnd.forall(_ >= 0)) {
val normalized = l.labelName.replace('$', '_').replace('.', '_')
val comment = (l.category match {
case 'F' => "function "
case 'A' => "initialized array "
case 'V' => "initialized variable "
case 'a' => "array "
case 'v' => "variable "
case _ => ""
}) + l.labelName
Some(f"${l.mesenSymbol}%s:${shiftedStart}%04X${shiftedEnd.fold("")(e => f"-$e%04X")}%s:$normalized%s:$comment%s")
} else {
None
}
}.mkString("\n")
}
override def formatLine(label: FormattableLabel): String = throw new UnsupportedOperationException()
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
override def fileExtension(bank: Int): String = ".mlb"
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = false
}
object Ld65OutputFormat extends DebugOutputFormat {
override def formatLine(label: FormattableLabel): String = throw new UnsupportedOperationException()
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
override def fileExtension(bank: Int): String = ".dbg"
override def filePerBank: Boolean = false
override def addOutputExtension: Boolean = true
override def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
val q = '"'
val allBanksInFile = b.allBanks()
val allBanks = (labels.map(_.bankName).distinct ++ allBanksInFile).distinct
val result = mutable.ListBuffer[String]()
result += "version\tmajor=2,minor=0"
result += s"info\tcsym=0,file=0,lib=0,line=0,mod=0,scope=1,seg=${allBanks.size},span=0,sym=${labels.size},type=4"
for ((bank, ix) <- allBanks.zipWithIndex) {
val common = s"seg\tid=${ix},name=$q${bank}$q,start=0x${b.getStart(bank).toHexString},size=0x0,addrsize=absolute"
if (allBanksInFile.contains(bank)) {
result += common + s",ooffs=${b.ld65Offset(bank)}"
} else {
result += common
}
}
result += "scope\tid=0,name=\"\""
for ((label, ix) <- labels.sortBy(l => (l.bankName, l.startValue, l.labelName)).zipWithIndex) {
val name = label.labelName.replace('$', '@').replace('.', '@')
val sb = new StringBuilder
sb ++= s"sym\tid=${ix},name=$q${name}$q,addrsize=absolute,"
label.endValue match {
case Some(e) =>
if (!labels.exists(l => l.ne(label) && l.startValue >= label.startValue && l.startValue <= e)) {
sb ++= s"size=${ e - label.startValue + 1 },"
}
case None =>
}
sb ++= s"scope=0,val=0x${label.startValue.toHexString},seg=${allBanks.indexOf(label.bankName)},type=lab"
result += sb.toString
}
result.mkString("\n")
}
}

View File

@ -119,25 +119,35 @@ object Main {
else if (l.startsWith("__")) 7
else 0
}
val sortedLabels = result.labels.groupBy(_._2).values.map(_.minBy(a => labelUnimportance(a._1) -> a._1)).toSeq.sortBy(_._2)
val sortedLabels: Seq[FormattableLabel] =
result.labels.groupBy(_._2).values
.map(_.minBy(a => labelUnimportance(a._1) -> a._1)).toSeq.sortBy(_._2)
.map { case (l, (b, s)) =>
val bankNumber = options.platform.bankNumbers.getOrElse(b, 0)
val mesenCategory = options.platform.getMesenLabelCategory(b, s)
result.endLabels.get(l) match {
case Some((c, e)) => FormattableLabel(l, b, bankNumber, s, Some(e), c, mesenCategory)
case _ => FormattableLabel(l, b, bankNumber, s, None, 'x', mesenCategory)
}
}
val sortedBreakpoints = result.breakpoints
val format = c.outputLabelsFormatOverride.getOrElse(platform.outputLabelsFormat)
val basename = if (format.addOutputExtension) output + platform.fileExtension else output
if (format.filePerBank) {
val banks = sortedLabels.map(_._2._1).toSet ++ sortedBreakpoints.map(_._2).toSet
val banks: Set[Int] = sortedLabels.map(_.bankNumber).toSet ++ sortedBreakpoints.map(_._1).toSet
banks.foreach{ bank =>
val labels = sortedLabels.filter(_._2._1.==(bank))
val labels = sortedLabels.filter(_.bankNumber.==(bank))
val breakpoints = sortedBreakpoints.filter(_._1.==(bank))
val labelOutput = basename + format.fileExtension(bank)
val path = Paths.get(labelOutput)
errorReporting.debug("Writing labels to " + path.toAbsolutePath)
Files.write(path, format.formatAll(labels, breakpoints).getBytes(StandardCharsets.UTF_8))
Files.write(path, format.formatAll(result.bankLayoutInFile, labels, breakpoints).getBytes(StandardCharsets.UTF_8))
}
} else {
val labelOutput = basename + format.fileExtension(0)
val path = Paths.get(labelOutput)
errorReporting.debug("Writing labels to " + path.toAbsolutePath)
Files.write(path, format.formatAll(sortedLabels, sortedBreakpoints).getBytes(StandardCharsets.UTF_8))
Files.write(path, format.formatAll(result.bankLayoutInFile, sortedLabels, sortedBreakpoints).getBytes(StandardCharsets.UTF_8))
}
}
val defaultPrgOutput = if (output.endsWith(platform.fileExtension)) output else output + platform.fileExtension
@ -165,7 +175,7 @@ object Main {
f"${path.getFileName}%s ${start}%04X ${start}%04X ${codeLength}%04X".getBytes(StandardCharsets.UTF_8))
}
}
errorReporting.debug(s"Total time: ${Math.round((System.nanoTime() - startTime)/1e6)} ms")
errorReporting.info(s"Total time: ${Math.round((System.nanoTime() - startTime)/1e6)} ms")
c.runFileName.foreach{ program =>
if (File.separatorChar == '\\') {
if (!new File(program).exists() && !new File(program + ".exe").exists()) {
@ -241,10 +251,10 @@ object Main {
log.debug(s"Failed to find the default include path: $err")
case Right(path) =>
log.debug(s"Automatically detected include path: $path")
return c.copy(includePath = List(path) ++ c.extraIncludePath)
return c.copy(includePath = List(System.getProperty("user.dir"), path) ++ c.extraIncludePath)
}
}
c.copy(includePath = c.includePath ++ c.extraIncludePath)
c.copy(includePath = System.getProperty("user.dir") :: (c.includePath ++ c.extraIncludePath))
}
private def assembleForMos(c: Context, platform: Platform, options: CompilationOptions): AssemblerOutput = {
@ -283,6 +293,7 @@ object Main {
if (options.flag(CompilationFlag.EmitHudsonOpcodes)) HudsonOptimizations.All else Nil,
if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) SixteenOptimizations.AllForEmulation else Nil,
if (options.flag(CompilationFlag.EmitNative65816Opcodes)) SixteenOptimizations.AllForNative else Nil,
if (options.flag(CompilationFlag.IdentityPage)) IdentityPageOptimizations.All else Nil,
if (options.flag(CompilationFlag.DangerousOptimizations)) DangerousOptimizations.All else Nil
).flatten
val goodCycle = List.fill(optLevel - 2)(OptimizationPresets.Good ++ goodExtras).flatten
@ -321,7 +332,9 @@ object Main {
val assemblyOptimizations = optLevel match {
case 0 => Nil
case _ =>
if (options.flag(CompilationFlag.EmitZ80Opcodes))
if (options.flag(CompilationFlag.EmitR800Opcodes))
Z80OptimizationPresets.GoodForR800
else if (options.flag(CompilationFlag.EmitZ80Opcodes))
Z80OptimizationPresets.GoodForZ80
else if (options.flag(CompilationFlag.EmitIntel8080Opcodes))
Z80OptimizationPresets.GoodForIntel8080
@ -427,7 +440,7 @@ object Main {
p.toLowerCase(Locale.ROOT),
errorReporting.fatal("Invalid label file format: " + p))
c.copy(outputLabels = true, outputLabelsFormatOverride = Some(f))
}.description("Generate also the label file in the given format. Available options: vice, nesasm, sym.")
}.description("Generate also the label file in the given format. Available options: vice, nesasm, sym, raw.")
boolean("-fbreakpoints", "-fno-breakpoints").action((c,v) =>
c.changeFlag(CompilationFlag.EnableBreakpoints, v)
@ -487,7 +500,7 @@ object Main {
} else {
errorReporting.fatal("Invalid syntax for -D option")
}
}.description("Define a feature value for the preprocessor.")
}.description("Define a feature value for the preprocessor.").maxCount(Integer.MAX_VALUE)
boolean("-finput_intel_syntax", "-finput_zilog_syntax").repeatable().action((c,v) =>
c.changeFlag(CompilationFlag.UseIntelSyntaxForInput, v)
@ -650,7 +663,7 @@ object Main {
c.changeFlag(CompilationFlag.CompactReturnDispatchParams, v)
}.description("Whether parameter values in return dispatch statements may overlap other objects. Enabled by default.")
boolean("-fbounds-checking", "-fno-bounds-checking").action { (c, v) =>
c.changeFlag(CompilationFlag.VariableOverlap, v)
c.changeFlag(CompilationFlag.CheckIndexOutOfBounds, v)
}.description("Whether should insert bounds checking on array access.")
boolean("-flenient-encoding", "-fno-lenient-encoding").action { (c, v) =>
c.changeFlag(CompilationFlag.LenientTextEncoding, v)
@ -712,6 +725,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()
@ -742,6 +758,9 @@ object Main {
boolean("-fregister-variables", "-fno-register-variables").action { (c, v) =>
c.changeFlag(CompilationFlag.RegisterVariables, v)
}.description("Allow moving local variables into CPU registers. Enabled by default.")
boolean("-fidentity-page", "-fno-identity-page").action { (c, v) =>
c.changeFlag(CompilationFlag.IdentityPage, v)
}.description("Whether should use an identity page to optimize certain operations.")
flag("-Os", "--size").repeatable().action { c =>
c.changeFlag(CompilationFlag.OptimizeForSize, true).
changeFlag(CompilationFlag.OptimizeForSpeed, false).
@ -754,6 +773,7 @@ object Main {
}.description("Prefer faster code even if it is slightly bigger (experimental). Implies -finline.")
flag("-Ob", "--blast-processing").repeatable().action { c =>
c.changeFlag(CompilationFlag.OptimizeForSize, false).
changeFlag(CompilationFlag.IdentityPage, true).
changeFlag(CompilationFlag.OptimizeForSpeed, true).
changeFlag(CompilationFlag.OptimizeForSonicSpeed, true)
}.description("Prefer faster code even if it is much bigger (experimental). Implies -finline.")
@ -791,6 +811,10 @@ object Main {
c.changeFlag(CompilationFlag.DeprecationWarning, v)
}.description("Whether should warn about deprecated aliases. Default: enabled.")
boolean("-Wcomparisons", "-Wno-comparisons").repeatable().action { (c, v) =>
c.changeFlag(CompilationFlag.BytePointerComparisonWarning, v)
}.description("Whether should warn about comparisons between bytes and pointers. Default: enabled.")
boolean("-Wextra-comparisons", "-Wno-extra-comparisons").repeatable().action { (c, v) =>
c.changeFlag(CompilationFlag.ExtraComparisonWarnings, v)
}.description("Whether should warn about simplifiable unsigned integer comparisons. Default: disabled.")
@ -811,10 +835,18 @@ object Main {
c.changeFlag(CompilationFlag.RorWarning, v)
}.description("Whether should warn about the ROR instruction (6502 only). Default: disabled.")
boolean("-Woverflow", "-Wno-overflow").repeatable().action { (c, v) =>
c.changeFlag(CompilationFlag.ByteOverflowWarning, v)
}.description("Whether should warn about byte overflow. Default: enabled.")
boolean("-Wuseless", "-Wno-useless").repeatable().action { (c, v) =>
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

@ -42,6 +42,7 @@ class Platform(
val outputLabelsFormat: DebugOutputFormat,
val outputStyle: OutputStyle.Value
) {
def hasZeroPage: Boolean = cpuFamily == CpuFamily.M6502
def cpuFamily: CpuFamily.Value = CpuFamily.forType(this.cpu)
@ -60,6 +61,18 @@ class Platform(
val e2 = bankEndBefore(bank2)
e2 > s1 && s2 < e1
}
lazy val banksExplicitlyWrittenToOutput: Set[String] = bankNumbers.keySet.filter(b => defaultOutputPackager.writes(b)).toSet
def getMesenLabelCategory(bank: String, address: Int): Char = {
// see: https://www.mesen.ca/docs/debugging/debuggerintegration.html#mesen-label-files-mlb
val start = bankStart(bank)
val end = bankEndBefore(bank)
if (start > address || address >= end) 'G'
else if (banksExplicitlyWrittenToOutput(bank)) 'P'
else if (bank == "default") 'R'
else 'W' // TODO: distinguish between W and S
}
}
object Platform {

View File

@ -2,7 +2,7 @@ package millfork.assembly
import millfork.{CompilationFlag, CompilationOptions}
import millfork.compiler.LabelGenerator
import millfork.env.{NormalFunction, ThingInMemory}
import millfork.env.{Constant, NormalFunction, ThingInMemory}
import millfork.error.Logger
import millfork.node.NiceFunctionProperty
@ -10,8 +10,9 @@ import millfork.node.NiceFunctionProperty
* @author Karol Stasiak
*/
case class OptimizationContext(options: CompilationOptions,
labelMap: Map[String, (Int, Int)],
labelMap: Map[String, (String, Int)],
zreg: Option[ThingInMemory],
identityPage: Constant,
niceFunctionProperties: Set[(NiceFunctionProperty, String)]) {
@inline
def log: Logger = options.log
@ -25,4 +26,6 @@ trait AssemblyOptimization[T <: AbstractCode] {
def optimize(f: NormalFunction, code: List[T], context: OptimizationContext): List[T]
def requiredFlags: Set[CompilationFlag.Value] = Set.empty
def minimumRequiredLines: Int
}

View File

@ -168,6 +168,7 @@ case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant
def changesRegister(reg: M6809Register.Value): Boolean = {
import M6809Register._
if (MOpcode.NotActualOpcodes(opcode)) return false
def overlaps(other: M6809Register.Value): Boolean = {
if (reg == D && (other == A || other == B)) true
else if (other == D && (reg == A || reg == B)) true
@ -202,12 +203,13 @@ case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant
}
}
def changesCarryFlag: Boolean = !MOpcode.PreservesC(opcode)
def changesCarryFlag: Boolean = !MOpcode.NotActualOpcodes(opcode) && !MOpcode.PreservesC(opcode)
def readsRegister(reg: M6809Register.Value): Boolean = {
import M6809Register._
if (MOpcode.NotActualOpcodes(opcode)) return false
def overlaps(other: M6809Register.Value): Boolean = {
if (reg == D && (other == A || other == B)) true
else if (other == D && (reg == A || reg == B)) true
@ -252,15 +254,17 @@ case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant
case MUL => overlaps(D)
case ABX => reg == X || overlaps(B)
case NOP | SWI | SWI2 | SWI3 | SYNC => false
case LDB | LDA | LDX | LDY | LDU => false
case INC | DEC | ROL | ROR | ASL | ASR | LSR | CLR | COM | NEG | TST => false // variants for A and B handled before
case op if Branching(op) => false
case JMP => false
case JMP | RTS => false
case _ => true // TODO
}
}
def readsMemory(): Boolean = {
import MOpcode._
if (MOpcode.NotActualOpcodes(opcode)) return false
val opcodeIsForReading = opcode match {
case LDA | LDB | LDD | LDX | LDY | LDU | LDS | PULU | PULS => true
case ADDA | SUBA | ADCA | SBCA | ORA | EORA | ANDA | CMPA | BITA => true
@ -289,6 +293,7 @@ case class MLine(opcode: MOpcode.Value, addrMode: MAddrMode, parameter: Constant
def changesMemory(): Boolean = {
import MOpcode._
if (NotActualOpcodes(opcode)) return false
val opcodeIsForWriting = opcode match {
case LDA | LDB | LDD | LDX | LDY | LDU | LDS | PULU | PULS => false
case ADDA | SUBA | ADCA | SBCA | ORA | EORA | ANDA | CMPA | BITA => false

View File

@ -32,9 +32,10 @@ object MOpcode extends Enumeration {
TFR, TST,
DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC, CHANGED_MEM, BYTE, LABEL = Value
val NotActualOpcodes: Set[MOpcode.Value] = Set(DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC, CHANGED_MEM, BYTE, LABEL)
private val toMap: Map[String, MOpcode.Value] = {
val notActualOpcodes: Set[MOpcode.Value] = Set(DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC, CHANGED_MEM, BYTE, LABEL)
values.filterNot(notActualOpcodes).map(o => o.toString -> o).toMap ++ Map("BHS" -> BCC, "BLO" -> BCS, "LSL" -> ASL)
values.filterNot(NotActualOpcodes).map(o => o.toString -> o).toMap ++ Map("BHS" -> BCC, "BLO" -> BCS, "LSL" -> ASL)
}
val NoopDiscard: Set[MOpcode.Value] = Set(DISCARD_D, DISCARD_X, DISCARD_Y, DISCARD_CC)
val PrefixedBy10: Set[MOpcode.Value] = Set(CMPD, CMPY, LDS, LDY, SWI2, STS, STY) // TODO: branches

View File

@ -3,7 +3,8 @@ package millfork.assembly.m6809.opt
import millfork.assembly.AssemblyOptimization
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.m6809.{Absolute, DAccumulatorIndexed, Immediate, LongRelative, MAddrMode, MLine, MState, PostIncremented, RegisterSet}
import millfork.assembly.m6809.{Absolute, DAccumulatorIndexed, Immediate, Indexed, InherentA, InherentB, LongRelative, MAddrMode, MLine, MState, PostIncremented, RegisterSet}
import millfork.env.{CompoundConstant, Constant, MathOperator}
import millfork.node.M6809Register
/**
@ -19,15 +20,38 @@ object AlwaysGoodMOptimizations {
(Elidable & HasOpcodeIn(LDX, LEAX) & DoesntMatterWhatItDoesWith(MState.X, MState.NF, MState.ZF, MState.VF)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(LDY, LEAY) & DoesntMatterWhatItDoesWith(MState.Y, MState.NF, MState.ZF, MState.VF)) ~~> (_ => Nil),
(Elidable & HasOpcode(STB) & MatchAddrMode(0) & MatchParameter(1)) ~
(Linear & Not(ChangesB) & DoesntChangeIndexingInAddrMode(0)).* ~
(Linear & Not(ChangesB) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
(Elidable & HasOpcode(LDB) & MatchAddrMode(0) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.NF, MState.ZF, MState.VF)) ~~> (_.init),
(Elidable & HasOpcode(STD) & MatchAddrMode(0) & MatchParameter(1)) ~
(Linear & Not(ChangesB) & DoesntChangeIndexingInAddrMode(0)).* ~
(Linear & Not(ChangesB) & Not(ChangesA) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1)).* ~
(Elidable & HasOpcode(LDD) & MatchAddrMode(0) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.NF, MState.ZF, MState.VF)) ~~> (_.init),
)
val PointlessCompare = new RuleBasedAssemblyOptimization("Pointless compare",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(HasOpcodeIn(LDA, ANDA, ORA, EORA, ADDA, ADCA, SUBA, SBCA)) ~
(Elidable & HasOpcode(CMPA) & HasImmediate(0) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> {code => code.init},
(HasOpcodeIn(LDB, ANDB, ORB, EORB, ADDB, ADCB, SUBB, SBCB)) ~
(Elidable & HasOpcode(CMPB) & HasImmediate(0) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> {code => code.init},
(Elidable & HasOpcode(EORA) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.A, MState.NF, MState.VF, MState.CF, MState.HF)) ~~> { (code, ctx) =>
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
(Elidable & HasOpcode(EORB) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.B, MState.NF, MState.VF, MState.CF, MState.HF)) ~~> { (code, ctx) =>
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
)
val SimplifiableZeroStore = new RuleBasedAssemblyOptimization("Simplifiable zero store",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & HasOpcode(LDA) & HasImmediate(0) & DoesntMatterWhatItDoesWith(MState.CF)) ~~> {
@ -50,6 +74,75 @@ object AlwaysGoodMOptimizations {
},
)
val SimplifiableComparison = new RuleBasedAssemblyOptimization("Simplifiable comparison",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & HasOpcode(EORB) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
(Elidable & HasOpcode(ADDB) & HasImmediate(1)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(SUBB) & HasImmediate(1)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(INC) & HasAddrMode(InherentB)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(DEC) & HasAddrMode(InherentB)) ~
(Elidable & HasOpcode(CMPB) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.B, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(EORA) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
(Elidable & HasOpcode(ADDA) & HasImmediate(1)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(SUBA) & HasImmediate(1)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(INC) & HasAddrMode(InherentA)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(DEC) & HasAddrMode(InherentA)) ~
(Elidable & HasOpcode(CMPA) & HasAddrMode(Immediate) & MatchParameter(1)
& DoesntMatterWhatItDoesWith(MState.CF, MState.A, MState.VF, MState.HF)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
)
val PointlessRegisterTransfers = new RuleBasedAssemblyOptimization("Pointless register transfers",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & IsTfr(M6809Register.D, M6809Register.X)) ~
@ -144,14 +237,67 @@ object AlwaysGoodMOptimizations {
(_.map(_.copy(opcode = LDD))),
(Elidable & HasImmediate(0) & HasOpcodeIn(ORB, EORB, ADDB, ORA, EORA, ADDA, ADDD) & DoesntMatterWhatItDoesWith(MState.VF, MState.ZF, MState.NF, MState.CF, MState.HF)) ~~>
(_ => Nil),
(HasOpcode(LDB)) ~
(Elidable & HasOpcode(ANDB) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(BNE) & MatchParameter(1)) ~
(Elidable & HasOpcode(LDB)) ~
(HasOpcode(ANDB) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(HasOpcode(BEQ)) ~
(HasOpcode(LABEL) & MatchParameter(1)) ~~> { code =>
List(code.head, code(3).copy(opcode = ORB)) ++ code.drop(4)
},
(Elidable & HasOpcode(ADDB) & HasImmediate(1) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> { code => List(MLine.inherentB(INC)) },
(Elidable & HasOpcode(ADDA) & HasImmediate(1) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> { code => List(MLine.inherentA(INC)) },
(Elidable & HasOpcode(SUBB) & HasImmediate(1) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> { code => List(MLine.inherentB(DEC)) },
(Elidable & HasOpcode(SUBA) & HasImmediate(1) & DoesntMatterWhatItDoesWith(MState.VF, MState.CF, MState.HF)) ~~> { code => List(MLine.inherentA(DEC)) },
)
val SimplifiableAddressing = new RuleBasedAssemblyOptimization("Simplifiable addressing",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(NotFixed & MatchX(0) & HasAddrMode(Indexed(M6809Register.X, indirect = false)) & Not(HasOpcodeIn(LEAX, LEAY, LEAS, LEAU))) ~~> { (code, ctx) =>
val x = ctx.get[Constant](0)
code.map(l => l.copy(addrMode = Absolute(indirect = false), parameter = (x + l.parameter).quickSimplify))
},
(NotFixed & MatchY(0) & HasAddrMode(Indexed(M6809Register.Y, indirect = false)) & Not(HasOpcodeIn(LEAX, LEAY, LEAS, LEAU))) ~~> { (code, ctx) =>
val y = ctx.get[Constant](0)
code.map(l => l.copy(addrMode = Absolute(indirect = false), parameter = (y + l.parameter).quickSimplify))
},
(NotFixed & MatchX(0) & HasAddrMode(Indexed(M6809Register.X, indirect = false)) & HasOpcodeIn(LEAX, LEAY, LEAS, LEAU)) ~~> { (code, ctx) =>
val x = ctx.get[Constant](0)
code.map(l => l.copy(opcode = l.opcode match {
case LEAX => LDX
case LEAY => LDY
case LEAU => LDU
case LEAS => LDS
}, addrMode = Immediate, parameter = (x + l.parameter).quickSimplify))
},
(NotFixed & MatchY(0) & HasAddrMode(Indexed(M6809Register.X, indirect = false)) & HasOpcodeIn(LEAX, LEAY, LEAS, LEAU)) ~~> { (code, ctx) =>
val y = ctx.get[Constant](0)
code.map(l => l.copy(opcode = l.opcode match {
case LEAX => LDX
case LEAY => LDY
case LEAU => LDU
case LEAS => LDS
}, addrMode = Immediate, parameter = (y + l.parameter).quickSimplify))
},
)
val UnusedLabelRemoval = new RuleBasedAssemblyOptimization("Unused label removal",
needsFlowInfo = FlowInfoRequirement.JustLabels,
(Elidable & HasOpcode(LABEL) & HasCallerCount(0) & ParameterIsLocalLabel) ~~> (_ => Nil)
)
val All: Seq[AssemblyOptimization[MLine]] = Seq(
PointlessLoad,
PointlessCompare,
PointlessRegisterTransfers,
SimplifiableAddressing,
SimplifiableArithmetics,
SimplifiableComparison,
SimplifiableJumps,
SimplifiableZeroStore
SimplifiableZeroStore,
UnusedLabelRemoval
)
}

View File

@ -35,6 +35,11 @@ case class CpuStatus(a: Status[Int] = UnknownStatus,
def nzB(i: Long): CpuStatus =
this.copy(n = SingleStatus((i & 0x80) != 0), z = SingleStatus((i & 0xff) == 0))
def nzB(i: Status[Int]): CpuStatus = i match {
case SingleStatus(j) => this.copy(n = SingleStatus((j & 0x80) != 0), z = SingleStatus((j & 0xff) == 0))
case _ => this.copy(n = AnyStatus, z = AnyStatus)
}
def nzW(i: Long): CpuStatus =
this.copy(n = SingleStatus((i & 0x8000) != 0), z = SingleStatus((i & 0xffff) == 0))
@ -44,6 +49,11 @@ case class CpuStatus(a: Status[Int] = UnknownStatus,
case _ => this.nz
}
def nzW(i: Status[Int]): CpuStatus = i match {
case SingleStatus(j) => this.copy(n = SingleStatus((j & 0x8000) != 0), z = SingleStatus((j & 0xffff) == 0))
case _ => this.copy(n = AnyStatus, z = AnyStatus)
}
def ~(that: CpuStatus) = new CpuStatus(
a = this.a ~ that.a,
b = this.b ~ that.b,

View File

@ -52,7 +52,7 @@ object FlowAnalyzer {
}
val labelMap: () => Option[Map[String, Int]] = () => req match {
case FlowInfoRequirement.NoRequirement => None
case _ => Some(code.flatMap(_.parameter.extractLabels).groupBy(identity).mapValues(_.size).view.force)
case _ => Some(code.filter(m => m.opcode != MOpcode.LABEL).flatMap(_.parameter.extractLabels).groupBy(identity).mapValues(_.size).view.force)
}
val holder = new FlowHolder(forwardFlow, reverseFlow)
code.zipWithIndex.map{ case (line, i) => FlowInfo(holder, i, labelMap) -> line}

View File

@ -2,10 +2,11 @@ package millfork.assembly.m6809.opt
import millfork.CompilationFlag
import millfork.assembly.OptimizationContext
import millfork.assembly.m6809.{Immediate, MLine, MLine0}
import millfork.assembly.m6809.{Absolute, Immediate, Inherent, InherentA, InherentB, MLine, MLine0, TwoRegisters}
import millfork.assembly.opt.Status.SingleFalse
import millfork.assembly.opt.{AnyStatus, FlowCache, SingleStatus, Status}
import millfork.env._
import millfork.node.M6809NiceFunctionProperty.{DoesntChangeA, DoesntChangeB, DoesntChangeU, DoesntChangeX, DoesntChangeY}
import millfork.node.{M6809Register, NiceFunctionProperty}
import scala.util.control.Breaks._
@ -62,33 +63,108 @@ object ForwardFlowAnalysis {
case _ => None
}).fold(currentStatus)(_ ~ _)
case MLine0(JSR, _, MemoryAddressConstant(th)) =>
case MLine0(JSR, am, MemoryAddressConstant(th)) =>
var prU = bpInU
var prY = bpInY
var prA = false
var prB = false
var prX = false
(am, th) match {
case (Absolute(false), fun: FunctionInMemory) =>
val nfp = optimizationContext.niceFunctionProperties
val fn = fun.name
if (nfp(DoesntChangeX -> fn)) prX = true
if (nfp(DoesntChangeY -> fn)) prY = true
if (nfp(DoesntChangeU -> fn)) prU = true
if (nfp(DoesntChangeA -> fn)) prA = true
if (nfp(DoesntChangeB -> fn)) prB = true
case _ =>
}
currentStatus = initialStatus.copy(
memStack = currentStatus.memStack,
u = if (bpInU) currentStatus.u else AnyStatus,
y = if (bpInY) currentStatus.y else AnyStatus
u = if (prU) currentStatus.u else AnyStatus,
x = if (prX) currentStatus.x else AnyStatus,
y = if (prY) currentStatus.y else AnyStatus,
a = if (prA) currentStatus.a else AnyStatus,
b = if (prB) currentStatus.b else AnyStatus
)
case MLine0(JSR | BYTE, _, _) =>
currentStatus = initialStatus
case MLine0(NOP, _, _) =>
()
case MLine0(op, Immediate, constant) if ForwardFlowAnalysisForImmediate.hasDefinition(op) =>
ForwardFlowAnalysisForImmediate.get(op)(constant, currentStatus)
case MLine0(op, Inherent, _) if ForwardFlowAnalysisForInherent.hasDefinition(op) =>
ForwardFlowAnalysisForInherent.get(op)(currentStatus)
case MLine0(op, InherentA, _) if ForwardFlowAnalysisForInherentA.hasDefinition(op) =>
ForwardFlowAnalysisForInherentA.get(op)(currentStatus)
case MLine0(op, InherentB, _) if ForwardFlowAnalysisForInherentB.hasDefinition(op) =>
ForwardFlowAnalysisForInherentB.get(op)(currentStatus)
case MLine0(LDA, Immediate, NumericConstant(n, _)) =>
currentStatus = currentStatus.copy(a = SingleStatus(n.toInt & 0xff), v = SingleFalse).nzB(n)
case MLine0(LDB, Immediate, NumericConstant(n, _)) =>
currentStatus = currentStatus.copy(b = SingleStatus(n.toInt & 0xff), v = SingleFalse).nzB(n)
case MLine0(LDD, Immediate, NumericConstant(n, _)) =>
currentStatus = currentStatus.copy(
a = SingleStatus(n.toInt.>>(8) & 0xff),
b = SingleStatus(n.toInt & 0xff),
v = SingleFalse
).nzW(n)
case MLine0(LDX, Immediate, c) =>
currentStatus = currentStatus.copy(x = SingleStatus(c), v = SingleFalse).nzW(c)
case MLine0(LDY, Immediate, c) =>
currentStatus = currentStatus.copy(y = SingleStatus(c), v = SingleFalse).nzW(c)
case MLine0(LDA, _, _) =>
currentStatus = currentStatus.copy(a = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(LDB, _, _) =>
currentStatus = currentStatus.copy(b = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(LDD, _, _) =>
currentStatus = currentStatus.copy(a = AnyStatus, b = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(LDX, _, _) =>
currentStatus = currentStatus.copy(x = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(LDY, _, _) =>
currentStatus = currentStatus.copy(y = AnyStatus, n = AnyStatus, z = AnyStatus, v = SingleFalse)
case MLine0(STA | STB | STD | STX | STU | STY | STS, _, _) =>
// don't change
case MLine0(TFR, TwoRegisters(source, target), _) =>
import M6809Register._
(source, target) match {
case (A, B) => currentStatus = currentStatus.copy(b = currentStatus.a)
case (B, A) => currentStatus = currentStatus.copy(a = currentStatus.b)
case (CC, A) => currentStatus = currentStatus.copy(a = AnyStatus)
case (CC, B) => currentStatus = currentStatus.copy(b = AnyStatus)
case (A, CC) | (B, CC) => currentStatus = currentStatus.copy(c = AnyStatus, z = AnyStatus, n = AnyStatus, v = AnyStatus)
case (S, D) => currentStatus = currentStatus.copy(a = AnyStatus, b = AnyStatus)
case (S, X) => currentStatus = currentStatus.copy(x = AnyStatus)
case (S, Y) => currentStatus = currentStatus.copy(y = AnyStatus)
case (S, U) => currentStatus = currentStatus.copy(u = AnyStatus)
case (D, X) => currentStatus = currentStatus.copy(x = currentStatus.d.map(n => NumericConstant(n, 2)))
case (D, Y) => currentStatus = currentStatus.copy(y = currentStatus.d.map(n => NumericConstant(n, 2)))
case (D, U) => currentStatus = currentStatus.copy(u = currentStatus.d.map(n => NumericConstant(n, 2)))
case (X, Y) => currentStatus = currentStatus.copy(y = currentStatus.x)
case (X, U) => currentStatus = currentStatus.copy(u = currentStatus.x)
case (Y, X) => currentStatus = currentStatus.copy(x = currentStatus.y)
case (Y, U) => currentStatus = currentStatus.copy(u = currentStatus.y)
case (U, X) => currentStatus = currentStatus.copy(x = currentStatus.u)
case (U, Y) => currentStatus = currentStatus.copy(y = currentStatus.u)
case (X, D) =>
val (h, l) = currentStatus.x.toHiLo
currentStatus = currentStatus.copy(a = h, b = l)
case (Y, D) =>
val (h, l) = currentStatus.y.toHiLo
currentStatus = currentStatus.copy(a = h, b = l)
case (U, D) =>
val (h, l) = currentStatus.u.toHiLo
currentStatus = currentStatus.copy(a = h, b = l)
case _ => currentStatus = initialStatus
}
case MLine0(EXG, TwoRegisters(source, target), _) =>
import M6809Register._
(source, target) match {
case (A, B) | (B, A) => currentStatus = currentStatus.copy(a = currentStatus.b, b = currentStatus.a)
case (A, CC) | (CC, A) => currentStatus = currentStatus.copy(a = AnyStatus, c = AnyStatus, v = AnyStatus, n = AnyStatus, z = AnyStatus)
case (B, CC) | (CC, B) => currentStatus = currentStatus.copy(b = AnyStatus, c = AnyStatus, v = AnyStatus, n = AnyStatus, z = AnyStatus)
case (X, Y) | (Y, X) => currentStatus = currentStatus.copy(y = currentStatus.x, x = currentStatus.y)
case (U, Y) | (Y, U) => currentStatus = currentStatus.copy(y = currentStatus.u, u = currentStatus.y)
case (X, U) | (U, X) => currentStatus = currentStatus.copy(u = currentStatus.x, x = currentStatus.u)
case (X, D) | (D, X) =>
val (h, l) = currentStatus.x.toHiLo
currentStatus = currentStatus.copy(a = h, b = l, x = currentStatus.d.map(n => NumericConstant(n, 2)))
case (Y, D) | (D, Y) =>
val (h, l) = currentStatus.y.toHiLo
currentStatus = currentStatus.copy(a = h, b = l, y = currentStatus.d.map(n => NumericConstant(n, 2)))
case (U, D) | (D, U) =>
val (h, l) = currentStatus.u.toHiLo
currentStatus = currentStatus.copy(a = h, b = l, u = currentStatus.d.map(n => NumericConstant(n, 2)))
case _ => currentStatus = initialStatus
}
case MLine0(opcode, addrMode, _) =>
// TODO

View File

@ -0,0 +1,80 @@
package millfork.assembly.m6809.opt
import millfork.assembly.m6809.MOpcode
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.opt.{AnyStatus, SingleStatus, Status}
import millfork.assembly.opt.Status.SingleFalse
import millfork.env.Constant
/**
* @author Karol Stasiak
*/
object ForwardFlowAnalysisForImmediate {
private val map: Map[MOpcode.Value, (Constant, CpuStatus) => CpuStatus] = Map(
LDX -> {(c, currentStatus) =>
currentStatus.copy(x = SingleStatus(c), v = SingleFalse).nzW(c)
},
LDY -> {(c, currentStatus) =>
currentStatus.copy(y = SingleStatus(c), v = SingleFalse).nzW(c)
},
LDU -> {(c, currentStatus) =>
currentStatus.copy(u = SingleStatus(c), v = SingleFalse).nzW(c)
},
LDA -> {(c, currentStatus) =>
val value = Status.fromByte(c)
currentStatus.copy(a = value, v = SingleFalse).nzB(value)
},
LDB -> {(c, currentStatus) =>
val value = Status.fromByte(c)
currentStatus.copy(a = value, v = SingleFalse).nzB(value)
},
LDD -> {(c, currentStatus) =>
val value = Status.fromByte(c)
currentStatus.copy(a = value.hi, b = value.lo, v = SingleFalse).nzW(value)
},
ORA -> { (c, currentStatus) =>
val newValue = (currentStatus.a <*> Status.fromByte(c)) { (x, y) => (x | y) & 0xff }
currentStatus.copy(a = newValue, v = SingleFalse).nzB(newValue)
},
ORB -> { (c, currentStatus) =>
val newValue = (currentStatus.b <*> Status.fromByte(c)) { (x, y) => (x | y) & 0xff }
currentStatus.copy(b = newValue, v = SingleFalse).nzB(newValue)
},
EORA -> { (c, currentStatus) =>
val newValue = (currentStatus.a <*> Status.fromByte(c)) { (x, y) => (x ^ y) & 0xff }
currentStatus.copy(a = newValue, v = SingleFalse).nzB(newValue)
},
EORB -> { (c, currentStatus) =>
val newValue = (currentStatus.b <*> Status.fromByte(c)) { (x, y) => (x ^ y) & 0xff }
currentStatus.copy(b = newValue, v = SingleFalse).nzB(newValue)
},
ANDA -> { (c, currentStatus) =>
val newValue = (currentStatus.a <*> Status.fromByte(c)) { (x, y) => (x & y) & 0xff }
currentStatus.copy(a = newValue, v = SingleFalse).nzB(newValue)
},
ANDB -> { (c, currentStatus) =>
val newValue = (currentStatus.b <*> Status.fromByte(c)) { (x, y) => (x & y) & 0xff }
currentStatus.copy(b = newValue, v = SingleFalse).nzB(newValue)
},
ADDA -> { (c, currentStatus) =>
val (newValue, newCarry) = currentStatus.a.adc(Status.fromByte(c), SingleFalse)
currentStatus.copy(a = newValue, v = AnyStatus, c = newCarry).nzB(newValue)
},
ADDB -> { (c, currentStatus) =>
val (newValue, newCarry) = currentStatus.b.adc(Status.fromByte(c), SingleFalse)
currentStatus.copy(b = newValue, v = AnyStatus, c = newCarry).nzB(newValue)
},
ADCA -> { (c, currentStatus) =>
val (newValue, newCarry) = currentStatus.a.adc(Status.fromByte(c), currentStatus.c)
currentStatus.copy(a = newValue, v = AnyStatus, c = newCarry).nzB(newValue)
},
ADCB -> { (c, currentStatus) =>
val (newValue, newCarry) = currentStatus.b.adc(Status.fromByte(c), currentStatus.c)
currentStatus.copy(b = newValue, v = AnyStatus, c = newCarry).nzB(newValue)
},
)
def hasDefinition(opcode: MOpcode.Value): Boolean = map.contains(opcode)
def get(opcode: MOpcode.Value): (Constant, CpuStatus) => CpuStatus = map(opcode)
}

View File

@ -0,0 +1,28 @@
package millfork.assembly.m6809.opt
import millfork.assembly.m6809.MOpcode
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.opt.Status.SingleFalse
import millfork.assembly.opt.{AnyStatus, SingleStatus, Status}
import millfork.env.Constant
/**
* @author Karol Stasiak
*/
object ForwardFlowAnalysisForInherent {
private val map: Map[MOpcode.Value, CpuStatus => CpuStatus] = Map(
NOP -> identity,
MUL -> { currentStatus =>
val newD = (currentStatus.a <*> currentStatus.b) { (a, b) => ((a & 0xff) * (b & 0xff)) & 0xff }
currentStatus.copy(c = AnyStatus, z = AnyStatus, a = newD.hi, b = newD.hi)
},
SEX -> { currentStatus =>
val newA = currentStatus.b.map{ n => if (n.&(0x80) == 0) 0 else 0xff }
currentStatus.copy(v = Status.SingleFalse, n = AnyStatus, z = AnyStatus, a = newA)
},
)
def hasDefinition(opcode: MOpcode.Value): Boolean = map.contains(opcode)
def get(opcode: MOpcode.Value): CpuStatus => CpuStatus = map(opcode)
}

View File

@ -0,0 +1,47 @@
package millfork.assembly.m6809.opt
import millfork.assembly.m6809.MOpcode
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.opt.{AnyStatus, SingleStatus, Status}
/**
* @author Karol Stasiak
*/
object ForwardFlowAnalysisForInherentA {
private val map: Map[MOpcode.Value, CpuStatus => CpuStatus] = Map(
ASL -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.<<(1).&(0xff))
currentStatus.copy(a = newValue, c = currentStatus.a.bit7, v = AnyStatus).nzB(newValue)
},
LSR -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.>>(1).&(0x7f))
currentStatus.copy(a = newValue, c = currentStatus.a.bit0, v = AnyStatus).nzB(newValue)
},
CLR -> { currentStatus =>
currentStatus.copy(a = Status.SingleZero, c = Status.SingleFalse, v = Status.SingleFalse, n = Status.SingleFalse, z = Status.SingleFalse)
},
COM -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.^(0xff).&(0xff))
currentStatus.copy(a = newValue, c = Status.SingleTrue, v = Status.SingleFalse).nzB(newValue)
},
DEC -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.+(1).&(0xff))
currentStatus.copy(a = newValue, v = AnyStatus).nzB(newValue)
},
INC -> { currentStatus =>
val newValue = currentStatus.a.map(n => n.-(1).&(0xff))
currentStatus.copy(a = newValue, v = AnyStatus).nzB(newValue)
},
NEG -> { currentStatus =>
val newValue = currentStatus.a.map(n => (-n).&(0xff))
currentStatus.copy(a = newValue, c = AnyStatus, v = AnyStatus).nzB(newValue)
},
TST -> { currentStatus =>
currentStatus.copy(v = Status.SingleFalse).nzB(currentStatus.a)
},
)
def hasDefinition(opcode: MOpcode.Value): Boolean = map.contains(opcode)
def get(opcode: MOpcode.Value): CpuStatus => CpuStatus = map(opcode)
}

View File

@ -0,0 +1,47 @@
package millfork.assembly.m6809.opt
import millfork.assembly.m6809.MOpcode
import millfork.assembly.m6809.MOpcode._
import millfork.assembly.opt.{AnyStatus, Status}
/**
* @author Karol Stasiak
*/
object ForwardFlowAnalysisForInherentB {
private val map: Map[MOpcode.Value, CpuStatus => CpuStatus] = Map(
ASL -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.<<(1).&(0xff))
currentStatus.copy(b = newValue, c = currentStatus.b.bit7, v = AnyStatus).nzB(newValue)
},
LSR -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.>>(1).&(0x7f))
currentStatus.copy(b = newValue, c = currentStatus.b.bit0, v = AnyStatus).nzB(newValue)
},
CLR -> { currentStatus =>
currentStatus.copy(b = Status.SingleZero, c = Status.SingleFalse, v = Status.SingleFalse, n = Status.SingleFalse, z = Status.SingleFalse)
},
COM -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.^(0xff).&(0xff))
currentStatus.copy(b = newValue, c = Status.SingleTrue, v = Status.SingleFalse).nzB(newValue)
},
DEC -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.+(1).&(0xff))
currentStatus.copy(b = newValue, v = AnyStatus).nzB(newValue)
},
INC -> { currentStatus =>
val newValue = currentStatus.b.map(n => n.-(1).&(0xff))
currentStatus.copy(b = newValue, v = AnyStatus).nzB(newValue)
},
NEG -> { currentStatus =>
val newValue = currentStatus.b.map(n => (-n).&(0xff))
currentStatus.copy(b = newValue, c = AnyStatus, v = AnyStatus).nzB(newValue)
},
TST -> { currentStatus =>
currentStatus.copy(v = Status.SingleFalse).nzB(currentStatus.b)
},
)
def hasDefinition(opcode: MOpcode.Value): Boolean = map.contains(opcode)
def get(opcode: MOpcode.Value): CpuStatus => CpuStatus = map(opcode)
}

View File

@ -1,5 +1,7 @@
package millfork.assembly.m6809.opt
import jdk.jfr.BooleanFlag
import millfork.CompilationFlag
import millfork.assembly._
import millfork.assembly.m6809.{Absolute, MLine, MLine0, MOpcode, MState}
import millfork.assembly.opt.FlowCache
@ -81,6 +83,7 @@ object ReverseFlowAnalyzer {
val readsB: Set[String] = Set("call")
val readsX: Set[String] = Set("call")
val readsY: Set[String] = Set("")
val preservesX: Set[String] = Set("__divmod_u8u8u8u8")
val cache = new FlowCache[MLine, CpuImportance]("m6809 reverse")
private val importanceBeforeJsr: CpuImportance = CpuImportance(
@ -96,8 +99,8 @@ object ReverseFlowAnalyzer {
hf = Unimportant)
private val finalImportance: CpuImportance = CpuImportance(
a = Important, b = Important,
x = Important, y = Important, u = Important,
cf = Important, vf = Important, hf = Important, zf = Important, nf = Important)
x = Important, y = Important, u = Important, // TODO: correctly check which registers are important given the function and the compilation options
cf = Unimportant, vf = Unimportant, hf = Unimportant, zf = Unimportant, nf = Unimportant)
//noinspection RedundantNewCaseClass
def analyze(f: NormalFunction, code: List[MLine], optimizationContext: OptimizationContext): List[CpuImportance] = {
@ -108,9 +111,27 @@ object ReverseFlowAnalyzer {
var changed = true
changed = true
val actualFinalImportance = {
var tmp = f.returnType match {
case FlagBooleanType(_, _, _) => finalImportance.copy(cf = Important, zf = Important, nf = Important, vf = Important)
case t if t.size == 1 => finalImportance.copy(a = Unimportant)
case t if t.size == 0 => finalImportance.copy(a = Unimportant, b = Unimportant)
case _ => finalImportance
}
if (!f.inAssembly) {
tmp = tmp.copy(x = Unimportant)
if (!optimizationContext.options.flag(CompilationFlag.UseUForStack)) {
tmp = tmp.copy(u = Unimportant)
}
if (!optimizationContext.options.flag(CompilationFlag.UseYForStack)) {
tmp = tmp.copy(y = Unimportant)
}
}
tmp
}
while (changed) {
changed = false
var currentImportance = finalImportance
var currentImportance = actualFinalImportance
for (i <- codeArray.indices.reverse) {
import millfork.assembly.m6809.MOpcode._
import millfork.node.M6809NiceFunctionProperty._
@ -126,11 +147,22 @@ object ReverseFlowAnalyzer {
case MLine0(LABEL, _, MemoryAddressConstant(Label(L))) => true
case _ => false
}
currentImportance = if (labelIndex < 0) finalImportance else importanceArray(labelIndex) ~ currentImportance
currentImportance = if (labelIndex < 0) actualFinalImportance else importanceArray(labelIndex) ~ currentImportance
case MLine0(JMP | BRA, _, MemoryAddressConstant(Label(l))) =>
val L = l
val labelIndex = codeArray.indexWhere {
case MLine0(LABEL, _, MemoryAddressConstant(Label(L))) => true
case _ => false
}
currentImportance = if (labelIndex < 0) actualFinalImportance else importanceArray(labelIndex)
case _ =>
}
currentLine match {
case MLine0(RTS, _, _) =>
currentImportance = actualFinalImportance
case MLine0(LABEL, _, _) =>
// do nothing
case MLine0(JSR | JMP, Absolute(false), MemoryAddressConstant(fun: FunctionInMemory)) =>
// this case has to be handled first, because the generic JSR importance handler is too conservative
var result = importanceBeforeJsr
@ -161,6 +193,7 @@ object ReverseFlowAnalyzer {
if (readsB(fun.name)) result = result.copy(b = Important)
if (readsX(fun.name)) result = result.copy(x = Important)
if (readsY(fun.name)) result = result.copy(y = Important)
if (preservesX(fun.name)) result = result.copy(x = currentImportance.x)
currentImportance = result.copy(
a = if (niceFunctionProperties(DoesntChangeA -> fun.name)) currentImportance.a ~ result.a else result.a,
b = if (niceFunctionProperties(DoesntChangeB -> fun.name)) currentImportance.b ~ result.b else result.b,
@ -173,7 +206,7 @@ object ReverseFlowAnalyzer {
case MLine0(opcode, addrMode, _) =>
if (MOpcode.ChangesC(opcode)) currentImportance = currentImportance.copy(cf = Unimportant)
if (MOpcode.ChangesN(opcode)) currentImportance = currentImportance.copy(nf = Unimportant)
if (MOpcode.ChangesZ(opcode)) currentImportance = currentImportance.copy(zf = Unimportant)
if (MOpcode.ChangesH(opcode)) currentImportance = currentImportance.copy(hf = Unimportant)
if (MOpcode.ChangesZ(opcode)) currentImportance = currentImportance.copy(zf = Unimportant)
if (MOpcode.ReadsC(opcode)) currentImportance = currentImportance.copy(cf = Important)
if (MOpcode.ReadsH(opcode)) currentImportance = currentImportance.copy(hf = Important)

View File

@ -30,42 +30,52 @@ object FlowInfoRequirement extends Enumeration {
}
def assertLabels(x: FlowInfoRequirement.Value): Unit = x match {
case NoRequirement => FatalErrorReporting.reportFlyingPig("Backward flow info required")
case NoRequirement => FatalErrorReporting.reportFlyingPig("Label info required")
case _ => ()
}
}
trait AssemblyRuleSet{
def flatten: Seq[AssemblyRule]
def minimumRequiredLines: Int
}
class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInfoRequirement.Value, val rules: AssemblyRuleSet*) extends AssemblyOptimization[MLine] {
private val actualRules = rules.flatMap(_.flatten)
actualRules.foreach(_.pattern.validate(needsFlowInfo))
private val actualRulesWithIndex = actualRules.zipWithIndex
override val minimumRequiredLines: Int = rules.map(_.minimumRequiredLines).min
override def toString: String = name
override def optimize(f: NormalFunction, code: List[MLine], optimizationContext: OptimizationContext): List[MLine] = {
val taggedCode = FlowAnalyzer.analyze(f, code, optimizationContext, needsFlowInfo)
val (changed, optimized) = optimizeImpl(f, taggedCode, optimizationContext)
if (changed) optimized else code
optimizeImpl(f, code, taggedCode, optimizationContext)
}
def optimizeImpl(f: NormalFunction, code: List[(FlowInfo, MLine)], optimizationContext: OptimizationContext): (Boolean, List[MLine]) = {
final def optimizeImpl(f: NormalFunction, code: List[MLine], taggedCode: List[(FlowInfo, MLine)], optimizationContext: OptimizationContext): List[MLine] = {
val log = optimizationContext.log
code match {
case Nil => false -> Nil
taggedCode match {
case Nil => code
case head :: tail =>
for ((rule, index) <- actualRules.zipWithIndex) {
val codeLength = code.length
for {
(rule, index) <- actualRulesWithIndex
if codeLength >= rule.minimumRequiredLines
} {
val ctx = new AssemblyMatchingContext(
optimizationContext.options,
optimizationContext.labelMap,
optimizationContext.niceFunctionProperties,
head._1.labelUseCount(_)
)
rule.pattern.matchTo(ctx, code) match {
rule.pattern.matchTo(ctx, taggedCode) match {
case Some(rest: List[(FlowInfo, MLine)]) =>
val matchedChunkToOptimize: List[MLine] = code.take(code.length - rest.length).map(_._2)
val optimizedChunkLengthBefore = taggedCode.length - rest.length
val (matchedChunkToOptimize, restOfCode) = code.splitAt(optimizedChunkLengthBefore)
val optimizedChunk: List[MLine] = rule.result(matchedChunkToOptimize, ctx)
val optimizedChunkWithSource =
if (!ctx.compilationOptions.flag(CompilationFlag.LineNumbersInAssembly)) optimizedChunk
@ -74,34 +84,40 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf
else if (optimizedChunk.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else if (matchedChunkToOptimize.flatMap(_.source).toSet.size == 1) optimizedChunk.map(_.pos(SourceLine.merge(matchedChunkToOptimize.map(_.source))))
else optimizedChunk
log.debug(s"Applied $name ($index)")
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = code.head._1.statusBefore
val after = code(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
if (log.debugEnabled) {
log.debug(s"Applied $name ($index)")
}
if (log.traceEnabled) {
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
val before = head._1.statusBefore
val after = taggedCode(matchedChunkToOptimize.length - 1)._1.importanceAfter
log.trace(s"Before: $before")
log.trace(s"After: $after")
}
matchedChunkToOptimize.filter(_.isPrintable).foreach(l => log.trace(l.toString))
log.trace(" ↓")
optimizedChunkWithSource.filter(_.isPrintable).foreach(l => log.trace(l.toString))
}
if (needsFlowInfo != FlowInfoRequirement.NoRequirement) {
return true -> (optimizedChunkWithSource ++ optimizeImpl(f, rest, optimizationContext)._2)
return optimizedChunkWithSource ++ optimizeImpl(f, restOfCode, rest, optimizationContext)
} else {
return true -> optimize(f, optimizedChunkWithSource ++ rest.map(_._2), optimizationContext)
return optimize(f, optimizedChunkWithSource ++ restOfCode, optimizationContext)
}
case None => ()
}
}
val (changedTail, optimizedTail) = optimizeImpl(f, tail, optimizationContext)
(changedTail, head._2 :: optimizedTail)
val optimizedTail = optimizeImpl(f, code.tail, tail, optimizationContext)
if (optimizedTail eq code.tail) {
code
} else {
code.head :: optimizedTail
}
}
}
}
class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
val labelMap: Map[String, (Int, Int)],
val labelMap: Map[String, (String, Int)],
val niceFunctionProperties: Set[(NiceFunctionProperty, String)],
val labelUseCount: String => Int) {
@inline
@ -211,10 +227,14 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions,
case class AssemblyRule(pattern: AssemblyPattern, result: (List[MLine], AssemblyMatchingContext) => List[MLine]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = List(this)
override def minimumRequiredLines: Int = pattern.minimumRequiredLines
}
case class MultipleAssemblyRules(list: Seq[AssemblyRuleSet]) extends AssemblyRuleSet {
override def flatten: Seq[AssemblyRule] = list.flatMap(_.flatten)
override val minimumRequiredLines: Int = flatten.map(_.minimumRequiredLines).sum
}
trait AssemblyPattern {
@ -235,6 +255,8 @@ trait AssemblyPattern {
def captureLength(i: Int) = CaptureLength(i, this)
def minimumRequiredLines: Int
}
object HelperCheckers {
private def badAddrModes(am: MAddrMode): Boolean = am match {
@ -337,6 +359,8 @@ case class Capture(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = 0
}
case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPattern {
@ -349,6 +373,8 @@ case class CaptureLength(i: Int, pattern: AssemblyPattern) extends AssemblyPatte
}
override def toString: String = s"(?<$i>$pattern)"
override def minimumRequiredLines: Int = 0
}
@ -358,6 +384,8 @@ case class Where(predicate: AssemblyMatchingContext => Boolean) extends Assembly
}
override def toString: String = "Where(...)"
override def minimumRequiredLines: Int = 0
}
case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends AssemblyPattern {
@ -380,6 +408,8 @@ case class Concatenation(l: AssemblyPattern, r: AssemblyPattern) extends Assembl
case (_: Both, _) => s"($l) · $r"
case _ => s"$l · $r"
}
override val minimumRequiredLines: Int = l.minimumRequiredLines + r.minimumRequiredLines
}
case class Many(rule: MLinePattern) extends AssemblyPattern {
@ -405,6 +435,8 @@ case class Many(rule: MLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]*"
override def minimumRequiredLines: Int = 0
}
case class ManyWhereAtLeastOne(rule: MLinePattern, atLeastOneIsThis: MLinePattern) extends AssemblyPattern {
@ -439,6 +471,8 @@ case class ManyWhereAtLeastOne(rule: MLinePattern, atLeastOneIsThis: MLinePatter
}
override def toString: String = s"[∃$atLeastOneIsThis:$rule]*"
override def minimumRequiredLines: Int = 1
}
case class Opt(rule: MLinePattern) extends AssemblyPattern {
@ -461,6 +495,8 @@ case class Opt(rule: MLinePattern) extends AssemblyPattern {
}
override def toString: String = s"[$rule]?"
override def minimumRequiredLines: Int = 0
}
trait MLinePattern extends AssemblyPattern {
@ -488,6 +524,8 @@ trait MLinePattern extends AssemblyPattern {
else Both(x, this)
def hitRate: Double
override def minimumRequiredLines: Int = 1
}
//noinspection ScalaUnnecessaryParentheses
@ -509,6 +547,8 @@ case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) ext
val s2s = ctx.get[List[MLine]](ix2)
if (s1s.forall(s1 => s2s.forall(s2 => HelperCheckers.memoryAccessDoesntOverlap(s1, s2)))) Some(code) else None
}
override def minimumRequiredLines: Int = 0
}
case class MatchA(i: Int) extends MLinePattern {
@ -754,6 +794,8 @@ case object DebugMatching extends AssemblyPattern {
code.foreach(println)
Some(code)
}
override def minimumRequiredLines: Int = 0
}
case object Linear extends MLinePattern {
@ -1274,6 +1316,8 @@ case class MatchElidableCopyOf(i: Int, firstLinePattern: MLinePattern, lastLineP
}
Some(after)
}
override def minimumRequiredLines: Int = 0
}
case object IsNotALabelUsedManyTimes extends MLinePattern {
@ -1290,3 +1334,14 @@ case object IsNotALabelUsedManyTimes extends MLinePattern {
override def hitRate: Double = 0.92 // ?
}
object ParameterIsLocalLabel extends MLinePattern {
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: MLine): Boolean =
line match {
case MLine0(MOpcode.LABEL, _, MemoryAddressConstant(Label(l))) => l.startsWith(".")
case _ => false
}
override def hitRate: Double = 0.056
}

View File

@ -693,6 +693,8 @@ case class AssemblyLine(opcode: Opcode.Value, addrMode: AddrMode.Value, var para
parameter match {
case StructureConstant(_, List(a,b)) => s" $opcode $a,$b"
}
} else if (addrMode == LongRelative && opcode != BSR) { // BSR on Hudson is always 8-bit short, and on 65CE02 it's always 16-bit
s" L$opcode ${AddrMode.addrModeToString(addrMode, parameter.toString)}"
} else if (addrMode == ImmediateWithAbsolute || addrMode == ImmediateWithZeroPage) {
parameter match {
case StructureConstant(_, List(a,b)) => s" $opcode #$a,$b"

View File

@ -180,20 +180,31 @@ object Opcode extends Enumeration {
case "AXA" => AHX
case "AXS" => SBX // could mean SAX
case "BCC" => BCC
case "LBCC" => BCC
case "BCS" => BCS
case "LBCS" => BCS
case "BEQ" => BEQ
case "LBEQ" => BEQ
case "BIT" => BIT
case "BMI" => BMI
case "LBMI" => BMI
case "BNE" => BNE
case "LBNE" => BNE
case "BPL" => BPL
case "LBPL" => BPL
case "BRA" => BRA
case "LBRA" => BRA
case "BRK" => BRK
case "BRL" => BRL
case "BSR" => BSR
case "LBSR" => BSR
case "BVC" => BVC
case "LBVC" => BVC
case "BVS" => BVS
case "LBVS" => BVS
case "CLC" => CLC
case "CLD" => CLD
case "CLE" => CLE
case "CLI" => CLI
case "CLV" => CLV
case "CLX" => CLX
@ -232,6 +243,7 @@ object Opcode extends Enumeration {
case "LSE" => SRE
case "LSR" => LSR
case "LXA" => LXA
case "MAP" => MAP
case "NEG" => NEG
case "NOP" => NOP
case "OAL" => LXA
@ -247,17 +259,19 @@ object Opcode extends Enumeration {
case "PHW" => PHW
case "PHX" => PHX
case "PHY" => PHY
case "PHZ" => PHZ
case "PLA" => PLA
case "PLB" => PLB
case "PLD" => PLD
case "PLP" => PLP
case "PLX" => PLX
case "PLY" => PLY
case "PLZ" => PLZ
case "REP" => REP
case "RLA" => RLA
case "ROL" => ROL
case "ROR" => ROR
case "ROW" => ROR_W // TODO: is this correct?
case "ROW" => ROL_W
case "RRA" => RRA
case "RTI" => RTI
case "RTL" => RTL
@ -268,6 +282,7 @@ object Opcode extends Enumeration {
case "SBX" => SBX
case "SEC" => SEC
case "SED" => SED
case "SEE" => SEE
case "SEI" => SEI
case "SEP" => SEP
case "SET" => SET

View File

@ -995,16 +995,10 @@ object AlwaysGoodOptimizations {
val ConstantFlowAnalysis = new RuleBasedAssemblyOptimization("Constant flow analysis",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(MatchX(0) & HasAddrMode(AbsoluteX) & SupportsAbsolute & Elidable & HasParameterWhere({
case MemoryAddressConstant(th) => th.name == "identity$"
case _ => false
})) ~~> { (code, ctx) =>
(MatchX(0) & HasAddrMode(AbsoluteX) & SupportsAbsolute & Not(HasOpcode(BIT)) & Elidable & HasIdentityPageParameter) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = Immediate, parameter = NumericConstant(ctx.get[Int](0), 1)))
},
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable & HasParameterWhere({
case MemoryAddressConstant(th) => th.name == "identity$"
case _ => false
})) ~~> { (code, ctx) =>
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable & HasIdentityPageParameter) ~~> { (code, ctx) =>
code.map(l => l.copy(addrMode = Immediate, parameter = NumericConstant(ctx.get[Int](0), 1)))
},
(MatchY(0) & HasAddrMode(AbsoluteY) & SupportsAbsolute & Elidable) ~~> { (code, ctx) =>
@ -1605,6 +1599,15 @@ object AlwaysGoodOptimizations {
Where(ctx => ctx.get[Int](1).&(1) == 0)~~> { (lines, ctx) =>
lines.head.copy(opcode = ASL) :: lines.tail
},
(Elidable & HasOpcode(LDA)) ~
(Elidable & HasOpcode(AND) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(BNE) & MatchParameter(1)) ~
(Elidable & HasOpcode(LDA)) ~
(Elidable & HasOpcode(AND) & HasAddrMode(Immediate) & MatchParameter(0)) ~
(Elidable & HasOpcode(BEQ)) ~
(Elidable & HasOpcode(LABEL) & MatchParameter(1)) ~~> { (code, ctx) =>
List(code.head, code(3).copy(opcode = ORA)) ++ code.drop(4)
},
)
val SimplifiableIndexChanging = new RuleBasedAssemblyOptimization("Simplifiable index changing",
@ -2065,7 +2068,7 @@ object AlwaysGoodOptimizations {
val UnusedLabelRemoval = new RuleBasedAssemblyOptimization("Unused label removal",
needsFlowInfo = FlowInfoRequirement.JustLabels,
(Elidable & HasOpcode(LABEL) & HasCallerCount(0)) ~~> (_ => Nil)
(Elidable & HasOpcode(LABEL) & HasCallerCount(0) & ParameterIsLocalLabel) ~~> (_ => Nil)
)
val OperationsAroundShifting = new RuleBasedAssemblyOptimization("Operations around shifting",
@ -2982,7 +2985,9 @@ object AlwaysGoodOptimizations {
(Elidable & HasOpcode(EOR) & MatchImmediate(0)) ~
(Elidable & HasOpcode(CMP) & MatchImmediate(1) & DoesntMatterWhatItDoesWith(State.A, State.N, State.C)) ~~> { (code, ctx) =>
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, ctx.get[Constant](0), ctx.get[Constant](1))))
val c0 = ctx.get[Constant](0)
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = CompoundConstant(MathOperator.Exor, c0, c1).quickSimplify))
},
MultipleAssemblyRules(for {
@ -3106,6 +3111,42 @@ object AlwaysGoodOptimizations {
code(1).copy(opcode = branch, parameter = code(3).parameter),
code(4))
},
(Elidable & HasOpcode(INC) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CMP) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.A, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(INX) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CPX) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.X, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(INY) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CPY) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Y, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 - 1).quickSimplify))
},
(Elidable & HasOpcode(DEC) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CMP) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.A, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(DEX) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CPX) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.X, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
(Elidable & HasOpcode(DEY) & HasAddrMode(Implied)) ~
(Elidable & HasOpcode(CPY) & HasAddrMode(Immediate) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Y, State.C, State.V)) ~~> { (code, ctx) =>
val c1 = ctx.get[Constant](1)
List(code.last.copy(parameter = (c1 + 1).quickSimplify))
},
)
private val powersOf2: List[(Int, Int)] = List(

Some files were not shown because too many files have changed in this diff Show More