1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-05-29 04:41:30 +00:00

Many big important things:

– Add support for undocumented 8085 instructions
– Convert undocumented  8085 instructions to 8086
– Add new CPU types and categorize CPU types correctly
– Fix macro expansion in some situations
– Improve 8080 optimizations
– Improve documentation
– Other improvements
This commit is contained in:
Karol Stasiak 2019-06-12 12:06:02 +02:00
parent eaaa98a8e3
commit 0f179f79aa
28 changed files with 749 additions and 130 deletions

View File

@ -8,7 +8,7 @@
* Super experimental and very incomplete Intel 8086 support via 8080-to-8086 translation.
* Support for Intel 8085.
* Support for Intel 8085, together with illegal instructions.
* Added `memory_barrier` macro.
@ -48,7 +48,7 @@
* Fixed optimizations removing pointless stores to local variables.
* Fixed name clashes when passing parameters to functions.
* Fixed name clashes when passing parameters to functions and macros.
* Fixed `#use` not accessing all preprocessor parameters.

View File

@ -19,40 +19,36 @@ For binary releases, see: https://github.com/KarolS/millfork/releases
* other Commodore computers: C16, Plus/4, C128, PET, VIC-20 (stock or with RAM extensions)
* Famicom/NES (the second most important target)
* other 6502-based machines: Famicom/NES, Atari 8-bit computers, BBC Micro, Apple II+/IIe/Enhanced IIe, Atari 2600 (experimental)
* Atari 8-bit computers
* BBC Micro
* Apple II+/IIe/Enhanced IIe
* ZX Spectrum 48k
* NEC PC-88
* Amstrad CPC
* MSX
* Z80-based machines: ZX Spectrum 48k, NEC PC-88, Amstrad CPC, MSX
* CP/M
* Atari 2600 (experimental)
* Game Boy (experimental)
* MS-DOS (very experimental, via 8080-to-8086 translation)
* multiple supported target processors:
* well supported: MOS 6502, Ricoh 2A03/2A07, WDC 65C02, Intel 8080, Intel 8085, Zilog Z80
* reasonably well supported: Sharp LR35902, CSG 65CE02
* partially supported: Hudson Soft HuC6280, WDC 65816, Intel 8086
* inline assembly
* simple macros
* pay only for what you use: not a single byte of memory is used unless for code or explicitly declared variables
* simple memory model that avoids using the stack
* a simple memory model that avoids using the stack
* multi-pass whole-program optimizer (that will even optimize your hand-written assembly if you ask it to)
* support for multi-file programs (Commodore only) and banked cartridges
## Licensing
The compiler is distributed under GPLv3 (see [LICENSE](LICENSE)).
@ -74,6 +70,6 @@ Therefore, no attribution is needed if you are developing and distributing Millf
* more Z80 targets: TI-83, Sega Master System
* better support for 65816, Intel 8080, Sharp LR35902 and eZ80
* better support for 65816, Sharp LR35902 and eZ80
* support for Gameboy, SuperFamicom/SNES and Apple IIgs
* support for SuperFamicom/SNES and Apple IIgs

View File

@ -70,6 +70,32 @@ Original Z80 processors accidentally supported a bunch of extra undocumented ins
Millfork will not emit them.
The only exception is SLL, which will be emitted if it occurs in a handwritten assembly block.
## 8085
Since various assemblers use different mnemonics for undocumented opcodes,
Millfork supports multiple mnemonics per opcode. The default one is given first:
Intel syntax | Zilog syntax
**DSUB** | **DSUB**
**ARHL**, RRHL | **SRA HL**
**RLDE**, RDEL | **RL DE**
**LDHI n** | **LD DE,(HL+n)**
**LDSI n** | **LD DE,(SP+n)**
**LHLX** | **LD HL,(DE)**
**SHLX** | **LD (DE),HL**
**JK n**, JX5 n | **JP K,n**; JP X5,n
**JNK n**, JNX5 n | **JP NK,n**; JP NX5,n
**RSTV**, OVRST8 | **RSTV**
#### Generation
If enabled, The compiler will only emit the following undocumented instructions from Millfork code:
DSUB, LHLX, SHLX, LDSI, LDHI
The optimizer does not track the K and V flags. Use `JK`, `JNK` and `RSTV` with care.
## 8086
Undocumented instructions are not supported.

View File

@ -83,15 +83,16 @@ This may cause problems if the parameter table is stored next to a hardware regi
Whether the compiler should allow for invalid characters in string/character literals that use the default encodings and replace them with alternatives.
`.ini` equivalent: `lenient_encoding`. Default: yes on Apple II, no otherwise.
* `-fillegals`, `-fno-illegals` Whether should emit illegal (undocumented) NMOS or Z80 opcodes.
* `-fillegals`, `-fno-illegals` Whether should emit illegal (undocumented) NMOS 6502, Intel 8085 or Z80 opcodes.
`.ini` equivalent: `emit_illegals`.
Default: no.
#### 6502-related
* `-fcmos-ops`, `-fno-cmos-ops` Whether should emit CMOS opcodes.
* `-fcmos-ops`, `-fno-cmos-ops` Whether should emit 65C02 opcodes.
`.ini` equivalent: `emit_cmos`.
Default: yes if targeting a 65C02-compatible architecture, no otherwise.
* `-f65ce02-ops`, `-fno-65ce02-ops` Whether should emit 65CE02 opcodes.
`.ini` equivalent: `emit_65ce026`.
Default: yes if targeting 65CE02, no otherwise.
@ -128,13 +129,21 @@ Use a software stack for stack variables.
#### 8080/Z80-related
* `-f8085-ops`, `-fno-8085-ops` Whether should emit Intel 8085 opcodes.
`.ini` equivalent: `emit_8085`.
Default: yes if targeting Intel 8085 architecture, no otherwise.
* `-fz80-ops`, `-fno-z80-ops` Whether should emit Z80 opcodes.
`.ini` equivalents: `emit_z80` and `emit_x80`.
Default: yes if targeting a Z80-compatible architecture, no otherwise.
* `-fshadow-irq`, `-fno-shadow-irq`
Whether the interrupt routines should make use of Z80 shadow registers.
`.ini` equivalent: `use_shadow_registers_for_irq`. Default: yes on Z80, no otherwise.
* `-fuse-ix-for-stack`, `-fuse-iy-for-stack`, `-fno-use-index-for-stack`
Which of Z80 index registers should be used for accessing stack variables, if any.
`.ini` equivalent: `ix_stack` and `iy_stack`. Default: IX on Z80 and 8086, no otherwise.
`.ini` equivalent: `ix_stack` and `iy_stack`. Default: IX on Z80, no otherwise.
* `-fuse-ix-for-scratch`, `-fno-use-ix-for-scratch`
Allow using the IX register for other purposes.
@ -144,6 +153,11 @@ Allow using the IX register for other purposes.
Allow using the IY register for other purposes.
`.ini` equivalent: `iy_scratch`. Default: no.
#### 8086-related
Compiling to 8086 is based on translating from a mix of 8085 and Z80 instructions to 8086.
See [the 8086 support disclaimer](./../lang/x86disclaimer.md).
## Optimization options
* `-O0` Disable all optimizations except unused global symbol removal.

View File

@ -29,18 +29,21 @@ if a line ends with a backslash character, the value continues to the next line.
* `z80` (Zilog Z80)
* `strictz80` (Z80 without illegal instructions)
* `i8080` (Intel 8080)
* `i8085` (Intel 8085)
* `strict8085` (Intel 8085 without illegal instructions)
* `gameboy` (Sharp LR35902; experimental)
* `i8086` (Intel 8086; very experimental, very buggy and very, very incomplete
see the [8086 support disclaimer](../lang/x86disclaimer.md))
* `encoding` default encoding for console I/O, one of
`ascii`, `pet`/`petscii`, `petscr`/`cbmscr`, `atascii`, `bbc`, `jis`/`jisx`, `apple2`,
`iso_de`, `iso_no`/`iso_dk`, `iso_se`/`iso_fi`, `iso_yu`. Default: `ascii`
* `encoding` default encoding for console I/O. Default: `ascii`.
See [the list of available encodings](../lang/text.md).
* `screen_encoding` default encoding for screencodes (literals with encoding specified as `scr`).
Default: the same as `encoding`.
@ -51,12 +54,18 @@ Default: the same as `encoding`.
* `emit_illegals` whether the compiler should emit illegal instructions, default `false`
* `emit_cmos` whether the compiler should emit CMOS instructions, default is `true` on compatible processors and `false` elsewhere
* `emit_cmos` whether the compiler should emit 65C02 instructions, default is `true` on compatible processors and `false` elsewhere
* `emit_65816` which 65816 instructions should the compiler emit, either `no`, `emulation` or `native`
* `decimal_mode` whether the compiler should emit decimal instructions, default is `false` on `ricoh` and `strictricoh` and `true` elsewhere;
if disabled, a software decimal mode will be used
* `emit_8085` whether the compiler should emit Intel 8085 instructions, default is `true` on compatible processors and `false` elsewhere
* `emit_x80` whether the compiler should emit instructions present on Sharp LR35902 and Z80, but absent on Intel 8080, default is `true` on compatible processors and `false` elsewhere
* `emit_z80` whether the compiler should emit Zilog Z80 instructions not covered by `emit_x80`, default is `true` on compatible processors and `false` elsewhere
* `ro_arrays` (deprecated) whether the compiler should warn upon array writes, default is `false`

View File

@ -40,7 +40,7 @@ The following features are defined based on the chosen CPU and compilation optio
* `ARCH_X86` 1 if compiling for Intel 8086-like processor, 0 otherwise
* `CPU_65C02`, `CPU_65CE02`, `CPU_65816`, `CPU_HUC6280`, `CPU_8080`, `CPU_GAMEBOY`, `CPU_Z80`
* `CPU_65C02`, `CPU_65CE02`, `CPU_65816`, `CPU_HUC6280`, `CPU_8080`, `CPU_8085`, `CPU_GAMEBOY`, `CPU_Z80`, `CPU_8086`
1 if compiling for the exact given processor, 0 otherwise
* `CPU_6502` 1 if compiling for any pre-65C02 6502-like processor, 0 otherwise
@ -48,8 +48,8 @@ The following features are defined based on the chosen CPU and compilation optio
* `CPUFEATURE_DECIMAL_MODE` 1 if decimal mode is enabled, 0 otherwise
* `CPUFEATURE_65C02`, `CPUFEATURE_65CE02`, `CPUFEATURE_HUC6280`, `CPUFEATURE_65816_EMULATION`, `CPUFEATURE_65816_NATIVE`,
`CPUFEATURE_8080`, `CPUFEATURE_GAMEBOY`, `CPUFEATURE_Z80`,
`CPUFEATURE_6502_ILLEGALS`, `CPUFEATURE_Z80_ILLEGALS` 1 if given instruction subset is enabled, 0 otherwise
`CPUFEATURE_8080`, `CPUFEATURE_8085`, `CPUFEATURE_GAMEBOY`, `CPUFEATURE_Z80`,
`CPUFEATURE_6502_ILLEGALS`, `CPUFEATURE_8085_ILLEGALS`, `CPUFEATURE_Z80_ILLEGALS` 1 if given instruction subset is enabled, 0 otherwise
* `OPTIMIZE_FOR_SIZE`, `OPTIMIZE_FOR_SPEED`, `OPTIMIZE_INLINE`, `OPTIMIZE_IPO`
1 if given optimization setting is enabled, 0 otherwise
@ -58,13 +58,13 @@ The following features are defined based on the chosen CPU and compilation optio
* `USES_ZPREG` 1 if the zeropage pseudoregister is used, 0 otherwise
* `ZPREG_SIZE` size of the pseudoregister in bytes
* `ZPREG_SIZE` size of the pseudoregister in bytes, or 0 on platforms that don't use it
* `USES_IX_STACK`, `USES_IY_STACK` 1 if given index register is used as a base pointer for stack-allocated variables, 0 otherwise
* `USES_SHADOW_REGISTERS` 1 if interrupts preserve old registers in the shadow registers, 0 otherwise
* `USES_SHADOW_REGISTERS` 1 if interrupts preserve old registers in the shadow registers, 0 if they do it on stack
* `USES_SOFTWARE_STACK` 1 if using software stack for variables, 0 otherwise
* `USES_SOFTWARE_STACK` 1 if using software stack for variables (6502-like targets only), 0 otherwise
### Commonly used features
@ -152,6 +152,7 @@ To use such value in other files, consider this:
### `#pragma`
Changes the behaviour of the parser for the current file.
The change applies to the whole file, regardless of where the directive is located.
* `#pragma intel_syntax` interpret assembly using Intel syntax

View File

@ -4,7 +4,7 @@
Millfork does not support Intel 8086 directly.
Instead, it generates Intel 8080 code and translates it automatically to 8086 machine code.
For convenience, Z80 instructions using `IX` are also translated.
For convenience, most undocumented 8085 instructions and Z80 instructions using `IX` are also translated.
This means that:
@ -12,8 +12,9 @@ This means that:
* there is no support for writing 8086 assembly;
* Millfork currently translates majority of Intel 8080 assembly instructions to 8086 machine code,
so you can write 8080/Z80 assembly instead.
* Millfork currently translates majority of Intel 8085 assembly instructions to 8086 machine code,
so you can write 8080/Z80 assembly instead.
Instructions `RST` (8080), `RIM`, `SIM` (8085), `RSTV`, `ARHL`, `RLDE` (8085 undocumented) are not supported.
For example, code like
@ -41,6 +42,18 @@ is compiled to
Generated assembly output uses Intel 8086 syntax.
#### Configuring code generation
There are three options that influence the 8086 code generation:
* `ix_stack` (command line equivalent `-fuse-ix-for-stack` for enabling, `-fno-use-index-for-stack` for disabling)
* `emit_8085` (command line equivalent `-f8085-ops` for enabling, `-fno-8085-ops` for disabling)
* `emit_illegals` (command line equivalent `-fillegals` for enabling, `-fno-illegals` for disabling)
`emit_8085` and `emit_illegals` have effect only together.
#### Major deficiencies of generated code
* hardware multiplication is not used
@ -51,6 +64,9 @@ Generated assembly output uses Intel 8086 syntax.
* the overflow flag is not used
* signed comparisons are suboptimal and as buggy as on 8080
(8085 has the undocumented K flag that could be used here, but Millfork does not use it)
* `DAS` is not used
* conditional jumps are never optimized to short 2-byte jumps and always use 5 bytes
@ -74,5 +90,15 @@ The registers are translated as following:
SP → SP
IX → BP
The `SI` register is used as a temporary register for holding the address in `LDAX`/`STAX`
(`LD (DE),A`/`LD(BC),A`/`LD A,(DE)`/`LD A,(BC)` on Z80).
The `SI` register is used as a temporary register for holding the address in 8080's `LDAX`/`STAX`
(`LD (DE),A`/`LD(BC),A`/`LD A,(DE)`/`LD A,(BC)` on Z80)
and 8085's undocumented `LDSI`/`SHLX`/`LHLX` (`LD DE,SP+n`/`LD (DE),HL`/`LD HL,(DE)` in Z80 syntax).
The `DI` register is currently not used.
#### Future development
There won't be any major future development related to 8086 support,
unless a full 8086 backend that is independent from the 8080 backend is created.
The current solution was developed only as a proof of concept.

View File

@ -26,7 +26,7 @@ case class CompilationOptions(platform: Platform,
f -> commandLineFlags.getOrElse(f, platform.flagOverrides.getOrElse(f, Cpu.defaultFlags(platform.cpu)(f)))
}.toMap
def flag(f: CompilationFlag.Value) = flags(f)
def flag(f: CompilationFlag.Value): Boolean = flags(f)
{
log.setFatalWarnings(flags(CompilationFlag.FatalWarnings))
@ -34,14 +34,14 @@ case class CompilationOptions(platform: Platform,
var invalids = Set[CompilationFlag.Value]()
if (CpuFamily.forType(platform.cpu) != CpuFamily.M6502) invalids ++= Set(
EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, LUnixRelocatableCode,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, LUnixRelocatableCode, RorWarning, SoftwareStack)
if (CpuFamily.forType(platform.cpu) != CpuFamily.I80 && CpuFamily.forType(platform.cpu) != CpuFamily.I86) invalids ++= Set(
EmitIntel8080Opcodes, UseIxForStack, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
EmitIntel8085Opcodes, EmitIntel8080Opcodes, UseIxForStack, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
if (CpuFamily.forType(platform.cpu) != CpuFamily.I80) invalids ++= Set(
EmitIntel8085Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitEZ80Opcodes,
EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitEZ80Opcodes,
UseIyForStack, UseShadowRegistersForInterrupts)
invalids = invalids.filter(flags)
@ -50,7 +50,7 @@ case class CompilationOptions(platform: Platform,
log.error("Invalid flags enabled for the current CPU family: " + invalids.mkString(", "))
}
if (CpuFamily.forType(platform.cpu) != CpuFamily.M6502 && zpRegisterSize > 0) {
log.error("Invalid flags enabled for the current CPU family: zp_register" + invalids.mkString(", "))
log.error("Invalid flags enabled for the current CPU family: zp_register")
}
CpuFamily.forType(platform.cpu) match {
case CpuFamily.M6502 =>
@ -106,19 +106,19 @@ case class CompilationOptions(platform: Platform,
}
case CpuFamily.I80 =>
if (flags(EmitIllegals)) {
if (platform.cpu != Z80) {
if (platform.cpu != Z80 && platform.cpu != Intel8085) {
log.error("Illegal opcodes enabled for architecture that doesn't support them")
}
}
if (flags(UseIxForStack) || flags(UseIxForScratch)) {
if (platform.cpu != Z80) {
if (!Z80Compatible(platform.cpu)) {
log.error("IX register enabled for architecture that doesn't support it")
} else if (!flags(EmitZ80Opcodes)) {
log.error("IX register is enabled but instructions using it are disabled")
}
}
if (flags(UseIyForStack) || flags(UseIyForScratch)) {
if (platform.cpu != Z80) {
if (!Z80Compatible(platform.cpu)) {
log.error("IY register enabled for architecture that doesn't support it")
} else if (!flags(EmitZ80Opcodes)) {
log.error("IY register is enabled but instructions using it are disabled")
@ -134,19 +134,19 @@ case class CompilationOptions(platform: Platform,
log.error("Cannot use both IX and IY registers for stack variables simultaneously")
}
if (flags(UseShadowRegistersForInterrupts)) {
if (platform.cpu != Z80) {
if (!Z80Compatible(platform.cpu)) {
log.error("Shadow registers enabled for architecture that doesn't support them")
} else if (!flags(EmitZ80Opcodes)) {
log.error("Shadow registers are enabled but instructions using them are disabled")
}
}
if (flags(EmitZ80Opcodes)) {
if (platform.cpu != Z80 && platform.cpu != EZ80) {
if (!Z80Compatible(platform.cpu)) {
log.error("Z80 opcodes enabled for architecture that doesn't support them")
}
}
if (flags(EmitEZ80Opcodes)) {
if (platform.cpu != Z80 && platform.cpu != EZ80) {
if (platform.cpu != EZ80) {
log.error("eZ80 opcodes enabled for architecture that doesn't support them")
}
}
@ -156,24 +156,24 @@ case class CompilationOptions(platform: Platform,
}
}
if (flags(EmitExtended80Opcodes)) {
if (platform.cpu != Sharp && platform.cpu != Z80 && platform.cpu != EZ80) {
if (platform.cpu != Sharp && !Z80Compatible(platform.cpu)) {
log.error("Extended 8080-like opcodes enabled for architecture that doesn't support them")
}
}
if (flags(EmitIntel8080Opcodes)) {
if (platform.cpu != Intel8080 && platform.cpu != Intel8085 && platform.cpu != Z80 && platform.cpu != EZ80) {
if (!Intel8080Compatible(platform.cpu)) {
log.error("Intel 8080 opcodes enabled for architecture that doesn't support them")
}
}
if (flags(EmitIntel8085Opcodes)) {
if (platform.cpu != Intel8085) {
if (!Intel8085Compatible(platform.cpu)) {
log.error("Intel 8085 opcodes enabled for architecture that doesn't support them")
}
}
case CpuFamily.I86 =>
if (flags(EmitIllegals)) {
log.error("Illegal opcodes enabled for architecture that doesn't support them")
if (!flags(CompilationFlag.EmitSharpOpcodes) && !flags(CompilationFlag.EmitIntel8080Opcodes)) {
log.error("Either Sharp LR35902 or Intel 8080 opcodes have to be enabled")
}
case CpuFamily.I86 =>
}
}
@ -211,7 +211,8 @@ case class CompilationOptions(platform: Platform,
"CPUFEATURE_65816_EMULATION" -> toLong(flag(CompilationFlag.EmitEmulation65816Opcodes)),
"CPUFEATURE_65816_NATIVE" -> toLong(flag(CompilationFlag.EmitNative65816Opcodes)),
"CPUFEATURE_6502_ILLEGALS" -> toLong(platform.cpuFamily == CpuFamily.M6502 && flag(CompilationFlag.EmitIllegals)),
"CPUFEATURE_Z80_ILLEGALS" -> toLong(platform.cpuFamily == CpuFamily.I80 && flag(CompilationFlag.EmitIllegals)),
"CPUFEATURE_Z80_ILLEGALS" -> toLong(flag(CompilationFlag.EmitZ80Opcodes) && flag(CompilationFlag.EmitIllegals)),
"CPUFEATURE_8085_ILLEGALS" -> toLong(flag(CompilationFlag.EmitIntel8080Opcodes) && flag(CompilationFlag.EmitIllegals)),
"SYNTAX_INTEL" -> toLong(platform.cpuFamily == CpuFamily.I80 && flag(CompilationFlag.UseIntelSyntaxForInput)),
"SYNTAX_ZILOG" -> toLong(platform.cpuFamily == CpuFamily.I80 && !flag(CompilationFlag.UseIntelSyntaxForInput)),
"USES_ZPREG" -> toLong(platform.cpuFamily == CpuFamily.M6502 && zpRegisterSize > 0),
@ -227,13 +228,40 @@ case class CompilationOptions(platform: Platform,
}
object CpuFamily extends Enumeration {
val M6502, I80, M6800, I86, M68K, ARM = Value
/**
* Family based on the MOS 6502 processor and its descendants
*/
val M6502: CpuFamily.Value = Value
/**
* Family based on the Intel 8080 processor and similar architectures
*/
val I80: CpuFamily.Value = Value
/**
* Family based on the Motorola 6800 processor
*/
val M6800: CpuFamily.Value = Value
/**
* Family based on the Motorola 6809 processor
*/
val M6809: CpuFamily.Value = Value
/**
* Family based on the Intel 8086/8088 processor and its descendants
*/
val I86: CpuFamily.Value = Value
/**
* Family based on the Motorola 68000 processor and its descendants
*/
val M68K: CpuFamily.Value = Value
/**
* Family based on the ARM processors
*/
val ARM: CpuFamily.Value = Value
def forType(cpu: Cpu.Value): CpuFamily.Value = {
import Cpu._
cpu match {
case Mos | StrictMos | Ricoh | StrictRicoh | Cmos | HuC6280 | CE02 | Sixteen => M6502
case Intel8080 | Intel8085 | Sharp | Z80 | EZ80 => I80
case Intel8080 | Intel8085 | StrictIntel8085 | Sharp | Z80 | StrictZ80 | EZ80 => I80
case Intel8086 | Intel80186 => I86
}
}
@ -241,9 +269,91 @@ object CpuFamily extends Enumeration {
object Cpu extends Enumeration {
val Mos, StrictMos, Ricoh, StrictRicoh, Cmos, HuC6280, CE02, Sixteen, Intel8080, Intel8085, Z80, EZ80, Sharp, Intel8086, Intel80186 = Value
/**
* The MOS 6502/6507/6510 processor
*/
val Mos: Cpu.Value = Value
/**
* The MOS 6502/6507/6510 processor, without illegal instructions
*/
val StrictMos: Cpu.Value = Value
/**
* The Ricoh 2A03/2A07 processor
*/
val Ricoh: Cpu.Value = Value
/**
* The Ricoh 2A03/2A07 processor, without illegal instructions
*/
val StrictRicoh: Cpu.Value = Value
/**
* The WDC 65C02 processor
*/
val Cmos: Cpu.Value = Value
/**
* The Hudson Soft HuC6280 processor
*/
val HuC6280: Cpu.Value = Value
/**
* The CSG 65CE02 processor
*/
val CE02: Cpu.Value = Value
/**
* The WDC 65816 processor
*/
val Sixteen: Cpu.Value = Value
/**
* The Intel 8080 processor
*/
val Intel8080: Cpu.Value = Value
/**
* The Intel 8085 processor
*/
val Intel8085: Cpu.Value = Value
/**
* The Intel 8085 processor, without illegal instructions
*/
val StrictIntel8085: Cpu.Value = Value
/**
* The Zilog Z80 processor
*/
val Z80: Cpu.Value = Value
/**
* The Zilog Z80 processor, without illegal instructions
*/
val StrictZ80: Cpu.Value = Value
/**
* The Zilog eZ80 processor
*/
val EZ80: Cpu.Value = Value
/**
* The Sharp LR35902 processor
*/
val Sharp: Cpu.Value = Value
/**
* The Intel 8086 or 8088 processor
*/
val Intel8086: Cpu.Value = Value
/**
* The Intel 80186 or 80188 processor
*/
val Intel80186: Cpu.Value = Value
val CmosCompatible = Set(Cmos, HuC6280, CE02, Sixteen)
/**
* Processors that can run code for WDC 65C02
*/
val CmosCompatible: Set[Cpu.Value] = Set(Cmos, HuC6280, CE02, Sixteen)
/**
* Processors that can run code for Zilog Z80
*/
val Z80Compatible: Set[Cpu.Value] = Set(Z80, StrictZ80, EZ80)
/**
* Processors that can run code for Intel 8080
*/
val Intel8080Compatible: Set[Cpu.Value] = Set(Intel8080, Intel8085, StrictIntel8085, Z80, StrictZ80, EZ80)
/**
* Processors that can run code for Intel 8085
*/
val Intel8085Compatible: Set[Cpu.Value] = Set(Intel8085, StrictIntel8085)
import CompilationFlag._
@ -274,16 +384,16 @@ object Cpu extends Enumeration {
mosAlwaysDefaultFlags ++ Set(DecimalMode, EmitCmosOpcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, ReturnWordsViaAccumulator)
case Intel8080 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
case Intel8085 =>
case StrictIntel8085 | Intel8085 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitIntel8085Opcodes, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
case Z80 =>
case StrictZ80 | Z80 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts)
case EZ80 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts, EmitEZ80Opcodes)
case Sharp =>
i80AlwaysDefaultFlags ++ Set(EmitExtended80Opcodes, EmitSharpOpcodes)
case Intel8086 | Intel80186 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, UseIxForStack)
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, UseIxForStack, EmitIntel8085Opcodes, EmitIllegals)
}
def fromString(name: String)(implicit log: Logger): Cpu.Value = name match {
@ -306,7 +416,9 @@ object Cpu extends Enumeration {
case "65ce02" => CE02
case "ce02" => CE02
case "65816" => Sixteen
case "65c816" => Sixteen
case "816" => Sixteen
case "5a22" => Sixteen
case "ricoh" => Ricoh
case "2a03" => Ricoh
case "2a07" => Ricoh
@ -314,6 +426,7 @@ object Cpu extends Enumeration {
case "strict2a03" => StrictRicoh
case "strict2a07" => StrictRicoh
case "z80" => Z80
case "strictz80" => Z80
// disabled for now:
// case "ez80" => EZ80
case "gameboy" => Sharp
@ -326,6 +439,9 @@ object Cpu extends Enumeration {
case "8085" => Intel8085
case "i8085" => Intel8085
case "intel8085" => Intel8085
case "strict8085" => StrictIntel8085
case "stricti8085" => StrictIntel8085
case "strictintel8085" => StrictIntel8085
case "intel8086" => Intel8086
case "i8086" => Intel8086
case "8086" => Intel8086
@ -382,7 +498,7 @@ object CompilationFlag extends Enumeration {
val allWarnings: Set[CompilationFlag.Value] = Set(ExtraComparisonWarnings)
val fromString = Map(
val fromString: Map[String, CompilationFlag.Value] = Map(
"lunix" -> LUnixRelocatableCode,
"emit_illegals" -> EmitIllegals,
"emit_cmos" -> EmitCmosOpcodes,

View File

@ -256,12 +256,14 @@ object Main {
val assemblyOptimizations = optLevel match {
case 0 => Nil
case _ => platform.cpu match {
case Cpu.Z80 | Cpu.EZ80 => Z80OptimizationPresets.GoodForZ80
case Cpu.Intel8080 | Cpu.Intel8085 => Z80OptimizationPresets.GoodForIntel8080
case Cpu.Sharp => Z80OptimizationPresets.GoodForSharp
case _ => Nil
}
case _ =>
if (options.flag(CompilationFlag.EmitZ80Opcodes))
Z80OptimizationPresets.GoodForZ80
else if (options.flag(CompilationFlag.EmitIntel8080Opcodes))
Z80OptimizationPresets.GoodForIntel8080
else if (options.flag(CompilationFlag.EmitSharpOpcodes))
Z80OptimizationPresets.GoodForSharp
else Nil
}
// compile
@ -424,9 +426,17 @@ object Main {
boolean("-flarge-code", "-fsmall-code").action { (c, v) =>
c.changeFlag(CompilationFlag.LargeCode, v)
}.description("Whether should use 24-bit or 16-bit jumps to subroutines (not yet implemented).").hidden()
boolean("-f8085-ops", "-fno-8085-ops").action { (c, v) =>
c.changeFlag(CompilationFlag.EmitIntel8085Opcodes, v)
}.description("Whether should emit Intel 8085 opcodes.")
boolean("-fz80-ops", "-fno-z80-ops").action { (c, v) =>
c.changeFlag(CompilationFlag.EmitZ80Opcodes, v).changeFlag(CompilationFlag.EmitExtended80Opcodes, v)
}.description("Whether should emit Z80 opcodes.")
boolean("-fillegals", "-fno-illegals").action { (c, v) =>
c.changeFlag(CompilationFlag.EmitIllegals, v)
}.description("Whether should emit illegal (undocumented) NMOS opcodes. Requires -O2 or higher to have an effect.")
}.description("Whether should emit illegal (undocumented) opcodes. On 6502, requires -O2 or higher to have an effect.")
flag("-fzp-register=[0-15]").description("Set the size of the zeropage pseudoregister (6502 only).").dummy()
(0 to 15).foreach(i =>
flag("-fzp-register="+i).action(c => c.copy(zpRegisterSize = Some(i))).hidden()

View File

@ -250,11 +250,12 @@ object Platform {
"CPU_EZ80" -> toLong(cpu == Cpu.EZ80),
"CPU_8080" -> toLong(cpu == Cpu.Intel8080),
"CPU_8085" -> toLong(cpu == Cpu.Intel8085),
"CPU_8086" -> toLong(cpu == Cpu.Intel8086),
"CPU_80186" -> toLong(cpu == Cpu.Intel80186),
"CPU_GAMEBOY" -> toLong(cpu == Cpu.Sharp),
"ARCH_X86" -> toLong(CpuFamily.forType(cpu) == CpuFamily.I86),
"CPU_8086" -> toLong(cpu == Cpu.Intel8086),
"CPU_80186" -> toLong(cpu == Cpu.Intel80186),
"ARCH_6800" -> toLong(CpuFamily.forType(cpu) == CpuFamily.M6800),
"ARCH_6809" -> toLong(CpuFamily.forType(cpu) == CpuFamily.M6809),
"ARCH_ARM" -> toLong(CpuFamily.forType(cpu) == CpuFamily.ARM),
"ARCH_68K" -> toLong(CpuFamily.forType(cpu) == CpuFamily.M68K)
// TODO

View File

@ -11,10 +11,11 @@ import millfork.node.{Position, ZRegister}
*/
object ZFlag extends Enumeration {
val Z, P, C, S, H, N = Value
val Z, P, C, S, H, N, V, K = Value
val AllButSZ: Seq[Value] = Seq(P, C, H, N)
val AllButZ: Seq[Value] = Seq(P, C, H, N, S)
val All: Seq[Value] = Seq(P, C, H, N, S, Z)
}
sealed trait ZRegisters {
@ -379,6 +380,16 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case LDH_AD => s" LDH A,($parameter)"
case LD_HLSP => " LD HL,SP+" + parameter
case ADD_SP => " ADD SP," + parameter
case DSUB => " DSUB"
case RRHL => " SRA HL"
case RLDE => " RL DE"
case LD_DEHL => s" LD DE,HL+${parameter.toString}"
case LD_DESP => s" LD DE,SP+${parameter.toString}"
case RSTV => " RSTV"
case LHLX => " LD HL,(DE)"
case SHLX => " LD (DE),HL"
case EX_SP => registers match {
case OneRegister(r) => s" EX (SP),${asAssemblyString(r)}"
case _ => ???
@ -396,7 +407,6 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
}
s" $opcode$ps"
case op@(ADD | SBC | ADC) =>
val os = op.toString
val ps = (registers match {
case OneRegister(r) => s" ${asAssemblyString(r)}"
case OneRegisterOffset(r, o) => s" ${asAssemblyString(r, o)}"
@ -527,10 +537,12 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case IfFlagClear(ZFlag.Z) => s" JNZ ${parameter.toIntelString}"
case IfFlagClear(ZFlag.S) => s" JP ${parameter.toIntelString}"
case IfFlagClear(ZFlag.P) => s" JPO ${parameter.toIntelString}"
case IfFlagClear(ZFlag.K) => s" JNK ${parameter.toIntelString}"
case IfFlagSet(ZFlag.C) => s" JC ${parameter.toIntelString}"
case IfFlagSet(ZFlag.Z) => s" JZ ${parameter.toIntelString}"
case IfFlagSet(ZFlag.S) => s" JM ${parameter.toIntelString}"
case IfFlagSet(ZFlag.P) => s" JPE ${parameter.toIntelString}"
case IfFlagSet(ZFlag.K) => s" JK ${parameter.toIntelString}"
case _ => "???"
}
case CALL => registers match {
@ -603,8 +615,15 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case SCF => " STC"
case CCF => " CMC"
case EI => " EI"
case EI => " EI"
case EI => " EI"
case DSUB => " DSUB"
case RRHL => " ARHL"
case RLDE => " RLDE"
case LD_DEHL => s" LDHI ${parameter.toIntelString}"
case LD_DESP => s" LDSI ${parameter.toIntelString}"
case RSTV => " RSTV"
case LHLX => " LHLX"
case SHLX => " SHLX"
case _ => "???"
}
if (result.contains("???")) s" ??? (${this.toString.stripPrefix(" ")})" else result
@ -672,10 +691,12 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case IfFlagClear(ZFlag.Z) => " JE .next\n " -> "\n.next:"
case IfFlagClear(ZFlag.S) => " JS .next\n " -> "\n.next:"
case IfFlagClear(ZFlag.P) => " JPE .next\n " -> "\n.next:"
case IfFlagClear(ZFlag.K) => " JL .next\n " -> "\n.next:"
case IfFlagSet(ZFlag.C) => " JAE .next\n " -> "\n.next:"
case IfFlagSet(ZFlag.Z) => " JNE .next\n " -> "\n.next:"
case IfFlagSet(ZFlag.S) => " JNS .next\n " -> "\n.next:"
case IfFlagSet(ZFlag.P) => " JPO .next\n " -> "\n.next:"
case IfFlagSet(ZFlag.K) => " JGE .next\n " -> "\n.next:"
case _ => " ???" -> ""
}
prefix + (opcode match {
@ -752,6 +773,15 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case CPL => " NOT AL"
case SCF => " STC"
case CCF => " CMC"
case LD_HLSP => " LEA BX,[BX+${parameter.toIntelString}]"
case DSUB => " SUB BX, CX" // TODO: ???
case LD_DESP => s" LEA DX, [SP+${parameter.toIntelString}]"
case LD_DEHL => s" LEA DX, [BX+${parameter.toIntelString}]"
case RSTV => " JNO .next\n RST 8\n.next" // TODO
case LHLX => " MOV BX, WORD PTR [DX]"
case SHLX => " MOV WORD PTR [DX], BX"
case _ => "???"
}
if (result.contains("???")) s" ??? (${this.toString.stripPrefix(" ")})" else result
@ -862,6 +892,15 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case LDH_AD => false
case LD_HLIA | LD_HLDA => r == H || r == L | r == A
case LD_AHLI | LD_AHLD => r == H || r == L
case LD_HLSP => r == SP
case LD_DEHL => r == H || r == L
case LD_DESP => r == SP
case DSUB => r == B || r == C || r == H || r == L
case SHLX => r == D || r == E || r == H || r == L
case LHLX | RLDE => r == D || r == E
case RRHL => r == H || r == L
case _ => true // TODO
}
}
@ -991,7 +1030,7 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
}
case EX_DE_HL => r == D || r == E || r == H || r == L
case LDIR | LDDR => r == D || r == E || r == H || r == L || r == B || r == C
case JP | JR | RET | RETI | RETN |
case JP | JR | RET | RETI | RETN | SIM | RIM |
PUSH |
DISCARD_A | DISCARD_BC | DISCARD_DE | DISCARD_IX | DISCARD_IY | DISCARD_HL | DISCARD_F => false
case ADD | ADC | AND | OR | XOR | SUB | SBC | DAA | NEG | CPL | RLA | RRA | RLCA | RRCA => r == A
@ -1003,6 +1042,11 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case LDH_AC | LDH_AD => r == A
case LD_HLIA | LD_HLDA => r == H || r == L
case LD_AHLI | LD_AHLD => r == H || r == L | r == A
case LD_DESP | LD_DEHL | RLDE => r == D || r == E
case LHLX | RRHL | DSUB => r == H || r == L
case SHLX => false
case _ => true // TODO
}
}
@ -1033,6 +1077,9 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case LABEL | DI | EI | NOP => false
case LDH_AC | LDH_AD | LD_AHLI | LD_AHLD => false
case LDH_CA | LDH_DA | LD_HLIA | LD_HLDA => true
case LD_HLSP => false
case LHLX => true
case SHLX | LD_DESP | LD_DEHL | DSUB => false
case _ => true // TODO
}
}
@ -1058,6 +1105,9 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta
case LABEL | DI | EI | NOP | HALT => false
case LDH_AC | LDH_AD | LD_AHLI | LD_AHLD => true
case LDH_CA | LDH_DA | LD_HLIA | LD_HLDA => false
case LD_HLSP => false
case SHLX => true
case LHLX | LD_DESP | LD_DEHL | DSUB => false
case _ => true // TODO
}
}

View File

@ -25,6 +25,8 @@ object ZOpcode extends Enumeration {
DJNZ, JP, JR, CALL, RET, RETN, RETI, HALT,
// 8085:
RIM, SIM,
// 8085 undocumented
LD_DESP, LD_DEHL, RRHL, RLDE, DSUB, RSTV, LHLX, SHLX,
//sharp:
LD_AHLI, LD_AHLD, LD_HLIA, LD_HLDA, SWAP, LDH_DA, LDH_AD, LDH_CA, LDH_AC, LD_HLSP, ADD_SP, STOP,
DISCARD_A, DISCARD_F, DISCARD_HL, DISCARD_BC, DISCARD_DE, DISCARD_IX, DISCARD_IY, CHANGED_MEM,
@ -71,13 +73,15 @@ object ZOpcodeClasses {
val ChangesHLAlways = Set(
INI, INIR, OUTI, OUTIR, IND, INDR, OUTD, OUTDR,
LDI, LDIR, LDD, LDDR, CPI, CPIR, CPD, CPDR,
LD_AHLI, LD_AHLD, LD_HLIA, LD_HLDA,
LD_AHLI, LD_AHLD, LD_HLIA, LD_HLDA, LD_HLSP, DSUB,
RRHL, LHLX,
EXX, EX_DE_HL, CALL, JR, JP, LABEL)
val ChangesDEAlways = Set(
LDI, LDIR, LDD, LDDR,
LD_DESP, LD_DEHL, RLDE,
EXX, EX_DE_HL, CALL, JR, JP, LABEL)
val ChangesOnlyRegister: Set[ZOpcode.Value] = Set(INC, DEC, INC_16, DEC_16, POP, EX_SP, IN_C, IN_IMM, RL, RR, RLC, RRC, SLA, SRA, SRL, SLL) ++ SET ++ RES
val ChangesFirstRegister = Set(LD, LD_16, ADD_16, SBC_16)
val ChangesAAlways = Set(DAA, ADD, ADC, SUB, SBC, XOR, OR, AND, LD_AHLI, LD_AHLD, RIM)
val NonLinear = Set(JP, JR, CALL, LABEL, BYTE, EXX, EX_DE_HL, EX_SP, EXX, RET, RETI, RETN, HALT)
}
val NonLinear = Set(JP, JR, CALL, LABEL, BYTE, EXX, EX_DE_HL, EX_SP, EXX, RET, RETI, RETN, HALT, RST, RSTV)
}

View File

@ -428,8 +428,25 @@ object AlwaysGoodI80Optimizations {
(Elidable & HasOpcode(POP) & HasRegisterParam(ZRegister.AF) & DoesntMatterWhatItDoesWithFlags) ~~> { code =>
code.tail.init
},
//27-31
for5LargeRegisters(register => {
(Elidable & HasOpcode(PUSH) & HasRegisterParam(register)) ~
(Linear & IsLocallyAlignable).*.capture(1) ~
Where(ctx => ctx.isAlignableBlock(1)) ~
(Elidable & HasOpcode(POP) & HasRegisterParam(register) & DoesntMatterWhatItDoesWith(register)) ~~> { (code, ctx) =>
shallowerStack(code.tail.init)
}
}),
)
private def shallowerStack(lines: List[ZLine]): List[ZLine] = lines match {
case (x@ZLine0(LD_HLSP | LD_DESP, _, c)) :: xs => x.copy(parameter = c - 2) :: shallowerStack(xs)
case (x@ZLine0(LD_16, TwoRegisters(ZRegister.HL, ZRegister.IMM_16), c)) :: (y@ZLine0(ADD_16, TwoRegisters(ZRegister.HL, ZRegister.SP), _)) :: xs =>
x.copy(parameter = c - 2) :: y :: shallowerStack(xs)
case x :: xs => x :: shallowerStack(xs)
case Nil => Nil
}
val PointlessStackStashingFromFlow = new RuleBasedAssemblyOptimization("Pointless stack stashing from flow",
needsFlowInfo = FlowInfoRequirement.BothFlows,
// 0-4
@ -443,6 +460,18 @@ object AlwaysGoodI80Optimizations {
}),
)
val PointlessStackUnstashing = new RuleBasedAssemblyOptimization("Pointless stack unstashing",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
// 0-4
for5LargeRegisters(register => {
(Elidable & HasOpcode(POP) & HasRegisterParam(register)) ~
(Linear & Not(HasOpcodeIn(Set(POP, PUSH))) & Not(ReadsStackPointer) & Not(Concerns(register))).* ~
(Elidable & HasOpcode(PUSH) & HasRegisterParam(register) & DoesntMatterWhatItDoesWith(register)) ~~> { (code, ctx) =>
code.tail.init
}
}),
)
private def simplifiable16BitAddWithSplitTarget(targetH: ZRegister.Value, targetL: ZRegister.Value, target: ZRegister.Value, source: ZRegister.Value) = MultipleAssemblyRules(List(
(Is8BitLoad(targetH, ZRegister.IMM_8) & MatchImmediate(1)) ~
(Linear & Not(Changes(target))).* ~
@ -1249,6 +1278,11 @@ object AlwaysGoodI80Optimizations {
)
val PointlessExdehlFromFlow = new RuleBasedAssemblyOptimization("Pointless EX DE,HL from flow",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & HasOpcode(EX_DE_HL) & DoesntMatterWhatItDoesWith(DE, HL)) ~~> (_ => Nil),
)
val PointlessArithmetic = new RuleBasedAssemblyOptimization("Pointless arithmetic",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & HasOpcodeIn(Set(ADD, ADC, SUB, SBC, OR, AND, XOR, CP)) & DoesntMatterWhatItDoesWithFlags & DoesntMatterWhatItDoesWith(ZRegister.A)) ~~> (_ => Nil),
@ -1507,11 +1541,14 @@ object AlwaysGoodI80Optimizations {
ConstantInlinedShifting,
FreeHL,
LoopInvariant,
PointlessExdehl,
PointlessExdehlFromFlow,
PointlessArithmetic,
PointlessFlagChange,
PointlessLoad,
PointlessStackStashing,
PointlessStackStashingFromFlow,
PointlessStackUnstashing,
ReloadingKnownValueFromMemory,
ShiftingKnownValue,
SimplifiableMaths,

View File

@ -111,10 +111,11 @@ class ChangeRegisterPair(preferBC2DE: Boolean) extends AssemblyOptimization[ZLin
TwoRegisters(D, C) |
TwoRegisters(E, D) |
TwoRegisters(E, B), _) :: xs => false
case ZLine0(DSUB, _, _) :: xs => if (loaded.hasBC && dir == DE2BC) canOptimize(xs, dir, loaded) else false
case ZLine0(LD, TwoRegisters(r@(B|C|D|E), _), _) :: xs => canOptimize(xs, dir, loaded = loaded.load(r))
case ZLine0(LD, TwoRegistersOffset(r@(B|C|D|E), _, _), _) :: xs => canOptimize(xs, dir, loaded = loaded.load(r))
case ZLine0(LABEL | CALL | JR | JP, _, _) :: xs => canOptimize(xs, dir, loaded = Loaded())
case ZLine0(EX_DE_HL, _, _) :: xs => if (loaded.hasDE && dir == BC2DE) canOptimize(xs, dir, loaded) else false
case ZLine0(EX_DE_HL | LHLX | SHLX | LD_DEHL | LD_DESP, _, _) :: xs => if (loaded.hasDE && dir == BC2DE) canOptimize(xs, dir, loaded) else false
case ZLine0(DJNZ, _, _) :: xs => if (loaded.b && dir == DE2BC) canOptimize(xs, dir, loaded) else false
case x :: xs if !loaded.b && (x.readsRegister(B) || x.changesRegister(B)) => false
case x :: xs if !loaded.c && (x.readsRegister(C) || x.changesRegister(C)) => false

View File

@ -218,6 +218,8 @@ object ReverseFlowAnalyzer {
case ZLine0(DJNZ, _, MemoryAddressConstant(Label(l))) =>
val labelIndex = getLabelIndex(codeArray, l)
currentImportance = if (labelIndex < 0) finalImportance else (importanceArray(labelIndex) ~ currentImportance).butReadsRegister(ZRegister.B).butReadsFlag(ZFlag.Z)
case ZLine0(JP | JR, IfFlagSet(ZFlag.K) | IfFlagClear(ZFlag.K), MemoryAddressConstant(Label(l))) =>
currentImportance = finalImportance
case ZLine0(JP | JR, IfFlagSet(flag), MemoryAddressConstant(Label(l))) =>
val labelIndex = getLabelIndex(codeArray, l)
currentImportance = if (labelIndex < 0) finalImportance else importanceArray(labelIndex) ~ currentImportance.butReadsFlag(flag)
@ -247,7 +249,7 @@ object ReverseFlowAnalyzer {
currentImportance = currentImportance.butWritesRegister(t, o).butReadsRegister(s, o)
case ZLine0(LD | LD_16, TwoRegisters(t, s), _) =>
currentImportance = currentImportance.butWritesRegister(t).butReadsRegister(s)
case ZLine0(EX_DE_HL, TwoRegisters(t, s), _) =>
case ZLine0(EX_DE_HL, _, _) =>
currentImportance = currentImportance.copy(d = currentImportance.h, e = currentImportance.l, h = currentImportance.d, l = currentImportance.e)
case ZLine0(ADD_16, TwoRegisters(t, s), _) =>
currentImportance = currentImportance.butReadsRegister(t).butReadsRegister(s)
@ -461,10 +463,26 @@ object ReverseFlowAnalyzer {
currentImportance = currentImportance.butReadsRegister(ZRegister.A).copy(cf = Important, hf = Unimportant, nf = Unimportant)
case ZLine0(SCF, _, _) =>
currentImportance = currentImportance.copy(cf = Unimportant, hf = Unimportant, nf = Unimportant)
case ZLine0(LD_HLSP, _, _) =>
currentImportance = currentImportance.copy(h = Unimportant, l = Unimportant)
case ZLine0(RIM, _, _) =>
currentImportance = currentImportance.copy(a = Unimportant)
case ZLine0(SIM, _, _) =>
currentImportance = currentImportance.copy(a = Important)
case ZLine0(DSUB, _, _) =>
currentImportance = currentImportance.copy(
b = Important, c = Important, h = Important, l = Important,
zf = Unimportant, hf = Unimportant, nf = Unimportant, pf = Unimportant)
case ZLine0(SHLX, _, _) =>
currentImportance = currentImportance.copy(d = Important, e = Important, h = Important, l = Important)
case ZLine0(LHLX, _, _) =>
currentImportance = currentImportance.copy(d = Important, e = Important, h = Unimportant, l = Unimportant)
case ZLine0(LD_DEHL, _, _) =>
currentImportance = currentImportance.copy(h = Important, l = Important, d = Unimportant, e = Unimportant)
case ZLine0(LD_DESP, _, _) =>
currentImportance = currentImportance.copy(d = Unimportant, e = Unimportant)
case _ =>
currentImportance = finalImportance // TODO
}

View File

@ -8,6 +8,7 @@ import millfork.env._
import millfork.error.{FatalErrorReporting, Logger}
import millfork.node.ZRegister
import scala.annotation.tailrec
import scala.collection.mutable
/**
@ -131,7 +132,7 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions) {
if (clazz.isInstance(t)) {
t.asInstanceOf[AnyRef]
} else {
if (i eq null) {
if (t.asInstanceOf[AnyRef] eq null) {
log.fatal(s"Value at index $i is null")
} else {
throw new IllegalStateException(s"Value at index $i is a ${t.getClass.getSimpleName}, not a ${clazz.getSimpleName}")
@ -180,6 +181,24 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions) {
jumps.isEmpty
}
def isAlignableBlock(i: Int): Boolean = {
if (!isExternallyLinearBlock(i)) return false
import ZOpcode._
import ZRegister.{SP, HL, IMM_16}
@tailrec
def impl(list: List[ZLine]): Boolean = list match {
case ZLine0(PUSH | POP | CALL | RET | RETI | RETN | EX_SP | EXX | EX_AF_AF | RST | RSTV | HALT | STOP, _, _) :: _ => false
case ZLine0(LD_DESP | LD_HLSP, _, c) :: xs => if (c.isProvablyInRange(2, 127)) impl(xs) else false
case ZLine0(LD_16, TwoRegisters(HL, IMM_16), c) :: ZLine0(ADD_16, TwoRegisters(HL, SP), _) :: xs => if (c.isProvablyInRange(2, 127)) impl(xs) else false
case ZLine0(_, TwoRegisters(SP, _), _) :: _ => false
case ZLine0(_, TwoRegisters(_, SP), _) :: _ => false
case ZLine0(_, OneRegister(SP), _) :: _ => false
case _ :: xs => impl(xs)
case Nil => true
}
impl(get[List[ZLine]](i))
}
def areCompatibleForLoad(target: Int, source: Int): Boolean = {
val t = get[RegisterAndOffset](target).register
val s = get[RegisterAndOffset](source).register
@ -696,7 +715,7 @@ case object DoesntMatterWhatItDoesWithFlags extends AssemblyLinePattern {
FlowInfoRequirement.assertBackward(needsFlowInfo)
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean =
ZFlag.values.forall(r => flowInfo.importanceAfter.getFlag(r) != Important)
ZFlag.All.forall(r => flowInfo.importanceAfter.getFlag(r) != Important)
override def toString: String = "[¯\\_(ツ)_/¯:F]"
@ -734,7 +753,7 @@ case object DoesntMatterWhatItDoesWithFlagsExceptCarry extends AssemblyLinePatte
FlowInfoRequirement.assertBackward(needsFlowInfo)
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: ZLine): Boolean =
ZFlag.values.forall(r => r == ZFlag.C || flowInfo.importanceAfter.getFlag(r) != Important)
ZFlag.All.forall(r => r == ZFlag.C || flowInfo.importanceAfter.getFlag(r) != Important)
override def toString: String = "[¯\\_(ツ)_/¯:F\\C]"
@ -871,7 +890,7 @@ case object ReadsStackPointer extends TrivialAssemblyLinePattern {
case OneRegister(ZRegister.SP) => true
case _ => false
}
case LD_HLSP | PUSH | POP => true
case LD_HLSP | LD_DESP | PUSH | POP => true
case _ => false
}
}
@ -879,6 +898,26 @@ case object ReadsStackPointer extends TrivialAssemblyLinePattern {
override def hitRate: Double = 0.2 // ?
}
case object IsLocallyAlignable extends TrivialAssemblyLinePattern {
override def apply(line: ZLine): Boolean = {
import ZOpcode._
line.opcode match {
case CALL | PUSH | POP | EX_SP | RST | RSTV | RET | RETN | RETI | EXX | EX_AF_AF | HALT | STOP => false
case LD_HLSP | LD_DESP => line.parameter.isProvablyInRange(2, 127)
case ADD_16 => true
case LD_16 => line.registers match {
case TwoRegisters(_, ZRegister.SP) => false
case TwoRegisters(ZRegister.SP, _) => false
case OneRegister(ZRegister.SP) => false
case _ => true
}
case _ => true
}
}
override def hitRate: Double = 0.7 // ?
}
case class Changes(register: ZRegister.Value) extends TrivialAssemblyLinePattern {
override def apply(line: ZLine): Boolean = line.changesRegister(register)

View File

@ -1,11 +1,10 @@
package millfork.compiler
import millfork.assembly.AbstractCode
import millfork.assembly.mos.Opcode._
import millfork.assembly.mos._
import millfork.assembly.z80.ZOpcode
import millfork.env._
import millfork.error.ConsoleLogger
import millfork.node.{MosRegister, _}
import millfork.node._
/**
* @author Karol Stasiak
@ -60,6 +59,7 @@ abstract class MacroExpander[T <: AbstractCode] {
if (params.length != normalParams.length) {
ctx.log.error(s"Invalid number of params for macro function ${i.name}", position)
} else {
normalParams.foreach(param => i.environment.removeVariable(param.name))
params.zip(normalParams).foreach {
case (v@VariableExpression(_), MemoryVariable(paramName, paramType, _)) =>
actualCode = actualCode.map(stmt => replaceVariable(stmt, paramName.stripPrefix(i.environment.prefix), v).asInstanceOf[ExecutableStatement])
@ -74,13 +74,16 @@ abstract class MacroExpander[T <: AbstractCode] {
// fix local labels:
// TODO: do it even if the labels are in an inline assembly block inside a Millfork function
val localLabels = actualCode.flatMap {
case MosAssemblyStatement(LABEL, _, VariableExpression(l), _) => Some(l)
case MosAssemblyStatement(Opcode.LABEL, _, VariableExpression(l), _) => Some(l)
case Z80AssemblyStatement(ZOpcode.LABEL, _, _, VariableExpression(l), _) => Some(l)
case _ => None
}.toSet
val labelPrefix = ctx.nextLabel("il")
paramPreparation -> actualCode.map {
case s@MosAssemblyStatement(_, _, VariableExpression(v), _) if localLabels(v) =>
s.copy(expression = VariableExpression(labelPrefix + v))
case s@Z80AssemblyStatement(_, _, _, VariableExpression(v), _) if localLabels(v) =>
s.copy(expression = VariableExpression(labelPrefix + v))
case s => s
}
}

View File

@ -136,6 +136,7 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
def fixTsx(ctx: CompilationContext, code: List[ZLine]): List[ZLine] = code match {
case (ldhlsp@ZLine0(LD_HLSP, _, param)) :: xs => ldhlsp.copy(parameter = param + 2) :: fixTsx(ctx, xs)
case (lddesp@ZLine0(LD_DESP, _, param)) :: xs => lddesp.copy(parameter = param + 2) :: fixTsx(ctx, xs)
case (ldhl@ZLine0(LD_16, TwoRegisters(ZRegister.HL, ZRegister.IMM_16), param)) ::
(addhlsp@ZLine0(ADD_16, TwoRegisters(ZRegister.HL, ZRegister.SP), _)) ::
(ldsphl@ZLine0(LD_16, TwoRegisters(ZRegister.SP, ZRegister.HL), _)) ::
@ -397,7 +398,11 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
// TODO: signed words
case ZExpressionTarget.NOTHING => Nil
case ZExpressionTarget.HL =>
loadHL ++ List(ZLine.ld8(A,MEM_HL), ZLine.register(INC_16, HL), ZLine.ld8(H, MEM_HL), ZLine.ld8(L, A))
if (ctx.options.flag(CompilationFlag.EmitIntel8085Opcodes) && ctx.options.flag(CompilationFlag.EmitIllegals)) {
List(ZLine.imm8(LD_DESP, ctx.extraStackOffset + v.baseOffset), ZLine.implied(LHLX))
} else {
loadHL ++ List(ZLine.ld8(A, MEM_HL), ZLine.register(INC_16, HL), ZLine.ld8(H, MEM_HL), ZLine.ld8(L, A))
}
case ZExpressionTarget.BC =>
loadHL ++ List(ZLine.ld8(C,MEM_HL), ZLine.register(INC_16, HL), ZLine.ld8(B, MEM_HL))
case ZExpressionTarget.DE =>
@ -943,11 +948,11 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
case Some((lvo@LocalVariableAddressViaIX(offset), code)) =>
code ++
Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(lvo), r) ++
storeHLViaIX(ctx, offset, 2, false)
storeHLViaIX(ctx, offset, 2, signedSource = false)
case Some((lvo@LocalVariableAddressViaIY(offset), code)) =>
code ++
Z80Multiply.compileUnsignedWordByByteDivision(ctx, Left(lvo), r) ++
storeHLViaIY(ctx, offset, 2, false)
storeHLViaIY(ctx, offset, 2, signedSource = false)
case _ =>
ctx.log.error("Invalid left-hand side", l.position)
Nil
@ -959,10 +964,10 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
val (l, r, size) = assertArithmeticAssignmentLike(ctx, params)
size match {
case 1 =>
targetifyA(ctx, target, Z80Multiply.compileUnsignedByteDivision(ctx, Right(l), r, f.functionName == "%%"), false)
targetifyA(ctx, target, Z80Multiply.compileUnsignedByteDivision(ctx, Right(l), r, f.functionName == "%%"), isSigned = false)
case 2 =>
if (f.functionName == "%%") {
targetifyA(ctx, target, Z80Multiply.compileUnsignedWordByByteDivision(ctx, Right(l), r), false)
targetifyA(ctx, target, Z80Multiply.compileUnsignedWordByByteDivision(ctx, Right(l), r), isSigned = false)
} else {
targetifyHL(ctx, target, Z80Multiply.compileUnsignedWordByByteDivision(ctx, Right(l), r))
}
@ -1449,6 +1454,11 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
storeHLViaIX(ctx, v.baseOffset, v.typ.size, signedSource)
} else if (ctx.options.flag(CompilationFlag.UseIyForStack)){
storeHLViaIY(ctx, v.baseOffset, v.typ.size, signedSource)
} else if (ctx.options.flag(CompilationFlag.EmitIntel8085Opcodes) && ctx.options.flag(CompilationFlag.EmitIllegals)) {
List(ZLine.register(PUSH, DE),
ZLine.imm8(LD_DESP, v.baseOffset + 2),
ZLine.implied(SHLX),
ZLine.register(POP, DE))
} else if (ctx.options.flag(CompilationFlag.EmitIntel8080Opcodes)) {
List(
ZLine.register(PUSH, DE),
@ -1483,11 +1493,18 @@ object Z80ExpressionCompiler extends AbstractExpressionCompiler[ZLine] {
Z80ExpressionCompiler.stashHLIfChanged(ctx, ZLine.ld8(ZRegister.A, ZRegister.L) :: storeA(ctx, lo, signedSource)) ++
(ZLine.ld8(ZRegister.A, ZRegister.H) :: storeA(ctx, hi, signedSource))
case e:DerefExpression =>
List(ZLine.register(PUSH, ZRegister.HL)) ++ compileDerefPointer(ctx, e) ++ List(
ZLine.register(POP, ZRegister.BC),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.C),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.B))
if (ctx.options.flag(CompilationFlag.EmitIntel8085Opcodes) && ctx.options.flag(CompilationFlag.EmitIllegals)) {
List(ZLine.register(PUSH, ZRegister.HL)) ++ compileDerefPointer(ctx, e) ++ List(
ZLine.register(POP, ZRegister.DE),
ZLine.implied(EX_DE_HL),
ZLine.implied(SHLX))
} else {
List(ZLine.register(PUSH, ZRegister.HL)) ++ compileDerefPointer(ctx, e) ++ List(
ZLine.register(POP, ZRegister.BC),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.C),
ZLine.register(INC_16, ZRegister.HL),
ZLine.ld8(ZRegister.MEM_HL, ZRegister.B))
}
case _: SeparateBytesExpression =>
ctx.log.error("Invalid `:`", target.position)
Nil

View File

@ -280,15 +280,21 @@ object ZBuiltIns {
} else {
if (ctx.options.flag(CompilationFlag.EmitZ80Opcodes)) {
// TODO: optimize
result += ZLine.ld8(ZRegister.D, ZRegister.H)
result += ZLine.ld8(ZRegister.E, ZRegister.L)
result += ZLine.implied(EX_DE_HL)
result ++= Z80ExpressionCompiler.stashDEIfChanged(ctx, Z80ExpressionCompiler.compileToHL(ctx, expr))
result += ZLine.ld8(ZRegister.B, ZRegister.H)
result += ZLine.ld8(ZRegister.C, ZRegister.L)
result += ZLine.ld8(ZRegister.H, ZRegister.D)
result += ZLine.ld8(ZRegister.L, ZRegister.E)
result += ZLine.implied(EX_DE_HL)
result += ZLine.register(OR, ZRegister.A)
result += ZLine.registers(SBC_16, ZRegister.HL, ZRegister.BC)
} else if (ctx.options.flag(CompilationFlag.EmitIntel8085Opcodes) && ctx.options.flag(CompilationFlag.EmitIllegals)) {
// TODO: optimize
result += ZLine.implied(EX_DE_HL)
result ++= Z80ExpressionCompiler.stashDEIfChanged(ctx, Z80ExpressionCompiler.compileToHL(ctx, expr))
result += ZLine.ld8(ZRegister.B, ZRegister.H)
result += ZLine.ld8(ZRegister.C, ZRegister.L)
result += ZLine.implied(EX_DE_HL)
result += ZLine.implied(DSUB)
} else {
// TODO: optimize
result += ZLine.ld8(ZRegister.D, ZRegister.H)
@ -309,10 +315,21 @@ object ZBuiltIns {
}
}
if (hasConst) {
result ++= List(
ZLine.ldImm16(ZRegister.DE, const),
ZLine.registers(ADD_16, ZRegister.HL, ZRegister.DE)
)
if (const.isProvablyZero) {
// do nothing
} else if (const.isProvably(1)) {
result += ZLine.register(INC_16, ZRegister.HL)
} else if (ctx.options.flag(CompilationFlag.EmitIntel8085Opcodes) && ctx.options.flag(CompilationFlag.EmitIllegals) && const.isProvablyInRange(1, 127)) {
result ++= List(
ZLine.imm8(LD_DEHL, const),
ZLine.implied(EX_DE_HL)
)
} else {
result ++= List(
ZLine.ldImm16(ZRegister.DE, const),
ZLine.registers(ADD_16, ZRegister.HL, ZRegister.DE)
)
}
}
result.toList
}

View File

@ -22,6 +22,7 @@ sealed trait Constant {
def isProvablyZero: Boolean = false
def isProvably(value: Int): Boolean = false
def isProvablyInRange(startInclusive: Int, endInclusive: Int): Boolean = false
def isProvablyNonnegative: Boolean = false
def isProvablyGreaterOrEqualThan(other: Constant): Boolean = other match {
case NumericConstant(0, _) => true
@ -108,6 +109,7 @@ case class AssertByte(c: Constant) extends Constant {
override def isProvablyZero: Boolean = c.isProvablyZero
override def isProvably(i: Int): Boolean = c.isProvably(i)
override def isProvablyNonnegative: Boolean = c.isProvablyNonnegative
override def isProvablyInRange(startInclusive: Int, endInclusive: Int): Boolean = c.isProvablyInRange(startInclusive, endInclusive)
override def fitsProvablyIntoByte: Boolean = true
override def requiredSize: Int = 1
@ -142,6 +144,7 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant {
case NumericConstant(o, _) if o >= 0 => value >= o
case _ => false
})
override def isProvablyInRange(startInclusive: Int, endInclusive: Int): Boolean = value >= startInclusive && value <= endInclusive
override def isProvablyZero: Boolean = value == 0
override def isProvably(i: Int): Boolean = value == i
override def isProvablyNonnegative: Boolean = value >= 0
@ -200,6 +203,10 @@ case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant {
override def isProvablyGreaterOrEqualThan(other: Constant): Boolean = other match {
case NumericConstant(0, _) => true
case MemoryAddressConstant(otherThing) => thing == otherThing
case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(otherThing), c) =>
thing == otherThing && c.isProvablyNonnegative
case CompoundConstant(MathOperator.Plus, c, MemoryAddressConstant(otherThing)) =>
thing == otherThing && c.isProvablyNonnegative
case _ => false
}
@ -215,7 +222,7 @@ case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant {
case _ => false
}
override def fitsProvablyIntoByte: Boolean = thing.zeropage
override def fitsProvablyIntoByte: Boolean = thing.zeropage // TODO: check if it's true only on 6502
override def requiredSize = 2

View File

@ -63,6 +63,8 @@ class Z80Assembler(program: Program,
def requireIntel8085(): Unit = if (!options.flag(EmitIntel8085Opcodes)) log.error("Unsupported instruction: " + instr)
def requireIntel8085Illegals(): Unit = if (!options.flag(EmitIntel8085Opcodes) || !options.flag(EmitIllegals)) log.error("Unsupported instruction: " + instr)
def useSharpOpcodes():Boolean = {
if (!options.flag(EmitSharpOpcodes) && !options.flag(EmitIntel8080Opcodes))
log.error("Cannot determine which variant to emit : " + instr)
@ -229,6 +231,14 @@ class Z80Assembler(program: Program,
writeByte(bank, index, prefixByte(ix))
writeByte(bank, index + 1, 0xF9)
index + 2
case ZLine0(SRA, OneRegister(HL), _) =>
requireIntel8085Illegals()
writeByte(bank, index, 0x10)
index + 1
case ZLine0(RL, OneRegister(DE), _) =>
requireIntel8085Illegals()
writeByte(bank, index, 0x18)
index + 1
case ZLine0(op, OneRegister(ZRegister.IMM_8), param) if immediates.contains(op) =>
val o = immediates(op)
writeByte(bank, index, o)
@ -462,6 +472,11 @@ class Z80Assembler(program: Program,
writeByte(bank, index, 0xf2)
writeWord(bank, index + 1, param)
index + 3
case ZLine0(JP, IfFlagClear(ZFlag.K), param) =>
requireIntel8085Illegals()
writeByte(bank, index, 0xdd)
writeWord(bank, index + 1, param)
index + 3
case ZLine0(JP, IfFlagSet(ZFlag.Z), param) =>
writeByte(bank, index, 0xca)
@ -481,6 +496,11 @@ class Z80Assembler(program: Program,
writeByte(bank, index, 0xfa)
writeWord(bank, index + 1, param)
index + 3
case ZLine0(JP, IfFlagSet(ZFlag.K), param) =>
requireIntel8085Illegals()
writeByte(bank, index, 0xfd)
writeWord(bank, index + 1, param)
index + 3
case ZLine0(JP, OneRegister(HL), _) =>
writeByte(bank, index, 0xe9)
index + 1
@ -624,6 +644,40 @@ class Z80Assembler(program: Program,
requireSharp()
writeByte(bank, index, 0x10)
index + 1
case ZLine0(DSUB, _, _) =>
requireIntel8085Illegals()
writeByte(bank, index, 0x08)
index + 1
case ZLine0(LD_DEHL, _, param) =>
requireIntel8085Illegals()
writeByte(bank, index, 0x28)
writeByte(bank, index + 1, param)
index + 2
case ZLine0(LD_DESP, _, param) =>
requireIntel8085Illegals()
writeByte(bank, index, 0x38)
writeByte(bank, index + 1, param)
index + 2
case ZLine0(RSTV, _, _) =>
requireIntel8085Illegals()
writeByte(bank, index, 0xcb)
index + 1
case ZLine0(SHLX, _, _) =>
requireIntel8085Illegals()
writeByte(bank, index, 0xd9)
index + 1
case ZLine0(LHLX, _, _) =>
requireIntel8085Illegals()
writeByte(bank, index, 0xed)
index + 1
case ZLine0(RLDE, _, _) =>
requireIntel8085Illegals()
writeByte(bank, index, 0x18)
index + 1
case ZLine0(RRHL, _, _) =>
requireIntel8085Illegals()
writeByte(bank, index, 0x10)
index + 1
case _ =>
log.fatal("Cannot assemble " + instr)
index

View File

@ -63,6 +63,8 @@ class Z80ToX86Crossassembler(program: Program,
case IfFlagClear(ZFlag.Z) => 0x74 // JE
case IfFlagSet(ZFlag.P) => 0x7b // JPO
case IfFlagClear(ZFlag.P) => 0x7a // JPE
case IfFlagSet(ZFlag.K) => 0x7d // JGE
case IfFlagClear(ZFlag.K) => 0x7c // JL
}
override def emitInstruction(bank: String, options: CompilationOptions, index: Int, instr: ZLine): Int = {
@ -344,6 +346,47 @@ class Z80ToX86Crossassembler(program: Program,
writeByte(bank, index + 1, param)
index + 2
case ZLine0(LD_DEHL, _, param) =>
writeByte(bank, index, 0x8d)
writeByte(bank, index + 1 , 0x57)
writeByte(bank, index + 2, param)
index + 3
case ZLine0(LD_DESP, _, param) =>
writeByte(bank, index, 0x89)
writeByte(bank, index + 1, 0xe6)
writeByte(bank, index + 2, 0x8d)
writeByte(bank, index + 3 , 0x54)
writeByte(bank, index + 4, param)
index + 5
case ZLine0(LD_HLSP, _, param) =>
writeByte(bank, index, 0x89)
writeByte(bank, index + 1, 0xe6)
writeByte(bank, index + 2, 0x8d)
writeByte(bank, index + 3 , 0x5c)
writeByte(bank, index + 4, param)
index + 5
case ZLine0(DSUB, _, param) =>
writeByte(bank, index, 0x29)
writeByte(bank, index + 1, 0xcb)
index + 2
case ZLine0(SHLX, _, param) =>
writeByte(bank, index, 0x89)
writeByte(bank, index + 1, 0xd6)
writeByte(bank, index + 2, 0x89)
writeByte(bank, index + 3 , 0x1c)
index + 4
case ZLine0(LHLX, _, param) =>
writeByte(bank, index, 0x89)
writeByte(bank, index + 1, 0xd6)
writeByte(bank, index + 2, 0x8b)
writeByte(bank, index + 3 , 0x1c)
index + 4
case _ =>
println("TODO: " + instr)
???

View File

@ -138,6 +138,16 @@ case class Z80Parser(filename: String,
case (reg, addr) => (op, OneRegister(reg), None, addr.getOrElse(zero))
}
def one8RegisterOr8085Illegal(op: ZOpcode.Value, illegalTarget: ZRegister.Value, illegalOp: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Option[Expression], Expression)] = param(allowAbsolute = false).map{
case (reg@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(e)) => (op, OneRegister(reg), Some(e), zero)
case (reg, _) if reg == illegalTarget && options.flag(CompilationFlag.EmitIntel8085Opcodes) && options.flag(CompilationFlag.EmitIllegals) =>
(illegalOp, NoRegisters, None, zero)
case (reg, _) if reg == illegalTarget =>
log.error("This instruction requires support for undocumented 8085 instructions: " + ZLine.register(op, reg))
(op, OneRegister(ZRegister.A), None, zero)
case (reg, addr) => (op, OneRegister(reg), None, addr.getOrElse(zero))
}
def one8Or16Register(op8: ZOpcode.Value, op16: ZOpcode.Value): P[(ZOpcode.Value, OneRegister, Option[Expression], Expression)] = param(allowAbsolute = false).map{
case (reg@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(e)) => (op8, OneRegister(reg), Some(e), zero)
case (reg@(ZRegister.HL | ZRegister.DE | ZRegister.AF | ZRegister.SP | ZRegister.BC | ZRegister.IX | ZRegister.IY), addr) => (op16, OneRegister(reg), None, addr.getOrElse(zero))
@ -152,10 +162,19 @@ case class Z80Parser(filename: String,
}
private val jumpCondition: P[ZRegisters] = (HWS ~ (
if (options.flag(CompilationFlag.EmitIllegals) && options.flag(CompilationFlag.EmitIntel8085Opcodes))
"NZ" | "nz" | "nc" | "NC" | "NV" | "nv" |
"PO" | "po" | "PE" | "pe" |
"m" | "M" | "p" | "P" |
"c" | "C" | "Z" | "z" | "V" | "v").! ~ HWS).map {
"c" | "C" | "Z" | "z" | "V" | "v" |
"k" | "K" | "nk" | "NK" |
"x5" | "X5" | "nx5" | "NX5"
else
"NZ" | "nz" | "nc" | "NC" | "NV" | "nv" |
"PO" | "po" | "PE" | "pe" |
"m" | "M" | "p" | "P" |
"c" | "C" | "Z" | "z" | "V" | "v"
).! ~ HWS).map {
case "Z" | "z" => IfFlagSet(ZFlag.Z)
case "PE" | "pe" | "v" | "V" => IfFlagSet(ZFlag.P)
case "C" | "c" => IfFlagSet(ZFlag.C)
@ -164,6 +183,8 @@ case class Z80Parser(filename: String,
case "PO" | "po" | "NV" | "nv" => IfFlagClear(ZFlag.P)
case "NC" | "nc" => IfFlagClear(ZFlag.C)
case "P" | "p" => IfFlagClear(ZFlag.S)
case "K" | "k" | "X5" | "x5" => IfFlagSet(ZFlag.K)
case "NK" | "nk" |"NX5" | "nx5" => IfFlagClear(ZFlag.K)
case _ => NoRegisters // shouldn't happen
}
@ -265,17 +286,17 @@ case class Z80Parser(filename: String,
case "RRA" => imm(RRA)
case "RLCA" => imm(RLCA)
case "RRCA" => imm(RRCA)
case "RL" => one8Register(RL)
case "RL" => one8RegisterOr8085Illegal(RL, ZRegister.DE, RLDE)
case "RR" => one8Register(RR)
case "RLC" => one8Register(RLC)
case "RRC" => one8Register(RRC)
case "SLA" => one8Register(SLA)
case "SLL" => one8Register(SLL)
case "SRA" => one8Register(SRA)
case "SRA" => one8RegisterOr8085Illegal(SRA, ZRegister.HL, RRHL)
case "SRL" => one8Register(SRL)
case "SWAP" => one8Register(SWAP)
case "BIT" => (param(false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(false)).map {
case "BIT" => (param(allowAbsolute = false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(allowAbsolute = false)).map {
case (ZRegister.IMM_8, Some(LiteralExpression(n, _)), (r2, e2))
if n >= 0 && n <= 7 && r2 != ZRegister.MEM_BC && r2 != ZRegister.MEM_DE =>
(ZOpcodeClasses.BIT_seq(n.toInt), OneRegister(r2), e2, zero)
@ -283,7 +304,7 @@ case class Z80Parser(filename: String,
log.error("Invalid parameters for BIT", Some(pos))
(NOP, NoRegisters, None, zero)
}
case "SET" => (param(false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(false)).map {
case "SET" => (param(allowAbsolute = false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(allowAbsolute = false)).map {
case (ZRegister.IMM_8, Some(LiteralExpression(n, _)), (r2, e2))
if n >= 0 && n <= 7 && r2 != ZRegister.MEM_BC && r2 != ZRegister.MEM_DE =>
(ZOpcodeClasses.SET_seq(n.toInt), OneRegister(r2), e2, zero)
@ -291,7 +312,7 @@ case class Z80Parser(filename: String,
log.error("Invalid parameters for SET", Some(pos))
(NOP, NoRegisters, None, zero)
}
case "RES" => (param(false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(false)).map {
case "RES" => (param(allowAbsolute = false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(allowAbsolute = false)).map {
case (ZRegister.IMM_8, Some(LiteralExpression(n, _)), (r2, e2))
if n >= 0 && n <= 7 && r2 != ZRegister.MEM_BC && r2 != ZRegister.MEM_DE =>
(ZOpcodeClasses.RES_seq(n.toInt), OneRegister(r2), e2, zero)
@ -388,6 +409,20 @@ case class Z80Parser(filename: String,
(NOP, NoRegisters, None, zero)
}
case "LD" => (param(allowAbsolute = true, allowRI = true) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ param(allowAbsolute = true, allowRI = true)).map {
case (ZRegister.DE, None, (ZRegister.IMM_8 | ZRegister.IMM_16, Some(SumExpression((false, VariableExpression("sp" | "SP")) :: offset, false))))
if options.flags(CompilationFlag.EmitIntel8085Opcodes) && options.flags(CompilationFlag.EmitIllegals) =>
(LD_DESP, OneRegister(ZRegister.IMM_8), None, offset match {
case List((false, expr)) => expr
case (true, _) :: _ => SumExpression((false -> LiteralExpression(0, 1)) :: offset, decimal = false)
case _ => SumExpression(offset, decimal = false)
})
case (ZRegister.DE, None, (ZRegister.IMM_8 | ZRegister.IMM_16, Some(SumExpression((false, VariableExpression("hl" | "HL")) :: offset, false))))
if options.flags(CompilationFlag.EmitIntel8085Opcodes) && options.flags(CompilationFlag.EmitIllegals) =>
(LD_DEHL, OneRegister(ZRegister.IMM_8), None, offset match {
case List((false, expr)) => expr
case (true, _) :: _ => SumExpression((false -> LiteralExpression(0, 1)) :: offset, decimal = false)
case _ => SumExpression(offset, decimal = false)
})
case (ZRegister.HL, None, (ZRegister.IMM_8 | ZRegister.IMM_16, Some(SumExpression((false, VariableExpression("sp" | "SP")) :: offset, false))))
if options.flags(CompilationFlag.EmitSharpOpcodes) =>
(LD_HLSP, OneRegister(ZRegister.IMM_8), None, offset match {
@ -395,6 +430,12 @@ case class Z80Parser(filename: String,
case (true, _) :: _ => SumExpression((false -> LiteralExpression(0, 1)) :: offset, decimal = false)
case _ => SumExpression(offset, decimal = false)
})
case (ZRegister.MEM_DE, None, (ZRegister.HL, None))
if options.flags(CompilationFlag.EmitIntel8085Opcodes) && options.flags(CompilationFlag.EmitIllegals) =>
(SHLX, NoRegisters, None, zero)
case (ZRegister.HL, None, (ZRegister.MEM_DE, None))
if options.flags(CompilationFlag.EmitIntel8085Opcodes) && options.flags(CompilationFlag.EmitIllegals) =>
(SHLX, NoRegisters, None, zero)
case (ZRegister.A, None, (ZRegister.MEM_ABS_8, Some(VariableExpression("HLI" | "hli"))))
if options.flags(CompilationFlag.EmitSharpOpcodes) =>
(LD_AHLI, NoRegisters, None, zero)
@ -422,6 +463,9 @@ case class Z80Parser(filename: String,
case (r1, e1, (r2, e2)) => merge(SBC, SBC_16, skipTargetA = true)((r1, e1, r2, e2))
}
case "DSUB" => imm(DSUB)
case "RSTV" => imm(RSTV)
case _ =>
log.error("Unsupported opcode " + opcode, Some(pos))
imm(NOP)
@ -550,7 +594,18 @@ case class Z80Parser(filename: String,
case "CNZ" => asmExpression.map { e => (CALL, IfFlagClear(ZFlag.Z), None, e)}
case "CP" => asmExpression.map { e => (CALL, IfFlagClear(ZFlag.S), None, e)}
case "CPO" => asmExpression.map { e => (CALL, IfFlagClear(ZFlag.P), None, e)}
case "DSUB" => imm(DSUB)
case "JNK" | "JNX5" => asmExpression.map { e => (JP, IfFlagClear(ZFlag.K), None, e)}
case "JK" | "JX5" => asmExpression.map { e => (JP, IfFlagSet(ZFlag.K), None, e)}
case "RRHL" | "ARHL" => imm(RRHL)
case "RLDE" | "RDEL" => imm(RLDE)
case "RSTV" | "OVRST8" => imm(RSTV)
case "LDSI" => asmExpression.map { e => (LD_DESP, NoRegisters, None, e)}
case "LDHI" => asmExpression.map { e => (LD_DEHL, NoRegisters, None, e)}
case "SHLDE" | "SHLX" => imm(SHLX)
case "LHLDE" | "LHLX" =>imm(LHLX)
case _ =>
log.error("Unsupported opcode " + opcode, Some(pos))
imm(NOP)

View File

@ -1,7 +1,7 @@
package millfork.test
import millfork.Cpu
import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun}
import millfork.test.emu.{EmuBenchmarkRun, EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun}
import org.scalatest.{FunSuite, Matchers}
/**
@ -29,7 +29,7 @@ class MacroSuite extends FunSuite with Matchers {
}
test("Macros in assembly") {
EmuBenchmarkRun(
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086)(
"""
| macro void run(byte x) {
| output = x
@ -48,4 +48,29 @@ class MacroSuite extends FunSuite with Matchers {
m.readByte(0xc000) should equal(7)
}
}
test("Macros with loops and clashing variable names") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80, Cpu.Intel8086)(
"""
| macro void run(byte x) {
| while x != 0 {
| output += 1
| x -= 1
| }
| }
|
| byte output @$c000
|
| void main () {
| output = 0
| byte x
| x = 3
| run(x)
| x = 4
| run(x)
| }
""".stripMargin) { m =>
m.readByte(0xc000) should equal(7)
}
}
}

View File

@ -10,7 +10,7 @@ import org.scalatest.{FunSuite, Matchers}
class StackVarSuite extends FunSuite with Matchers {
test("Basic stack assignment") {
EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Intel8085, Cpu.Sharp, Cpu.Intel8086)("""
| byte output @$c000
| void main () {
| stack byte a
@ -91,7 +91,7 @@ class StackVarSuite extends FunSuite with Matchers {
}
test("Stack word addition") {
EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Intel8085, Cpu.Sharp, Cpu.Intel8086)("""
| word output @$c000
| void main () {
| stack word a
@ -109,7 +109,7 @@ class StackVarSuite extends FunSuite with Matchers {
}
test("Recursion") {
EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
EmuCrossPlatformBenchmarkRun(Cpu.StrictMos, Cpu.Z80, Cpu.Intel8080, Cpu.Intel8085, Cpu.Sharp, Cpu.Intel8086)("""
| array output [6] @$c000
| byte fails @$c010
| void main () {

View File

@ -9,7 +9,7 @@ import org.scalatest.{AppendedClues, FunSuite, Matchers}
class WordMathSuite extends FunSuite with Matchers with AppendedClues {
test("Word addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Intel8085, Cpu.Sharp, Cpu.Intel8086)("""
| word output @$c000
| word a
| void main () {
@ -21,7 +21,7 @@ class WordMathSuite extends FunSuite with Matchers with AppendedClues {
}
test("Cast word addition") {
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Intel8085, Cpu.Sharp, Cpu.Intel8086)("""
| byte output @$c000
| word a
| void main () {
@ -34,7 +34,7 @@ class WordMathSuite extends FunSuite with Matchers with AppendedClues {
}
test("Word subtraction") {
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Mos, Cpu.Z80, Cpu.Intel8080, Cpu.Intel8085, Cpu.Sharp, Cpu.Intel8086)("""
| word output @$c000
| word a
| void main () {
@ -46,7 +46,7 @@ class WordMathSuite extends FunSuite with Matchers with AppendedClues {
}
test("Word subtraction 2") {
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Intel8085, Cpu.Sharp, Cpu.Intel8086)("""
| word output @$c000
| word a
| void main () {
@ -58,7 +58,7 @@ class WordMathSuite extends FunSuite with Matchers with AppendedClues {
}
test("Word subtraction 3") {
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp, Cpu.Intel8086)("""
EmuCrossPlatformBenchmarkRun(Cpu.Sixteen, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Intel8085, Cpu.Sharp, Cpu.Intel8086)("""
| word output @$c000
| word a
| void main () {

View File

@ -919,7 +919,7 @@ class Z80AssemblySuite extends FunSuite with Matchers {
test("Intel 8085 instructions (Intel syntax)") {
EmuUnoptimizedIntel8085Run(
"""
| #pragma intelg_syntax
| #pragma intel_syntax
| asm void main () {
| ret
| rim
@ -928,4 +928,51 @@ class Z80AssemblySuite extends FunSuite with Matchers {
| }
""".stripMargin)
}
test("Illegal Intel 8085 instructions (Intel syntax)") {
EmuUnoptimizedIntel8085Run(
"""
| #pragma intel_syntax
| asm void main () {
| ret
| lhlx
| shlx
| arhl
| rlde
| ldhi 5
| ldsi 6
| rstv
| ovrst8
| jk main
| jnk main
| jx5 main
| jnx5 main
| dsub
| ret
| }
""".stripMargin)
}
test("Illegal Intel 8085 instructions (Zilog syntax)") {
EmuUnoptimizedIntel8085Run(
"""
| #pragma zilog_syntax
| asm void main () {
| ret
| ld hl,(de)
| ld (de), hl
| sra hl
| rl de
| ld de,hl+5
| ld de,sp+6
| rstv
| jp k, main
| jp nk, main
| jp x5, main
| jp nx5, main
| dsub
| ret
| }
""".stripMargin)
}
}

View File

@ -82,9 +82,11 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
CompilationFlag.OptimizeStdlib -> this.inline,
CompilationFlag.OptimizeForSize -> this.optimizeForSize,
CompilationFlag.SubroutineExtraction -> optimizeForSize,
CompilationFlag.EmitIllegals -> (cpu == millfork.Cpu.Z80),
CompilationFlag.EmitIllegals -> (cpu == millfork.Cpu.Z80 || cpu == millfork.Cpu.Intel8085),
CompilationFlag.LenientTextEncoding -> true)
val options = CompilationOptions(platform, millfork.Cpu.defaultFlags(cpu).map(_ -> true).toMap ++ extraFlags, None, 0, Map(), JobContext(log, new LabelGenerator))
println(cpu)
println(options.flags.filter(_._2).keys.toSeq.sorted)
log.hasErrors = false
log.verbosity = 999
var effectiveSource = source
@ -201,6 +203,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
}
Timings(ticks, ticks) -> memoryBank
case _ =>
// e.g. 8085 with illegals
Timings(-1, -1) -> memoryBank
}
log.clearErrors()