1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-26 11:29:28 +00:00

Merge pull request #1 from KarolS/master

merge from remote
This commit is contained in:
Freddy 2019-10-23 11:44:43 +02:00 committed by GitHub
commit 01a212dbee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1139 additions and 172 deletions

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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`.

View File

@ -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).

View File

@ -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
View 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**

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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?

View File

@ -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
View 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
}
}

View File

@ -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
View 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
View 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
View 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
View 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
View 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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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(

View File

@ -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)
}

View File

@ -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))

View File

@ -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)
))
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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))

View File

@ -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 =>

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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) {

View File

@ -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
}

View File

@ -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
}

View File

@ -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 = _

View File

@ -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(

View File

@ -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) =>

View File

@ -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")
}

View File

@ -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 =>
}
}
}

View File

@ -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(_ == '↑'))
}
}
}

View 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)
}
}

View File

@ -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=>

View 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)
}
}
}

View File

@ -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 => }
}
}

View File

@ -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)
}
}
}

View File

@ -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))
}
}
}