mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-01 06:29:53 +00:00
commit
01a212dbee
20
CHANGELOG.md
20
CHANGELOG.md
@ -6,15 +6,29 @@
|
||||
|
||||
* Added the ability to define custom segment layouts.
|
||||
|
||||
* Hardware definitions for Atari 8-bit (thanks to @FreddyOffenga)
|
||||
|
||||
* Fixed BLL headers for Atari Lynx (#7).
|
||||
|
||||
* Some improvements for Commander X16 (with help from @Ambez05)
|
||||
|
||||
* Specified consistent behaviour of `for` loops (#8)
|
||||
|
||||
* Fixed bugs with constant booleans.
|
||||
|
||||
* Fixes bugs with arithmetic promotions of signed values.
|
||||
* Fixed bugs with arithmetic promotions of signed values.
|
||||
|
||||
* Fixed a bug with unused global variable elimination (#10).
|
||||
|
||||
* Fixed a bug with variable overlapping (#11).
|
||||
|
||||
* 8080: Fixed and optimized 16-bit comparisons.
|
||||
|
||||
* 8080: Optimized some library functions.
|
||||
|
||||
* Optimized certain byte comparisons.
|
||||
* Optimized certain byte comparisons and pointer indexing.
|
||||
|
||||
* 6502: Optimized certain boolean conversions.
|
||||
* 6502: Optimized certain boolean conversions and some small array accesses.
|
||||
|
||||
* Unused built-in functions are now removed more accurately.
|
||||
|
||||
|
@ -74,7 +74,7 @@ and the most significant word is passed via the DE register pair
|
||||
|
||||
* callee may clobber all flags
|
||||
|
||||
* callee may clobber all registers except for IX, IY and shadow registers
|
||||
* callee may clobber all registers except for SP, IX, IY and shadow registers
|
||||
|
||||
## 8086
|
||||
|
||||
@ -118,4 +118,32 @@ and the most significant word is passed via the DX register
|
||||
|
||||
* callee may clobber all flags
|
||||
|
||||
* callee may clobber all registers except for BP
|
||||
* callee may clobber all registers except for SP and BP
|
||||
|
||||
## 6809
|
||||
|
||||
**WARNING!** Motorola 6809 support is not yet complete.
|
||||
|
||||
**TODO: this convention may change**
|
||||
|
||||
#### Parameters:
|
||||
|
||||
* if the function has one parameter of size one byte, it is passed via the B register
|
||||
|
||||
* if the function has one parameter of size two bytes, it is passed via the D register
|
||||
|
||||
* otherwise, all parameters are passed via static locations
|
||||
|
||||
#### Return values:
|
||||
|
||||
* one-byte return values are passed via the B register
|
||||
|
||||
* two-byte return values are passed via the D register
|
||||
|
||||
* otherwise, the return value is passed via a static location
|
||||
|
||||
#### Register preservation:
|
||||
|
||||
* callee may clobber all flags
|
||||
|
||||
* callee may clobber all registers except for S and U
|
||||
|
@ -39,6 +39,9 @@ Currently, such functions may be evaluated either once or twice. This might be f
|
||||
|
||||
* when using modifying operators: calling functions on the right-hand-side index expression than modify any of the variables used on the left hand side
|
||||
|
||||
* when using `for` loops operators: calling non-pure functions in the range limits (like in `for i,f(),to,g()`).
|
||||
Currently, such functions may be evaluated any number of times. This might be fixed in the future.
|
||||
|
||||
* jumping across the scope of for loop that uses a fixed list or across functions
|
||||
|
||||
* division by zero and modulo by zero
|
||||
|
@ -28,6 +28,8 @@ no extension for BBC micro program file,
|
||||
`.inf` for BBC Micro file metadata,
|
||||
`.asm` for assembly output,
|
||||
`.lbl`, `.nl`, `.fns`, or `.sym` for label file.
|
||||
Default: If compiling one file with `.mfk` extension, the same name as the input file. Otherwise, `a`.
|
||||
|
||||
|
||||
* `-s` – Generate also the assembly output. It is not compatible with any assembler, but it serves purely informational purpose. The file has the same nam as the output file and the extension is `.asm`.
|
||||
|
||||
|
@ -44,6 +44,9 @@ if a line ends with a backslash character, the value continues to the next line.
|
||||
* `i8086` (Intel 8086; very experimental, very buggy and very, very incomplete –
|
||||
see the [8086 support disclaimer](../lang/x86disclaimer.md))
|
||||
|
||||
* `6809` (Motorola 6809; very experimental, very buggy and very, very incomplete –
|
||||
many language features simply do not work at all for this target)
|
||||
|
||||
* `encoding` – default encoding for console I/O. Default: `ascii`.
|
||||
See [the list of available encodings](../lang/text.md).
|
||||
|
||||
|
@ -34,6 +34,8 @@
|
||||
|
||||
* [Inline 8080/LR35902/Z80 assembly syntax](lang/assemblyz80.md)
|
||||
|
||||
* [Inline 6809 assembly syntax](lang/assembly6809.md)
|
||||
|
||||
* [Important guidelines regarding reentrancy](lang/reentrancy.md)
|
||||
|
||||
* [List of keywords](lang/keywords.md)
|
||||
|
134
docs/lang/assembly6809.md
Normal file
134
docs/lang/assembly6809.md
Normal file
@ -0,0 +1,134 @@
|
||||
[< back to index](../doc_index.md)
|
||||
|
||||
# Using 6809 assembly within Millfork programs
|
||||
|
||||
**WARNING!** Motorola 6809 support is not yet complete.
|
||||
|
||||
There are two ways to include raw assembly code in your Millfork programs:
|
||||
|
||||
* inline assembly code blocks
|
||||
|
||||
* whole assembly functions
|
||||
|
||||
## Assembly syntax
|
||||
|
||||
Millfork inline assembly uses the same three-letter opcodes as most other 6809 assemblers.
|
||||
|
||||
Labels have to be followed by a colon and they can optionally be on a separate line.
|
||||
Indentation is not important:
|
||||
|
||||
first: INC a
|
||||
second:
|
||||
INC b
|
||||
INC c
|
||||
|
||||
|
||||
Label names have to start with a letter and can contain digits, underscores and letters.
|
||||
This means than they cannot start with a period like in many other assemblers.
|
||||
Similarly, anonymous labels designated with `+` or `-` are also not supported.
|
||||
|
||||
Assembly can refer to variables and constants defined in Millfork,
|
||||
but you need to be careful with using absolute vs immediate addressing:
|
||||
|
||||
const byte fiveConstant = 5
|
||||
byte fiveVariable = 5
|
||||
|
||||
byte ten() {
|
||||
byte result
|
||||
asm {
|
||||
LDB fiveVariable // not LDB #fiveVariable
|
||||
ADDB #fiveConstant
|
||||
STB result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Any assembly opcode can be prefixed with `?`, which allows the optimizer change it or elide it if needed.
|
||||
Opcodes without that prefix will be always compiled as written.
|
||||
|
||||
The '!' prefix marks the statement as volatile, which means it will be a subject to certain, but not all optimizations,
|
||||
in order to preserve its semantics.
|
||||
|
||||
You can insert macros into assembly, by prefixing them with `+` and using the same syntax as in Millfork:
|
||||
|
||||
macro void run(byte x) {
|
||||
output = x
|
||||
}
|
||||
|
||||
byte output @$c000
|
||||
|
||||
void main () {
|
||||
byte a
|
||||
a = 7
|
||||
asm {
|
||||
+ run(a)
|
||||
}
|
||||
}
|
||||
|
||||
You can insert raw bytes into your assembly using the array syntax:
|
||||
|
||||
[ $00, $00 ]
|
||||
"this is a string to print" bbc
|
||||
["this is a string to print but this time it's zero-terminated so it will actually work" bbc, 0]
|
||||
[for x,0,until,8 [x]]
|
||||
|
||||
## Assembly functions
|
||||
|
||||
Assembly functions can be declared as `macro` or not.
|
||||
|
||||
A macro assembly function is inserted into the calling function like an inline assembly block,
|
||||
and therefore usually it shouldn't end with `RTS`.
|
||||
|
||||
A non-macro assembly function should end with `RTS`, `JMP`, or `BRA` as appropriate,
|
||||
or it should be an external function.
|
||||
|
||||
For both macro and non-macro assembly functions,
|
||||
the return type can be any valid return type, like for Millfork functions.
|
||||
If the size of the return type is one byte,
|
||||
then the result is passed via the B register.
|
||||
If the size of the return type is two bytes,
|
||||
then the result is passed via the D register.
|
||||
|
||||
### Assembly function parameters
|
||||
|
||||
An assembly function can have parameters.
|
||||
They differ from what is used by Millfork functions.
|
||||
|
||||
Macro assembly functions can have the following parameter types:
|
||||
|
||||
* reference parameters: `byte ref paramname`: every occurrence of the parameter will be replaced with the variable given as an argument
|
||||
|
||||
* constant parameters: `byte const paramname`: every occurrence of the parameter will be replaced with the constant value given as an argument
|
||||
|
||||
For example, if you have:
|
||||
|
||||
macro asm void increase(byte ref v, byte const inc) {
|
||||
LDB v
|
||||
ADDB #inc
|
||||
STB v
|
||||
}
|
||||
|
||||
and call `increase(score, 10)`, the entire call will compile into:
|
||||
|
||||
LDB score
|
||||
ADDB #10
|
||||
STB score
|
||||
|
||||
Non-macro functions can only have their parameters passed via registers:
|
||||
|
||||
* `byte a`, `byte b`: a single byte passed via the given CPU register; any 1-byte type can be used
|
||||
|
||||
* `word d`, `word x`, `word y`: a 2-byte word byte passed via given 16-bit register; any 2-byte type can be used
|
||||
|
||||
Parameters passed via other registers (`U`, `S` etc.) or combinations of registers do not work yet.
|
||||
|
||||
**Work in progress**:
|
||||
Only the following combinations of register parameters work reliably:
|
||||
|
||||
* zero or one register parameters
|
||||
|
||||
Macro assembly functions cannot have any parameter passed via registers.
|
||||
|
||||
## Safe assembly
|
||||
|
||||
**TODO**
|
@ -161,7 +161,8 @@ Note you cannot mix those operators, so `a <= b < c` is not valid.
|
||||
|
||||
* `>`, `<`, `<=`, `>=`: inequality
|
||||
`byte > byte`
|
||||
`simple word > simple word`
|
||||
`simple word > word`
|
||||
`word > simple word`
|
||||
`simple long > simple long`
|
||||
|
||||
Currently, `>`, `<`, `<=`, `>=` operators perform signed comparison
|
||||
|
@ -372,7 +372,7 @@ for <variable> : [ <comma separated expressions> ] {
|
||||
|
||||
* `<variable>` – an already defined numeric variable
|
||||
|
||||
* `<direction>` – the range to traverse:
|
||||
* `<direction>` – the type of range to traverse:
|
||||
|
||||
* `to` – from `<start>` inclusive to `<end>` inclusive, in ascending order
|
||||
(e.g. `0,to,9` to traverse 0, 1,... 9)
|
||||
@ -388,6 +388,21 @@ for <variable> : [ <comma separated expressions> ] {
|
||||
* `paralleluntil` – the same as `until`, but the iterations may be executed in any order
|
||||
|
||||
There is no `paralleldownto`, because it would do the same as `parallelto` with swapped arguments.
|
||||
|
||||
If:
|
||||
|
||||
* the left argument to `until`, `paralleluntil`, `to` or `parallelto` is greater than the right argument
|
||||
|
||||
* the left argument to `downto` is smaller than the right argument
|
||||
|
||||
then the loop counter overflows and wraps around.
|
||||
For example, `for i,254,to,1` with `i` being a byte, iterates over 254, 255, 0, 1.
|
||||
|
||||
If the arguments to `until` or `paralleluntil` are equal, zero iterations are executed.
|
||||
|
||||
`<start>` and `<end>` may be evaluated an arbitrary number of times.
|
||||
It's recommended to use only constants, variables or other really simple expressions.
|
||||
|
||||
|
||||
* `<enum type>` – traverse enum constants of given type, in arbitrary order
|
||||
|
||||
|
@ -57,10 +57,12 @@ If the type `T` is of size 1, you can index the pointer like a raw pointer.
|
||||
|
||||
If the type `T` is of size 2, you can index the pointer only with the constant 0.
|
||||
|
||||
You can create pointer values by suffixing `.pointer` to the name of a variable, function or array.
|
||||
|
||||
Examples:
|
||||
|
||||
pointer.t p
|
||||
p.raw // expression of type pointer, pointing to the same location in memory as 'p'
|
||||
p.pointer // expression of type pointer, pointing to the same location in memory as 'p'
|
||||
p.lo // equivalent to 'p.raw.lo'
|
||||
p.hi // equivalent to 'p.raw.lo'
|
||||
p[0] // valid only if the type 't' is of size 1 or 2, accesses the pointed element
|
||||
|
@ -18,13 +18,14 @@ The goal of Millfork is to succeed where Atalan failed.
|
||||
Large programs in Millfork have been developed for Commodore 64.
|
||||
|
||||
Millfork was also tested (via emulators) to run trivial programs on other 8-bit Commodore computers,
|
||||
Atari 8-bit computers, Apple II, BBC Micro, ZX Spectrum 48k, NEC PC-88, MSX, CP/M, NES, Game Boy, Atari 2600 and MS-DOS.
|
||||
Atari 8-bit computers, Apple II, BBC Micro, ZX Spectrum 48k, NEC PC-88, MSX, CP/M, NES, Game Boy, Atari Lynx, Atari 2600 and MS-DOS.
|
||||
|
||||
Support for other devices using supported processors can be easily added, usually without modifying the compiler.
|
||||
|
||||
### What microprocessors are supported?
|
||||
|
||||
* MOS 6502 and its descendants: 6510, 65C02, Ricoh 2A03, and to a lesser degree CSG 65CE02, Hudson Soft HuC6280 and WDC 65816. 6509 is not supported and will not be.
|
||||
* MOS 6502 and its descendants: 6510, 65C02, Ricoh 2A03, and to a lesser degree CSG 65CE02, Hudson Soft HuC6280 and WDC 65816.
|
||||
6509 is not supported and will not be, however with some care you can treat it like a normal 6502.
|
||||
|
||||
* Intel 8080, Intel 8085, Zilog Z80, Sharp LR35902 (also known as GBZ80)
|
||||
|
||||
@ -32,6 +33,8 @@ Support for other devices using supported processors can be easily added, usuall
|
||||
The generated code is very large and very slow.
|
||||
|
||||
* Support for Motorola 6809 is coming in the future.
|
||||
You can test the work in progress by creating a custom platform using the 6809 CPU,
|
||||
but vast majority of Millfork programs will not even compile.
|
||||
|
||||
### Why Millfork when I can use assembly?
|
||||
|
||||
|
@ -55,6 +55,10 @@ how to create a program made of multiple files loaded on demand
|
||||
|
||||
* [Lynx demo example](atari_lynx/atari_lynx_demo.mfk) – a simple sprite demo
|
||||
|
||||
## Atari 8-bit examples
|
||||
|
||||
* [Rainbow example](a8/rainbow.mfk) – simple scrolling rasterbars
|
||||
|
||||
## Game Boy examples
|
||||
|
||||
* [GB test example](gb/gbtest.mfk) – a partial port of the NES example, with a rudimentary experimental text output implementation
|
||||
|
10
examples/a8/rainbow.mfk
Normal file
10
examples/a8/rainbow.mfk
Normal file
@ -0,0 +1,10 @@
|
||||
byte clock @20
|
||||
byte a
|
||||
|
||||
void main() {
|
||||
while(true){
|
||||
a = (antic_vcount + clock) << 1
|
||||
antic_wsync = a
|
||||
gtia_colpf2 = a
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
[compilation]
|
||||
arch=strict
|
||||
modules=a8_kernel,default_panic,stdlib
|
||||
modules=a8_hardware,a8_kernel,default_panic,stdlib
|
||||
encoding=atascii
|
||||
screen_encoding=atasciiscr
|
||||
|
||||
|
24
include/a8_antic.mfk
Normal file
24
include/a8_antic.mfk
Normal file
@ -0,0 +1,24 @@
|
||||
// Atari 8-bit ANTIC hardware
|
||||
|
||||
#if not(ATARI_8)
|
||||
#warn a8_antic module should be used only on Atari computer-compatible targets
|
||||
#endif
|
||||
|
||||
volatile byte antic_dmactl @$D400 // direct memory access control
|
||||
volatile byte antic_chactl @$D401 // character mode control
|
||||
volatile byte antic_dlistl @$D402 // display list pointer low-byte
|
||||
volatile byte antic_dlisth @$D403 // display list pointer high-byte
|
||||
volatile byte antic_hscrol @$D404 // horizontal scroll enable
|
||||
volatile byte antic_vscrol @$D405 // vertical scroll enable
|
||||
volatile byte antic_unuse0 @$D406 // unused
|
||||
volatile byte antic_pmbase @$D407 // msb of p/m base address
|
||||
volatile byte antic_unuse1 @$D408 // unused
|
||||
volatile byte antic_chbase @$D409 // character base address
|
||||
volatile byte antic_wsync @$D40A // wait for horizontal synchronization
|
||||
volatile byte antic_vcount @$D40B // vertical line counter
|
||||
volatile byte antic_penh @$D40C // light pen horizontal position
|
||||
volatile byte antic_penv @$D40D // light pen vertical position
|
||||
volatile byte antic_nmien @$D40E // non-maskable interrupt enable
|
||||
|
||||
// nmi reset status
|
||||
volatile byte antic_nmires @$D40F
|
64
include/a8_gtia.mfk
Normal file
64
include/a8_gtia.mfk
Normal file
@ -0,0 +1,64 @@
|
||||
// Atari 8-bit GTIA hardware
|
||||
|
||||
#if not(ATARI_8)
|
||||
#warn a8_gtia module should be used only on Atari computer-compatible targets
|
||||
#endif
|
||||
|
||||
// read/write
|
||||
volatile byte gtia_hposp0 @$D000 // horizontal position player 0
|
||||
volatile byte gtia_hposp1 @$D001 // horizontal position player 1
|
||||
volatile byte gtia_hposp2 @$D002 // horizontal position player 2
|
||||
volatile byte gtia_hposp3 @$D003 // horizontal position player 3
|
||||
volatile byte gtia_hposm0 @$D004 // horizontal position missile 0
|
||||
volatile byte gtia_hposm1 @$D005 // horizontal position missile 1
|
||||
volatile byte gtia_hposm2 @$D006 // horizontal position missile 2
|
||||
volatile byte gtia_hposm3 @$D007 // horizontal position missile 3
|
||||
volatile byte gtia_sizep0 @$D008 // size of player 0
|
||||
volatile byte gtia_sizep1 @$D009 // size of player 1
|
||||
volatile byte gtia_sizep2 @$D00A // size of player 2
|
||||
volatile byte gtia_sizep3 @$D00B // size of player 3
|
||||
volatile byte gtia_sizem @$D00C // size of missiles
|
||||
volatile byte gtia_grafp0 @$D00D // graphics shape player 0
|
||||
volatile byte gtia_grafp1 @$D00E // graphics shape player 1
|
||||
volatile byte gtia_grafp2 @$D00F // graphics shape player 2
|
||||
volatile byte gtia_grafp3 @$D010 // graphics shape player 3
|
||||
volatile byte gtia_grafm @$D011 // graphics shape missiles
|
||||
volatile byte gtia_colpm0 @$D012 // color player and missile 0
|
||||
volatile byte gtia_colpm1 @$D013 // color player and missile 1
|
||||
volatile byte gtia_colpm2 @$D014 // color player and missile 2
|
||||
volatile byte gtia_colpm3 @$D015 // color player and missile 3
|
||||
volatile byte gtia_colpf0 @$D016 // color playfield 0
|
||||
volatile byte gtia_colpf1 @$D017 // color playfield 1
|
||||
volatile byte gtia_colpf2 @$D018 // color playfield 2
|
||||
volatile byte gtia_colpf3 @$D019 // color playfield 3
|
||||
volatile byte gtia_colbk @$D01A // color background
|
||||
volatile byte gtia_prior @$D01B // priority selection
|
||||
volatile byte gtia_vdelay @$D01C // vertical delay
|
||||
volatile byte gtia_gractl @$D01D // stick/paddle latch, p/m control
|
||||
volatile byte gtia_hitclr @$D01E // clear p/m collision
|
||||
volatile byte gtia_consol @$D01F // console buttons
|
||||
|
||||
// read
|
||||
volatile byte gtia_m0pf @$D000 // missile 0 to playfield collision
|
||||
volatile byte gtia_m1pf @$D001 // missile 1 to playfield collision
|
||||
volatile byte gtia_m2pf @$D002 // missile 2 to playfield collision
|
||||
volatile byte gtia_m3pf @$D003 // missile 3 to playfield collision
|
||||
volatile byte gtia_p0pf @$D004 // player 0 to playfield collision
|
||||
volatile byte gtia_p1pf @$D005 // player 1 to playfield collision
|
||||
volatile byte gtia_p2pf @$D006 // player 2 to playfield collision
|
||||
volatile byte gtia_p3pf @$D007 // player 3 to playfield collision
|
||||
volatile byte gtia_m0pl @$D008 // missile 0 to player collision
|
||||
volatile byte gtia_m1pl @$D009 // missile 1 to player collision
|
||||
volatile byte gtia_m2pl @$D00A // missile 2 to player collision
|
||||
volatile byte gtia_m3pl @$D00B // missile 3 to player collision
|
||||
volatile byte gtia_p0pl @$D00C // player 0 to player collision
|
||||
volatile byte gtia_p1pl @$D00D // player 1 to player collision
|
||||
volatile byte gtia_p2pl @$D00E // player 2 to player collision
|
||||
volatile byte gtia_p3pl @$D00F // player 3 to player collision
|
||||
volatile byte gtia_trig0 @$D010 // joystick trigger 0
|
||||
volatile byte gtia_trig1 @$D011 // joystick trigger 1
|
||||
volatile byte gtia_trig2 @$D012 // joystick trigger 2
|
||||
volatile byte gtia_trig3 @$D013 // joystick trigger 3
|
||||
|
||||
// pal/ntsc flag
|
||||
volatile byte gtia_pal @$D014
|
8
include/a8_hardware.mfk
Normal file
8
include/a8_hardware.mfk
Normal file
@ -0,0 +1,8 @@
|
||||
#if not(ATARI_8)
|
||||
#warn a8_hardware module should be used only on Atari computer-compatible targets
|
||||
#endif
|
||||
|
||||
import a8_antic
|
||||
import a8_gtia
|
||||
import a8_pia
|
||||
import a8_pokey
|
17
include/a8_pia.mfk
Normal file
17
include/a8_pia.mfk
Normal file
@ -0,0 +1,17 @@
|
||||
// Atari 8-bit PIA hardware
|
||||
|
||||
#if not(ATARI_8)
|
||||
#warn a8_pia module should be used only on Atari computer-compatible targets
|
||||
#endif
|
||||
|
||||
// port A data r/w
|
||||
volatile byte pia_porta @$D300
|
||||
|
||||
// port B data r/w
|
||||
volatile byte pia_portb @$D301
|
||||
|
||||
// port A control
|
||||
volatile byte pia_pactl @$D302
|
||||
|
||||
// port B control
|
||||
volatile byte pia_pbctl @$D303
|
43
include/a8_pokey.mfk
Normal file
43
include/a8_pokey.mfk
Normal file
@ -0,0 +1,43 @@
|
||||
// Atari 8-bit POKEY hardware
|
||||
|
||||
#if not(ATARI_8)
|
||||
#warn a8_pokey module should be used only on Atari computer-compatible targets
|
||||
#endif
|
||||
|
||||
// write
|
||||
volatile byte pokey_audf1 @$D200 // audio channel #1 frequency
|
||||
volatile byte pokey_audc1 @$D201 // audio channel #1 control
|
||||
volatile byte pokey_audf2 @$D202 // audio channel #2 frequency
|
||||
volatile byte pokey_audc2 @$D203 // audio channel #2 control
|
||||
volatile byte pokey_audf3 @$D204 // audio channel #3 frequency
|
||||
volatile byte pokey_audc3 @$D205 // audio channel #3 control
|
||||
volatile byte pokey_audf4 @$D206 // audio channel #4 frequency
|
||||
volatile byte pokey_audc4 @$D207 // audio channel #4 control
|
||||
volatile byte pokey_audctl @$D208 // audio control
|
||||
volatile byte pokey_stimer @$D209 // start pokey timers
|
||||
volatile byte pokey_skrest @$D20A // reset serial port status reg.
|
||||
volatile byte pokey_potgo @$D20B // start paddle scan sequence
|
||||
volatile byte pokey_unuse1 @$D20C // unused
|
||||
volatile byte pokey_serout @$D20D // serial port data output
|
||||
volatile byte pokey_irqen @$D20E // interrupt request enable
|
||||
volatile byte pokey_skctl @$D20F // serial port control
|
||||
|
||||
// read
|
||||
volatile byte pokey_pot0 @$D200 // paddle 0 value
|
||||
volatile byte pokey_pot1 @$D201 // paddle 1 value
|
||||
volatile byte pokey_pot2 @$D202 // paddle 2 value
|
||||
volatile byte pokey_pot3 @$D203 // paddle 3 value
|
||||
volatile byte pokey_pot4 @$D204 // paddle 4 value
|
||||
volatile byte pokey_pot5 @$D205 // paddle 5 value
|
||||
volatile byte pokey_pot6 @$D206 // paddle 6 value
|
||||
volatile byte pokey_pot7 @$D207 // paddle 7 value
|
||||
volatile byte pokey_allpot @$D208 // eight paddle port status
|
||||
volatile byte pokey_kbcode @$D209 // keyboard code
|
||||
volatile byte pokey_random @$D20A // random number generator
|
||||
volatile byte pokey_unuse2 @$D20B // unused
|
||||
volatile byte pokey_unuse3 @$D20C // unused
|
||||
volatile byte pokey_serin @$D20D // serial port input
|
||||
volatile byte pokey_irqst @$D20E // interrupt request status
|
||||
|
||||
// serial port status
|
||||
volatile byte pokey_skstat @$D20F
|
@ -102,6 +102,14 @@ inline void vera_poke(int24 address, byte value) {
|
||||
vera_data1 = value
|
||||
}
|
||||
|
||||
inline byte vera_peek(int24 address) {
|
||||
vera_addr_lo = address.b0
|
||||
vera_addr_mi = address.b1
|
||||
vera_addr_hi = address.b2
|
||||
vera_ctrl = 0
|
||||
return vera_data1
|
||||
}
|
||||
|
||||
inline void vera_fill(int24 address, byte value, word size) {
|
||||
word i
|
||||
vera_addr_lo = address.b0
|
||||
|
@ -29,6 +29,7 @@ nav:
|
||||
- Functions: lang/functions.md
|
||||
- Inline 6502 assembly: lang/assembly.md
|
||||
- Inline 8080/LR35902/Z80 assembly: lang/assemblyz80.md
|
||||
- Inline 6809 assembly: lang/assembly6809.md
|
||||
- Reentrancy guidelines: lang/reentrancy.md
|
||||
- List of keywords: lang/keywords.md
|
||||
- Library reference:
|
||||
|
@ -66,7 +66,14 @@ object Main {
|
||||
case (f, b) => errorReporting.debug(f" $f%-30s : $b%s")
|
||||
}
|
||||
|
||||
val output = c.outputFileName.getOrElse("a")
|
||||
val output = c.outputFileName match {
|
||||
case Some(ofn) => ofn
|
||||
case None => c.inputFileNames match {
|
||||
case List(ifn) if ifn.endsWith(".mfk") =>
|
||||
new File(ifn.stripSuffix(".mfk")).getName
|
||||
case _ => "a"
|
||||
}
|
||||
}
|
||||
val assOutput = output + ".asm"
|
||||
// val prgOutputs = (platform.outputStyle match {
|
||||
// case OutputStyle.Single => List("default")
|
||||
@ -369,10 +376,10 @@ object Main {
|
||||
|
||||
fluff("Main options:", "")
|
||||
|
||||
parameter("-o", "--out").required().placeholder("<file>").action { (p, c) =>
|
||||
parameter("-o", "--out").placeholder("<file>").action { (p, c) =>
|
||||
assertNone(c.outputFileName, "Output already defined")
|
||||
c.copy(outputFileName = Some(p))
|
||||
}.description("The output file name, without extension.").onWrongNumber(_ => errorReporting.fatalQuit("No output file specified"))
|
||||
}.description("The output file name, without extension.")
|
||||
|
||||
flag("-s").action { c =>
|
||||
c.copy(outputAssembly = true)
|
||||
|
@ -2567,6 +2567,22 @@ object AlwaysGoodOptimizations {
|
||||
AssemblyLine.immediate(ADC, 0),
|
||||
code(5).copy(opcode = STA)) //STX
|
||||
},
|
||||
|
||||
(Elidable & HasOpcode(LDX) & HasImmediate(0)) ~
|
||||
(Elidable & HasOpcode(BCC) & MatchParameter(1)) ~
|
||||
(Elidable & HasOpcode(INX)) ~
|
||||
(Elidable & HasOpcode(LABEL) & MatchParameter(1) & IsNotALabelUsedManyTimes) ~
|
||||
(Elidable & HasOpcode(TXA) & DoesntMatterWhatItDoesWith(State.C, State.X)) ~~> { (code, ctx) =>
|
||||
List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL))
|
||||
},
|
||||
|
||||
(Elidable & HasOpcode(LDX) & HasImmediate(0)) ~
|
||||
(Elidable & HasOpcode(BCC) & MatchParameter(1)) ~
|
||||
(Elidable & HasOpcode(INX)) ~
|
||||
(Elidable & HasOpcode(LABEL) & MatchParameter(1) & IsNotALabelUsedManyTimes) ~
|
||||
(Elidable & HasOpcode(TXA) & DoesntMatterWhatItDoesWith(State.C)) ~~> { (code, ctx) =>
|
||||
List(AssemblyLine.immediate(LDA, 0), AssemblyLine.implied(ROL), AssemblyLine.implied(TAX))
|
||||
},
|
||||
)
|
||||
|
||||
val NonetBitOp = new RuleBasedAssemblyOptimization("Nonet bit operation",
|
||||
@ -2976,6 +2992,21 @@ object AlwaysGoodOptimizations {
|
||||
}
|
||||
}),
|
||||
|
||||
(Elidable & HasOpcode(LDA) & HasImmediate(1)) ~
|
||||
(Elidable & HasOpcode(BIT)) ~
|
||||
(Elidable & HasOpcode(BEQ) & MatchParameter(1)) ~
|
||||
(Elidable & HasOpcode(LDA) & HasImmediate(0)) ~
|
||||
(Elidable & HasOpcode(LABEL) & MatchParameter(1) & IsNotALabelUsedManyTimes & DoesntMatterWhatItDoesWith(State.N, State.Z, State.C, State.V)) ~~> { code =>
|
||||
List(code(1).copy(opcode = LDA), code.head.copy(opcode = AND), code.head.copy(opcode = EOR))
|
||||
},
|
||||
|
||||
(Elidable & HasOpcode(LDA) & HasImmediate(1)) ~
|
||||
(Elidable & HasOpcode(BIT)) ~
|
||||
(Elidable & HasOpcode(BNE) & MatchParameter(1)) ~
|
||||
(Elidable & HasOpcode(LDA) & HasImmediate(0)) ~
|
||||
(Elidable & HasOpcode(LABEL) & MatchParameter(1) & IsNotALabelUsedManyTimes & DoesntMatterWhatItDoesWith(State.N, State.Z, State.C, State.V)) ~~> { code =>
|
||||
List(code(1).copy(opcode = LDA), code.head.copy(opcode = AND))
|
||||
},
|
||||
)
|
||||
|
||||
private val powersOf2: List[(Int, Int)] = List(
|
||||
|
@ -102,7 +102,7 @@ object LaterOptimizations {
|
||||
|
||||
private def TwoDifferentLoadsWithNoFlagChangeInBetween(opcode1: Opcode.Value, middle: AssemblyLinePattern, opcode2: Opcode.Value, transferOpcode: Opcode.Value) = {
|
||||
(HasOpcode(opcode1) & MatchAddrMode(0) & MatchParameter(1)) ~
|
||||
(Linear & Not(ChangesMemory) & middle & Not(HasOpcode(opcode2))).* ~
|
||||
(Linear & Not(ChangesMemory) & DoesntChangeIndexingInAddrMode(0) & middle & Not(HasOpcode(opcode2))).* ~
|
||||
(HasOpcode(opcode2) & Elidable & MatchAddrMode(0) & MatchParameter(1)) ~~> { c =>
|
||||
c.init :+ AssemblyLine.implied(transferOpcode)
|
||||
}
|
||||
|
@ -543,6 +543,7 @@ object AbstractExpressionCompiler {
|
||||
|
||||
def checkAssignmentTypeAndGetSourceType(ctx: CompilationContext, source: Expression, target: LhsExpression): Type = {
|
||||
val sourceType = getExpressionType(ctx, source)
|
||||
if (target == BlackHoleExpression) return sourceType
|
||||
val targetType = getExpressionType(ctx, target)
|
||||
if (!sourceType.isAssignableTo(targetType)) {
|
||||
ctx.log.error(s"Cannot assign `$sourceType` to `$targetType`", target.position.orElse(source.position))
|
||||
|
@ -148,7 +148,9 @@ abstract class AbstractStatementCompiler[T <: AbstractCode] {
|
||||
endEvaluated.foreach { value =>
|
||||
val max = f.direction match {
|
||||
case ForDirection.To | ForDirection.ParallelTo | ForDirection.DownTo => value
|
||||
case ForDirection.Until | ForDirection.ParallelUntil => value - 1
|
||||
case ForDirection.Until | ForDirection.ParallelUntil =>
|
||||
// dirty hack:
|
||||
if (value.quickSimplify.isProvablyZero) value else value - 1
|
||||
case _ => Constant.Zero
|
||||
}
|
||||
if (!max.quickSimplify.fitsInto(v.typ)) {
|
||||
@ -162,15 +164,25 @@ abstract class AbstractStatementCompiler[T <: AbstractCode] {
|
||||
val end = ctx.nextLabel("of")
|
||||
val (main, extra) = compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, f.start).pos(p) :: f.body)
|
||||
main ++ labelChunk(end) -> extra
|
||||
case (ForDirection.Until | ForDirection.ParallelUntil, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s >= e =>
|
||||
case (ForDirection.Until | ForDirection.ParallelUntil, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s == e =>
|
||||
Nil -> Nil
|
||||
|
||||
case (ForDirection.To | ForDirection.ParallelTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s == e =>
|
||||
val end = ctx.nextLabel("of")
|
||||
val (main, extra) = compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, f.start).pos(p) :: f.body)
|
||||
main ++ labelChunk(end) -> extra
|
||||
case (ForDirection.To | ForDirection.ParallelTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, _))) if s > e =>
|
||||
Nil -> Nil
|
||||
|
||||
case (ForDirection.To | ForDirection.ParallelTo, _, Some(NumericConstant(255, _))) if indexType.size == 1 =>
|
||||
compile(ctx, List(
|
||||
Assignment(vex, f.start).pos(p),
|
||||
DoWhileStatement(f.body, List(increment), FunctionCallExpression("!=", List(vex, LiteralExpression(0, 1).pos(p))), names).pos(p)
|
||||
))
|
||||
|
||||
case (ForDirection.To | ForDirection.ParallelTo, _, Some(NumericConstant(0xffff, _))) if indexType.size == 2 =>
|
||||
compile(ctx, List(
|
||||
Assignment(vex, f.start).pos(p),
|
||||
DoWhileStatement(f.body, List(increment), FunctionCallExpression("!=", List(vex, LiteralExpression(0, 2).pos(p))), names).pos(p)
|
||||
))
|
||||
|
||||
case (ForDirection.Until | ForDirection.ParallelUntil, Some(c), Some(NumericConstant(256, _)))
|
||||
if variable.map(_.typ.size).contains(1) && c.requiredSize == 1 && c.isProvablyNonnegative =>
|
||||
@ -200,8 +212,6 @@ abstract class AbstractStatementCompiler[T <: AbstractCode] {
|
||||
val end = ctx.nextLabel("of")
|
||||
val (main, extra) = compile(ctx.addLabels(names, Label(end), Label(end)), Assignment(vex, LiteralExpression(s, ssize)).pos(p) :: f.body)
|
||||
main ++ labelChunk(end) -> extra
|
||||
case (ForDirection.DownTo, Some(NumericConstant(s, ssize)), Some(NumericConstant(e, esize))) if s < e =>
|
||||
Nil -> Nil
|
||||
case (ForDirection.DownTo, Some(NumericConstant(s, 1)), Some(NumericConstant(0, _))) if s > 0 =>
|
||||
compile(ctx, List(
|
||||
Assignment(
|
||||
@ -219,10 +229,7 @@ abstract class AbstractStatementCompiler[T <: AbstractCode] {
|
||||
compile(ctx, List(
|
||||
Assignment(
|
||||
vex,
|
||||
SumExpression(
|
||||
List(false -> f.start, false -> LiteralExpression(1, 1).pos(p)),
|
||||
decimal = false
|
||||
).pos(p)
|
||||
f.start #-# 1
|
||||
).pos(p),
|
||||
DoWhileStatement(
|
||||
decrement :: f.body,
|
||||
@ -237,7 +244,7 @@ abstract class AbstractStatementCompiler[T <: AbstractCode] {
|
||||
compile(ctx, List(
|
||||
Assignment(vex, f.start).pos(p),
|
||||
WhileStatement(
|
||||
FunctionCallExpression("<", List(vex, f.end)).pos(p),
|
||||
FunctionCallExpression("!=", List(vex, f.end)).pos(p),
|
||||
f.body, List(increment), names).pos(p)
|
||||
))
|
||||
// case (ForDirection.To | ForDirection.ParallelTo, _, Some(NumericConstant(n, _))) if n > 0 && n < 255 =>
|
||||
@ -265,15 +272,12 @@ abstract class AbstractStatementCompiler[T <: AbstractCode] {
|
||||
val endMinusOne = (f.end #-# 1).pos(p)
|
||||
compile(ctx, List(
|
||||
Assignment(vex, f.start).pos(p),
|
||||
IfStatement(
|
||||
FunctionCallExpression(">=", List(vex, f.end)).pos(p),
|
||||
List(DoWhileStatement(
|
||||
f.body,
|
||||
List(decrement),
|
||||
FunctionCallExpression("!=", List(vex, endMinusOne)).pos(p),
|
||||
names
|
||||
).pos(p)),
|
||||
Nil)
|
||||
DoWhileStatement(
|
||||
f.body,
|
||||
List(decrement),
|
||||
FunctionCallExpression("!=", List(vex, endMinusOne)).pos(p),
|
||||
names
|
||||
).pos(p)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +250,7 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
|
||||
}
|
||||
}
|
||||
|
||||
def optimizeExpr(expr: Expression, currentVarValues: VV): Expression = {
|
||||
def optimizeExpr(expr: Expression, currentVarValues: VV, optimizeSum: Boolean = false): Expression = {
|
||||
val pos = expr.position
|
||||
// stdlib:
|
||||
if (optimizeStdlib) {
|
||||
@ -360,9 +360,13 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
|
||||
case DerefExpression(inner, 0, _) =>
|
||||
optimizeExpr(inner, currentVarValues).pos(pos)
|
||||
case DerefExpression(inner, offset, targetType) =>
|
||||
("pointer." + targetType.name) <| (
|
||||
("pointer" <| optimizeExpr(inner, currentVarValues).pos(pos)) #+# LiteralExpression(offset, 2)
|
||||
)
|
||||
if (offset == 0) {
|
||||
("pointer." + targetType.name) <| ("pointer" <| optimizeExpr(inner, currentVarValues).pos(pos))
|
||||
} else {
|
||||
("pointer." + targetType.name) <| (
|
||||
("pointer" <| optimizeExpr(inner, currentVarValues).pos(pos)) #+# LiteralExpression(offset, 2)
|
||||
)
|
||||
}
|
||||
case IndexedExpression(name, index) =>
|
||||
ctx.log.fatal("Oops!")
|
||||
case _ =>
|
||||
@ -392,26 +396,43 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
|
||||
ok = false
|
||||
LiteralExpression(0, 1)
|
||||
} else {
|
||||
val inner = optimizeExpr(result, currentVarValues).pos(pos)
|
||||
val inner = optimizeExpr(result, currentVarValues, optimizeSum = true).pos(pos)
|
||||
val fieldOffset = subvariables.head._2
|
||||
val fieldType = subvariables.head._3
|
||||
pointerWrap match {
|
||||
case 0 =>
|
||||
DerefExpression(inner, fieldOffset, fieldType)
|
||||
case 1 =>
|
||||
("pointer." + fieldType.name) <| (
|
||||
("pointer" <| inner) #+# LiteralExpression(fieldOffset, 2)
|
||||
)
|
||||
if (fieldOffset == 0) {
|
||||
("pointer." + fieldType.name) <| ("pointer" <| inner)
|
||||
} else {
|
||||
("pointer." + fieldType.name) <| (
|
||||
("pointer" <| inner) #+# LiteralExpression(fieldOffset, 2)
|
||||
)
|
||||
}
|
||||
case 2 =>
|
||||
("pointer" <| inner) #+# LiteralExpression(fieldOffset, 2)
|
||||
case 10 =>
|
||||
"lo" <| (
|
||||
if (fieldOffset == 0) {
|
||||
"pointer" <| inner
|
||||
} else {
|
||||
("pointer" <| inner) #+# LiteralExpression(fieldOffset, 2)
|
||||
)
|
||||
}
|
||||
case 10 =>
|
||||
if (fieldOffset == 0) {
|
||||
"lo" <| ("pointer" <| inner)
|
||||
} else {
|
||||
"lo" <| (
|
||||
("pointer" <| inner) #+# LiteralExpression(fieldOffset, 2)
|
||||
)
|
||||
}
|
||||
case 11 =>
|
||||
"hi" <| (
|
||||
("pointer" <| inner) #+# LiteralExpression(fieldOffset, 2)
|
||||
)
|
||||
if (fieldOffset == 0) {
|
||||
"hi" <| ("pointer" <| inner)
|
||||
} else {
|
||||
"hi" <| (
|
||||
("pointer" <| inner) #+# LiteralExpression(fieldOffset, 2)
|
||||
)
|
||||
}
|
||||
|
||||
case _ => throw new IllegalStateException
|
||||
}
|
||||
}
|
||||
@ -429,9 +450,9 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
|
||||
}
|
||||
result
|
||||
case DerefDebuggingExpression(inner, 1) =>
|
||||
DerefExpression(optimizeExpr(inner, currentVarValues), 0, env.get[VariableType]("byte")).pos(pos)
|
||||
DerefExpression(optimizeExpr(inner, currentVarValues, optimizeSum = true), 0, env.get[VariableType]("byte")).pos(pos)
|
||||
case DerefDebuggingExpression(inner, 2) =>
|
||||
DerefExpression(optimizeExpr(inner, currentVarValues), 0, env.get[VariableType]("word")).pos(pos)
|
||||
DerefExpression(optimizeExpr(inner, currentVarValues, optimizeSum = true), 0, env.get[VariableType]("word")).pos(pos)
|
||||
case e@TextLiteralExpression(characters) =>
|
||||
val name = ctx.env.getTextLiteralArrayName(e)
|
||||
VariableExpression(name).pos(pos)
|
||||
@ -447,6 +468,18 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
|
||||
if optimize && pointlessCast(t1, arg) =>
|
||||
ctx.log.debug(s"Pointless cast $t1(...)", pos)
|
||||
optimizeExpr(arg, currentVarValues)
|
||||
case FunctionCallExpression(op@("<<" | "<<'"), List(l, r)) =>
|
||||
env.eval(l) match {
|
||||
case Some(c) if c.isProvablyZero =>
|
||||
env.eval(r) match {
|
||||
case Some(rc) =>
|
||||
LiteralExpression(0, c.requiredSize)
|
||||
case _ =>
|
||||
FunctionCallExpression(op, List(optimizeExpr(l, currentVarValues), optimizeExpr(r, currentVarValues)))
|
||||
}
|
||||
case _ =>
|
||||
FunctionCallExpression(op, List(optimizeExpr(l, currentVarValues), optimizeExpr(r, currentVarValues)))
|
||||
}
|
||||
case FunctionCallExpression("nonet", args) =>
|
||||
// Eliminating variables may eliminate carry
|
||||
FunctionCallExpression("nonet", args.map(arg => optimizeExpr(arg, Map()))).pos(pos)
|
||||
@ -457,6 +490,12 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
|
||||
case _ =>
|
||||
FunctionCallExpression(name, args.map(arg => optimizeExpr(arg, currentVarValues))).pos(pos)
|
||||
}
|
||||
case SumExpression(expressions, false) if optimizeSum =>
|
||||
SumExpression(expressions.map{
|
||||
case (minus, arg) => minus -> optimizeExpr(arg, currentVarValues)
|
||||
}.filterNot{
|
||||
case (_, e) => env.eval(e).exists(_.isProvablyZero)
|
||||
}, decimal = false)
|
||||
case SumExpression(expressions, decimal) =>
|
||||
// don't collapse additions, let the later stages deal with it
|
||||
// expecially important when inside a nonet operation
|
||||
@ -467,31 +506,50 @@ abstract class AbstractStatementPreprocessor(protected val ctx: CompilationConte
|
||||
targetType.size match {
|
||||
case 1 => IndexedExpression(name, optimizeExpr(index, Map())).pos(pos)
|
||||
case _ =>
|
||||
val arraySizeInBytes = pointy match {
|
||||
case p:ConstantPointy => p.sizeInBytes
|
||||
val constantOffset: Option[Long] = env.eval(index) match {
|
||||
case Some(z) if z.isProvablyZero => Some(0L)
|
||||
case Some(NumericConstant(n, _)) =>
|
||||
if (targetType.size * (n+1) <= 256) Some(targetType.size * n) else None
|
||||
case _ => None
|
||||
}
|
||||
val scaledIndex = arraySizeInBytes match {
|
||||
case Some(n) if n <= 256 => targetType.size match {
|
||||
case 1 => "byte" <| index
|
||||
case 2 => "<<" <| ("byte" <| index, LiteralExpression(1, 1))
|
||||
case 4 => "<<" <| ("byte" <| index, LiteralExpression(2, 1))
|
||||
case 8 => "<<" <| ("byte" <| index, LiteralExpression(3, 1))
|
||||
case _ => "*" <| ("byte" <| index, LiteralExpression(targetType.size, 1))
|
||||
}
|
||||
case Some(n) if n <= 512 && targetType.size == 2 =>
|
||||
"nonet" <| ("<<" <| ("byte" <| index, LiteralExpression(1, 1)))
|
||||
case _ => targetType.size match {
|
||||
case 1 => "word" <| index
|
||||
case 2 => "<<" <| ("word" <| index, LiteralExpression(1, 1))
|
||||
case 4 => "<<" <| ("word" <| index, LiteralExpression(2, 1))
|
||||
case 8 => "<<" <| ("word" <| index, LiteralExpression(3, 1))
|
||||
case _ => "*" <| ("word" <| index, LiteralExpression(targetType.size, 1))
|
||||
}
|
||||
constantOffset match {
|
||||
case Some(o) if o >= 0 && o <= 256 - targetType.size =>
|
||||
if (pointy.isArray) {
|
||||
DerefExpression(
|
||||
"pointer" <| VariableExpression(name).pos(pos),
|
||||
o.toInt, pointy.elementType).pos(pos)
|
||||
} else {
|
||||
DerefExpression(
|
||||
VariableExpression(name).pos(pos),
|
||||
o.toInt, pointy.elementType).pos(pos)
|
||||
}
|
||||
case _ =>
|
||||
val arraySizeInBytes = pointy match {
|
||||
case p: ConstantPointy => p.sizeInBytes
|
||||
case _ => None
|
||||
}
|
||||
val scaledIndex = arraySizeInBytes match {
|
||||
case Some(n) if n <= 256 => targetType.size match {
|
||||
case 1 => "byte" <| index
|
||||
case 2 => "<<" <| ("byte" <| index, LiteralExpression(1, 1))
|
||||
case 4 => "<<" <| ("byte" <| index, LiteralExpression(2, 1))
|
||||
case 8 => "<<" <| ("byte" <| index, LiteralExpression(3, 1))
|
||||
case _ => "*" <| ("byte" <| index, LiteralExpression(targetType.size, 1))
|
||||
}
|
||||
case Some(n) if n <= 512 && targetType.size == 2 =>
|
||||
"nonet" <| ("<<" <| ("byte" <| index, LiteralExpression(1, 1)))
|
||||
case _ => targetType.size match {
|
||||
case 1 => "word" <| index
|
||||
case 2 => "<<" <| ("word" <| index, LiteralExpression(1, 1))
|
||||
case 4 => "<<" <| ("word" <| index, LiteralExpression(2, 1))
|
||||
case 8 => "<<" <| ("word" <| index, LiteralExpression(3, 1))
|
||||
case _ => "*" <| ("word" <| index, LiteralExpression(targetType.size, 1))
|
||||
}
|
||||
}
|
||||
DerefExpression(
|
||||
("pointer" <| VariableExpression(name).pos(pos)) #+# optimizeExpr(scaledIndex, Map()),
|
||||
0, pointy.elementType).pos(pos)
|
||||
}
|
||||
DerefExpression(
|
||||
("pointer" <| VariableExpression(name).pos(pos)) #+# optimizeExpr(scaledIndex, Map()),
|
||||
0, pointy.elementType).pos(pos)
|
||||
}
|
||||
case _ => expr // TODO
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package millfork.compiler.m6809
|
||||
import millfork.assembly.BranchingOpcodeMapping
|
||||
import millfork.assembly.m6809.{MLine, NonExistent}
|
||||
import millfork.compiler.{AbstractCompiler, AbstractExpressionCompiler, AbstractStatementCompiler, BranchSpec, CompilationContext}
|
||||
import millfork.node.{Assignment, BreakStatement, ContinueStatement, DoWhileStatement, ExecutableStatement, Expression, ExpressionStatement, ForEachStatement, ForStatement, IfStatement, M6809AssemblyStatement, ReturnDispatchStatement, ReturnStatement, VariableExpression, WhileStatement}
|
||||
import millfork.node.{Assignment, BlackHoleExpression, BreakStatement, ContinueStatement, DoWhileStatement, ExecutableStatement, Expression, ExpressionStatement, ForEachStatement, ForStatement, IfStatement, M6809AssemblyStatement, ReturnDispatchStatement, ReturnStatement, VariableExpression, WhileStatement}
|
||||
import millfork.assembly.m6809.MOpcode._
|
||||
import millfork.env.{FatBooleanType, Label, ThingInMemory}
|
||||
|
||||
@ -43,6 +43,7 @@ object M6809StatementCompiler extends AbstractStatementCompiler[MLine] {
|
||||
???
|
||||
}
|
||||
case Assignment(destination, source) =>
|
||||
if (destination == BlackHoleExpression) return M6809ExpressionCompiler.compile(ctx, source, MExpressionTarget.NOTHING, BranchSpec.None) -> Nil
|
||||
val destinationType = AbstractExpressionCompiler.getExpressionType(ctx, destination)
|
||||
AbstractExpressionCompiler.checkAssignmentType(ctx, source, destinationType)
|
||||
destinationType.size match {
|
||||
|
@ -724,7 +724,7 @@ object BuiltIns {
|
||||
val rc = MosExpressionCompiler.compileToAX(ctx, rhs)
|
||||
(lc, rc, effectiveComparisonType) match {
|
||||
case (
|
||||
List(lcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), lch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage, _)),
|
||||
List(lcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), lch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage | LongAbsolute, _)),
|
||||
_,
|
||||
ComparisonType.NotEqual
|
||||
) =>
|
||||
@ -735,7 +735,7 @@ object BuiltIns {
|
||||
AssemblyLine.relative(BNE, Label(x)))
|
||||
case (
|
||||
_,
|
||||
List(rcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), rch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage, _)),
|
||||
List(rcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), rch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage | LongAbsolute, _)),
|
||||
ComparisonType.NotEqual
|
||||
) =>
|
||||
return lc ++ List(
|
||||
@ -744,7 +744,7 @@ object BuiltIns {
|
||||
rch.copy(opcode = CPX),
|
||||
AssemblyLine.relative(BNE, Label(x)))
|
||||
case (
|
||||
List(lcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), lch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage, _)),
|
||||
List(lcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), lch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage | LongAbsolute, _)),
|
||||
_,
|
||||
ComparisonType.Equal
|
||||
) =>
|
||||
@ -757,7 +757,7 @@ object BuiltIns {
|
||||
AssemblyLine.label(skip))
|
||||
case (
|
||||
_,
|
||||
List(rcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), rch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage, _)),
|
||||
List(rcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), rch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage | LongAbsolute, _)),
|
||||
ComparisonType.Equal
|
||||
) =>
|
||||
val skip = ctx.nextLabel("cp")
|
||||
@ -767,6 +767,42 @@ object BuiltIns {
|
||||
rch.copy(opcode = CPX),
|
||||
AssemblyLine.relative(BEQ, Label(x)),
|
||||
AssemblyLine.label(skip))
|
||||
case (
|
||||
List(lcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), lch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage | LongAbsolute, _)),
|
||||
List(rcl@AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), rch@AssemblyLine0(LDX, Absolute | Immediate | ZeroPage | LongAbsolute, _)),
|
||||
_
|
||||
) =>
|
||||
(Nil,
|
||||
List(lch.copy(opcode = CMP)),
|
||||
List(lcl.copy(opcode = CMP)),
|
||||
List(rch.copy(opcode = CMP)),
|
||||
List(rcl.copy(opcode = CMP)))
|
||||
case (
|
||||
_,
|
||||
List(AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), AssemblyLine0(LDX, Absolute | Immediate | ZeroPage | LongAbsolute, _)),
|
||||
_
|
||||
) =>
|
||||
if (ctx.options.zpRegisterSize < 2) {
|
||||
ctx.log.error("Too complex expressions in comparison", lhs.position.orElse(rhs.position))
|
||||
(Nil, Nil, Nil, Nil, Nil)
|
||||
} else {
|
||||
val reg = ctx.env.get[ThingInMemory]("__reg.loword")
|
||||
return lc ++ List(AssemblyLine.zeropage(STA, reg), AssemblyLine.zeropage(STX, reg, 1)) ++ compileWordComparison(
|
||||
ctx, effectiveComparisonType, VariableExpression("__reg.loword").pos(lhs.position), rhs, BranchIfTrue(x))
|
||||
}
|
||||
case (
|
||||
List(AssemblyLine0(LDA, Absolute | Immediate | ZeroPage | LongAbsolute, _), AssemblyLine0(LDX, Absolute | Immediate | ZeroPage | LongAbsolute, _)),
|
||||
_,
|
||||
_
|
||||
) =>
|
||||
if (ctx.options.zpRegisterSize < 2) {
|
||||
ctx.log.error("Too complex expressions in comparison", lhs.position.orElse(rhs.position))
|
||||
(Nil, Nil, Nil, Nil, Nil)
|
||||
} else {
|
||||
val reg = ctx.env.get[ThingInMemory]("__reg.loword")
|
||||
return rc ++ List(AssemblyLine.zeropage(STA, reg), AssemblyLine.zeropage(STX, reg, 1)) ++ compileWordComparison(
|
||||
ctx, effectiveComparisonType, lhs, VariableExpression("__reg.loword").pos(rhs.position), BranchIfTrue(x))
|
||||
}
|
||||
case _ =>
|
||||
// TODO comparing expressions
|
||||
ctx.log.error("Too complex expressions in comparison", lhs.position.orElse(rhs.position))
|
||||
|
@ -408,7 +408,14 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
Nil
|
||||
}
|
||||
case DerefExpression(inner, offset, targetType) =>
|
||||
val (prepare, addr, am) = getPhysicalPointerForDeref(ctx, inner)
|
||||
val (prepare, addr, am, fast) = getPhysicalPointerForDeref(ctx, inner)
|
||||
if (targetType.size == 1) {
|
||||
fast match {
|
||||
case Some((fastBase, fastIndex)) =>
|
||||
return preserveRegisterIfNeeded(ctx, MosRegister.A, fastIndex(offset)) ++ List(AssemblyLine.absoluteY(STA, fastBase))
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
val lo = preserveRegisterIfNeeded(ctx, MosRegister.A, prepare) ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(STA, am, addr))
|
||||
if (targetType.size == 1) {
|
||||
lo
|
||||
@ -523,17 +530,42 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
compile(ctx, expr, Some(p -> env.get[Variable]("__reg.b2b3")), BranchSpec.None)
|
||||
}
|
||||
|
||||
def getPhysicalPointerForDeref(ctx: CompilationContext, pointerExpression: Expression): (List[AssemblyLine], Constant, AddrMode.Value) = {
|
||||
def getPhysicalPointerForDeref(ctx: CompilationContext, pointerExpression: Expression): (List[AssemblyLine], Constant, AddrMode.Value, Option[(Constant, Int => List[AssemblyLine])]) = {
|
||||
pointerExpression match {
|
||||
case VariableExpression(name) =>
|
||||
val p = ctx.env.get[ThingInMemory](name)
|
||||
if (p.isInstanceOf[MfArray]) return (Nil, p.toAddress, AddrMode.AbsoluteY)
|
||||
if (p.zeropage) return (Nil, p.toAddress, AddrMode.IndexedY)
|
||||
p match {
|
||||
case array: MfArray => return (Nil, p.toAddress, AddrMode.AbsoluteY,
|
||||
if (array.sizeInBytes <= 256) Some(p.toAddress, (i: Int) => List(AssemblyLine.immediate(LDY, i))) else None)
|
||||
case _ =>
|
||||
}
|
||||
if (p.zeropage) return (Nil, p.toAddress, AddrMode.IndexedY, None)
|
||||
case _ =>
|
||||
}
|
||||
ctx.env.eval(pointerExpression) match {
|
||||
case Some(addr) => (Nil, addr, AddrMode.AbsoluteY)
|
||||
case _ => (compileToZReg(ctx, pointerExpression), ctx.env.get[ThingInMemory]("__reg.loword").toAddress, AddrMode.IndexedY)
|
||||
case Some(addr) => (Nil, addr, AddrMode.AbsoluteY, None)
|
||||
case _ =>
|
||||
val baseAndIndex: Option[(Expression, Expression)] = pointerExpression match {
|
||||
case SumExpression(List((false, base), (false, index)), false) => Some(base -> index)
|
||||
case FunctionCallExpression(pointerType, List(SumExpression(List((false, base), (false, index)), false))) if pointerType.startsWith("pointer.") =>
|
||||
Some(base -> index)
|
||||
case _ => None
|
||||
}
|
||||
(compileToZReg(ctx, pointerExpression), ctx.env.get[ThingInMemory]("__reg.loword").toAddress, AddrMode.IndexedY, baseAndIndex match {
|
||||
case Some((base, index)) =>
|
||||
val itype = AbstractExpressionCompiler.getExpressionType(ctx, index)
|
||||
if (itype.size != 1 || itype.isSigned) {
|
||||
None
|
||||
} else {
|
||||
ctx.env.eval(base).map { baseConst =>
|
||||
baseConst -> { (i: Int) =>
|
||||
val b = ctx.env.get[Type]("byte")
|
||||
compile(ctx, index #+# i, Some(b -> RegisterVariable(MosRegister.Y, b)), BranchSpec.None)
|
||||
}
|
||||
}
|
||||
}
|
||||
case _ => None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1035,19 +1067,27 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
case DerefExpression(inner, offset, targetType) =>
|
||||
val (prepare, addr, am) = getPhysicalPointerForDeref(ctx, inner)
|
||||
(targetType.size, am) match {
|
||||
case (1, AbsoluteY) =>
|
||||
val (prepare, addr, am, fast) = getPhysicalPointerForDeref(ctx, inner)
|
||||
(fast, targetType.size, am) match {
|
||||
case (Some((fastBase, fastIndex)), 1, _) =>
|
||||
fastIndex(offset) ++ List(AssemblyLine.absoluteY(LDA, fastBase)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
|
||||
case (_, 1, AbsoluteY) =>
|
||||
prepare ++ List(AssemblyLine.absolute(LDA, addr + offset)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
|
||||
case (1, _) =>
|
||||
case (_, 1, _) =>
|
||||
prepare ++ List(AssemblyLine.immediate(LDY, offset), AssemblyLine(LDA, am, addr)) ++ expressionStorageFromA(ctx, exprTypeAndVariable, expr.position, exprType.isSigned)
|
||||
case (2, AbsoluteY) =>
|
||||
case (Some((fastBase, fastIndex)), 2, _) =>
|
||||
fastIndex(offset) ++ List(
|
||||
AssemblyLine.absoluteY(LDA, fastBase),
|
||||
AssemblyLine.implied(INY),
|
||||
AssemblyLine.absoluteY(LDX, fastBase)) ++
|
||||
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
|
||||
case (_, 2, AbsoluteY) =>
|
||||
prepare ++
|
||||
List(
|
||||
AssemblyLine.absolute(LDA, addr + offset),
|
||||
AssemblyLine.absolute(LDX, addr + offset + 1)) ++
|
||||
expressionStorageFromAX(ctx, exprTypeAndVariable, expr.position)
|
||||
case (2, _) =>
|
||||
case (_, 2, _) =>
|
||||
prepare ++
|
||||
List(
|
||||
AssemblyLine.immediate(LDY, offset+1),
|
||||
@ -2038,6 +2078,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
def compileAssignment(ctx: CompilationContext, source: Expression, target: LhsExpression): List[AssemblyLine] = {
|
||||
val env = ctx.env
|
||||
val sourceType = AbstractExpressionCompiler.checkAssignmentTypeAndGetSourceType(ctx, source, target)
|
||||
if (target == BlackHoleExpression) return compile(ctx, source, None, NoBranching)
|
||||
val lhsType = AbstractExpressionCompiler.getExpressionType(ctx, target)
|
||||
val b = env.get[Type]("byte")
|
||||
val w = env.get[Type]("word")
|
||||
@ -2057,7 +2098,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
ctx.log.error("Invalid left-hand-side use of `:`")
|
||||
Nil
|
||||
case DerefExpression(inner, offset, targetType) =>
|
||||
val (prepare, addr, am) = getPhysicalPointerForDeref(ctx, inner)
|
||||
val (prepare, addr, am, fastTarget) = getPhysicalPointerForDeref(ctx, inner)
|
||||
env.eval(source) match {
|
||||
case Some(constant) =>
|
||||
am match {
|
||||
@ -2103,7 +2144,7 @@ object MosExpressionCompiler extends AbstractExpressionCompiler[AssemblyLine] {
|
||||
Nil
|
||||
}
|
||||
case DerefExpression(innerSource, sourceOffset, _) =>
|
||||
val (prepareSource, addrSource, amSource) = getPhysicalPointerForDeref(ctx, innerSource)
|
||||
val (prepareSource, addrSource, amSource, fastSource) = getPhysicalPointerForDeref(ctx, innerSource)
|
||||
(am, amSource) match {
|
||||
case (AbsoluteY, AbsoluteY) =>
|
||||
prepare ++ prepareSource ++ (0 until targetType.size).flatMap { i =>
|
||||
|
@ -192,14 +192,21 @@ object Z80Comparisons {
|
||||
case _ =>
|
||||
}
|
||||
|
||||
val (calculated, useBC) = if (calculateLeft.exists(Z80ExpressionCompiler.changesBC)) {
|
||||
if (calculateLeft.exists(Z80ExpressionCompiler.changesDE)) {
|
||||
calculateRight ++ List(ZLine.register(ZOpcode.PUSH, ZRegister.HL)) ++ Z80ExpressionCompiler.fixTsx(ctx, calculateLeft) ++ List(ZLine.register(ZOpcode.POP, ZRegister.BC)) -> false
|
||||
} else {
|
||||
calculateRight ++ List(ZLine.ld8(ZRegister.D, ZRegister.H), ZLine.ld8(ZRegister.E, ZRegister.L)) ++ calculateLeft -> false
|
||||
}
|
||||
} else {
|
||||
calculateRight ++ List(ZLine.ld8(ZRegister.B, ZRegister.H), ZLine.ld8(ZRegister.C, ZRegister.L)) ++ calculateLeft -> true
|
||||
val (calculated, useBC) = calculateRight match {
|
||||
case List(ZLine0(LD_16, TwoRegisters(ZRegister.HL, ZRegister.IMM_16), c)) =>
|
||||
(calculateLeft :+ ZLine.ldImm16(ZRegister.BC, c)) -> true
|
||||
case List(ZLine0(LD_16, TwoRegisters(ZRegister.HL, ZRegister.MEM_ABS_16), c)) if ctx.options.flag(CompilationFlag.EmitZ80Opcodes) =>
|
||||
(calculateLeft :+ ZLine.ldAbs16(ZRegister.BC, c)) -> true
|
||||
case _ =>
|
||||
if (calculateLeft.exists(Z80ExpressionCompiler.changesBC)) {
|
||||
if (calculateLeft.exists(Z80ExpressionCompiler.changesDE)) {
|
||||
calculateRight ++ List(ZLine.register(ZOpcode.PUSH, ZRegister.HL)) ++ Z80ExpressionCompiler.fixTsx(ctx, calculateLeft) ++ List(ZLine.register(ZOpcode.POP, ZRegister.BC)) -> true
|
||||
} else {
|
||||
calculateRight ++ List(ZLine.ld8(ZRegister.D, ZRegister.H), ZLine.ld8(ZRegister.E, ZRegister.L)) ++ calculateLeft -> false
|
||||
}
|
||||
} else {
|
||||
calculateRight ++ List(ZLine.ld8(ZRegister.B, ZRegister.H), ZLine.ld8(ZRegister.C, ZRegister.L)) ++ calculateLeft -> true
|
||||
}
|
||||
}
|
||||
val (effectiveCompType, label) = branches match {
|
||||
case BranchIfFalse(la) => ComparisonType.negate(compType) -> la
|
||||
|
@ -94,6 +94,7 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
|
||||
case s: LabelStatement =>
|
||||
List(ZLine.label(env.prefix + s.name)) -> Nil
|
||||
case Assignment(destination, source) =>
|
||||
if (destination == BlackHoleExpression) return Z80ExpressionCompiler.compile(ctx, source, ZExpressionTarget.NOTHING, NoBranching) -> Nil
|
||||
val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, source)
|
||||
val targetType = AbstractExpressionCompiler.getExpressionType(ctx, destination)
|
||||
AbstractExpressionCompiler.checkAssignmentType(ctx, source, targetType)
|
||||
|
7
src/main/scala/millfork/env/Constant.scala
vendored
7
src/main/scala/millfork/env/Constant.scala
vendored
@ -394,7 +394,7 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
|
||||
case MathOperator.Or | MathOperator.Exor | MathOperator.Plus | MathOperator.Minus =>
|
||||
lhs.isProvablyDivisibleBy256 && rhs.isProvablyDivisibleBy256
|
||||
case MathOperator.Shl =>
|
||||
rhs.isProvablyGreaterOrEqualThan(NumericConstant(8, 1))
|
||||
rhs.isProvablyGreaterOrEqualThan(NumericConstant(8, 1)) || lhs.isProvablyDivisibleBy256
|
||||
case _ => false
|
||||
}
|
||||
|
||||
@ -454,6 +454,11 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co
|
||||
case MathOperator.Modulo => Constant.Zero
|
||||
case _ => CompoundConstant(operator, l, r)
|
||||
}
|
||||
case (NumericConstant(0, _), c) =>
|
||||
operator match {
|
||||
case MathOperator.Shl => l
|
||||
case _ => CompoundConstant(operator, l, r)
|
||||
}
|
||||
case (c, NumericConstant(0, 1)) =>
|
||||
operator match {
|
||||
case MathOperator.Plus => c
|
||||
|
@ -119,6 +119,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
if (forZpOnly && !options.platform.hasZeroPage) {
|
||||
return
|
||||
}
|
||||
if (nf.exists(_.name.endsWith(".trampoline"))) {
|
||||
return
|
||||
}
|
||||
if (log.traceEnabled) log.trace("Allocating variables in " + nf.map(f => "function " + f.name).getOrElse("global scope"))
|
||||
val b = get[Type]("byte")
|
||||
val p = get[Type]("pointer")
|
||||
val params = nf.fold(List[String]()) { f =>
|
||||
@ -146,6 +150,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
} else 3
|
||||
val toAdd = things.values.flatMap {
|
||||
case m: UninitializedMemory if passForAlloc(m.alloc) == pass && nf.isDefined == isLocalVariableName(m.name) && !m.name.endsWith(".addr") && maybeGet[Thing](m.name + ".array").isEmpty =>
|
||||
if (log.traceEnabled) log.trace("Allocating " + m.name)
|
||||
val vertex = if (options.flag(CompilationFlag.VariableOverlap)) {
|
||||
nf.fold[VariableVertex](GlobalVertex) { f =>
|
||||
if (m.alloc == VariableAllocationMethod.Static) {
|
||||
|
7
src/main/scala/millfork/env/Pointy.scala
vendored
7
src/main/scala/millfork/env/Pointy.scala
vendored
@ -7,16 +7,19 @@ trait Pointy {
|
||||
def indexType: VariableType
|
||||
def elementType: VariableType
|
||||
def readOnly: Boolean
|
||||
def isArray: Boolean
|
||||
}
|
||||
|
||||
case class StackVariablePointy(offset: Int, indexType: VariableType, elementType: VariableType) extends Pointy {
|
||||
override def name: Option[String] = None
|
||||
override def readOnly: Boolean = false
|
||||
override def isArray: Boolean = false
|
||||
}
|
||||
|
||||
case class VariablePointy(addr: Constant, indexType: VariableType, elementType: VariableType, zeropage: Boolean) extends Pointy {
|
||||
override def name: Option[String] = None
|
||||
override def readOnly: Boolean = false
|
||||
override def isArray: Boolean = false
|
||||
}
|
||||
|
||||
case class ConstantPointy(value: Constant,
|
||||
@ -26,4 +29,6 @@ case class ConstantPointy(value: Constant,
|
||||
indexType: VariableType,
|
||||
elementType: VariableType,
|
||||
alignment: MemoryAlignment,
|
||||
override val readOnly: Boolean) extends Pointy
|
||||
override val readOnly: Boolean) extends Pointy {
|
||||
override def isArray: Boolean = elementCount.isDefined
|
||||
}
|
||||
|
@ -182,6 +182,12 @@ class StandardCallGraph(program: Program, log: Logger) extends CallGraph(program
|
||||
if (multiaccessibleFunctions(a.function) || multiaccessibleFunctions(b.function)) {
|
||||
return false
|
||||
}
|
||||
if (a.function + ".trampoline" == b.function) {
|
||||
return false
|
||||
}
|
||||
if (a.function == b.function + ".trampoline") {
|
||||
return false
|
||||
}
|
||||
if (callEdges(a.function -> b.function) || callEdges(b.function -> a.function)) {
|
||||
return false
|
||||
}
|
||||
|
@ -39,14 +39,14 @@ sealed trait Expression extends Node {
|
||||
def isPure: Boolean
|
||||
def getAllIdentifiers: Set[String]
|
||||
|
||||
def #+#(smallInt: Int): Expression = (this #+# LiteralExpression(smallInt, 1).pos(this.position)).pos(this.position)
|
||||
def #+#(smallInt: Int): Expression = if (smallInt == 0) this else (this #+# LiteralExpression(smallInt, 1).pos(this.position)).pos(this.position)
|
||||
def #+#(that: Expression): Expression = that match {
|
||||
case SumExpression(params, false) => SumExpression((false -> this) :: params, decimal = false)
|
||||
case _ => SumExpression(List(false -> this, false -> that), decimal = false)
|
||||
}
|
||||
def #*#(smallInt: Int): Expression =
|
||||
if (smallInt == 1) this else FunctionCallExpression("*", List(this, LiteralExpression(smallInt, 1).pos(this.position))).pos(this.position)
|
||||
def #-#(smallInt: Int): Expression = (this #-# LiteralExpression(smallInt, 1).pos(this.position)).pos(this.position)
|
||||
def #-#(smallInt: Int): Expression = if (smallInt == 0) this else (this #-# LiteralExpression(smallInt, 1).pos(this.position)).pos(this.position)
|
||||
def #-#(that: Expression): Expression = SumExpression(List(false -> this, true -> that), decimal = false)
|
||||
|
||||
@transient var typeCache: Type = _
|
||||
|
@ -79,29 +79,31 @@ object UnusedGlobalVariables extends NodeOptimization {
|
||||
case x => Some(ExpressionStatement(x).pos(s.position))
|
||||
}
|
||||
} else Some(s)
|
||||
case s@Assignment(VariableExpression(n), VariableExpression(_)) =>
|
||||
if (globalsToRemove(n)) Nil else Some(s)
|
||||
case s@Assignment(VariableExpression(n), expr@VariableExpression(n2)) =>
|
||||
if (globalsToRemove(extractThingName(n))) {
|
||||
if (globalsToRemove(extractThingName(n))) None else Some(Assignment(BlackHoleExpression, expr).pos(s.position))
|
||||
} else Some(s)
|
||||
case s@Assignment(VariableExpression(n), LiteralExpression(_, _)) =>
|
||||
if (globalsToRemove(n)) Nil else Some(s)
|
||||
if (globalsToRemove(extractThingName(n))) Nil else Some(s)
|
||||
case s@Assignment(VariableExpression(n), expr) =>
|
||||
if (globalsToRemove(n)) Some(ExpressionStatement(expr).pos(s.position)) else Some(s)
|
||||
if (globalsToRemove(extractThingName(n))) Some(ExpressionStatement(expr).pos(s.position)) else Some(s)
|
||||
case s@Assignment(SeparateBytesExpression(he@VariableExpression(h), le@VariableExpression(l)), expr) =>
|
||||
if (globalsToRemove(h)) {
|
||||
if (globalsToRemove(l))
|
||||
if (globalsToRemove(extractThingName(h))) {
|
||||
if (globalsToRemove(extractThingName(l)))
|
||||
Some(ExpressionStatement(expr).pos(s.position))
|
||||
else
|
||||
Some(Assignment(SeparateBytesExpression(BlackHoleExpression, le).pos(he.position), expr).pos(s.position))
|
||||
} else {
|
||||
if (globalsToRemove(l))
|
||||
if (globalsToRemove(extractThingName(l)))
|
||||
Some(Assignment(SeparateBytesExpression(he, BlackHoleExpression).pos(he.position), expr).pos(s.position))
|
||||
else
|
||||
Some(s)
|
||||
}
|
||||
case s@Assignment(SeparateBytesExpression(h, le@VariableExpression(l)), expr) =>
|
||||
if (globalsToRemove(l)) Some(Assignment(SeparateBytesExpression(h, BlackHoleExpression).pos(h.position), expr).pos(s.position))
|
||||
if (globalsToRemove(extractThingName(l))) Some(Assignment(SeparateBytesExpression(h, BlackHoleExpression).pos(h.position), expr).pos(s.position))
|
||||
else Some(s)
|
||||
case s@Assignment(SeparateBytesExpression(he@VariableExpression(h), l), expr) =>
|
||||
if (globalsToRemove(h)) Some(Assignment(SeparateBytesExpression(BlackHoleExpression, l).pos(he.position), expr).pos(s.position))
|
||||
if (globalsToRemove(extractThingName(h))) Some(Assignment(SeparateBytesExpression(BlackHoleExpression, l).pos(he.position), expr).pos(s.position))
|
||||
else Some(s)
|
||||
case s: IfStatement =>
|
||||
Some(s.copy(
|
||||
|
@ -81,8 +81,10 @@ object UnusedLocalVariables extends NodeOptimization {
|
||||
case x => Some(ExpressionStatement(x).pos(s.position))
|
||||
}
|
||||
} else Some(s)
|
||||
case s@Assignment(VariableExpression(n), VariableExpression(_)) =>
|
||||
if (localsToRemove(extractThingName(n))) Nil else Some(s)
|
||||
case s@Assignment(VariableExpression(n), expr@VariableExpression(_)) =>
|
||||
if (localsToRemove(extractThingName(n))) {
|
||||
if (localsToRemove(extractThingName(n))) None else Some(Assignment(BlackHoleExpression, expr).pos(s.position))
|
||||
} else Some(s)
|
||||
case s@Assignment(VariableExpression(n), LiteralExpression(_, _)) =>
|
||||
if (localsToRemove(extractThingName(n))) Nil else Some(s)
|
||||
case s@Assignment(VariableExpression(n), expr) =>
|
||||
|
@ -50,6 +50,52 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
|
||||
newPosition
|
||||
}
|
||||
|
||||
val comment: P[Unit] = P("//" ~/ CharsWhile(c => c != '\n' && c != '\r', min = 0) ~ ("\r\n" | "\r" | "\n"))
|
||||
|
||||
val semicolon: P[Unit] = P(";" ~/ CharsWhileIn("; \t", min = 0) ~/ position("line break after a semicolon").map(_ => ()) ~/ (comment | "\r\n" | "\r" | "\n").opaque("<line break>"))
|
||||
|
||||
val semicolonComment: P[Unit] = P(";" ~/ CharsWhile(c => c != '\n' && c != '\r' && c != '{' && c != '}', min = 0) ~/ position("line break instead of braces").map(_ => ()) ~/ ("\r\n" | "\r" | "\n").opaque("<line break>"))
|
||||
|
||||
val AWS: P[Unit] = P((CharIn(" \t\n\r") | NoCut(semicolon) | NoCut(comment)).rep(min = 0)).opaque("<any whitespace>")
|
||||
|
||||
val AWS_asm: P[Unit] = P((CharIn(" \t\n\r") | NoCut(semicolonComment) | NoCut(comment)).rep(min = 0)).opaque("<any whitespace>")
|
||||
|
||||
val EOL: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | semicolon | comment).opaque("<first line break>") ~ AWS).opaque("<line break>")
|
||||
|
||||
val EOL_asm: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | comment | semicolonComment).opaque("<first line break>") ~ AWS).opaque("<line break>")
|
||||
|
||||
val EOLOrComma: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | "," | semicolon | comment).opaque("<first line break or comma>") ~ AWS).opaque("<line break or comma>")
|
||||
|
||||
|
||||
val elidable: P[Elidability.Value] = (("!" | "?").! ~/ HWS).?.map{
|
||||
case Some("?") => Elidability.Elidable
|
||||
case Some("!") => Elidability.Volatile
|
||||
case _ => Elidability.Fixed
|
||||
}
|
||||
|
||||
val externFunctionBody: P[Option[List[Statement]]] = P("extern" ~/ PassWith(None))
|
||||
|
||||
val bankDeclaration: P[Option[String]] = ("segment" ~/ AWS ~/ "(" ~/ AWS ~/ identifier ~/ AWS ~/ ")" ~/ AWS).?
|
||||
|
||||
val breakStatement: P[Seq[ExecutableStatement]] = ("break" ~ !letterOrDigit ~/ HWS ~ identifier.?).map(l => Seq(BreakStatement(l.getOrElse(""))))
|
||||
|
||||
val continueStatement: P[Seq[ExecutableStatement]] = ("continue" ~ !letterOrDigit ~/ HWS ~ identifier.?).map(l => Seq(ContinueStatement(l.getOrElse(""))))
|
||||
|
||||
val importStatement: P[Seq[ImportStatement]] = ("import" ~ !letterOrDigit ~/ SWS ~/ identifier).map(x => Seq(ImportStatement(x)))
|
||||
|
||||
val forDirection: P[ForDirection.Value] =
|
||||
("parallel" ~ HWS ~ "to").!.map(_ => ForDirection.ParallelTo) |
|
||||
("parallel" ~ HWS ~ "until").!.map(_ => ForDirection.ParallelUntil) |
|
||||
"until".!.map(_ => ForDirection.Until) |
|
||||
"to".!.map(_ => ForDirection.To) |
|
||||
("down" ~/ HWS ~/ "to").!.map(_ => ForDirection.DownTo)
|
||||
|
||||
private def flags_(allowed: String*): P[Set[String]] = StringIn(allowed: _*).!.rep(min = 0, sep = SWS).map(_.toSet).opaque("<flags>")
|
||||
|
||||
val variableFlags: P[Set[String]] = flags_("const", "static", "volatile", "stack", "register")
|
||||
|
||||
val functionFlags: P[Set[String]] = flags_("asm", "inline", "interrupt", "macro", "noinline", "reentrant", "kernal_interrupt")
|
||||
|
||||
val codec: P[((TextCodec, Boolean), Boolean)] = P(position("text codec identifier") ~ identifier.?.map(_.getOrElse(""))).map {
|
||||
case (_, "" | "default") => (options.platform.defaultCodec -> false) -> options.flag(CompilationFlag.LenientTextEncoding)
|
||||
case (_, "z" | "defaultz") => (options.platform.defaultCodec -> true) -> options.flag(CompilationFlag.LenientTextEncoding)
|
||||
@ -117,7 +163,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
|
||||
p <- position()
|
||||
bank <- bankDeclaration
|
||||
flags <- variableFlags ~ HWS
|
||||
typ <- identifier ~ SWS
|
||||
typ <- identifier ~/ SWS
|
||||
vars <- singleVariableDefinition.rep(min = 1, sep = "," ~/ HWS)
|
||||
_ <- &(EOL) ~/ ""
|
||||
} yield {
|
||||
@ -585,26 +631,10 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
|
||||
|
||||
object MfParser {
|
||||
|
||||
val comment: P[Unit] = P("//" ~/ CharsWhile(c => c != '\n' && c != '\r', min = 0) ~ ("\r\n" | "\r" | "\n"))
|
||||
|
||||
val semicolon: P[Unit] = P(";" ~/ CharsWhileIn("; \t", min = 0) ~ (comment | "\r\n" | "\r" | "\n"))
|
||||
|
||||
val semicolonComment: P[Unit] = P(";" ~/ CharsWhile(c => c != '\n' && c != '\r' && c != '{' && c != '}', min = 0) ~ ("\r\n" | "\r" | "\n"))
|
||||
|
||||
val SWS: P[Unit] = P(CharsWhileIn(" \t", min = 1)).opaque("<horizontal whitespace>")
|
||||
|
||||
val HWS: P[Unit] = P(CharsWhileIn(" \t", min = 0)).opaque("<horizontal whitespace>")
|
||||
|
||||
val AWS: P[Unit] = P((CharIn(" \t\n\r") | NoCut(semicolon) | NoCut(comment)).rep(min = 0)).opaque("<any whitespace>")
|
||||
|
||||
val AWS_asm: P[Unit] = P((CharIn(" \t\n\r") | NoCut(semicolonComment) | NoCut(comment)).rep(min = 0)).opaque("<any whitespace>")
|
||||
|
||||
val EOL: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | semicolon | comment).opaque("<first line break>") ~ AWS).opaque("<line break>")
|
||||
|
||||
val EOL_asm: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | comment | semicolonComment).opaque("<first line break>") ~ AWS).opaque("<line break>")
|
||||
|
||||
val EOLOrComma: P[Unit] = P(HWS ~ ("\r\n" | "\r" | "\n" | "," | semicolon | comment).opaque("<first line break or comma>") ~ AWS).opaque("<line break or comma>")
|
||||
|
||||
val letter: P[String] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_").!)
|
||||
|
||||
val letterOrDigit: P[Unit] = P(CharIn("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_.$1234567890"))
|
||||
@ -714,33 +744,4 @@ object MfParser {
|
||||
val nonStatementLevel = 1 // everything but not `=`
|
||||
val mathLevel = 4 // the `:` operator
|
||||
|
||||
val elidable: P[Elidability.Value] = (("!" | "?").! ~/ HWS).?.map{
|
||||
case Some("?") => Elidability.Elidable
|
||||
case Some("!") => Elidability.Volatile
|
||||
case _ => Elidability.Fixed
|
||||
}
|
||||
|
||||
val externFunctionBody: P[Option[List[Statement]]] = P("extern" ~/ PassWith(None))
|
||||
|
||||
val bankDeclaration: P[Option[String]] = ("segment" ~/ AWS ~/ "(" ~/ AWS ~/ identifier ~/ AWS ~/ ")" ~/ AWS).?
|
||||
|
||||
val breakStatement: P[Seq[ExecutableStatement]] = ("break" ~ !letterOrDigit ~/ HWS ~ identifier.?).map(l => Seq(BreakStatement(l.getOrElse(""))))
|
||||
|
||||
val continueStatement: P[Seq[ExecutableStatement]] = ("continue" ~ !letterOrDigit ~/ HWS ~ identifier.?).map(l => Seq(ContinueStatement(l.getOrElse(""))))
|
||||
|
||||
val importStatement: P[Seq[ImportStatement]] = ("import" ~ !letterOrDigit ~/ SWS ~/ identifier).map(x => Seq(ImportStatement(x)))
|
||||
|
||||
val forDirection: P[ForDirection.Value] =
|
||||
("parallel" ~ HWS ~ "to").!.map(_ => ForDirection.ParallelTo) |
|
||||
("parallel" ~ HWS ~ "until").!.map(_ => ForDirection.ParallelUntil) |
|
||||
"until".!.map(_ => ForDirection.Until) |
|
||||
"to".!.map(_ => ForDirection.To) |
|
||||
("down" ~/ HWS ~/ "to").!.map(_ => ForDirection.DownTo)
|
||||
|
||||
private def flags_(allowed: String*): P[Set[String]] = StringIn(allowed: _*).!.rep(min = 0, sep = SWS).map(_.toSet).opaque("<flags>")
|
||||
|
||||
val variableFlags: P[Set[String]] = flags_("const", "static", "volatile", "stack", "register")
|
||||
|
||||
val functionFlags: P[Set[String]] = flags_("asm", "inline", "interrupt", "macro", "noinline", "reentrant", "kernal_interrupt")
|
||||
|
||||
}
|
||||
|
@ -610,4 +610,19 @@ class ArraySuite extends FunSuite with Matchers with AppendedClues {
|
||||
m.readLong(0xc024) should equal(0) withClue "b4..b7 of a[4] initted from stack int40"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
test("Fast small array indexing") {
|
||||
EmuBenchmarkRun(
|
||||
"""
|
||||
| array(word) words[10] @$c000
|
||||
| noinline byte getLo(byte i) = words[i].lo
|
||||
| noinline byte getHi(byte i) = words[i].hi
|
||||
| noinline word get(byte i) = words[i]
|
||||
| void main () {
|
||||
| get(5)
|
||||
| }
|
||||
""".stripMargin){ m =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -581,4 +581,52 @@ class ComparisonSuite extends FunSuite with Matchers {
|
||||
}
|
||||
}
|
||||
|
||||
test("Complex word comparisons") {
|
||||
val code =
|
||||
"""
|
||||
| byte output @$c000
|
||||
| struct st { word x }
|
||||
| st obj
|
||||
| noinline word f() = 400
|
||||
| void main() {
|
||||
| pointer.st p
|
||||
| p = obj.pointer
|
||||
| p->x = 400
|
||||
| output = 0
|
||||
|
|
||||
| if p->x == 400 { output += 1 } // ↑
|
||||
| if p->x != 400 { output -= 1 }
|
||||
| if p->x >= 400 { output += 1 } // ↑
|
||||
| if p->x <= 400 { output += 1 } // ↑
|
||||
| if p->x > 400 { output -= 1 }
|
||||
| if p->x < 400 { output -= 1 }
|
||||
|
|
||||
| if 400 == p->x { output += 1 } // ↑
|
||||
| if 400 != p->x { output -= 1 }
|
||||
| if 400 <= p->x { output += 1 } // ↑
|
||||
| if 400 >= p->x { output += 1 } // ↑
|
||||
| if 400 < p->x { output -= 1 }
|
||||
| if 400 > p->x { output -= 1 }
|
||||
|
|
||||
| if f() == 400 { output += 1 } // ↑
|
||||
| if f() != 400 { output -= 1 }
|
||||
| if f() >= 400 { output += 1 } // ↑
|
||||
| if f() <= 400 { output += 1 } // ↑
|
||||
| if f() > 400 { output -= 1 }
|
||||
| if f() < 400 { output -= 1 }
|
||||
|
|
||||
| if 400 == f() { output += 1 } // ↑
|
||||
| if 400 != f() { output -= 1 }
|
||||
| if 400 <= f() { output += 1 } // ↑
|
||||
| if 400 >= f() { output += 1 } // ↑
|
||||
| if 400 < f() { output -= 1 }
|
||||
| if 400 > f() { output -= 1 }
|
||||
|
|
||||
| }
|
||||
|""".stripMargin
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)(code) { m =>
|
||||
m.readByte(0xc000) should equal(code.count(_ == '↑'))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
163
src/test/scala/millfork/test/ForLoopSecondSuite.scala
Normal file
163
src/test/scala/millfork/test/ForLoopSecondSuite.scala
Normal file
@ -0,0 +1,163 @@
|
||||
package millfork.test
|
||||
|
||||
import millfork.Cpu
|
||||
import millfork.test.emu.EmuUnoptimizedCrossPlatformRun
|
||||
import org.scalactic.Prettifier
|
||||
import org.scalatest.{AppendedClues, FunSuite, Matchers}
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
class ForLoopSecondSuite extends FunSuite with Matchers with AppendedClues {
|
||||
|
||||
sealed trait IterationBound
|
||||
|
||||
case class ViaVar(i: Int) extends IterationBound {
|
||||
override def toString: String = s"v(=$i)"
|
||||
}
|
||||
|
||||
case class ViaConst(i: Int) extends IterationBound {
|
||||
override def toString: String = i.toString
|
||||
}
|
||||
|
||||
|
||||
def subcase(typ: String, from: IterationBound, dir: String, to: IterationBound, expectedCount: Int)(platforms: Cpu.Value*)(implicit pos: org.scalactic.source.Position, prettifier: Prettifier): Unit = {
|
||||
val params = (from, to) match {
|
||||
case (ViaVar(s), ViaVar(e)) => s"$typ s, $typ e"
|
||||
case (ViaVar(s), ViaConst(e)) => s"$typ s"
|
||||
case (ViaConst(s), ViaVar(e)) => s"$typ e"
|
||||
case (ViaConst(s), ViaConst(e)) => ""
|
||||
}
|
||||
val args = (from, to) match {
|
||||
case (ViaVar(s), ViaVar(e)) => s"$s, $e"
|
||||
case (ViaVar(s), ViaConst(_)) => s"$s"
|
||||
case (ViaConst(_), ViaVar(e)) => s"$e"
|
||||
case (ViaConst(_), ViaConst(_)) => ""
|
||||
}
|
||||
val start = from match {
|
||||
case ViaVar(_) => "s"
|
||||
case ViaConst(s) => s"$s"
|
||||
}
|
||||
val end = to match {
|
||||
case ViaVar(_) => "e"
|
||||
case ViaConst(e) => s"$e"
|
||||
}
|
||||
val src =
|
||||
s"""
|
||||
| word output @0xc000
|
||||
| void main () {
|
||||
| output = 0
|
||||
| loopy($args)
|
||||
| }
|
||||
| noinline void loopy($params) {
|
||||
| $typ i
|
||||
| for i,$start,$dir,$end { output += 1}
|
||||
| }
|
||||
""".stripMargin
|
||||
EmuUnoptimizedCrossPlatformRun(platforms: _*)(src) { m =>
|
||||
m.readWord(0xc000) should equal(expectedCount) withClue s"(for i,$from,$dir,$to)"
|
||||
}
|
||||
}
|
||||
def byteSubcase(from: IterationBound, dir: String, to: IterationBound, expectedCount: Int)(implicit pos: org.scalactic.source.Position, prettifier: Prettifier): Unit = {
|
||||
subcase("byte", from, dir, to, expectedCount)(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)
|
||||
}
|
||||
def wordSubcase(from: IterationBound, dir: String, to: IterationBound, expectedCount: Int)(implicit pos: org.scalactic.source.Position, prettifier: Prettifier): Unit = {
|
||||
subcase("word", from, dir, to, expectedCount)(Cpu.Mos, Cpu.Z80/*, Cpu.Motorola6809*/)
|
||||
}
|
||||
|
||||
test("Basic iteration count test") {
|
||||
byteSubcase(ViaConst(1), "to", ViaConst(3), 3)
|
||||
byteSubcase(ViaVar(1), "to", ViaConst(3), 3)
|
||||
byteSubcase(ViaConst(1), "to", ViaVar(3), 3)
|
||||
byteSubcase(ViaVar(1), "to", ViaVar(3), 3)
|
||||
byteSubcase(ViaConst(1), "until", ViaConst(3), 2)
|
||||
byteSubcase(ViaVar(1), "until", ViaConst(3), 2)
|
||||
byteSubcase(ViaConst(1), "until", ViaVar(3), 2)
|
||||
byteSubcase(ViaVar(1), "until", ViaVar(3), 2)
|
||||
byteSubcase(ViaConst(11), "downto", ViaConst(3), 9)
|
||||
byteSubcase(ViaVar(11), "downto", ViaConst(3), 9)
|
||||
byteSubcase(ViaConst(11), "downto", ViaVar(3), 9)
|
||||
byteSubcase(ViaVar(11), "downto", ViaVar(3), 9)
|
||||
|
||||
wordSubcase(ViaConst(1), "to", ViaConst(3), 3)
|
||||
wordSubcase(ViaVar(1), "to", ViaConst(3), 3)
|
||||
wordSubcase(ViaConst(1), "to", ViaVar(3), 3)
|
||||
wordSubcase(ViaVar(1), "to", ViaVar(3), 3)
|
||||
wordSubcase(ViaConst(1), "until", ViaConst(3), 2)
|
||||
wordSubcase(ViaVar(1), "until", ViaConst(3), 2)
|
||||
wordSubcase(ViaConst(1), "until", ViaVar(3), 2)
|
||||
wordSubcase(ViaVar(1), "until", ViaVar(3), 2)
|
||||
wordSubcase(ViaConst(11), "downto", ViaConst(3), 9)
|
||||
wordSubcase(ViaVar(11), "downto", ViaConst(3), 9)
|
||||
wordSubcase(ViaConst(11), "downto", ViaVar(3), 9)
|
||||
wordSubcase(ViaVar(11), "downto", ViaVar(3), 9)
|
||||
}
|
||||
|
||||
test("Edge case iteration count test") {
|
||||
byteSubcase(ViaConst(0), "to", ViaConst(255), 256)
|
||||
byteSubcase(ViaVar(0), "to", ViaConst(255), 256)
|
||||
byteSubcase(ViaConst(0), "to", ViaVar(255), 256)
|
||||
byteSubcase(ViaVar(0), "to", ViaVar(255), 256)
|
||||
byteSubcase(ViaConst(0), "until", ViaConst(255), 255)
|
||||
byteSubcase(ViaVar(0), "until", ViaConst(255), 255)
|
||||
byteSubcase(ViaConst(0), "until", ViaVar(255), 255)
|
||||
byteSubcase(ViaVar(0), "until", ViaVar(255), 255)
|
||||
byteSubcase(ViaConst(255), "downto", ViaConst(0), 256)
|
||||
byteSubcase(ViaVar(255), "downto", ViaConst(0), 256)
|
||||
byteSubcase(ViaConst(255), "downto", ViaVar(0), 256)
|
||||
byteSubcase(ViaVar(255), "downto", ViaVar(0), 256)
|
||||
}
|
||||
|
||||
test("Empty case iteration count test") {
|
||||
byteSubcase(ViaConst(5), "until", ViaConst(5), 0)
|
||||
byteSubcase(ViaVar(5), "until", ViaConst(5), 0)
|
||||
byteSubcase(ViaConst(5), "until", ViaVar(5), 0)
|
||||
byteSubcase(ViaVar(5), "until", ViaVar(5), 0)
|
||||
|
||||
wordSubcase(ViaConst(5), "until", ViaConst(5), 0)
|
||||
wordSubcase(ViaVar(5), "until", ViaConst(5), 0)
|
||||
wordSubcase(ViaConst(5), "until", ViaVar(5), 0)
|
||||
wordSubcase(ViaVar(5), "until", ViaVar(5), 0)
|
||||
}
|
||||
|
||||
test("Wrong directions in 'until' cases") {
|
||||
byteSubcase(ViaConst(5), "until", ViaConst(4), 255)
|
||||
byteSubcase(ViaVar(5), "until", ViaConst(4), 255)
|
||||
byteSubcase(ViaConst(5), "until", ViaVar(4), 255)
|
||||
byteSubcase(ViaVar(5), "until", ViaVar(4), 255)
|
||||
|
||||
wordSubcase(ViaConst(65000), "until", ViaConst(0), 536)
|
||||
wordSubcase(ViaVar(65000), "until", ViaConst(0), 536)
|
||||
wordSubcase(ViaConst(65000), "until", ViaVar(0), 536)
|
||||
wordSubcase(ViaVar(65000), "until", ViaVar(0), 536)
|
||||
|
||||
wordSubcase(ViaConst(65000), "until", ViaConst(1), 537)
|
||||
wordSubcase(ViaVar(65000), "until", ViaConst(1), 537)
|
||||
wordSubcase(ViaConst(65000), "until", ViaVar(1), 537)
|
||||
wordSubcase(ViaVar(65000), "until", ViaVar(1), 537)
|
||||
}
|
||||
|
||||
test("Wrong directions in 'to' cases") {
|
||||
byteSubcase(ViaConst(1), "to", ViaConst(0), 256)
|
||||
byteSubcase(ViaVar(1), "to", ViaConst(0), 256)
|
||||
byteSubcase(ViaConst(1), "to", ViaVar(0), 256)
|
||||
byteSubcase(ViaVar(1), "to", ViaVar(0), 256)
|
||||
|
||||
wordSubcase(ViaConst(65000), "to", ViaConst(0), 537)
|
||||
wordSubcase(ViaVar(65000), "to", ViaConst(0), 537)
|
||||
wordSubcase(ViaConst(65000), "to", ViaVar(0), 537)
|
||||
wordSubcase(ViaVar(65000), "to", ViaVar(0), 537)
|
||||
}
|
||||
|
||||
test("Wrong directions in 'downto' cases") {
|
||||
byteSubcase(ViaConst(1), "downto", ViaConst(2), 256)
|
||||
byteSubcase(ViaVar(1), "downto", ViaConst(2), 256)
|
||||
byteSubcase(ViaConst(1), "downto", ViaVar(2), 256)
|
||||
byteSubcase(ViaVar(1), "downto", ViaVar(2), 256)
|
||||
|
||||
wordSubcase(ViaConst(1000), "downto", ViaConst(65000), 1537)
|
||||
wordSubcase(ViaVar(1000), "downto", ViaConst(65000), 1537)
|
||||
wordSubcase(ViaConst(1000), "downto", ViaVar(65000), 1537)
|
||||
wordSubcase(ViaVar(1000), "downto", ViaVar(65000), 1537)
|
||||
}
|
||||
}
|
@ -166,15 +166,17 @@ class ForLoopSuite extends FunSuite with Matchers {
|
||||
| for i, zero, paralleluntil, ff { }
|
||||
| flag = 4
|
||||
| for i, zero, parallelto, ff { }
|
||||
| flag = 5
|
||||
| for i, ff, until, zero { _panic() }
|
||||
| flag = 6
|
||||
| for i, ff, paralleluntil, zero { _panic() }
|
||||
| flag = 7
|
||||
| for i, ff, paralleluntil, zero { _panic() }
|
||||
| flag = 8
|
||||
| for i, zero, until, ff { }
|
||||
| flag = 9
|
||||
| for i, zero, until, zero { _panic() }
|
||||
| flag = 10
|
||||
| for i, ff, until, ff { _panic() }
|
||||
| flag = 11
|
||||
| for i, zero, paralleluntil, zero { _panic() }
|
||||
| flag = 12
|
||||
| for i, ff, paralleluntil, ff { _panic() }
|
||||
| flag = ff
|
||||
| }
|
||||
| void _panic(){while(true){}}
|
||||
""".stripMargin){ m=>
|
||||
|
108
src/test/scala/millfork/test/Issue11Test.scala
Normal file
108
src/test/scala/millfork/test/Issue11Test.scala
Normal file
@ -0,0 +1,108 @@
|
||||
package millfork.test
|
||||
|
||||
import millfork.Cpu
|
||||
import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCmosRun}
|
||||
import org.scalatest.{FunSuite, Matchers}
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
class Issue11Test extends FunSuite with Matchers {
|
||||
|
||||
test("Test issue 11") {
|
||||
val src =
|
||||
"""
|
||||
|import c64_basic
|
||||
|import stdio
|
||||
|
|
||||
|struct Box {
|
||||
| byte x,
|
||||
| byte y,
|
||||
| byte width,
|
||||
| byte height
|
||||
|}
|
||||
|
|
||||
|struct Phys_Obj {
|
||||
| Box pos,
|
||||
| byte xfrac,
|
||||
| byte yfrac,
|
||||
| sbyte xvel,
|
||||
| sbyte yvel,
|
||||
| byte xaccel,
|
||||
| byte yaccel,
|
||||
| byte xspeed,
|
||||
| bool on_ground,
|
||||
| bool jumping,
|
||||
| bool can_jump
|
||||
|}
|
||||
|
|
||||
|Phys_Obj player1
|
||||
|byte output @$c000
|
||||
|
|
||||
|inline pointer get_metatile_column(word metatiles_column) {
|
||||
| //This is the function where the original player pointer
|
||||
| //gets corrupted.
|
||||
| return pointer(metatiles_column)
|
||||
|}
|
||||
|
|
||||
|byte get_tile(byte column, byte row) {
|
||||
| byte tile
|
||||
| pointer metatiles_column
|
||||
|
|
||||
| metatiles_column = get_metatile_column($0000)
|
||||
| tile = $00
|
||||
| return tile
|
||||
|}
|
||||
|
|
||||
|void check_background_collis(byte screenx, byte screeny, byte spritewidth, byte spriteheight) {
|
||||
| byte new_column1
|
||||
| byte new_column2
|
||||
| byte new_row1
|
||||
| byte new_row2
|
||||
| byte i
|
||||
| byte limit
|
||||
| byte current_tile
|
||||
|
|
||||
| current_tile = get_tile(0,0)
|
||||
|}
|
||||
|
|
||||
|void check_player_collis_and_update_player_loc(pointer.Phys_Obj player) {
|
||||
| sbyte temp_vel
|
||||
| byte old_playerx
|
||||
| byte old_playerx_frac
|
||||
| byte old_playery
|
||||
| byte old_playery_frac
|
||||
|
|
||||
| old_playerx = player1.pos.x
|
||||
| old_playery = player1.pos.y
|
||||
| old_playerx_frac = player1.xfrac
|
||||
| old_playery_frac = player1.yfrac
|
||||
|
|
||||
| check_background_collis(player1.pos.x, player1.pos.y, player1.pos.width, player1.pos.height)
|
||||
|}
|
||||
|
|
||||
|inline void game_logic(pointer.Phys_Obj player) {
|
||||
| player->pos.x = 15
|
||||
|
|
||||
| //comment out this function call and the player
|
||||
| //pointer should still point to the correct location
|
||||
| check_player_collis_and_update_player_loc(player)
|
||||
| //after the function call, player points to garbage
|
||||
|
|
||||
| //expected val: 15
|
||||
| //if player no longer points to player1: garbage
|
||||
| output = player->pos.x
|
||||
|}
|
||||
|
|
||||
|void main() {
|
||||
| pointer.Phys_Obj player
|
||||
| player = pointer.Phys_Obj(player1.addr)
|
||||
| game_logic(player)
|
||||
|}
|
||||
|""".stripMargin
|
||||
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos)(src) { m =>
|
||||
m.readByte(0xc000) should equal(15)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package millfork.test
|
||||
|
||||
import millfork.Cpu
|
||||
import millfork.test.emu.{EmuNodeOptimizedRun, EmuUnoptimizedCrossPlatformRun}
|
||||
import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuNodeOptimizedRun, EmuUnoptimizedCrossPlatformRun}
|
||||
import org.scalatest.{FunSuite, Matchers}
|
||||
|
||||
/**
|
||||
@ -53,4 +53,19 @@ class NodeOptimizationSuite extends FunSuite with Matchers {
|
||||
m.readLong(0xc000) should equal (0x3000000)
|
||||
}
|
||||
}
|
||||
|
||||
test("Unused global struct variable") {
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Motorola6809)(
|
||||
"""
|
||||
| struct p { byte x }
|
||||
| p b
|
||||
| void foo (byte x) {
|
||||
| byte y
|
||||
| b.x = x
|
||||
|}
|
||||
| void main () {
|
||||
| foo(1)
|
||||
| }
|
||||
""".stripMargin) { m => }
|
||||
}
|
||||
}
|
||||
|
@ -322,4 +322,21 @@ class PointerSuite extends FunSuite with Matchers with AppendedClues {
|
||||
m.readWord(0xc000) should equal(5)
|
||||
}
|
||||
}
|
||||
|
||||
test("Fast pointer indexing") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos) (
|
||||
"""
|
||||
|pointer.word p
|
||||
|array(word) input [6]
|
||||
|word output @$c000
|
||||
|void main () {
|
||||
| input[3] = 555
|
||||
| output = f(input.pointer)
|
||||
|}
|
||||
|noinline word f(pointer.word p) = p[3]
|
||||
""".stripMargin
|
||||
){ m =>
|
||||
m.readWord(0xc000) should equal(555)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,11 +39,11 @@ object ShouldNotParse extends Matchers {
|
||||
}
|
||||
parserF.toAst match {
|
||||
case Success(program, _) =>
|
||||
fail("Parse succeded")
|
||||
fail("Parse succeeded")
|
||||
case f: Failure[_, _] =>
|
||||
println(f.extra.toString)
|
||||
println(f.lastParser.toString)
|
||||
log.info("Expected syntax error: " + parserF.lastLabel, Some(parserF.lastPosition))
|
||||
log.warn("Last parser: " + f.lastParser, Some(parserF.indexToPosition(f.index, f.lastParser.toString)))
|
||||
log.warn("Expected syntax error: " + parserF.lastLabel, Some(parserF.lastPosition))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user