1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-04-08 18:37:17 +00:00

Preliminary support for 65816, 65CE02 and HuC6280

This commit is contained in:
Karol Stasiak 2018-03-03 01:21:57 +01:00
parent b3e96a0c6e
commit 6ed5d51260
42 changed files with 2119 additions and 333 deletions

View File

@ -4,6 +4,8 @@
* **Breaking change!** Renamed `inline` to `macro`.
* Added preliminary support for 65CE02, HuC6280 and 65816 processors.
* Added new `-O1` optimization preset; old `-O1` became `-O2`, old `-O2` became `-O3` and so on.
* Added support for parameters for macros written in Millfork.
@ -18,6 +20,8 @@
* Added return dispatch statements.
* Added octal and quaternary literals.
* Fixed several optimization bugs.
* Fixed several C64 library bugs.

View File

@ -16,6 +16,8 @@ For binary releases, see: https://github.com/KarolS/millfork/releases (latest: 0
* Commodore 64 (the primary target)
* Commodore 64 with SuperCPU (experimental, incomplete and very buggy)
* Commodore 16 and Plus/4
* Commodore 128
@ -47,3 +49,5 @@ For binary releases, see: https://github.com/KarolS/millfork/releases (latest: 0
* multi-part programs
* more targets: Famicon/NES, BBC Micro/Electron, Oric computers, PC-Engine/Turbografx-16, Atari Lynx
* support for 65816, targetting SuperCPU, SuperFamicom/SNES and Apple IIgs

View File

@ -30,13 +30,13 @@ Millfork supports multiple mnemonics per opcode. The default one is given first:
* **RRA**
* **SAX**
* **SAX**\*
* **SHX**, XAS
* **SHY**, SAY
* **SHY**, SAY\*
* **SBX**, AXS\*
* **SBX**, AXS\*\*
* **SRE**, LSE
@ -46,7 +46,10 @@ Millfork supports multiple mnemonics per opcode. The default one is given first:
* **XAA**, ANE
\* AXS is also used for SAX in some assemblers, but Millfork always interprets AXS as a synonym for SBX
\* HuC2680 has different instructions also called SAX and SAY,
but Millfork can distinguish between them and the NMOS illegal instructions based on the addressing mode.
\*\* AXS is also used for SAX in some assemblers. Millfork interprets AXS based on the addressing mode.
## Generation

View File

@ -32,16 +32,34 @@
## Code generation options
* `-fcmos-ops`, `-fno-cmos-ops` Whether should emit CMOS opcodes.
`.ini` equivalent: `emit_cmos`. Default: yes if targeting 65C02, no otherwise.
`.ini` equivalent: `emit_cmos`.
Default: yes if targeting a 65C02-compatible architecture, no otherwise.
* `-fillegals`, `-fno-illegals` Whether should emit illegal (undocumented) NMOS opcodes.
`.ini` equivalent: `emit_illegals`. Default: no.
`.ini` equivalent: `emit_illegals`.
Default: no.
* `-f65ce02-ops`, `-fno-65ce02-ops` Whether should emit 65CE02 opcodes.
`.ini` equivalent: `emit_65ce026`.
Default: yes if targeting 65CE02, no otherwise.
* `-fhuc6280-ops`, `-fno-huc6280-ops` Whether should emit HuC6280 opcodes.
`.ini` equivalent: `emit_huc6280`.
Default: yes if targeting HuC6280, no otherwise.
* `-fno-65816-ops`, `-femulation-65816-ops`, `-fnative-65816-ops` Which subset of 65816 instructions to support.
`-fnative-65816-ops` is required to use any 16-bit operations.
Currently, there is not much support in the compiler for the native mode.
`.ini` equivalent: `emit_65816`.
Default: native if targeting 65816, no otherwise.
* `-fjmp-fix`, `-fno-jmp-fix` Whether should prevent indirect JMP bug on page boundary.
`.ini` equivalent: `prevent_jmp_indirect_bug`. Default: no if targeting 65C02, yes otherwise.
`.ini` equivalent: `prevent_jmp_indirect_bug`.
Default: no if targeting a 65C02-compatible architecture, yes otherwise.
* `-fdecimal-mode`, `-fno-decimal-mode` Whether decimal mode should be available.
`.ini` equivalent: `decimal_mode`. Default: no if targeting Ricoh, yes otherwise.
`.ini` equivalent: `decimal_mode`.
Default: no if targeting Ricoh, yes otherwise.
* `-fvariable-overlap`, `-fno-variable-overlap` Whether variables should overlap if their scopes do not intersect.
Default: yes.

View File

@ -1,6 +1,6 @@
# Target platforms
Currently, Millfork supports creating disk- or tape-based programs for Commodore and Atari 8-bit computers,
Currently, Millfork supports creating disk- or tape-based programs for Commodore, Apple and Atari 8-bit computers,
but it may be expanded to support other 6502-based platforms in the future.
## Supported platforms
@ -9,6 +9,8 @@ The following platforms are currently supported:
* `c64` Commodore 64
* `c64_scpu` Commodore 64 with SuperCPU (very buggy)
* `c16` Commodore 16
* `plus4` Commodore Plus/4
@ -57,15 +59,21 @@ Every platform is defined in an `.ini` file with an appropriate name.
* `arch` CPU architecture. It defines which instructions are available. Available values:
* `nmos`
* `nmos` (original 6502)
* `strict` (= NMOS without illegal instructions)
* `strict` (NMOS without illegal instructions)
* `ricoh` (= NMOS without decimal mode)
* `ricoh` (Ricoh 2A03/2A07, NMOS without decimal mode)
* `strictricoh`
* `strictricoh` (Ricoh 2A03/2A07 without illegal instructions)
* `cmos` (= 65C02)
* `cmos` (WDC 65C02 or 65SC02)
* `65ce02` (CSG 65CE02; experimental)
* `huc6280` (Hudson HuC6280; experimental)
* `65816` (WDC 65816/65802; experimental; currently only programs that use only 16-bit addressing are supported)
* `modules` comma-separated list of modules that will be automatically imported
@ -73,13 +81,16 @@ Every platform is defined in an `.ini` file with an appropriate name.
* `emit_illegals` whether the compiler should emit illegal instructions, default `false`
* `emit_cmos` whether the compiler should emit CMOS instructions, default is `true` on `cmos` and `false` elsewhere
* `emit_cmos` whether the compiler should emit CMOS 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
* `ro_arrays` whether the compiler should warn upon array writes, default is `false`
* `prevent_jmp_indirect_bug` whether the compiler should try to avoid the indirect JMP bug, default is `false` on `cmos` and `true` elsewhere
* `prevent_jmp_indirect_bug` whether the compiler should try to avoid the indirect JMP bug,
default is `false` on 65C02-compatible processors and `true` elsewhere
#### `[allocation]` section

View File

@ -11,7 +11,8 @@ There are two ways to include raw assembly code in your Millfork programs:
Millfork inline assembly uses the same three-letter opcodes as most other 6502 assemblers.
Indexing syntax is also the same. Only instructions available on the current CPU architecture are available.
Currently, `RMBx`/`SMBx`/`BBRx`/`BBSx` are not supported yet.
**Work in progress**:
Currently, `RMBx`/`SMBx`/`BBRx`/`BBSx` and some extra 65CE02/HuC6280/65816 instructions are not supported yet.
Undocumented instructions are supported using various opcodes
@ -136,4 +137,32 @@ it should abide to the following rules:
* end non-inline assembly functions with `RTS`, `JMP` or `RTI` as appropriate
* on NMOS 6502:
* don't use `XAA`, `LXA`, `AHX`, `SHX`, `SHY`, `LAS` and `TAS` instructions
* on 65816:
* keep the direct page register set to $0000
* keep the M and X flags set to 1 (8-bit registers by default, native mode)
* if running in the native mode, be careful with the stack pointer (you should keep it between $000100 and $0001FF)
* do not change the data page register (keep an eye at the `PLD`, `MVN`, `MVP` instructions)
* explicitly use 16-bit immediate operands when appropriate; the assembler doesn't track flags and assumes 8-bit immediates by default
* use far jumps unless you're sure that the called function returns with an `RTS`
* on 65CE02:
* keep the `B` register set to $00
* don't change the `E` flag
* on HuC6280
* don't use the `SET` instruction
The above list is not exhaustive.

View File

@ -6,6 +6,10 @@ Decimal: `1`, `10`
Binary: `%0101`, `0b101001`
Quaternary: `0q2131`
Octal: `0o172`
Hexadecimal: `$D323`, `0x2a2`
## String literals

21
include/c64_scpu.ini Normal file
View File

@ -0,0 +1,21 @@
; Commodore 64 with a SuperCPU
; assuming a program loaded from disk or tape
[compilation]
arch=65816
modules=c64_hardware,loader_0801,c64_kernal,c64_panic,stdlib
emit_65816=emulation
[allocation]
main_org=$80D
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$45,$47,$4B
himem_style=per_bank
himem_start=after_code
himem_end=$9FFF
[output]
style=per_bank
format=startaddr,allocated
extension=prg

View File

@ -21,17 +21,46 @@ case class CompilationOptions(platform: Platform, commandLineFlags: Map[Compilat
ErrorReporting.warn("Decimal mode enabled for Ricoh architecture", this)
}
}
if (platform.cpu != Cmos) {
if (platform.cpu == Sixteen) {
if (flags(LargeCode)) {
ErrorReporting.warn("Large code model doesn't work correctly yet", this)
}
}
if (platform.cpu != Sixteen) {
if (flags(LargeCode)) {
ErrorReporting.error("Cannot use large code model on architectures other than 65816")
}
if (flags(ReturnWordsViaAccumulator)) {
ErrorReporting.error("Cannot return words via accumulator on non-65816 architecture")
}
if (flags(EmitNative65816Opcodes) || flags(EmitEmulation65816Opcodes)) {
ErrorReporting.error("65816 opcodes enabled for non-65816 architecture")
}
}
if (platform.cpu != CE02) {
if (flags(Emit65CE02Opcodes)) {
ErrorReporting.error("65CE02 opcodes enabled for non-65CE02 architecture")
}
}
if (flags(Emit65CE02Opcodes)) {
ErrorReporting.warn("65CE02 opcodes are highly experimental", this)
}
if (platform.cpu != HuC6280) {
if (flags(EmitHudsonOpcodes)) {
ErrorReporting.error("HuC6280 opcodes enabled for non-HuC6280 architecture")
}
}
if (!CmosCompatible(platform.cpu)) {
if (!flags(PreventJmpIndirectBug)) {
ErrorReporting.warn("JMP bug prevention should be enabled for non-CMOS architecture", this)
}
if (flags(EmitCmosOpcodes)) {
ErrorReporting.warn("CMOS opcodes enabled for non-CMOS architecture", this)
ErrorReporting.error("CMOS opcodes enabled for non-CMOS architecture")
}
}
if (flags(EmitIllegals)) {
if (platform.cpu == Cmos) {
ErrorReporting.warn("Illegal opcodes enabled for CMOS architecture", this)
if (CmosCompatible(platform.cpu)) {
ErrorReporting.error("Illegal opcodes enabled for architecture that doesn't support them")
}
if (platform.cpu == StrictRicoh || platform.cpu == StrictMos) {
ErrorReporting.warn("Illegal opcodes enabled for strict architecture", this)
@ -41,7 +70,9 @@ case class CompilationOptions(platform: Platform, commandLineFlags: Map[Compilat
object Cpu extends Enumeration {
val Mos, StrictMos, Ricoh, StrictRicoh, Cmos = Value
val Mos, StrictMos, Ricoh, StrictRicoh, Cmos, HuC6280, CE02, Sixteen = Value
val CmosCompatible = Set(Cmos, HuC6280, CE02, Sixteen)
import CompilationFlag._
@ -50,7 +81,10 @@ object Cpu extends Enumeration {
case Mos => Set(DecimalMode, PreventJmpIndirectBug, VariableOverlap, CompactReturnDispatchParams)
case Ricoh => Set(PreventJmpIndirectBug, VariableOverlap, CompactReturnDispatchParams)
case StrictRicoh => Set(PreventJmpIndirectBug, VariableOverlap, CompactReturnDispatchParams)
case Cmos => Set(EmitCmosOpcodes, VariableOverlap, CompactReturnDispatchParams)
case Cmos => Set(DecimalMode, EmitCmosOpcodes, VariableOverlap, CompactReturnDispatchParams)
case HuC6280 => Set(DecimalMode, EmitCmosOpcodes, EmitHudsonOpcodes, VariableOverlap, CompactReturnDispatchParams)
case CE02 => Set(DecimalMode, EmitCmosOpcodes, Emit65CE02Opcodes, VariableOverlap, CompactReturnDispatchParams)
case Sixteen => Set(DecimalMode, EmitCmosOpcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, ReturnWordsViaAccumulator, VariableOverlap, CompactReturnDispatchParams)
}
def fromString(name: String): Cpu.Value = name match {
@ -59,21 +93,33 @@ object Cpu extends Enumeration {
case "6510" => Mos
case "strict" => StrictMos
case "cmos" => Cmos
case "65sc02" => Cmos
case "sc02" => Cmos
case "65c02" => Cmos
case "c02" => Cmos
case "hudson" => HuC6280
case "huc6280" => HuC6280
case "c6280" => HuC6280
case "6280" => HuC6280
case "65ce02" => CE02
case "ce02" => CE02
case "65816" => Sixteen
case "816" => Sixteen
case "ricoh" => Ricoh
case "2a03" => Ricoh
case "2a07" => Ricoh
case "strictricoh" => StrictRicoh
case "strict2a03" => StrictRicoh
case "strict2a07" => StrictRicoh
case _ => ErrorReporting.fatal("Unknown CPU achitecture")
case _ => ErrorReporting.fatal("Unknown CPU achitecture: " + name)
}
}
object CompilationFlag extends Enumeration {
val
// compilation options:
EmitIllegals, EmitCmosOpcodes, DecimalMode, ReadOnlyArrays, PreventJmpIndirectBug,
EmitIllegals, EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
DecimalMode, ReadOnlyArrays, PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator,
// optimization options:
DetailedFlowAnalysis, DangerousOptimizations, InlineFunctions, OptimizeForSize, OptimizeForSpeed, OptimizeForSonicSpeed,
// memory allocation options
@ -90,6 +136,8 @@ object CompilationFlag extends Enumeration {
val fromString = Map(
"emit_illegals" -> EmitIllegals,
"emit_cmos" -> EmitCmosOpcodes,
"emit_65ce02" -> Emit65CE02Opcodes,
"emit_huc6280" -> EmitHudsonOpcodes,
"decimal_mode" -> DecimalMode,
"ro_arrays" -> ReadOnlyArrays,
"ror_warn" -> RorWarning,

View File

@ -65,7 +65,10 @@ object Main {
"c64"
})
val options = CompilationOptions(platform, c.flags)
ErrorReporting.debug("Effective flags: " + options.flags)
ErrorReporting.debug("Effective flags: ")
options.flags.toSeq.sortBy(_._1).foreach{
case (f, b) => ErrorReporting.debug(f" $f%-30s : $b%s")
}
val output = c.outputFileName.getOrElse("a")
val assOutput = output + ".asm"
@ -94,7 +97,11 @@ object Main {
case _ =>
val extras = List(
if (options.flag(CompilationFlag.EmitIllegals)) UndocumentedOptimizations.All else Nil,
if (options.flag(CompilationFlag.Emit65CE02Opcodes)) CE02Optimizations.All else Nil,
if (options.flag(CompilationFlag.EmitCmosOpcodes)) CmosOptimizations.All else LaterOptimizations.Nmos,
if (options.flag(CompilationFlag.EmitHudsonOpcodes)) HudsonOptimizations.All else Nil,
if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) SixteenOptimizations.AllForEmulation else Nil,
if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) SixteenOptimizations.AllForNative else Nil,
if (options.flag(CompilationFlag.DangerousOptimizations)) DangerousOptimizations.All else Nil,
).flatten
val goodCycle = List.fill(optLevel - 2)(OptimizationPresets.Good).flatten
@ -191,9 +198,32 @@ object Main {
boolean("-fcmos-ops", "-fno-cmos-ops").action { (c, v) =>
c.changeFlag(CompilationFlag.EmitCmosOpcodes, v)
}.description("Whether should emit CMOS opcodes.")
boolean("-f65ce02-ops", "-fno-65ce02-ops").action { (c, v) =>
c.changeFlag(CompilationFlag.Emit65CE02Opcodes, v)
}.description("Whether should emit 65CE02 opcodes.")
boolean("-fhuc6280-ops", "-fno-huc6280-ops").action { (c, v) =>
c.changeFlag(CompilationFlag.EmitHudsonOpcodes, v)
}.description("Whether should emit HuC6280huc6280 opcodes.")
flag("-fno-65816-ops").action { c =>
c.changeFlag(CompilationFlag.EmitEmulation65816Opcodes, b = false)
c.changeFlag(CompilationFlag.EmitNative65816Opcodes, b = false)
c.changeFlag(CompilationFlag.ReturnWordsViaAccumulator, b = false)
}.description("Don't emit 65816 opcodes.")
flag("-femulation-65816-ops").action { c =>
c.changeFlag(CompilationFlag.EmitEmulation65816Opcodes, b = true)
c.changeFlag(CompilationFlag.EmitNative65816Opcodes, b = false)
c.changeFlag(CompilationFlag.ReturnWordsViaAccumulator, b = false)
}.description("Emit 65816 opcodes (experimental).")
flag("-fnative-65816-ops").action { c =>
c.changeFlag(CompilationFlag.EmitEmulation65816Opcodes, b = true)
c.changeFlag(CompilationFlag.EmitNative65816Opcodes, b = true)
}.description("Emit 65816 opcodes (experimental).")
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("-fillegals", "-fno-illegals").action { (c, v) =>
c.changeFlag(CompilationFlag.EmitIllegals, v)
}.description("Whether should emit illegal (undocumented) NMOS opcodes. Required -O2 or higher to have an effect.")
}.description("Whether should emit illegal (undocumented) NMOS opcodes. Requires -O2 or higher to have an effect.")
boolean("-fjmp-fix", "-fno-jmp-fix").action { (c, v) =>
c.changeFlag(CompilationFlag.PreventJmpIndirectBug, v)
}.description("Whether should prevent indirect JMP bug on page boundary.")

View File

@ -173,6 +173,7 @@ object OptimizationPresets {
AlwaysGoodOptimizations.ReverseFlowAnalysis,
AlwaysGoodOptimizations.SimplifiableBitOpsSequence,
AlwaysGoodOptimizations.SimplifiableCondition,
AlwaysGoodOptimizations.SimplifiableStackOperation,
AlwaysGoodOptimizations.SmarterShiftingOfWords,
AlwaysGoodOptimizations.SmarterShiftingBytes,
AlwaysGoodOptimizations.UnconditionalJumpRemoval,

View File

@ -20,6 +20,7 @@ class Platform(
val allocator: VariableAllocator,
val org: Int,
val fileExtension: String,
var defaultCodeBank: Int = 0,
)
object Platform {
@ -55,11 +56,35 @@ object Platform {
val cs = conf.getSection("compilation")
val cpu = Cpu.fromString(cs.get(classOf[String], "arch", "strict"))
val flagOverrides = CompilationFlag.fromString.flatMap { case (k, f) =>
cs.get(classOf[String], k, "").toLowerCase match {
val value65816 = cs.get(classOf[String], "emit_65816", "")
val flagOverrides = (value65816.toLowerCase match {
case "" => Nil
case "false" | "none" | "no" | "off" | "0" =>
List(
CompilationFlag.EmitEmulation65816Opcodes -> false,
CompilationFlag.EmitNative65816Opcodes -> false,
CompilationFlag.ReturnWordsViaAccumulator -> false)
case "emulation" =>
List(
CompilationFlag.EmitEmulation65816Opcodes -> true,
CompilationFlag.EmitNative65816Opcodes -> false,
CompilationFlag.ReturnWordsViaAccumulator -> false)
case "native" =>
List(
CompilationFlag.EmitEmulation65816Opcodes -> true,
CompilationFlag.EmitNative65816Opcodes -> true)
case _ =>
ErrorReporting.error(s"Unsupported `emit_65816` value: $value65816")
Nil
}).toMap ++ CompilationFlag.fromString.flatMap { case (k, f) =>
val value = cs.get(classOf[String], k, "")
value.toLowerCase match {
case "" => None
case "false" | "off" | "0" => Some(f -> false)
case "true" | "on" | "1" => Some(f -> true)
case "false" | "off" | "no" | "0" => Some(f -> false)
case "true" | "on" | "yes" | "1" => Some(f -> true)
case _ =>
ErrorReporting.error(s"Unsupported `$k` value: $value")
None
}
}
val startingModules = cs.get(classOf[String], "modules", "").split("[, ]+").filter(_.nonEmpty).toList
@ -104,10 +129,22 @@ object Platform {
Integer.parseInt(s.substring(1), 16)
} else if (s.startsWith("0x")) {
Integer.parseInt(s.substring(2), 16)
} else if (s.startsWith("0X")) {
Integer.parseInt(s.substring(2), 16)
} else if (s.startsWith("%")) {
Integer.parseInt(s.substring(1), 2)
} else if (s.startsWith("0b")) {
Integer.parseInt(s.substring(2), 2)
} else if (s.startsWith("0B")) {
Integer.parseInt(s.substring(2), 2)
} else if (s.startsWith("0o")) {
Integer.parseInt(s.substring(2), 8)
} else if (s.startsWith("0O")) {
Integer.parseInt(s.substring(2), 8)
} else if (s.startsWith("0q")) {
Integer.parseInt(s.substring(2), 4)
} else if (s.startsWith("0Q")) {
Integer.parseInt(s.substring(2), 4)
} else {
s.toInt
}

View File

@ -1,9 +1,7 @@
package millfork.assembly
import java.lang.management.MemoryType
import millfork.{CompilationFlag, CompilationOptions}
import millfork.assembly.Opcode._
import millfork.assembly.opt.ReadsA
import millfork.compiler.{CompilationContext, MfCompiler}
import millfork.env._
@ -11,148 +9,317 @@ import millfork.env._
object OpcodeClasses {
val ReadsAAlways = Set(
ADC, AND, BIT, CMP, EOR, ORA, PHA, SBC, STA, TAX, TAY,
SAX, SBX, ANC, DCP, ISC, RRA, RLA, SRE, SLO, LXA, XAA, AHX, TAS
ADC, AND, BIT, CMP, EOR, ORA, PHA, SBC, STA,
ADC_W, AND_W, BIT_W, CMP_W, EOR_W, ORA_W, PHA_W, SBC_W, STA_W,
TAX, TAY,
SAX, SBX, ANC, DCP, ISC, RRA, RLA, SRE, SLO, LXA, XAA, AHX, TAS,
TAZ, TAB,
HuSAX, SAY, TAM,
TCD, TCS, XBA,
)
val ReadsAIfImplied = Set(
ASL, LSR, ROL, ROR, INC, DEC,
DEC_W, INC_W, ROL_W, ROR_W, ASL_W, LSR_W,
)
val ReadsAHAlways = Set(
ADC_W, AND_W, BIT_W, CMP_W, EOR_W, ORA_W, PHA_W, SBC_W, STA_W,
TCD, TCS, XBA,
)
val ReadsAHIfImplied = Set(
DEC_W, INC_W, ROL_W, ROR_W, ASL_W, LSR_W,
)
val ReadsAIfImplied = Set(ASL, LSR, ROL, ROR, INC, DEC)
val ReadsXAlways = Set(
CPX, DEX, INX, STX, TXA, TXS, SBX,
PLX,
XAA, SAX, AHX, SHX, TAS
CPX, DEX, INX, STX,
CPX_W, DEX_W, INX_W, STX_W,
TXA, TXS, SBX,
PLX, PLX_W,
XAA, SAX, AHX, SHX, TAS,
HuSAX, SXY,
TXY,
)
val ReadsYAlways = Set(CPY, DEY, INY, STY, TYA, PLY, SHY)
val ReadsYAlways = Set(CPY, DEY, INY, STY, TYA, PLY, SHY, SAY, SXY, TYX)
val ReadsIZAlways = Set(CPZ, DEZ, INZ, STZ, TZA, PLZ)
val ReadsM = Set(
ORA, AND, EOR, ADC, SBC, CMP, LDA, STA,
ORA_W, AND_W, EOR_W, ADC_W, SBC_W, CMP_W, LDA_W, STA_W,
STZ, BIT,
STZ_W, BIT_W,
PHA, PLA,
PHA_W, PLA_W,
DEC, INC, ROL, ROR, ASL, LSR,
DEC_W, INC_W, ROL_W, ROR_W, ASL_W, LSR_W,
TAX, TXA, TAY, TYA)
val ReadsW = Set(
LDX, LDY, CPX, CPY, STX, STY, INX, INY, DEX, DEY,
LDX_W, LDY_W, CPX_W, CPY_W, STX_W, STY_W, INX_W, INY_W, DEX_W, DEY_W,
PLX, PLY, PHX, PHY,
PLX_W, PLY_W, PHX_W, PHY_W,
TAX, TXA, TAY, TYA, TXY, TYX)
val ReadsZ = Set(BNE, BEQ, PHP)
val ReadsN = Set(BMI, BPL, PHP)
val ReadsNOrZ = ReadsZ ++ ReadsN
val ReadsV = Set(BVS, BVC, PHP)
val ReadsD = Set(PHP, ADC, SBC, RRA, ARR, ISC, DCP) // TODO: ??
val ReadsD = Set(PHP, ADC, SBC, RRA, ARR, ISC, ADC_W, SBC_W)
val ReadsC = Set(
PHP, ADC, SBC, BCC, BCS, ROL, ROR,
ALR, ARR, ISC, RLA, RRA, SLO, SRE // TODO: ??
PHP, BCC, BCS,
ADC, SBC, ROL, ROR,
ADC_W, SBC_W, ROL_W, ROR_W,
ALR, ARR, ISC, RLA, RRA, SLO, SRE,
XCE
)
val ChangesAAlways = Set(
TXA, TYA, PLA,
TXA, TYA, PLA, PLA_W,
ORA, AND, EOR, ADC, LDA, SBC,
ORA_W, AND_W, EOR_W, ADC_W, LDA_W, SBC_W,
SLO, RLA, SRE, RRA, LAX, ISC,
XAA, ANC, ALR, ARR, LXA, LAS
XAA, ANC, ALR, ARR, LXA, LAS,
TZA, NEG,
TMA,
XBA, TDC,
)
val ChangesAIfImplied = Set(ASL, LSR, ROL, ROR, INC, DEC)
val ChangesAIfImplied = Set(
ASL, LSR, ROL, ROR, INC, DEC,
ASL_W, LSR_W, ROL_W, ROR_W, INC_W, DEC_W,
)
val ChangesAHAlways = Set(
PLA_W,
ORA_W, AND_W, EOR_W, ADC_W, LDA_W, SBC_W,
XBA, TDC,
)
val ChangesAHIfImplied = Set(
ASL, LSR, ROL, ROR, INC, DEC,
ASL_W, LSR_W, ROL_W, ROR_W, INC_W, DEC_W,
)
val ChangesX = Set(
DEX, INX, TAX, LDX, TSX,
DEX, INX, LDX,
DEX_W, INX_W, LDX_W,
TAX, TSX,
SBX, LAX, LXA, LAS,
PLX,
PLX, PLX_W,
TYX, SXY,
)
val ChangesY = Set(
DEY, INY, TAY, LDY
DEY, INY, LDY,
DEY_W, INY_W, LDY_W,
TAY,
PLY, PLY_W,
TXY, SXY,
)
val ChangesIZ = Set(
DEZ, INZ, TAZ, LDZ,
)
val ChangesS = Set(
PHA, PLA, PHP, PLP, TXS,
PHX, PHY, PLX, PLY, TAS, LAS
PHA, PLA,
PHA_W, PLA_W,
PHP, PLP, TXS,
PHX, PHY, PLX, PLY,
PHX_W, PHY_W, PLX_W, PLY_W,
TAS, LAS,
PHZ,
PHB, PHD, PHK, PLB, PLD, RTL,
PEA, PEI, PER,
XCE, TCS, TYS,
)
val ChangesMemoryAlways = Set(
STA, STY, STZ, STX,
STA_W, STY_W, STZ_W, STX_W,
TRB, TSB,
SAX, DCP, ISC,
SLO, RLA, SRE, RRA,
AHX, SHY, SHX, TAS, LAS
AHX, SHY, SHX, TAS, LAS,
COP,
)
val ChangesMemoryIfNotImplied = Set(
DEC, INC, ASL, ROL, LSR, ROR
DEC, INC, ASL, ROL, LSR, ROR,
DEC_W, INC_W, ASL_W, ROL_W, LSR_W, ROR_W,
)
val ReadsMemoryIfNotImpliedOrImmediate = Set(
LDY, CPX, CPY,
LDY, CPX, CPY, BIT,
LDY_W, CPX_W, CPY_W, BIT_W,
ORA, AND, EOR, ADC, LDA, CMP, SBC,
ORA_W, AND_W, EOR_W, ADC_W, LDA_W, CMP_W, SBC_W,
ASL, ROL, LSR, ROR, LDX, DEC, INC,
ASL_W, ROL_W, LSR_W, ROR_W, LDX_W, DEC_W, INC_W,
SLO, RLA, SRE, RRA, LAX, DCP, ISC,
LAS,
TRB, TSB
TRB, TSB,
TRB_W, TSB_W,
)
val AccessesWordInMemory = Set(
LDA_W, LDX_W, LDY_W,
STA_W, STX_W, STY_W,
CMP_W, CPX_W, CPY_W,
DEC_W, INC_W, ASL_W, ROL_W, LSR_W, ROR_W,
ORA_W, AND_W, EOR_W, ADC_W, SBC_W,
TSB_W, TRB_W, BIT_W,
PHW,
)
val AccessesWordInMemoryAlwaysIfNotImplied = Set(
)
val OverwritesA = Set(
LDA, PLA, TXA, TYA,
LAX, LAS
LDA, PLA,
LDA_W, PLA_W,
TXA, TYA,
LAX, LAS,
TBA, TZA,
HuSAX, SAY,
TDC, TSC,
)
val OverwritesAH = Set(
LDA_W, PLA_W,
TDC, TSC,
)
val OverwritesX = Set(
TAX, LDX, TSX, PLX,
LAX, LAS
LAX, LAS,
TYX, HuSAX, SXY,
)
val OverwritesY = Set(
TAY, LDY, PLY
TAY, LDY, PLY,
TSY, TXY, SAY, SXY,
)
val OverwritesC = Set(CLC, SEC, PLP)
val OverwritesIZ = Set(
TAZ, LDZ, PLZ,
)
val OverwritesC = Set(CLC, SEC, PLP, XCE)
val OverwritesD = Set(CLD, SED, PLP)
val OverwritesI = Set(CLI, SEI, PLP)
val OverwritesV = Set(CLV, PLP)
val ConcernsAAlways = ReadsAAlways ++ ChangesAAlways
val ConcernsAHAlways = ReadsAHAlways ++ ChangesAHAlways
val ConcernsAIfImplied = ReadsAIfImplied ++ ChangesAIfImplied
val ConcernsAHIfImplied = ReadsAHIfImplied ++ ChangesAHIfImplied
val ConcernsXAlways = ReadsXAlways | ChangesX
val ConcernsYAlways = ReadsYAlways | ChangesY
val ConcernsIZAlways = ReadsIZAlways | ChangesIZ
val ChangesStack = Set(
PHA, PLA, PHP, PLP,
PHX, PLX, PHY, PLY,
PHA_W, PLA_W,
PHX_W, PLX_W, PHY_W, PLY_W,
TXS,
JSR, RTS, RTI,
TAS, LAS,
PHW, PHZ, PLZ,
TYS, TCS,
RTL, BSR,
PHB, PHD, PHK, PLB, PLD,
PEA, PEI, PER,
XCE,
)
val ConcernsStack = ChangesStack + TSX
val ConcernsStackAlways = ChangesStack ++ Set(TSX, TSY, TSC)
val ConcernsS = ChangesS ++ Set(TSX, TSY, TSC)
val ChangesNAndZ = Set(
ADC, AND, ASL, BIT, CMP, CPX, CPY, DEC, DEX, DEY, EOR, INC, INX, INY, LDA,
ADC_W, AND_W, ASL_W, BIT_W, CMP_W, CPX_W, CPY_W, DEC_W, DEX_W, DEY_W, EOR_W, INC_W, INX_W, INY_W, LDA_W,
LDX, LDY, LSR, ORA, PLP, ROL, ROR, SBC, TAX, TAY, TXA, TYA,
LDX_W, LDY_W, LSR_W, ORA_W, ROL_W, ROR_W, SBC_W,
LAX, SBX, ANC, ALR, ARR, DCP, ISC, RLA, RRA, SLO, SRE, SAX,
TSB, TRB // These two do not change N, but lets pretend they do for simplicity
TSB, TRB, // These two do not change N, but lets pretend they do for simplicity
TSB_W, TRB_W,
NEG, ASR,
CPZ, DEZ, INZ, LDZ,
REP, SEP, // People usually don't use there to change N or Z, but let's assume they do
)
val ChangesC = Set(
CLC, SEC, ADC, ASL, CMP, CPX, CPY, LSR, PLP, ROL, ROR, SBC,
SBX, ANC, ALR, ARR, DCP, ISC, RLA, RRA, SLO, SRE
PLP, CLC, SEC,
ADC, ASL, CMP, CPX, CPY, LSR, ROL, ROR, SBC,
ADC_W, ASL_W, CMP_W, CPX_W, CPY_W, LSR_W, ROL_W, ROR_W, SBC_W,
SBX, ANC, ALR, ARR, DCP, ISC, RLA, RRA, SLO, SRE,
CPZ, ASR,
XCE,
REP, SEP, // People usually don't use there to change C, but let's assume they do
)
val ChangesV = Set(
ADC, BIT, PLP, SBC,
ARR, ISC, RRA,
REP, SEP, // People usually don't use there to change V, but let's assume they do
)
val SupportsAbsoluteX = Set(
ORA, AND, EOR, ADC, CMP, SBC,
ORA_W, AND_W, EOR_W, ADC_W, CMP_W, SBC_W,
ASL, ROL, LSR, ROR, DEC, INC,
ASL_W, ROL_W, LSR_W, ROR_W, DEC_W, INC_W,
SLO, RLA, SRE, RRA, DCP, ISC,
STA, LDA, LDY, STZ, SHY,
STA, LDA, LDY, STZ,
STA_W, LDA_W, LDY_W, STZ_W,
SHY,
)
val SupportsAbsoluteY = Set(
ORA, AND, EOR, ADC, CMP, SBC,
ORA_W, AND_W, EOR_W, ADC_W, CMP_W, SBC_W,
SLO, RLA, SRE, RRA, DCP, ISC,
STA, LDA, LDX,
STA_W, LDA_W, LDX_W,
LAX, AHX, SHX, TAS, LAS,
)
val SupportsAbsolute = Set(
ORA, AND, EOR, ADC, STA, LDA, CMP, SBC,
ORA_W, AND_W, EOR_W, ADC_W, STA_W, LDA_W, CMP_W, SBC_W,
ASL, ROL, LSR, ROR, STX, LDX, DEC, INC,
ASL_W, ROL_W, LSR_W, ROR_W, STX_W, LDX_W, DEC_W, INC_W,
SLO, RLA, SRE, RRA, SAX, LAX, DCP, ISC,
STY, LDY,
BIT, JMP, JSR,
STZ, TRB, TSB,
LDZ,
)
val SupportsZeroPageIndirect = Set(ORA, AND, EOR, ADC, STA, LDA, CMP, SBC)
val SupportsIndexedZ = Set(
ORA, AND, EOR, ADC, STA, LDA, CMP, SBC,
ORA_W, AND_W, EOR_W, ADC_W, STA_W, LDA_W, CMP_W, SBC_W,
)
val SupportsLongIndexedZ = Set(
ORA, AND, EOR, ADC, STA, LDA, CMP, SBC,
ORA_W, AND_W, EOR_W, ADC_W, STA_W, LDA_W, CMP_W, SBC_W,
)
val ShortConditionalBranching = Set(BEQ, BNE, BMI, BPL, BVC, BVS, BCC, BCS)
val ShortBranching = ShortConditionalBranching + BRA
val AllDirectJumps = ShortBranching + JMP
val AllDirectJumps = ShortBranching ++ Set(JMP, BRL)
val AllLinear = Set(
ORA, AND, EOR,
ORA_W, AND_W, EOR_W,
ADC, SBC, CMP, CPX, CPY,
ADC_W, SBC_W, CMP_W, CPX_W, CPY_W,
DEC, DEX, DEY, INC, INX, INY,
DEC_W, DEX_W, DEY_W, INC_W, INX_W, INY_W,
ASL, ROL, LSR, ROR,
ASL_W, ROL_W, LSR_W, ROR_W,
LDA, STA, LDX, STX, LDY, STY,
LDA_W, STA_W, LDX_W, STX_W, LDY_W, STY_W,
TAX, TXA, TAY, TYA, TXS, TSX,
PLA, PLP, PHA, PHP,
BIT, NOP,
PLA_W, PHA_W,
BIT, BIT_W, NOP,
CLC, SEC, CLD, SED, CLI, SEI, CLV,
STZ, PHX, PHY, PLX, PLY, TSB, TRB,
STZ_W, PHX_W, PHY_W, PLX_W, PLY_W, TSB_W, TRB_W,
SLO, RLA, SRE, RRA, SAX, LAX, DCP, ISC,
ANC, ALR, ARR, XAA, LXA, SBX,
CPZ, LDZ, INZ, DEZ,
TAZ, TZA, TYS, TSY,
TBA,
PLZ, PHZ, PHW,
CLA, CLX, CLY,
CSH, CSL,
TXY, TYX, XBA,
PHD, PHB, PHK,
DISCARD_AF, DISCARD_XF, DISCARD_YF)
val NoopDiscardsFlags = Set(DISCARD_AF, DISCARD_XF, DISCARD_YF)
@ -165,6 +332,11 @@ object OpcodeClasses {
object AssemblyLine {
val accu8: AssemblyLine = AssemblyLine.immediate(SEP, 0x20)
val accu16: AssemblyLine = AssemblyLine.immediate(REP, 0x20)
val index8: AssemblyLine = AssemblyLine.immediate(SEP, 0x10)
val index16: AssemblyLine = AssemblyLine.immediate(REP, 0x10)
def treatment(lines: List[AssemblyLine], state: State.Value): Treatment.Value =
lines.map(_.treatment(state)).foldLeft(Treatment.Unchanged)(_ ~ _)
@ -216,7 +388,12 @@ object AssemblyLine {
case v@RelativeVariable(_, _, _, true) =>
List(AssemblyLine.zeropage(opcode, v.toAddress + offset))
case v: VariableInMemory => List(AssemblyLine.absolute(opcode, v.toAddress + offset))
case v: StackVariable => List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(opcode, v.baseOffset + offset + ctx.extraStackOffset))
case v: StackVariable =>
if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) {
List(AssemblyLine.stackRelative(opcode, v.baseOffset + offset + ctx.extraStackOffset))
} else {
List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(opcode, v.baseOffset + offset + ctx.extraStackOffset))
}
}
}
@ -229,6 +406,10 @@ object AssemblyLine {
def absolute(opcode: Opcode.Value, addr: Constant) =
AssemblyLine(opcode, AddrMode.Absolute, addr)
def absoluteOrLongAbsolute(opcode: Opcode.Value, thing: ThingInMemory, options: CompilationOptions) =
if (thing.isFar(options)) AssemblyLine(opcode, AddrMode.LongAbsolute, thing.toAddress)
else AssemblyLine(opcode, AddrMode.Absolute, thing.toAddress)
def absolute(opcode: Opcode.Value, thing: ThingInMemory, offset: Int = 0) =
AssemblyLine(opcode, AddrMode.Absolute, thing.toAddress + offset)
@ -258,6 +439,9 @@ object AssemblyLine {
def indexedY(opcode: Opcode.Value, thing: ThingInMemory, offset: Int = 0) =
AssemblyLine(opcode, AddrMode.IndexedY, thing.toAddress + offset)
def stackRelative(opcode: Opcode.Value, addr: Int) =
AssemblyLine(opcode, AddrMode.Stack, NumericConstant(addr & 0xff, 1))
}
case class AssemblyLine(opcode: Opcode.Value, addrMode: AddrMode.Value, var parameter: Constant, elidable: Boolean = true) {
@ -270,43 +454,65 @@ case class AssemblyLine(opcode: Opcode.Value, addrMode: AddrMode.Value, var para
def reads(state: State.Value): Boolean = state match {
case A => if (addrMode == Implied) ReadsAIfImplied(opcode) else ReadsAAlways(opcode)
case X => addrMode == AbsoluteX || addrMode == ZeroPageX || addrMode == IndexedX || ReadsXAlways(opcode)
case Y => addrMode == AbsoluteY || addrMode == ZeroPageY || addrMode == IndexedY || ReadsYAlways(opcode)
case AH => if (addrMode == Implied) ReadsAHIfImplied(opcode) else ReadsAHAlways(opcode)
case X => addrMode == AbsoluteX || addrMode == LongAbsoluteX || addrMode == ZeroPageX || addrMode == IndexedX || ReadsXAlways(opcode)
case Y => addrMode == AbsoluteY || addrMode == ZeroPageY || addrMode == IndexedY || addrMode == LongIndexedY || ReadsYAlways(opcode)
case C => ReadsC(opcode)
case D => ReadsD(opcode)
case N => ReadsN(opcode)
case V => ReadsV(opcode)
case Z => ReadsZ(opcode)
case IZ => addrMode == IndexedZ || ReadsIZAlways(opcode)
case M => ReadsM(opcode)
case W => ReadsW(opcode)
}
def treatment(state: State.Value): Treatment.Value = opcode match {
case LABEL => Unchanged // TODO: ???
case NOP => Unchanged
case JSR | JMP | BEQ | BNE | BMI | BPL | BRK | BCC | BVC | BCS | BVS => Changed
case JSR | JMP | BEQ | BNE | BMI | BPL | BRK | BCC | BVC | BCS | BVS | BSR => Changed
case CLC => if (state == C) Cleared else Unchanged
case SEC => if (state == C) Set else Unchanged
case CLV => if (state == V) Cleared else Unchanged
case CLD => if (state == D) Cleared else Unchanged
case SED => if (state == D) Set else Unchanged
case SEP => parameter match {
case NumericConstant(n, _) =>
if (isAffectedBySepRep(state, n)) Set else Unchanged
case _ => Changed
}
case REP => parameter match {
case NumericConstant(n, _) =>
if (isAffectedBySepRep(state, n)) Cleared else Unchanged
case _ => Changed
}
case XCE => Changed
case _ => state match { // TODO: smart detection of constants
case A =>
if (ChangesAAlways(opcode) || addrMode == Implied && ChangesAIfImplied(opcode))
Changed
else
Unchanged
case AH =>
if (ChangesAHAlways(opcode) || addrMode == Implied && ChangesAHIfImplied(opcode))
Changed
else
Unchanged
case X => if (ChangesX(opcode)) Changed else Unchanged
case Y => if (ChangesY(opcode)) Changed else Unchanged
case IZ => if (ChangesIZ(opcode)) Changed else Unchanged
case C => if (ChangesC(opcode)) Changed else Unchanged
case V => if (ChangesV(opcode)) Changed else Unchanged
case N | Z => if (ChangesNAndZ(opcode)) Changed else Unchanged
case D => Unchanged
case W | M | D => Unchanged
}
}
def sizeInBytes: Int = addrMode match {
case Implied => 1
case Relative | ZeroPageX | ZeroPage | ZeroPageY | ZeroPageIndirect | IndexedX | IndexedY | Immediate => 2
case AbsoluteIndexedX | AbsoluteX | Absolute | AbsoluteY | Indirect => 3
case Relative | ZeroPageX | ZeroPage | ZeroPageY | IndexedZ | IndexedX | IndexedY | IndexedSY | Stack | LongIndexedY | LongIndexedZ | Immediate => 2
case AbsoluteIndexedX | AbsoluteX | Absolute | AbsoluteY | Indirect | LongRelative | WordImmediate => 3
case LongAbsolute | LongAbsoluteX | LongIndirect => 4
case DoesNotExist => 0
}
@ -314,10 +520,17 @@ case class AssemblyLine(opcode: Opcode.Value, addrMode: AddrMode.Value, var para
case Implied => 1000
case Relative | Immediate => 2000
case ZeroPage => 2001
case ZeroPageX | ZeroPageY => 2002
case IndexedX | IndexedY => 2003
case Absolute => 3000
case AbsoluteX | AbsoluteY | Indirect => 3001
case Stack | ZeroPageX | ZeroPageY => 2002
case IndexedX | IndexedY | IndexedZ => 2003
case IndexedSY | LongIndexedY | LongIndexedZ => 2004
case WordImmediate => 3000
case Absolute => 3001
case AbsoluteX | AbsoluteY | Indirect => 3002
case AbsoluteIndexedX => 3003
case LongAbsolute => 4000
case LongAbsoluteX => 4001
case LongIndirect => 4002
case TripleAbsolute => 7000
case DoesNotExist => 1
}
@ -329,6 +542,10 @@ case class AssemblyLine(opcode: Opcode.Value, addrMode: AddrMode.Value, var para
} else if (addrMode == DoesNotExist) {
s" ; $opcode"
} else {
s" $opcode ${AddrMode.addrModeToString(addrMode, parameter.toString)}"
val op = opcode match {
case HuSAX => "SAX"
case _ => opcode.toString
}
s" $op ${AddrMode.addrModeToString(addrMode, parameter.toString)}"
}
}

View File

@ -6,7 +6,38 @@ import millfork.error.ErrorReporting
import millfork.node.Position
object State extends Enumeration {
val A, X, Y, Z, D, C, N, V = Value
val
// standard 6502 8-bit registers
A, X, Y,
// hi bytes of registers on 65816
AH, XH, YH,
// extra 65816 registers
DP, DBL, PB,
// extra register of both 65816 (it's called the high byte of DB) and 65CE02 (it's called B)
DBH,
// extra 65CE02 register
IZ,
// 8 extra HuC6280 MMU registers
MM,
// standard 6502 flags
Z, D, C, N, V,
// extra 65816 flags; W means X flag, not to confuse with the X register
E, M, W = Value
def isAffectedBySepRep(state: State.Value, n: Long): Boolean = {
state match {
case C => (n & 1) == 0
case Z => (n & 2) == 0
case D => (n & 8) == 0
case W | XH | YH => (n & 0x10) == 0
case M => (n & 0x20) == 0
case V => (n & 0x40) == 0
case N => (n & 0x80) == 0
case _ => false
}
}
}
object Treatment extends Enumeration {
@ -38,14 +69,92 @@ object Opcode extends Enumeration {
SBC, SEC, SED, SEI, STA, STX, STY,
TAX, TAY, TXA, TXS, TSX, TYA,
// illegals:
LXA, XAA, ANC, ARR, ALR, SBX,
LAX, SAX, RLA, RRA, SLO, SRE, DCP, ISC,
TAS, LAS, SHX, SHY, AHX,
// 65C02:
STZ, PHX, PHY, PLX, PLY,
BRA, TRB, TSB, STP, WAI,
// BBR, BBS, RMB, SMB,
// 65CE02:
CPZ, LDZ, DEZ, INZ,
PHW,
// DEW, INW, ASW, ROW, // aliases for DEC_W, INC_W, ASL_W, ROL_W (?)
NEG, ASR,
TAZ, TZA, PHZ, PLZ,
TSY, TYS,
TAB, TBA,
// CLE, SEE,
BSR,
// MAP,
//HuC6280:
CLA, CLX, CLY,
CSH, CSL,
SAY, SXY, HuSAX,
// SET,
// ST0, ST1, ST2,
// BSR, // the same as on 65CE02
TAM, TMA,
// TAI, TIA, TDD, TIN, TII, // memcpy instructions
TST,
//65816:
BRL,
COP,
// MVN, MVP,
PEA, PEI, PER,
PHB, PHD, PHK, PLB, PLD, // there's no PLK for the same reason Intel removed POP CS from 80186
REP, SEP,
RTL,
TCD, TDC, TSC, TCS,
TXY, TYX, XBA,
XCE,
DEC_W, INC_W, ROL_W, ROR_W, ASL_W, LSR_W,
ORA_W, AND_W, EOR_W, ADC_W, LDA_W, STA_W, CMP_W, SBC_W, STZ_W, BIT_W, TRB_W, TSB_W,
LDX_W, LDY_W, STX_W, STY_W, CPX_W, CPY_W,
INX_W, INY_W, DEX_W, DEY_W,
PHA_W, PLA_W,
PHX_W, PHY_W, PLY_W, PLX_W,
DISCARD_AF, DISCARD_XF, DISCARD_YF,
LABEL = Value
def widen(opcode: Opcode.Value): Option[Opcode.Value] = opcode match {
case ORA => Some(ORA_W)
case AND => Some(AND_W)
case EOR => Some(EOR_W)
case ADC => Some(ADC_W)
case SBC => Some(SBC_W)
case CMP => Some(CMP_W)
case LDA => Some(LDA_W)
case STA => Some(STA_W)
case STZ => Some(STZ_W)
case LDX => Some(LDX_W)
case LDY => Some(LDY_W)
case STX => Some(STX_W)
case STY => Some(STY_W)
case INX => Some(INX_W)
case INY => Some(INX_W)
case DEX => Some(DEX_W)
case DEY => Some(DEY_W)
case CPX => Some(CPX_W)
case CPY => Some(CPY_W)
case INC => Some(INC_W)
case DEC => Some(DEC_W)
case ROL => Some(ROL_W)
case ROR => Some(ROR_W)
case ASL => Some(ASL_W)
case LSR => Some(LSR_W)
case _ => None
}
def lookup(opcode: String, position: Option[Position]): Opcode.Value = opcode.toUpperCase(Locale.ROOT) match {
case "ADC" => ADC
case "AHX" => AHX
@ -56,8 +165,10 @@ object Opcode extends Enumeration {
case "ARR" => ARR
case "ASL" => ASL
case "ASO" => SLO
case "ASR" => ASR
case "ASW" => ASL_W
case "AXA" => AHX
case "AXS" => SBX // TODO: could mean SAX
case "AXS" => SBX // could mean SAX
case "BCC" => BCC
case "BCS" => BCS
case "BEQ" => BEQ
@ -67,6 +178,8 @@ object Opcode extends Enumeration {
case "BPL" => BPL
case "BRA" => BRA
case "BRK" => BRK
case "BRL" => BRL
case "BSR" => BSR
case "BVC" => BVC
case "BVS" => BVS
case "CLC" => CLC
@ -74,18 +187,24 @@ object Opcode extends Enumeration {
case "CLI" => CLI
case "CLV" => CLV
case "CMP" => CMP
case "COP" => COP
case "CPX" => CPX
case "CPY" => CPY
case "CPZ" => CPZ
case "DCM" => DCP
case "DCP" => DCP
case "DEC" => DEC
case "DEW" => DEC_W
case "DEX" => DEX
case "DEY" => DEY
case "DEZ" => DEZ
case "EOR" => EOR
case "INC" => INC
case "INS" => ISC
case "INW" => INC_W
case "INX" => INX
case "INY" => INY
case "INZ" => INZ
case "ISC" => ISC
case "JMP" => JMP
case "JSR" => JSR
@ -94,33 +213,48 @@ object Opcode extends Enumeration {
case "LDA" => LDA
case "LDX" => LDX
case "LDY" => LDY
case "LDZ" => LDZ
case "LSE" => SRE
case "LSR" => LSR
case "LXA" => LXA
case "NEG" => NEG
case "NOP" => NOP
case "OAL" => LXA
case "ORA" => ORA
case "PEA" => PEA
case "PEI" => PEI
case "PER" => PER
case "PHA" => PHA
case "PHB" => PHB
case "PHD" => PHD
case "PHK" => PHK
case "PHP" => PHP
case "PHW" => PHW
case "PHX" => PHX
case "PHY" => PHY
case "PLA" => PLA
case "PLB" => PLB
case "PLD" => PLD
case "PLP" => PLP
case "PLX" => PLX
case "PLY" => PLY
case "REP" => REP
case "RLA" => RLA
case "ROL" => ROL
case "ROR" => ROR
case "ROW" => ROR_W // TODO: is this correct?
case "RRA" => RRA
case "RTI" => RTI
case "RTL" => RTL
case "RTS" => RTS
case "SAX" => SAX // TODO: could mean SBX
case "SAY" => SHY
case "SAX" => SAX // could mean SBX; also, HuC6280 has another SAX that means something else
case "SAY" => SAY // could mean SHY
case "SBC" => SBC
case "SBX" => SBX
case "SEC" => SEC
case "SED" => SED
case "SEI" => SEI
case "SEP" => SEP
case "SHX" => SHX
case "SHY" => SHY
case "SLO" => SLO
@ -130,18 +264,36 @@ object Opcode extends Enumeration {
case "STX" => STX
case "STY" => STY
case "STZ" => STZ
case "TAB" => TAB
case "TAM" => TAM
case "TAS" => TAS
case "TAX" => TAX
case "TAY" => TAY
case "TAZ" => TAZ
case "TBA" => TBA
case "TCD" => TCD
case "TDC" => TDC
case "TCS" => TCS
case "TSC" => TSC
case "TMA" => TMA
case "TRB" => TRB
case "TSB" => TSB
case "TSX" => TSX
case "TSY" => TSY
case "TXA" => TXA
case "TXS" => TXS
case "TXY" => TXY
case "TYA" => TYA
case "TYS" => TYS
case "TYX" => TYX
case "TZA" => TZA
case "WAI" => WAI
case "XAA" => XAA
case "XAS" => SHX
case "XBA" => XBA
case "XCE" => XCE
// TODO: add all of those
case _ =>
ErrorReporting.error(s"Invalid opcode `$opcode`", position)
LABEL
@ -152,39 +304,51 @@ object Opcode extends Enumeration {
object AddrMode extends Enumeration {
val Implied,
Immediate,
WordImmediate,
Relative,
LongRelative,
ZeroPage,
ZeroPageX,
ZeroPageY,
Absolute,
AbsoluteX,
AbsoluteY,
LongAbsolute,
LongAbsoluteX,
Indirect,
LongIndirect,
IndexedX,
IndexedY,
IndexedSY,
IndexedZ,
Stack,
LongIndexedY,
LongIndexedZ,
AbsoluteIndexedX,
ZeroPageIndirect,
TripleAbsolute,
Undecided,
DoesNotExist = Value
def argumentLength(a: AddrMode.Value): Int = a match {
case Absolute | AbsoluteX | AbsoluteY | Indirect =>
2
case _ =>
1
}
def addrModeToString(am: AddrMode.Value, argument: String): String = {
am match {
case Implied => ""
case Immediate => "#" + argument
case WordImmediate => "##" + argument
case AbsoluteX | ZeroPageX => argument + ", X"
case AbsoluteY | ZeroPageY => argument + ", Y"
case IndexedX | AbsoluteIndexedX => "(" + argument + ", X)"
case Stack => argument + ", S"
case IndexedY => "(" + argument + "), Y"
case Indirect | ZeroPageIndirect => "(" + argument + ")"
case IndexedSY => "(" + argument + ", S), Y"
case IndexedZ => "(" + argument + "), Z"
case Indirect => "(" + argument + ")"
case LongIndexedY => "[" + argument + "], Y"
case LongIndexedZ => "[" + argument + "], Z"
case LongIndirect => "[" + argument + "]"
case ZeroPage => argument // + "\t;zp"
case LongAbsolute => "FAR " + argument
case LongAbsoluteX => "FAR " + argument + ", X"
case _ => argument;
}
}

View File

@ -5,7 +5,7 @@ import java.util.concurrent.atomic.AtomicInteger
import millfork.assembly.AddrMode._
import millfork.assembly.Opcode._
import millfork.assembly.OpcodeClasses._
import millfork.assembly._
import millfork.assembly.{opt, _}
import millfork.env._
/**
@ -745,6 +745,21 @@ object AlwaysGoodOptimizations {
(Elidable & HasOpcodeIn(Set(CLV)) & DoesntMatterWhatItDoesWith(State.V)) ~~> (_ => Nil),
)
val SimplifiableStackOperation = new RuleBasedAssemblyOptimization("Simplifiable stack operation",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & HasOpcode(TSX)) ~
(Elidable & HasOpcode(INX)) ~
(Elidable & HasOpcode(TXS)) ~
(Elidable & HasOpcode(PHA) & DoesntMatterWhatItDoesWith(State.Z, State.N)) ~~> (_ => List(AssemblyLine.implied(TSX), AssemblyLine.absoluteX(STA, 0x101))),
(Elidable & HasOpcode(TSX)) ~
(Elidable & HasOpcode(INX)) ~
(Elidable & HasOpcode(TXS) & DoesntMatterWhatItDoesWith(State.Z, State.N, State.A)) ~~> (_ => List(AssemblyLine.implied(PLA))),
(Elidable & HasOpcode(TSX)) ~
(Elidable & HasOpcode(INX)) ~
(Elidable & HasOpcode(TXS)) ~
(ConcernsA & Not(ConcernsStack) & Linear & DoesntMatterWhatItDoesWith(State.Z, State.N, State.A)) ~~> (code => List(code.last, AssemblyLine.implied(PLA))),
)
val SimplifiableBitOpsSequence = new RuleBasedAssemblyOptimization("Simplifiable sequence of bit operations",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(EOR) & MatchImmediate(0)) ~
@ -909,7 +924,7 @@ object AlwaysGoodOptimizations {
val jump = Elidable & HasOpcodeIn(Set(JMP, if (firstSet) BCS else BCC, if (zeroIfSet) BEQ else BNE)) & MatchParameter(1)
val elseLabel = Elidable & HasOpcode(LABEL) & MatchParameter(0)
val afterLabel = Elidable & HasOpcode(LABEL) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.C, State.N, State.V, State.Z)
val store = Elidable & (Not(ReadsC) & Linear | HasOpcodeIn(Set(RTS, JSR, RTI)))
val store = Elidable & (Not(ReadsC) & Linear | HasOpcodeIn(Set(RTS, JSR, RTI, RTL, BSR)))
val secondReturn = (Elidable & HasOpcodeIn(Set(RTS, RTI) | NoopDiscardsFlags)).*.capture(6)
val where = Where { ctx =>
ctx.get[List[AssemblyLine]](4) == ctx.get[List[AssemblyLine]](5) ||
@ -1366,7 +1381,7 @@ object AlwaysGoodOptimizations {
first.head.parameter == second.head.parameter &&
(first.head.addrMode == Immediate) == (second.head.addrMode == Immediate) && first.tail.zip(second.tail).forall(p => {
p._1.opcode == p._2.opcode && p._1.parameter.quickSimplify == p._2.parameter.quickSimplify && (p._1.addrMode == Immediate) == (p._2.addrMode == Immediate)
}) && (for (s1 <- first; s2 <- between) yield HelperCheckers.memoryAccessDoesntOverlap(s1.addrMode, s1.parameter, s2.addrMode, s2.parameter)).forall(identity) && {
}) && (for (s1 <- first; s2 <- between) yield HelperCheckers.memoryAccessDoesntOverlap(s1, s2)).forall(identity) && {
var currentD = false
var currentCDefined = false
var noAdditionDependency = true
@ -1415,10 +1430,10 @@ object AlwaysGoodOptimizations {
true
}) ~
(Linear & DoesNotConcernMemoryAt(3,4) & DoesNotConcernMemoryAt(3,5)).* ~
(Elidable & MatchParameter(6) & HasAddrModeIn(Set(ZeroPageIndirect, IndexedY))) ~~> { (code, ctx) =>
(Elidable & MatchParameter(6) & HasAddrModeIn(Set(IndexedZ, IndexedY))) ~~> { (code, ctx) =>
val addr = ctx.get[Int](2)
val last = code.last
code.init :+ last.copy(parameter = NumericConstant(addr, 2), addrMode = if (last.addrMode == ZeroPageIndirect) Absolute else AbsoluteY)
code.init :+ last.copy(parameter = NumericConstant(addr, 2), addrMode = if (last.addrMode == IndexedZ) Absolute else AbsoluteY)
},
(HasOpcode(STA) & MatchA(0) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(4)) ~
@ -1437,10 +1452,10 @@ object AlwaysGoodOptimizations {
true
}) ~
(Linear & DoesNotConcernMemoryAt(3,4) & DoesNotConcernMemoryAt(3,5)).* ~
(Elidable & MatchParameter(6) & HasAddrModeIn(Set(ZeroPageIndirect, IndexedY))) ~~> { (code, ctx) =>
(Elidable & MatchParameter(6) & HasAddrModeIn(Set(IndexedZ, IndexedY))) ~~> { (code, ctx) =>
val addr = ctx.get[Int](2)
val last = code.last
code.init :+ last.copy(parameter = NumericConstant(addr, 2), addrMode = if (last.addrMode == ZeroPageIndirect) Absolute else AbsoluteY)
code.init :+ last.copy(parameter = NumericConstant(addr, 2), addrMode = if (last.addrMode == IndexedZ) Absolute else AbsoluteY)
},
)

View File

@ -0,0 +1,20 @@
package millfork.assembly.opt
import millfork.assembly.AddrMode._
import millfork.assembly.AssemblyLine
import millfork.assembly.Opcode._
import millfork.assembly.OpcodeClasses._
/**
* @author Karol Stasiak
*/
object CE02Optimizations {
val UseAsr = new RuleBasedAssemblyOptimization("Use 65CE02 instruction ASR",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(Elidable & HasOpcode(CMP) & HasImmediate(0x80)) ~
(Elidable & HasOpcode(ROR)) ~~> (_ => List(AssemblyLine.implied(ASR))),
)
val All: List[AssemblyOptimization] = List(UseAsr)
}

View File

@ -102,10 +102,15 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
case AssemblyLine(_, AbsoluteY, _, _) :: xs if loaded != Some(Y) => false
case AssemblyLine(_, ZeroPageY, _, _) :: xs if loaded != Some(Y) => false
case AssemblyLine(_, IndexedY, _, _) :: xs if dir == Y2X || loaded != Some(Y) => false
case AssemblyLine(_, LongIndexedY, _, _) :: xs if dir == Y2X || loaded != Some(Y) => false
case AssemblyLine(_, AbsoluteX, _, _) :: xs if loaded != Some(X) => false
case AssemblyLine(_, LongAbsoluteX, _, _) :: xs if loaded != Some(X) => false
case AssemblyLine(_, ZeroPageX, _, _) :: xs if loaded != Some(X) => false
case AssemblyLine(_, IndexedX, _, _) :: xs if dir == X2Y || loaded != Some(X) => false
case AssemblyLine(_, AbsoluteIndexedX, _, _) :: xs if dir == X2Y => false
case AssemblyLine(SHX | SHY | AHX, _, _, _) :: xs => false
case AssemblyLine(TXY, _, _, e) :: xs => e && loaded == Some(X) && canOptimize(xs, dir, Some(Y))
case AssemblyLine(TYX, _, _, e) :: xs => e && loaded == Some(Y) && canOptimize(xs, dir, Some(X))
// using a wrong index register for one instruction is fine
case AssemblyLine(LDY | TAY, _, _, _) :: AssemblyLine(_, IndexedY, _, _) :: xs if dir == Y2X =>
@ -121,13 +126,13 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
case AssemblyLine(LDX | TAX, _, _, _) :: AssemblyLine(INX | DEX, _, _, _) :: AssemblyLine(INC | DEC | ASL | ROL | ROR | LSR | STZ, AbsoluteX | ZeroPageX, _, _) :: xs if dir == X2Y =>
canOptimize(xs, dir, None)
case AssemblyLine(INC | DEC | ASL | ROL | ROR | LSR | STZ, AbsoluteX | ZeroPageX, _, _) :: xs if dir == X2Y => false
case AssemblyLine(INC | DEC | ASL | ROL | ROR | LSR | STZ | LDZ | BIT, AbsoluteX | ZeroPageX, _, _) :: xs if dir == X2Y => false
case AssemblyLine(LAX, _, _, _) :: xs => false
case AssemblyLine(JSR, _, _, _) :: xs => false // TODO
case AssemblyLine(JMP, _, _, _) :: xs => canOptimize(xs, dir, None)
case AssemblyLine(JSR | BSR, _, _, _) :: xs => false // TODO
case AssemblyLine(JMP, Absolute, _, _) :: xs => canOptimize(xs, dir, None) // TODO
case AssemblyLine(op, _, _, _) :: xs if OpcodeClasses.ShortBranching(op) => canOptimize(xs, dir, None)
case AssemblyLine(RTS, _, _, _) :: xs => canOptimize(xs, dir, None)
case AssemblyLine(RTS | RTL | BRA | BRL, _, _, _) :: xs => canOptimize(xs, dir, None)
case AssemblyLine(LABEL, _, _, _) :: xs => canOptimize(xs, dir, None)
case AssemblyLine(DISCARD_XF, _, _, _) :: xs => canOptimize(xs, dir, loaded.filter(_ != X))
case AssemblyLine(DISCARD_YF, _, _, _) :: xs => canOptimize(xs, dir, loaded.filter(_ != Y))
@ -137,9 +142,9 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
(e || dir == Y2X) && canOptimize(xs, dir, Some(X))
case AssemblyLine(TAY | LDY | PLY, _, _, e) :: xs =>
(e || dir == X2Y) && canOptimize(xs, dir, Some(Y))
case AssemblyLine(TXA | STX | PHX | CPX | INX | DEX, _, _, e) :: xs =>
case AssemblyLine(TXA | STX | PHX | CPX | INX | DEX | HuSAX, _, _, e) :: xs =>
(e || dir == Y2X) && loaded == Some(X) && canOptimize(xs, dir, Some(X))
case AssemblyLine(TYA | STY | PHY | CPY | INY | DEY, _, _, e) :: xs =>
case AssemblyLine(TYA | STY | PHY | CPY | INY | DEY | SAY, _, _, e) :: xs =>
(e || dir == X2Y) && loaded == Some(Y) && canOptimize(xs, dir, Some(Y))
case AssemblyLine(SAX | TXS | SBX, _, _, _) :: xs => dir == Y2X && loaded == Some(X) && canOptimize(xs, dir, Some(X))
@ -167,17 +172,20 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
:: xs => a :: i :: b :: switchX2Y(xs)
case (x@AssemblyLine(TAX, _, _, _)) :: xs => x.copy(opcode = TAY) :: switchX2Y(xs)
case (x@AssemblyLine(TXA, _, _, _)) :: xs => x.copy(opcode = TYA) :: switchX2Y(xs)
case (x@AssemblyLine(TXY | TYX, _, _, _)) :: xs => x.copy(opcode = TYX) :: switchX2Y(xs) // keep the transfer for the flags
case (x@AssemblyLine(STX, _, _, _)) :: xs => x.copy(opcode = STY) :: switchX2Y(xs)
case (x@AssemblyLine(LDX, _, _, _)) :: xs => x.copy(opcode = LDY) :: switchX2Y(xs)
case (x@AssemblyLine(INX, _, _, _)) :: xs => x.copy(opcode = INY) :: switchX2Y(xs)
case (x@AssemblyLine(DEX, _, _, _)) :: xs => x.copy(opcode = DEY) :: switchX2Y(xs)
case (x@AssemblyLine(CPX, _, _, _)) :: xs => x.copy(opcode = CPY) :: switchX2Y(xs)
case (x@AssemblyLine(HuSAX, _, _, _)) :: xs => x.copy(opcode = SAY) :: switchX2Y(xs)
case AssemblyLine(LAX, _, _, _) :: xs => ErrorReporting.fatal("Unexpected LAX")
case AssemblyLine(TXS, _, _, _) :: xs => ErrorReporting.fatal("Unexpected TXS")
case AssemblyLine(TSX, _, _, _) :: xs => ErrorReporting.fatal("Unexpected TSX")
case AssemblyLine(SBX, _, _, _) :: xs => ErrorReporting.fatal("Unexpected SBX")
case AssemblyLine(SAX, _, _, _) :: xs => ErrorReporting.fatal("Unexpected SAX")
case AssemblyLine(SXY, _, _, _) :: xs => ErrorReporting.fatal("Unexpected SXY")
case (x@AssemblyLine(_, AbsoluteX, _, _)) :: xs => x.copy(addrMode = AbsoluteY) :: switchX2Y(xs)
case (x@AssemblyLine(_, ZeroPageX, _, _)) :: xs => x.copy(addrMode = ZeroPageY) :: switchX2Y(xs)
@ -197,11 +205,14 @@ class ChangeIndexRegisterOptimization(preferX2Y: Boolean) extends AssemblyOptimi
:: xs => code.take(3) ++ switchY2X(xs)
case (x@AssemblyLine(TAY, _, _, _)) :: xs => x.copy(opcode = TAX) :: switchY2X(xs)
case (x@AssemblyLine(TYA, _, _, _)) :: xs => x.copy(opcode = TXA) :: switchY2X(xs)
case (x@AssemblyLine(TYX | TXY, _, _, _)) :: xs => x.copy(opcode = TXY) :: switchY2X(xs) // keep the transfer for the flags
case (x@AssemblyLine(STY, _, _, _)) :: xs => x.copy(opcode = STX) :: switchY2X(xs)
case (x@AssemblyLine(LDY, _, _, _)) :: xs => x.copy(opcode = LDX) :: switchY2X(xs)
case (x@AssemblyLine(INY, _, _, _)) :: xs => x.copy(opcode = INX) :: switchY2X(xs)
case (x@AssemblyLine(DEY, _, _, _)) :: xs => x.copy(opcode = DEX) :: switchY2X(xs)
case (x@AssemblyLine(CPY, _, _, _)) :: xs => x.copy(opcode = CPX) :: switchY2X(xs)
case (x@AssemblyLine(SAY, _, _, _)) :: xs => x.copy(opcode = HuSAX) :: switchY2X(xs)
case AssemblyLine(SXY, _, _, _) :: xs => ErrorReporting.fatal("Unexpected SXY")
case (x@AssemblyLine(_, AbsoluteY, _, _)) :: xs => x.copy(addrMode = AbsoluteX) :: switchY2X(xs)
case (x@AssemblyLine(_, ZeroPageY, _, _)) :: xs => x.copy(addrMode = ZeroPageX) :: switchY2X(xs)

View File

@ -1,10 +1,10 @@
package millfork.assembly.opt
import millfork.assembly.{AssemblyLine, Opcode}
import millfork.assembly.{AssemblyLine, Opcode, State}
import millfork.assembly.Opcode._
import millfork.assembly.AddrMode._
import millfork.assembly.OpcodeClasses._
import millfork.env.{Constant, NormalFunction}
import millfork.env._
/**
* @author Karol Stasiak
@ -15,22 +15,39 @@ object CmosOptimizations {
val ZeroStoreAsStz = new RuleBasedAssemblyOptimization("Zero store",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(HasA(0) & HasOpcode(STA) & Elidable & HasAddrModeIn(StzAddrModes)) ~~> {code =>
(HasA(0) & HasZ(0) & HasOpcode(STA) & Elidable & HasAddrModeIn(StzAddrModes)) ~~> {code =>
code.head.copy(opcode = STZ) :: Nil
},
(HasX(0) & HasOpcode(STX) & Elidable & HasAddrModeIn(StzAddrModes)) ~~> {code =>
(HasX(0) & HasZ(0) & HasOpcode(STX) & Elidable & HasAddrModeIn(StzAddrModes)) ~~> {code =>
code.head.copy(opcode = STZ) :: Nil
},
(HasY(0) & HasOpcode(STY) & Elidable & HasAddrModeIn(StzAddrModes)) ~~> {code =>
(HasY(0) & HasZ(0) & HasOpcode(STY) & Elidable & HasAddrModeIn(StzAddrModes)) ~~> {code =>
code.head.copy(opcode = STZ) :: Nil
},
)
val SimplerBitFlipping = new RuleBasedAssemblyOptimization("Simpler bit flipping",
needsFlowInfo = FlowInfoRequirement.BackwardFlow,
(Elidable & HasOpcode(LDA) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(0)) ~
(Elidable & HasOpcode(ORA) & MatchImmediate(1)) ~
(Elidable & HasOpcode(STA) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(0) & DoesntMatterWhatItDoesWith(State.A, State.Z, State.N)) ~~> { code =>
List(code(1).copy(opcode = Opcode.LDA), code.head.copy(opcode = TSB))
},
(Elidable & HasOpcode(LDA) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(0)) ~
(Elidable & HasOpcode(AND) & MatchImmediate(1)) ~
(Elidable & HasOpcode(STA) & HasAddrModeIn(Set(Absolute, ZeroPage)) & MatchParameter(0) & DoesntMatterWhatItDoesWith(State.A, State.Z, State.N)) ~~> { code =>
List(
code(1).copy(opcode = Opcode.LDA, parameter = CompoundConstant(MathOperator.Exor, NumericConstant(255, 1), code(1).parameter)),
code.head.copy(opcode = TRB))
},
)
val OptimizeZeroIndex = new RuleBasedAssemblyOptimization("Optimizing zero index",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(Elidable & HasY(0) & HasAddrMode(IndexedY) & HasOpcodeIn(SupportsZeroPageIndirect)) ~~> (code => code.map(_.copy(addrMode = ZeroPageIndirect))),
(Elidable & HasX(0) & HasAddrMode(IndexedX) & HasOpcodeIn(SupportsZeroPageIndirect)) ~~> (code => code.map(_.copy(addrMode = ZeroPageIndirect))),
(Elidable & HasY(0) & HasZ(0) & HasAddrMode(IndexedY) & HasOpcodeIn(SupportsIndexedZ)) ~~> (code => code.map(_.copy(addrMode = IndexedZ))),
(Elidable & HasX(0) & HasZ(0) & HasAddrMode(IndexedX) & HasOpcodeIn(SupportsIndexedZ)) ~~> (code => code.map(_.copy(addrMode = IndexedZ))),
(Elidable & HasX(0) & HasZ(0) & HasAddrMode(AbsoluteIndexedX) & HasOpcode(JMP)) ~~> (code => code.map(_.copy(addrMode = Indirect))),
)
val All: List[AssemblyOptimization] = List(OptimizeZeroIndex, ZeroStoreAsStz)
val All: List[AssemblyOptimization] = List(OptimizeZeroIndex, SimplerBitFlipping, ZeroStoreAsStz)
}

View File

@ -1,5 +1,6 @@
package millfork.assembly.opt
import millfork.{CompilationFlag, CompilationOptions}
import millfork.assembly.{AssemblyLine, OpcodeClasses, State}
import millfork.env.{Label, MemoryAddressConstant, NormalFunction, NumericConstant}
@ -27,6 +28,12 @@ sealed trait Status[T] {
object Status {
implicit class BoolStatusOps(val inner: Status[Boolean]) extends AnyVal {
def withHiddenHi: Status[Boolean] = inner match {
case SingleStatus(false) => inner
case _ => AnyStatus()
}
}
implicit class IntStatusOps(val inner: Status[Int]) extends AnyVal {
def map[T](f: Int => T): Status[T] = inner match {
case SingleStatus(x) => SingleStatus(f(x))
@ -88,11 +95,14 @@ case class AnyStatus[T]() extends Status[T] {
case class CpuStatus(a: Status[Int] = UnknownStatus(),
x: Status[Int] = UnknownStatus(),
y: Status[Int] = UnknownStatus(),
iz: Status[Int] = UnknownStatus(),
z: Status[Boolean] = UnknownStatus(),
n: Status[Boolean] = UnknownStatus(),
c: Status[Boolean] = UnknownStatus(),
v: Status[Boolean] = UnknownStatus(),
d: Status[Boolean] = UnknownStatus(),
m: Status[Boolean] = UnknownStatus(),
w: Status[Boolean] = UnknownStatus()
) {
override def toString: String = s"A=$a,X=$x,Y=$y,Z=$z,N=$n,C=$c,V=$v,D=$d"
@ -107,47 +117,65 @@ case class CpuStatus(a: Status[Int] = UnknownStatus(),
a = this.a ~ that.a,
x = this.x ~ that.x,
y = this.y ~ that.y,
iz = this.iz ~ that.iz,
z = this.z ~ that.z,
n = this.n ~ that.n,
c = this.c ~ that.c,
v = this.v ~ that.v,
d = this.d ~ that.d,
m = this.m ~ that.m,
w = this.w ~ that.w,
)
def hasClear(state: State.Value): Boolean = state match {
case State.A => a.contains(0)
case State.X => x.contains(0)
case State.Y => y.contains(0)
case State.IZ => iz.contains(0)
case State.Z => z.contains(false)
case State.N => n.contains(false)
case State.C => c.contains(false)
case State.V => v.contains(false)
case State.D => d.contains(false)
case State.M => m.contains(false)
case State.W => w.contains(false)
case _ => false
}
def hasSet(state: State.Value): Boolean = state match {
case State.A => false
case State.X => false
case State.Y => false
case State.IZ => false
case State.Z => z.contains(true)
case State.N => n.contains(true)
case State.C => c.contains(true)
case State.V => v.contains(true)
case State.D => d.contains(true)
case State.M => m.contains(true)
case State.W => w.contains(true)
case _ => false
}
}
object CoarseFlowAnalyzer {
//noinspection RedundantNewCaseClass
def analyze(f: NormalFunction, code: List[AssemblyLine]): List[CpuStatus] = {
val flagArray = Array.fill[CpuStatus](code.length)(CpuStatus())
def analyze(f: NormalFunction, code: List[AssemblyLine], compilationOptions: CompilationOptions): List[CpuStatus] = {
val emptyIz: Status[Int] = if (compilationOptions.flag(CompilationFlag.Emit65CE02Opcodes)) UnknownStatus() else SingleStatus(0)
val emptyStatus = CpuStatus(iz = emptyIz)
val flagArray = Array.fill[CpuStatus](code.length)(emptyStatus)
val codeArray = code.toArray
val initialStatus = new CpuStatus(d = SingleStatus(false))
val initialStatus = new CpuStatus(
d = SingleStatus(false),
m = SingleStatus(true),
w = SingleStatus(true),
iz = emptyIz
)
var changed = true
while (changed) {
changed = false
var currentStatus: CpuStatus = if (f.interrupt) CpuStatus() else initialStatus
var currentStatus: CpuStatus = if (f.interrupt) emptyStatus else initialStatus
for (i <- codeArray.indices) {
import millfork.assembly.Opcode._
import millfork.assembly.AddrMode._
@ -161,7 +189,7 @@ object CoarseFlowAnalyzer {
currentStatus = codeArray.indices.flatMap(j => codeArray(j) match {
case AssemblyLine(_, _, MemoryAddressConstant(Label(L)), _) => Some(flagArray(j))
case _ => None
}).fold(CpuStatus())(_ ~ _)
}).fold(emptyStatus)(_ ~ _)
case AssemblyLine(BCC, _, _, _) =>
currentStatus = currentStatus.copy(c = currentStatus.c ~ SingleStatus(true))
@ -191,6 +219,25 @@ object CoarseFlowAnalyzer {
case AssemblyLine(CLV, _, _, _) =>
currentStatus = currentStatus.copy(v = SingleStatus(false))
case AssemblyLine(REP, Immediate, NumericConstant(nn, _), _) =>
if ((nn & 1) != 0) currentStatus = currentStatus.copy(c = SingleStatus(false))
if ((nn & 2) != 0) currentStatus = currentStatus.copy(z = SingleStatus(false))
if ((nn & 8) != 0) currentStatus = currentStatus.copy(d = SingleStatus(false))
if ((nn & 0x10) != 0) currentStatus = currentStatus.copy(w = SingleStatus(false))
if ((nn & 0x20) != 0) currentStatus = currentStatus.copy(m = SingleStatus(false))
if ((nn & 0x40) != 0) currentStatus = currentStatus.copy(v = SingleStatus(false))
if ((nn & 0x80) != 0) currentStatus = currentStatus.copy(n = SingleStatus(false))
case AssemblyLine(SEP, Immediate, NumericConstant(nn, _), _) =>
if ((nn & 1) != 0) currentStatus = currentStatus.copy(c = SingleStatus(true))
if ((nn & 2) != 0) currentStatus = currentStatus.copy(z = SingleStatus(true))
if ((nn & 8) != 0) currentStatus = currentStatus.copy(d = SingleStatus(true))
if ((nn & 0x10) != 0) currentStatus = currentStatus.copy(w = SingleStatus(true))
if ((nn & 0x20) != 0) currentStatus = currentStatus.copy(m = SingleStatus(true))
if ((nn & 0x40) != 0) currentStatus = currentStatus.copy(v = SingleStatus(true))
if ((nn & 0x80) != 0) currentStatus = currentStatus.copy(n = SingleStatus(true))
case AssemblyLine(XCE, _, _, _) =>
currentStatus = currentStatus.copy(c = AnyStatus(), m = AnyStatus(), x = AnyStatus())
case AssemblyLine(JSR, _, _, _) =>
currentStatus = initialStatus
@ -203,9 +250,9 @@ object CoarseFlowAnalyzer {
case AssemblyLine(LDA, Immediate, NumericConstant(nn, _), _) =>
val n = nn.toInt & 0xff
currentStatus = currentStatus.nz(n).copy(a = SingleStatus(n))
case AssemblyLine(LAX, Immediate, NumericConstant(nn, _), _) =>
case AssemblyLine(LDZ, Immediate, NumericConstant(nn, _), _) =>
val n = nn.toInt & 0xff
currentStatus = currentStatus.nz(n).copy(a = SingleStatus(n), x = SingleStatus(n))
currentStatus = currentStatus.nz(n).copy(iz = SingleStatus(n))
case AssemblyLine(ADC, Immediate, NumericConstant(nn, _), _) =>
val n = nn.toInt
@ -231,6 +278,21 @@ object CoarseFlowAnalyzer {
c = currentStatus.a.map(i => (i & n & 1) == 0),
a = currentStatus.a.map(i => (i & n & 0xff) >> 1))
case AssemblyLine(ADC_W, WordImmediate, NumericConstant(nn, _), _) =>
val n = nn.toInt & 0xff
val newA = currentStatus.a.adc(n, currentStatus.c, currentStatus.d)
currentStatus = currentStatus.copy(n = AnyStatus(), z = newA.z().withHiddenHi, a = newA, c = AnyStatus(), v = AnyStatus())
case AssemblyLine(EOR_W, WordImmediate, NumericConstant(nn, _), _) =>
val n = nn.toInt & 0xff
currentStatus = currentStatus.copy(n = AnyStatus(), z = currentStatus.a.z(_ ^ n).withHiddenHi, a = currentStatus.a.map(_ ^ n))
case AssemblyLine(AND_W, WordImmediate, NumericConstant(nn, _), _) =>
val n = nn.toInt & 0xff
currentStatus = currentStatus.copy(n = AnyStatus(), z = currentStatus.a.z(_ & n).withHiddenHi, a = currentStatus.a.map(_ & n))
case AssemblyLine(ORA_W, WordImmediate, NumericConstant(nn, _), _) =>
val n = nn.toInt & 0xff
currentStatus = currentStatus.copy(n = AnyStatus(), z = currentStatus.a.z(_ | n).withHiddenHi, a = currentStatus.a.map(_ | n))
case AssemblyLine(INX, Implied, _, _) =>
currentStatus = currentStatus.copy(n = currentStatus.x.n(_ + 1), z = currentStatus.x.z(_ + 1), x = currentStatus.x.map(v => (v + 1) & 0xff))
case AssemblyLine(DEX, Implied, _, _) =>
@ -243,6 +305,22 @@ object CoarseFlowAnalyzer {
currentStatus = currentStatus.copy(n = currentStatus.a.n(_ + 1), z = currentStatus.a.z(_ + 1), a = currentStatus.a.map(v => (v + 1) & 0xff))
case AssemblyLine(DEC, Implied, _, _) =>
currentStatus = currentStatus.copy(n = currentStatus.a.n(_ - 1), z = currentStatus.a.z(_ - 1), a = currentStatus.a.map(v => (v - 1) & 0xff))
case AssemblyLine(NEG, Implied, _, _) =>
currentStatus = currentStatus.copy(n = currentStatus.a.n(256 - _), z = currentStatus.a.z(256 - _), a = currentStatus.a.map(v => (256 - v) & 0xff))
case AssemblyLine(INX_W, Implied, _, _) =>
currentStatus = currentStatus.copy(n = AnyStatus(), z = currentStatus.x.z(_ + 1).withHiddenHi, x = currentStatus.x.map(v => (v + 1) & 0xff))
case AssemblyLine(DEX_W, Implied, _, _) =>
currentStatus = currentStatus.copy(n = AnyStatus(), z = currentStatus.x.z(_ - 1).withHiddenHi, x = currentStatus.x.map(v => (v - 1) & 0xff))
case AssemblyLine(INY_W, Implied, _, _) =>
currentStatus = currentStatus.copy(n = AnyStatus(), z = currentStatus.y.z(_ + 1).withHiddenHi, y = currentStatus.y.map(v => (v + 1) & 0xff))
case AssemblyLine(DEY_W, Implied, _, _) =>
currentStatus = currentStatus.copy(n = AnyStatus(), z = currentStatus.y.z(_ - 1).withHiddenHi, y = currentStatus.y.map(v => (v - 1) & 0xff))
case AssemblyLine(INC_W, Implied, _, _) =>
currentStatus = currentStatus.copy(n = AnyStatus(), z = currentStatus.a.z(_ + 1).withHiddenHi, a = currentStatus.a.map(v => (v + 1) & 0xff))
case AssemblyLine(DEC_W, Implied, _, _) =>
currentStatus = currentStatus.copy(n = AnyStatus(), z = currentStatus.a.z(_ - 1).withHiddenHi, a = currentStatus.a.map(v => (v - 1) & 0xff))
case AssemblyLine(TAX, _, _, _) =>
currentStatus = currentStatus.copy(x = currentStatus.a, n = currentStatus.a.n(), z = currentStatus.a.z())
case AssemblyLine(TXA, _, _, _) =>
@ -251,11 +329,22 @@ object CoarseFlowAnalyzer {
currentStatus = currentStatus.copy(y = currentStatus.a, n = currentStatus.a.n(), z = currentStatus.a.z())
case AssemblyLine(TYA, _, _, _) =>
currentStatus = currentStatus.copy(a = currentStatus.y, n = currentStatus.y.n(), z = currentStatus.y.z())
case AssemblyLine(TAZ, _, _, _) =>
currentStatus = currentStatus.copy(iz = currentStatus.a, n = currentStatus.a.n(), z = currentStatus.a.z())
case AssemblyLine(TZA, _, _, _) =>
currentStatus = currentStatus.copy(a = currentStatus.iz, n = currentStatus.iz.n(), z = currentStatus.iz.z())
case AssemblyLine(ASL, Implied, _, _) =>
currentStatus = currentStatus.copy(a = currentStatus.a.map(v => (v << 1) & 0xff), n = currentStatus.a.n(_ << 1), z = currentStatus.a.z(_ << 1),c = currentStatus.a.map(a => a.&(0xff).!=(0)))
case AssemblyLine(LSR, Implied, _, _) =>
currentStatus = currentStatus.copy(a = currentStatus.a.map(a => a.>>(1).&(0x7f)), n = currentStatus.a.n(a => a.>>(1).&(0x7f)), z = currentStatus.a.z(a => a.>>(1).&(0x7f)),c = currentStatus.a.map(a => a.&(1).!=(0)))
case AssemblyLine(ASR, Implied, _, _) =>
currentStatus = currentStatus.copy(a = currentStatus.a.map(a => a.toByte.>>(1).&(0xff)), n = currentStatus.a.n(a => a.toByte.>>(1).&(0xff)), z = currentStatus.a.z(a => a.toByte.>>(1).&(0xff)),c = currentStatus.a.map(a => a.&(1).!=(0)))
case AssemblyLine(ASL_W, Implied, _, _) =>
currentStatus = currentStatus.copy(a = currentStatus.a.map(v => (v << 1) & 0xff), n = AnyStatus(), z = currentStatus.a.z(_ << 1).withHiddenHi, c = AnyStatus())
case AssemblyLine(LSR_W, Implied, _, _) =>
currentStatus = currentStatus.copy(a = AnyStatus(), n = AnyStatus(), z = currentStatus.a.z(a => a.>>(1).&(0x7f)).withHiddenHi, c = currentStatus.a.map(a => a.&(1).!=(0)))
case AssemblyLine(opcode, addrMode, parameter, _) =>
if (OpcodeClasses.ChangesX(opcode)) currentStatus = currentStatus.copy(x = AnyStatus())

View File

@ -39,7 +39,7 @@ object FlowAnalyzer {
if (options.flag(CompilationFlag.DetailedFlowAnalysis)) {
() => QuantumFlowAnalyzer.analyze(f, code).map(_.collapse)
} else {
() => CoarseFlowAnalyzer.analyze(f, code)
() => CoarseFlowAnalyzer.analyze(f, code, options)
}
case FlowInfoRequirement.BackwardFlow | FlowInfoRequirement.JustLabels | FlowInfoRequirement.NoRequirement =>
() => List.fill(code.size)(EmptyCpuStatus)

View File

@ -0,0 +1,21 @@
package millfork.assembly.opt
import millfork.assembly.AssemblyLine
import millfork.assembly.Opcode._
import millfork.assembly.AddrMode._
import millfork.env.NumericConstant
/**
* @author Karol Stasiak
*/
object HudsonOptimizations {
val All: List[AssemblyOptimization] = List()
def removeLoadZero(code: List[AssemblyLine]): List[AssemblyLine] = code.map{
case AssemblyLine(LDA, Immediate, NumericConstant(0, _), true) => AssemblyLine.implied(CLA)
case AssemblyLine(LDX, Immediate, NumericConstant(0, _), true) => AssemblyLine.implied(CLX)
case AssemblyLine(LDY, Immediate, NumericConstant(0, _), true) => AssemblyLine.implied(CLY)
case l => l
}
}

View File

@ -90,7 +90,7 @@ object LoopUnrolling {
(Elidable & HasOpcode(LDX) & MatchNumericImmediate(Start)).capture(Initialization) ~
(Elidable & HasOpcode(BEQ) & MatchParameter(Skip)) ~
(Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~
((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI))) & Not(ChangesX)).*.capture(Body) ~
((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL))) & Not(ChangesX)).*.capture(Body) ~
(Elidable & HasOpcodeIn(Set(DEX, INX))).capture(Step)
).capture(BodyWithStep) ~
(Elidable & HasOpcode(CPX) & MatchNumericImmediate(End)).? ~
@ -104,7 +104,7 @@ object LoopUnrolling {
},
(Elidable & HasOpcode(LDX) & MatchNumericImmediate(Start)).capture(Initialization) ~
(Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~
((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI))) & Not(ChangesX)).*.capture(Body) ~
((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL))) & Not(ChangesX)).*.capture(Body) ~
(Elidable & HasOpcodeIn(Set(DEX, INX))).capture(Step)
).capture(BodyWithStep) ~
(Elidable & HasOpcode(CPX) & MatchNumericImmediate(End)).? ~
@ -118,7 +118,7 @@ object LoopUnrolling {
(Elidable & HasOpcode(LDY) & MatchNumericImmediate(Start)).capture(Initialization) ~
(Elidable & HasOpcode(BEQ) & MatchParameter(Skip)) ~
(Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~
((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI))) & Not(ChangesY)).*.capture(Body) ~
((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL))) & Not(ChangesY)).*.capture(Body) ~
(Elidable & HasOpcodeIn(Set(DEY, INY))).capture(Step)
).capture(BodyWithStep) ~
(Elidable & HasOpcode(CPY) & MatchNumericImmediate(End)).? ~
@ -132,7 +132,7 @@ object LoopUnrolling {
},
(Elidable & HasOpcode(LDY) & MatchNumericImmediate(Start)).capture(Initialization) ~
(Elidable & HasOpcode(LABEL) & MatchParameter(Back)) ~
((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI))) & Not(ChangesY)).*.capture(Body) ~
((Elidable & Not(HasOpcodeIn(Set(RTS, JSR, RTI, RTL))) & Not(ChangesY)).*.capture(Body) ~
(Elidable & HasOpcodeIn(Set(DEY, INY))).capture(Step)
).capture(BodyWithStep) ~
(Elidable & HasOpcode(CPY) & MatchNumericImmediate(End)).? ~

View File

@ -1,7 +1,9 @@
package millfork.assembly.opt
import millfork.CompilationOptions
import millfork.assembly.{AssemblyLine, Opcode, OpcodeClasses, State}
import millfork.env._
import millfork.error.ErrorReporting
import millfork.node.Register
import scala.collection.immutable
@ -33,13 +35,17 @@ case object UnknownImportance extends Importance {
//noinspection RedundantNewCaseClass
case class CpuImportance(a: Importance = UnknownImportance,
ah: Importance = UnknownImportance,
x: Importance = UnknownImportance,
y: Importance = UnknownImportance,
iz: Importance = UnknownImportance,
n: Importance = UnknownImportance,
z: Importance = UnknownImportance,
v: Importance = UnknownImportance,
c: Importance = UnknownImportance,
d: Importance = UnknownImportance,
m: Importance = UnknownImportance,
w: Importance = UnknownImportance,
) {
override def toString: String = s"A=$a,X=$x,Y=$y,Z=$z,N=$n,C=$c,V=$v,D=$d"
@ -47,38 +53,48 @@ case class CpuImportance(a: Importance = UnknownImportance,
a = this.a ~ that.a,
x = this.x ~ that.x,
y = this.y ~ that.y,
iz = this.iz ~ that.iz,
z = this.z ~ that.z,
n = this.n ~ that.n,
c = this.c ~ that.c,
v = this.v ~ that.v,
d = this.d ~ that.d,
m = this.m ~ that.m,
w = this.w ~ that.w,
)
def isUnimportant(state: State.Value): Boolean = state match {
// UnknownImportance is usually an effect of unreachable code
case State.A => a != Important
case State.AH => ah != Important
case State.X => x != Important
case State.Y => y != Important
case State.IZ => iz != Important
case State.Z => z != Important
case State.N => n != Important
case State.C => c != Important
case State.V => v != Important
case State.D => d != Important
case State.M => m != Important
case State.W => w != Important
}
}
object ReverseFlowAnalyzer {
val aluAdders = Set(Opcode.ADC, Opcode.SBC, Opcode.ISC, Opcode.DCP)
val aluAdders = Set(Opcode.ADC, Opcode.SBC, Opcode.ISC, Opcode.DCP, Opcode.ADC_W, Opcode.SBC_W)
//noinspection RedundantNewCaseClass
def analyze(f: NormalFunction, code: List[AssemblyLine]): List[CpuImportance] = {
val importanceArray = Array.fill[CpuImportance](code.length)(new CpuImportance())
val codeArray = code.toArray
val initialStatus = new CpuStatus(d = SingleStatus(false))
var changed = true
val finalImportance = new CpuImportance(a = Important, x = Important, y = Important, c = Important, v = Important, d = Important, z = Important, n = Important)
val finalImportance = new CpuImportance(
a = Important, ah = Important,
x = Important, y = Important, iz = Important,
c = Important, v = Important, d = Important, z = Important, n = Important,
m = Important, w = Important)
changed = true
while (changed) {
changed = false
@ -91,7 +107,7 @@ object ReverseFlowAnalyzer {
importanceArray(i) = currentImportance
}
codeArray(i) match {
case AssemblyLine(opcode, Relative, MemoryAddressConstant(Label(l)), _) if OpcodeClasses.ShortBranching(opcode) =>
case AssemblyLine(opcode, Relative | LongRelative, MemoryAddressConstant(Label(l)), _) if OpcodeClasses.ShortConditionalBranching(opcode) =>
val L = l
val labelIndex = codeArray.indexWhere {
case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(L)), _) => true
@ -101,21 +117,27 @@ object ReverseFlowAnalyzer {
case _ =>
}
codeArray(i) match {
case AssemblyLine(JSR | JMP, Absolute, MemoryAddressConstant(fun:FunctionInMemory), _) =>
case AssemblyLine(JSR | JMP, Absolute | LongAbsolute, MemoryAddressConstant(fun:FunctionInMemory), _) =>
var result = new CpuImportance(
a = Unimportant,
ah = Unimportant,
x = Unimportant,
y = Unimportant,
iz = Unimportant,
z = Unimportant,
n = Unimportant,
c = Unimportant,
v = Unimportant,
d = Important)
d = Important,
m = Important,
w = Important)
fun.params match {
case AssemblyParamSignature(params) =>
params.foreach(_.variable match {
case RegisterVariable(Register.A, _) =>
result = result.copy(a = Important)
case RegisterVariable(Register.AW, _) =>
result = result.copy(a = Important, ah = Important)
case RegisterVariable(Register.X, _) =>
result = result.copy(x = Important)
case RegisterVariable(Register.Y, _) =>
@ -131,16 +153,16 @@ object ReverseFlowAnalyzer {
case _ =>
}
currentImportance = result
case AssemblyLine(JSR | BRK, _, _, _) =>
case AssemblyLine(JSR | BRK | COP, _, _, _) =>
currentImportance = finalImportance
case AssemblyLine(JMP | BRA, Absolute | Relative, MemoryAddressConstant(Label(l)), _) =>
case AssemblyLine(JMP | BRA, Absolute | Relative | LongAbsolute | LongRelative, MemoryAddressConstant(Label(l)), _) =>
val L = l
val labelIndex = codeArray.indexWhere {
case AssemblyLine(LABEL, _, MemoryAddressConstant(Label(L)), _) => true
case _ => false
}
currentImportance = if (labelIndex < 0) finalImportance else importanceArray(labelIndex)
case AssemblyLine(JMP, Indirect | AbsoluteIndexedX, _, _) =>
case AssemblyLine(JMP, Indirect | AbsoluteIndexedX | LongIndirect, _, _) =>
currentImportance = finalImportance
case AssemblyLine(BNE | BEQ, _, _, _) =>
currentImportance = currentImportance.copy(z = Important)
@ -148,16 +170,50 @@ object ReverseFlowAnalyzer {
currentImportance = currentImportance.copy(n = Important)
case AssemblyLine(SED | CLD, _, _, _) =>
currentImportance = currentImportance.copy(d = Unimportant)
case AssemblyLine(RTS, _, _, _) =>
case AssemblyLine(RTS | RTL, _, _, _) =>
currentImportance = finalImportance
case AssemblyLine(TAX, _, _, _) =>
currentImportance = currentImportance.copy(a = currentImportance.x ~ currentImportance.a ~ currentImportance.n ~ currentImportance.z, x = Unimportant, n = Unimportant, z = Unimportant, m = Important, w = Important)
case AssemblyLine(TAY, _, _, _) =>
currentImportance = currentImportance.copy(a = currentImportance.y ~ currentImportance.a ~ currentImportance.n ~ currentImportance.z, y = Unimportant, n = Unimportant, z = Unimportant, m = Important, w = Important)
case AssemblyLine(TXA, _, _, _) =>
currentImportance = currentImportance.copy(x = currentImportance.a ~ currentImportance.x ~ currentImportance.n ~ currentImportance.z, a = Unimportant, n = Unimportant, z = Unimportant, m = Important, w = Important)
case AssemblyLine(TYA, _, _, _) =>
currentImportance = currentImportance.copy(y = currentImportance.a ~ currentImportance.y ~ currentImportance.n ~ currentImportance.z, a = Unimportant, n = Unimportant, z = Unimportant, m = Important, w = Important)
case AssemblyLine(TAZ, _, _, _) =>
currentImportance = currentImportance.copy(a = currentImportance.iz ~ currentImportance.a ~ currentImportance.n ~ currentImportance.z, iz = Unimportant, n = Unimportant, z = Unimportant, m = Important, w = Important)
case AssemblyLine(TZA, _, _, _) =>
currentImportance = currentImportance.copy(iz = currentImportance.a ~ currentImportance.iz ~ currentImportance.n ~ currentImportance.z, a = Unimportant, n = Unimportant, z = Unimportant, m = Important, w = Important)
case AssemblyLine(TXY, _, _, _) =>
currentImportance = currentImportance.copy(x = currentImportance.y ~ currentImportance.x ~ currentImportance.n ~ currentImportance.z, y = Unimportant, n = Unimportant, z = Unimportant, m = Important, w = Important)
case AssemblyLine(TYX, _, _, _) =>
currentImportance = currentImportance.copy(y = currentImportance.x ~ currentImportance.y ~ currentImportance.n ~ currentImportance.z, x = Unimportant, n = Unimportant, z = Unimportant, m = Important, w = Important)
case AssemblyLine(HuSAX, _, _, _) =>
currentImportance = currentImportance.copy(a = currentImportance.x, x = currentImportance.a, m = Important, w = Important)
case AssemblyLine(SAY, _, _, _) =>
currentImportance = currentImportance.copy(y = currentImportance.a, a = currentImportance.y, m = Important, w = Important)
case AssemblyLine(SXY, _, _, _) =>
currentImportance = currentImportance.copy(y = currentImportance.x, x = currentImportance.y, m = Important, w = Important)
case AssemblyLine(RTI, _, _, _) =>
currentImportance = new CpuImportance(a = Unimportant, x = Unimportant, y = Unimportant, z = Unimportant, n = Unimportant, c = Unimportant, v = Unimportant, d = Unimportant)
currentImportance = new CpuImportance(
a = Unimportant, ah = Unimportant,
x = Unimportant, y = Unimportant, iz = Unimportant,
z = Unimportant, n = Unimportant, c = Unimportant, v = Unimportant, d = Unimportant,
m = Unimportant, w = Unimportant)
case AssemblyLine(DISCARD_XF, _, _, _) =>
currentImportance = currentImportance.copy(x = Unimportant, n = Unimportant, z = Unimportant, c = Unimportant, v = Unimportant)
case AssemblyLine(DISCARD_YF, _, _, _) =>
currentImportance = currentImportance.copy(y = Unimportant, n = Unimportant, z = Unimportant, c = Unimportant, v = Unimportant)
currentImportance = currentImportance.copy(y = Unimportant, iz = Unimportant, n = Unimportant, z = Unimportant, c = Unimportant, v = Unimportant)
case AssemblyLine(DISCARD_AF, _, _, _) =>
currentImportance = currentImportance.copy(a = Unimportant, n = Unimportant, z = Unimportant, c = Unimportant, v = Unimportant)
case AssemblyLine(REP | SEP, _, NumericConstant(n, _), _) =>
if ((n & 1) != 0) currentImportance = currentImportance.copy(c = Unimportant)
if ((n & 2) != 0) currentImportance = currentImportance.copy(z = Unimportant)
if ((n & 8) != 0) currentImportance = currentImportance.copy(d = Unimportant)
if ((n & 0x10) != 0) currentImportance = currentImportance.copy(w = Unimportant)
if ((n & 0x20) != 0) currentImportance = currentImportance.copy(m = Unimportant)
if ((n & 0x40) != 0) currentImportance = currentImportance.copy(v = Unimportant)
if ((n & 0x80) != 0) currentImportance = currentImportance.copy(n = Unimportant)
case AssemblyLine(opcode, addrMode, _, _) =>
val reallyIgnoreC =
currentImportance.c == Unimportant &&
@ -177,18 +233,27 @@ object ReverseFlowAnalyzer {
if (OpcodeClasses.ChangesV(opcode)) currentImportance = currentImportance.copy(v = Unimportant)
if (OpcodeClasses.ChangesNAndZ(opcode)) currentImportance = currentImportance.copy(n = Unimportant, z = Unimportant)
if (OpcodeClasses.OverwritesA(opcode)) currentImportance = currentImportance.copy(a = Unimportant)
if (OpcodeClasses.OverwritesAH(opcode)) currentImportance = currentImportance.copy(ah = Unimportant)
if (OpcodeClasses.OverwritesX(opcode)) currentImportance = currentImportance.copy(x = Unimportant)
if (OpcodeClasses.OverwritesY(opcode)) currentImportance = currentImportance.copy(y = Unimportant)
if (OpcodeClasses.OverwritesIZ(opcode)) currentImportance = currentImportance.copy(iz = Unimportant)
if (OpcodeClasses.ReadsC(opcode) && !reallyIgnoreC) currentImportance = currentImportance.copy(c = Important)
if (OpcodeClasses.ReadsD(opcode)) currentImportance = currentImportance.copy(d = Important)
if (OpcodeClasses.ReadsV(opcode)) currentImportance = currentImportance.copy(v = Important)
if (OpcodeClasses.ReadsXAlways(opcode)) currentImportance = currentImportance.copy(x = Important)
if (OpcodeClasses.ReadsYAlways(opcode)) currentImportance = currentImportance.copy(y = Important)
if (OpcodeClasses.ReadsIZAlways(opcode)) currentImportance = currentImportance.copy(iz = Important)
if (OpcodeClasses.ReadsM(opcode)) currentImportance = currentImportance.copy(m = Important)
if (OpcodeClasses.ReadsW(opcode)) currentImportance = currentImportance.copy(w = Important)
if (OpcodeClasses.ReadsAAlways(opcode) && !reallyIgnoreA) currentImportance = currentImportance.copy(a = Important)
if (OpcodeClasses.ReadsAHAlways(opcode)) currentImportance = currentImportance.copy(ah = Important)
if (OpcodeClasses.ReadsAIfImplied(opcode) && addrMode == Implied) currentImportance = currentImportance.copy(a = Important)
if (addrMode == AbsoluteX || addrMode == IndexedX || addrMode == ZeroPageX || addrMode == AbsoluteIndexedX)
if (OpcodeClasses.ReadsAHIfImplied(opcode) && addrMode == Implied) currentImportance = currentImportance.copy(ah = Important)
if (addrMode == AbsoluteX || addrMode == LongAbsoluteX || addrMode == IndexedX || addrMode == ZeroPageX || addrMode == AbsoluteIndexedX)
currentImportance = currentImportance.copy(x = Important)
if (addrMode == AbsoluteY || addrMode == IndexedY || addrMode == ZeroPageY)
if (addrMode == IndexedZ /*|| addrMode == LongIndexedZ*/)
currentImportance = currentImportance.copy(iz = Important)
if (addrMode == AbsoluteY || addrMode == IndexedY || addrMode == ZeroPageY || addrMode == LongIndexedY || addrMode == IndexedSY)
currentImportance = currentImportance.copy(y = Important)
}
}

View File

@ -142,15 +142,16 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions) {
val labels = mutable.Set[String]()
val jumps = mutable.Set[String]()
get[List[AssemblyLine]](i).foreach {
case AssemblyLine(Opcode.RTS | Opcode.RTI | Opcode.BRK, _, _, _) =>
// JSR and BSR are allowed
case AssemblyLine(Opcode.RTS | Opcode.RTI | Opcode.RTL | Opcode.BRK, _, _, _) =>
return false
case AssemblyLine(Opcode.JMP, AddrMode.Indirect, _, _) =>
case AssemblyLine(Opcode.JMP, AddrMode.Indirect | AddrMode.AbsoluteIndexedX | AddrMode.LongIndirect, _, _) =>
return false
case AssemblyLine(Opcode.LABEL, _, MemoryAddressConstant(Label(l)), _) =>
labels += l
case AssemblyLine(Opcode.JMP, AddrMode.Absolute, MemoryAddressConstant(Label(l)), _) =>
jumps += l
case AssemblyLine(Opcode.JMP, AddrMode.Absolute, _, _) =>
case AssemblyLine(Opcode.JMP, AddrMode.Absolute | AddrMode.LongAbsolute, _, _) =>
return false
case AssemblyLine(_, AddrMode.Relative, MemoryAddressConstant(Label(l)), _) =>
jumps += l
@ -191,25 +192,29 @@ trait AssemblyPattern {
}
object HelperCheckers {
import AddrMode._
private val badAddrModes = Set(IndexedX, IndexedY, ZeroPageIndirect, AbsoluteIndexedX)
private val goodAddrModes = Set(Implied, Immediate, Relative)
private val badAddrModes = Set(IndexedX, IndexedY, IndexedZ, LongIndexedY, LongIndexedZ, IndexedSY, Indirect, TripleAbsolute, Stack)
private val goodAddrModes = Set(Implied, Immediate, WordImmediate, Relative, LongRelative)
def memoryAccessDoesntOverlap(l1: AssemblyLine, l2: AssemblyLine): Boolean = {
memoryAccessDoesntOverlap(l1.addrMode, l1.parameter, l2.addrMode, l2.parameter)
}
def memoryAccessDoesntOverlap(a1: AddrMode.Value, p1: Constant, a2: AddrMode.Value, p2: Constant): Boolean = {
val a1 = l1.addrMode
val a2 = l2.addrMode
if (badAddrModes(a1) || badAddrModes(a2)) return false
if (goodAddrModes(a1) || goodAddrModes(a2)) return true
if ((a1 == IndexedSY) != (a2 == IndexedSY)) return true // bold assertion, but usually true
val p1 = l1.parameter
val p2 = l2.parameter
val w1 = OpcodeClasses.AccessesWordInMemory(l1.opcode)
val w2 = OpcodeClasses.AccessesWordInMemory(l2.opcode)
def handleKnownDistance(distance: Short): Boolean = {
val indexingAddrModes = Set(AbsoluteIndexedX, AbsoluteX, ZeroPageX, AbsoluteY, ZeroPageY)
// `distance` is the distance between the first byte that can be addressed by l1 (b1) and the first byte that can be addressed by l2 (b2): (b2-b1)
val indexingAddrModes = Set(AbsoluteIndexedX, AbsoluteX, ZeroPageX, AbsoluteY, ZeroPageY, LongAbsoluteX)
val a1Indexing = indexingAddrModes(a1)
val a2Indexing = indexingAddrModes(a2)
(a1Indexing, a2Indexing) match {
case (false, false) => distance != 0
case (true, false) => distance > 255 || distance < 0
case (false, true) => distance > 0 || distance < -255
case (true, true) => distance > 255 || distance < -255
case (false, false) => distance != 0 && (distance != 1 || !w1) && (distance != -1 || !w2)
case (true, false) => distance > 255 || distance < 0 && (distance != 256 || !w1) && (distance != -1 || !w2)
case (false, true) => distance > 0 || distance < -255 && (distance != 1 || !w1) && (distance != -256 || !w2)
case (true, true) => distance > 255 || distance < -255 && (distance != 265 || !w1) && (distance != -256 || !w2)
}
}
@ -436,7 +441,7 @@ case class WhereNoMemoryAccessOverlapBetweenTwoLineLists(ix1: Int, ix2: Int) ext
override def matchTo(ctx: AssemblyMatchingContext, code: List[(FlowInfo, AssemblyLine)]): Option[List[(FlowInfo, AssemblyLine)]] = {
val s1s = ctx.get[List[AssemblyLine]](ix1)
val s2s = ctx.get[List[AssemblyLine]](ix2)
if (s1s.forall(s1 => s2s.forall(s2 => HelperCheckers.memoryAccessDoesntOverlap(s1.addrMode, s1.parameter, s2.addrMode, s2.parameter)))) Some(code) else None
if (s1s.forall(s1 => s2s.forall(s2 => HelperCheckers.memoryAccessDoesntOverlap(s1, s2)))) Some(code) else None
}
}
@ -502,6 +507,14 @@ case class HasY(value: Int) extends AssemblyLinePattern {
flowInfo.statusBefore.y.contains(value)
}
case class HasZ(value: Int) extends AssemblyLinePattern {
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
FlowInfoRequirement.assertForward(needsFlowInfo)
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
flowInfo.statusBefore.iz.contains(value)
}
case class DoesntMatterWhatItDoesWith(states: State.Value*) extends AssemblyLinePattern {
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
FlowInfoRequirement.assertBackward(needsFlowInfo)
@ -520,6 +533,38 @@ case class HasSet(state: State.Value) extends AssemblyLinePattern {
flowInfo.hasSet(state)
}
object HasAccu8 extends AssemblyLinePattern {
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
FlowInfoRequirement.assertForward(needsFlowInfo)
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
flowInfo.hasSet(State.M)
}
object HasAccu16 extends AssemblyLinePattern {
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
FlowInfoRequirement.assertForward(needsFlowInfo)
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
flowInfo.hasClear(State.M)
}
object HasIndex8 extends AssemblyLinePattern {
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
FlowInfoRequirement.assertForward(needsFlowInfo)
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
flowInfo.hasSet(State.W)
}
object HasIndex16 extends AssemblyLinePattern {
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
FlowInfoRequirement.assertForward(needsFlowInfo)
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
flowInfo.hasClear(State.W)
}
case class HasClear(state: State.Value) extends AssemblyLinePattern {
override def validate(needsFlowInfo: FlowInfoRequirement.Value): Unit =
FlowInfoRequirement.assertForward(needsFlowInfo)
@ -598,6 +643,11 @@ case object ReadsA extends TrivialAssemblyLinePattern {
OpcodeClasses.ReadsAAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ReadsAIfImplied(line.opcode)
}
case object ReadsAH extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean =
OpcodeClasses.ReadsAHAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ReadsAHIfImplied(line.opcode)
}
case object ReadsMemory extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean =
line.addrMode match {
@ -632,38 +682,76 @@ case object ConcernsA extends TrivialAssemblyLinePattern {
OpcodeClasses.ConcernsAAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ConcernsAIfImplied(line.opcode)
}
case object ConcernsAH extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean =
OpcodeClasses.ConcernsAHAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ConcernsAHIfImplied(line.opcode)
}
case object ConcernsX extends TrivialAssemblyLinePattern {
val XAddrModes = Set(AddrMode.AbsoluteX, AddrMode.IndexedX, AddrMode.ZeroPageX)
val XAddrModes = Set(AddrMode.AbsoluteX, AddrMode.AbsoluteIndexedX, AddrMode.LongAbsoluteX, AddrMode.IndexedX, AddrMode.ZeroPageX)
override def apply(line: AssemblyLine): Boolean =
OpcodeClasses.ConcernsXAlways(line.opcode) || XAddrModes(line.addrMode)
}
case object ConcernsY extends TrivialAssemblyLinePattern {
val YAddrModes = Set(AddrMode.AbsoluteY, AddrMode.IndexedY, AddrMode.ZeroPageY)
val YAddrModes = Set(AddrMode.AbsoluteY, AddrMode.IndexedSY, AddrMode.IndexedY, AddrMode.LongIndexedY, AddrMode.ZeroPageY)
override def apply(line: AssemblyLine): Boolean =
OpcodeClasses.ConcernsYAlways(line.opcode) || YAddrModes(line.addrMode)
}
case object ConcernsStack extends TrivialAssemblyLinePattern {
val SAddrModes = Set(AddrMode.IndexedSY, AddrMode.Stack)
override def apply(line: AssemblyLine): Boolean =
OpcodeClasses.ConcernsStackAlways(line.opcode) || SAddrModes(line.addrMode)
}
case object ConcernsIZ extends TrivialAssemblyLinePattern {
val IZAddrModes = Set(AddrMode.IndexedZ, AddrMode.LongIndexedZ)
override def apply(line: AssemblyLine): Boolean =
OpcodeClasses.ConcernsIZAlways(line.opcode) || IZAddrModes(line.addrMode)
}
case object ChangesA extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean =
OpcodeClasses.ChangesAAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ChangesAIfImplied(line.opcode)
}
case object ChangesAH extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean =
OpcodeClasses.ChangesAHAlways(line.opcode) || line.addrMode == AddrMode.Implied && OpcodeClasses.ChangesAHIfImplied(line.opcode)
}
case object ChangesM extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean = line match {
case AssemblyLine(Opcode.SEP | Opcode.REP, AddrMode.Immediate, NumericConstant(n, _), _) => (n & 0x20) != 0
case AssemblyLine(Opcode.SEP | Opcode.REP | Opcode.PLP | Opcode.XCE, _, _, _) => true
case _ => false
}
}
case object ChangesW extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean = line match {
case AssemblyLine(Opcode.SEP | Opcode.REP, AddrMode.Immediate, NumericConstant(n, _), _) => (n & 0x10) != 0
case AssemblyLine(Opcode.SEP | Opcode.REP | Opcode.PLP | Opcode.XCE, _, _, _) => true
case _ => false
}
}
case object ChangesMemory extends TrivialAssemblyLinePattern {
override def apply(line: AssemblyLine): Boolean =
OpcodeClasses.ChangesMemoryAlways(line.opcode) || line.addrMode != AddrMode.Implied && OpcodeClasses.ChangesMemoryIfNotImplied(line.opcode)
}
case class DoesntChangeMemoryAt(addrMode1: Int, param1: Int) extends AssemblyLinePattern {
case class DoesntChangeMemoryAt(addrMode1: Int, param1: Int, opcode: Opcode.Value = Opcode.NOP) extends AssemblyLinePattern {
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = {
val p1 = ctx.get[Constant](param1)
val p2 = line.parameter
val a1 = ctx.get[AddrMode.Value](addrMode1)
val a2 = line.addrMode
val changesSomeMemory = OpcodeClasses.ChangesMemoryAlways(line.opcode) || line.addrMode != AddrMode.Implied && OpcodeClasses.ChangesMemoryIfNotImplied(line.opcode)
!changesSomeMemory || HelperCheckers.memoryAccessDoesntOverlap(a1, p1, a2, p2)
// TODO: NOP
// this will break if the actual instruction was 16-bit
!changesSomeMemory || HelperCheckers.memoryAccessDoesntOverlap(AssemblyLine(opcode, a1, p1), line)
}
}
@ -675,10 +763,10 @@ case object ConcernsMemory extends TrivialAssemblyLinePattern {
case class DoesNotConcernMemoryAt(addrMode1: Int, param1: Int) extends AssemblyLinePattern {
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean = {
val p1 = ctx.get[Constant](param1)
val p2 = line.parameter
val a1 = ctx.get[AddrMode.Value](addrMode1)
val a2 = line.addrMode
HelperCheckers.memoryAccessDoesntOverlap(a1, p1, a2, p2)
// TODO: NOP
// this will break if the actual instruction was 16-bit
HelperCheckers.memoryAccessDoesntOverlap(AssemblyLine(Opcode.NOP, a1, p1), line)
}
}
@ -788,8 +876,11 @@ case class MatchNumericImmediate(i: Int) extends AssemblyLinePattern {
case class DoesntChangeIndexingInAddrMode(i: Int) extends AssemblyLinePattern {
override def matchLineTo(ctx: AssemblyMatchingContext, flowInfo: FlowInfo, line: AssemblyLine): Boolean =
ctx.get[AddrMode.Value](i) match {
case AddrMode.ZeroPageX | AddrMode.AbsoluteX | AddrMode.IndexedX | AddrMode.AbsoluteIndexedX => !OpcodeClasses.ChangesX.contains(line.opcode)
case AddrMode.ZeroPageY | AddrMode.AbsoluteY | AddrMode.IndexedY => !OpcodeClasses.ChangesY.contains(line.opcode)
case AddrMode.ZeroPageX | AddrMode.AbsoluteX | AddrMode.LongAbsoluteX | AddrMode.IndexedX | AddrMode.AbsoluteIndexedX => !OpcodeClasses.ChangesX.contains(line.opcode)
case AddrMode.ZeroPageY | AddrMode.AbsoluteY | AddrMode.IndexedY | AddrMode.LongIndexedY => !OpcodeClasses.ChangesY.contains(line.opcode)
case AddrMode.IndexedZ | AddrMode.LongIndexedZ => !OpcodeClasses.ChangesIZ.contains(line.opcode)
case AddrMode.Stack => !OpcodeClasses.ChangesS.contains(line.opcode)
case AddrMode.IndexedSY => !OpcodeClasses.ChangesS.contains(line.opcode) && !OpcodeClasses.ChangesY.contains(line.opcode)
case _ => true
}

View File

@ -0,0 +1,195 @@
package millfork.assembly.opt
import millfork.assembly.Opcode._
import millfork.assembly.AddrMode._
import millfork.assembly.OpcodeClasses._
import millfork.assembly.{AssemblyLine, OpcodeClasses, State}
import millfork.env.{Constant, NumericConstant}
/**
* @author Karol Stasiak
*/
object SixteenOptimizations {
val AccumulatorSwapping = new RuleBasedAssemblyOptimization("Accumulator swapping",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & HasOpcode(PHA) & HasAccu8 & DoesntMatterWhatItDoesWith(State.AH, State.A, State.N, State.Z)) ~
(Linear & Not(ConcernsStack)) ~
(Elidable & HasOpcode(PLA) & DoesntMatterWhatItDoesWith(State.AH)) ~~> { code =>
AssemblyLine.implied(XBA) :: (code.tail.init :+ AssemblyLine.implied(XBA))
},
(Elidable & HasOpcode(TAX) & HasAccu8 & HasIndex8 & DoesntMatterWhatItDoesWith(State.AH, State.A, State.N, State.Z)) ~
(Linear & Not(ConcernsX)) ~
(Elidable & HasOpcode(TXA) & DoesntMatterWhatItDoesWith(State.AH, State.X)) ~~> { code =>
AssemblyLine.implied(XBA) :: (code.tail.init :+ AssemblyLine.implied(XBA))
},
(Elidable & HasOpcode(TAY) & HasAccu8 & HasIndex8 & DoesntMatterWhatItDoesWith(State.AH, State.A, State.N, State.Z)) ~
(Linear & Not(ConcernsY)) ~
(Elidable & HasOpcode(TYA) & DoesntMatterWhatItDoesWith(State.AH, State.Y)) ~~> { code =>
AssemblyLine.implied(XBA) :: (code.tail.init :+ AssemblyLine.implied(XBA))
},
)
val RepSepWeakening = new RuleBasedAssemblyOptimization("REP/SEP weakening",
needsFlowInfo = FlowInfoRequirement.BothFlows,
(Elidable & HasOpcodeIn(Set(SEP, REP)) & HasImmediate(0)) ~~> (_ => Nil),
(Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.C)) ~
Where(c => c.get[Int](0).&(0x1).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xFE
if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i))
},
(Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.Z)) ~
Where(c => c.get[Int](0).&(0x2).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xFD
if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i))
},
(Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.D)) ~
Where(c => c.get[Int](0).&(0x8).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xF7
if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i))
},
(Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.W)) ~
Where(c => c.get[Int](0).&(0x10).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xEF
if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i))
},
(Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.M)) ~
Where(c => c.get[Int](0).&(0x20).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xDF
if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i))
},
(Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.V)) ~
Where(c => c.get[Int](0).&(0x40).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xBF
if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i))
},
(Elidable & HasOpcodeIn(Set(SEP, REP)) & MatchNumericImmediate(0) & DoesntMatterWhatItDoesWith(State.N)) ~
Where(c => c.get[Int](0).&(0x80).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0x7F
if (i == 0) Nil else List(AssemblyLine.immediate(code.head.opcode, i))
},
(Elidable & HasOpcode(SEP) & HasSet(State.C) & MatchNumericImmediate(0)) ~
Where(c => c.get[Int](0).&(0x1).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xFE
if (i == 0) Nil else List(AssemblyLine.immediate(SEP, i))
},
(Elidable & HasOpcode(REP) & HasClear(State.C) & MatchNumericImmediate(0)) ~
Where(c => c.get[Int](0).&(0x1).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xFE
if (i == 0) Nil else List(AssemblyLine.immediate(REP, i))
},
(Elidable & HasOpcode(SEP) & HasSet(State.W) & MatchNumericImmediate(0)) ~
Where(c => c.get[Int](0).&(0x10).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xEF
if (i == 0) Nil else List(AssemblyLine.immediate(SEP, i))
},
(Elidable & HasOpcode(REP) & HasClear(State.W) & MatchNumericImmediate(0)) ~
Where(c => c.get[Int](0).&(0x10).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xEF
if (i == 0) Nil else List(AssemblyLine.immediate(REP, i))
},
(Elidable & HasOpcode(SEP) & HasSet(State.M) & MatchNumericImmediate(0)) ~
Where(c => c.get[Int](0).&(0x20).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xDF
if (i == 0) Nil else List(AssemblyLine.immediate(SEP, i))
},
(Elidable & HasOpcode(REP) & HasClear(State.M) & MatchNumericImmediate(0)) ~
Where(c => c.get[Int](0).&(0x20).!=(0)) ~~> { (code, ctx) =>
val i = ctx.get[Int](0) & 0xDF
if (i == 0) Nil else List(AssemblyLine.immediate(REP, i))
},
)
val PointlessLoadAfterLoadOrStore = new RuleBasedAssemblyOptimization("Pointless 16-bit load after load or store",
needsFlowInfo = FlowInfoRequirement.NoRequirement,
(HasOpcodeIn(Set(LDA_W, STA_W)) & HasAddrMode(WordImmediate) & MatchParameter(1)) ~
(Linear & Not(ChangesA) & Not(ChangesAH)).* ~
(Elidable & HasOpcode(LDA_W) & HasAddrMode(WordImmediate) & MatchParameter(1)) ~~> (_.init),
(HasOpcodeIn(Set(LDA_W, STA_W)) & MatchAddrMode(0) & MatchParameter(1)) ~
(Linear & Not(ChangesA) & Not(ChangesAH) & DoesntChangeIndexingInAddrMode(0) & DoesntChangeMemoryAt(0, 1, LDA_W)).* ~
(Elidable & HasOpcode(LDA_W) & MatchAddrMode(0) & MatchParameter(1)) ~~> (_.init),
)
val OptimizeZeroIndex = new RuleBasedAssemblyOptimization("Optimizing zero index for far pointers",
needsFlowInfo = FlowInfoRequirement.ForwardFlow,
(Elidable & HasY(0) /*& HasZ(0)*/ & HasIndex8 & HasAddrMode(LongIndexedY) & HasOpcodeIn(SupportsLongIndexedZ)) ~~> (code => code.map(_.copy(addrMode = LongIndexedZ))),
)
private val SupportsStackAddressing = Set(
ADC, AND, EOR, ORA, LDA, STA, SBC, CMP,
)
val OptimizeStackRelative = new RuleBasedAssemblyOptimization("Optimizing stack variables",
needsFlowInfo = FlowInfoRequirement.BothFlows,
((Elidable & HasOpcode(TSX)) ~
(Elidable & HasOpcode(INX)).*.captureLength(60) ~
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.N, State.Z)).*.captureLength(61)).capture(3) ~
(Not(ConcernsX) & Not(ChangesS)).*.capture(2) ~
(Elidable & SupportsStackAddressing & HasAddrMode(AbsoluteX) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.X)) ~
Where(ctx => ctx.get[Constant](1) match {
case NumericConstant(x, _) => x >= 0x100 && x <= 0x1ff
case _ => false
}) ~~> { (code, ctx) =>
ctx.get[List[AssemblyLine]](2) ++ List(
code.last.copy(addrMode = Stack, parameter = (ctx.get[Constant](1) + ctx.get[Int](60) - ctx.get[Int](61)-0x100).quickSimplify)
)
},
((Elidable & HasOpcode(TSX)) ~
(Elidable & HasOpcode(INX)).*.captureLength(60) ~
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.N, State.Z)).*.captureLength(61)).capture(3) ~
(Not(ConcernsX) & Not(ChangesS)).*.capture(2) ~
(Elidable & SupportsStackAddressing & HasAddrMode(AbsoluteX) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z)) ~
Where(ctx => ctx.get[Constant](1) match {
case NumericConstant(x, _) => x >= 0x100 && x <= 0x1ff
case _ => false
}) ~~> { (code, ctx) =>
ctx.get[List[AssemblyLine]](2) ++ List(
code.last.copy(addrMode = Stack, parameter = (ctx.get[Constant](1) + ctx.get[Int](60) - ctx.get[Int](61)-0x100).quickSimplify)
) ++ ctx.get[List[AssemblyLine]](3)
},
((Elidable & HasOpcode(TSX)) ~
(Elidable & HasOpcode(INX)).*.captureLength(60) ~
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.N, State.Z)).*.captureLength(61)).capture(3) ~
(Not(ConcernsX) & Not(ChangesS)).*.capture(2) ~
(Elidable & HasOpcode(LDY) & HasAddrMode(AbsoluteX) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.X, State.A)) ~
Where(ctx => ctx.get[Constant](1) match {
case NumericConstant(x, _) => x >= 0x100 && x <= 0x1ff
case _ => false
}) ~~> { (code, ctx) =>
ctx.get[List[AssemblyLine]](2) ++ List(
AssemblyLine(LDA, Stack,(ctx.get[Constant](1) + ctx.get[Int](60) - ctx.get[Int](61)-0x100).quickSimplify),
AssemblyLine.implied(TAY)
)
},
((Elidable & HasOpcode(TSX)) ~
(Elidable & HasOpcode(INX)).*.captureLength(60) ~
(Elidable & HasOpcode(DEX) & DoesntMatterWhatItDoesWith(State.N, State.Z)).*.captureLength(61)).capture(3) ~
(Not(ConcernsX) & Not(ChangesS)).*.capture(2) ~
(Elidable & HasOpcode(LDY) & HasAddrMode(AbsoluteX) & MatchParameter(1) & DoesntMatterWhatItDoesWith(State.N, State.Z, State.A)) ~
Where(ctx => ctx.get[Constant](1) match {
case NumericConstant(x, _) => x >= 0x100 && x <= 0x1ff
case _ => false
}) ~~> { (code, ctx) =>
ctx.get[List[AssemblyLine]](2) ++ List(
AssemblyLine(LDA, Stack,(ctx.get[Constant](1) + ctx.get[Int](60) - ctx.get[Int](61)-0x100).quickSimplify),
AssemblyLine.implied(TAY)
) ++ ctx.get[List[AssemblyLine]](3)
},
)
// TODO: rewrite most 8-bit optimizations that are applicable to 16-bit code
val AllForEmulation: List[AssemblyOptimization] = List(AccumulatorSwapping, OptimizeZeroIndex, RepSepWeakening, OptimizeStackRelative)
val AllForNative: List[AssemblyOptimization] = List(PointlessLoadAfterLoadOrStore)
val All: List[AssemblyOptimization] = AllForEmulation ++ AllForNative
}

View File

@ -15,49 +15,87 @@ import scala.collection.mutable.ListBuffer
*/
object VariableToRegisterOptimization extends AssemblyOptimization {
case class Features(
blastProcessing: Boolean,
izIsAlwaysZero: Boolean,
indexRegisterTransfers: Boolean,
identityArray: Constant)
// If any of these opcodes is present within a method,
// then it's too hard to assign any variable to a register.
private val opcodesThatAlwaysPrecludeXAllocation = Set(
JSR, STX, TXA, INX, DEX, CPX,
LDX_W, STX_W, CPX_W, DEX_W, INX_W,
PHX, PLX,
SBX, SAX, LXA, XAA, AHX, SHX, SHY, LAS, TAS)
SBX, SAX, LXA, XAA, AHX, SHX, SHY, LAS, TAS,
HuSAX, SXY, TXY, TXY,
SEP, REP,
)
private val opcodesThatAlwaysPrecludeYAllocation = Set(
JSR, STY, TYA, INY, DEY, CPY,
LDY_W, STY_W, CPY_W, DEY_W, INY_W,
PHY, PLY,
AHX, SHX, SHY, LAS, TAS)
AHX, SHX, SHY, LAS, TAS,
SAY, SXY, TXY, TYX,
SEP, REP,
)
private val opcodesThatAlwaysPrecludeZAllocation = Set(
JSR, STZ, TZA, INZ, DEZ, CPZ,
PHZ, PLZ,
SEP, REP,
)
private val opcodesThatAlwaysPrecludeAAllocation = Set(
JSR, PHA, PLA,
ADC, SBC, ORA, EOR, AND,
ADC, SBC, ORA, EOR, AND, BIT,
ADC_W, SBC_W, ORA_W, EOR_W, AND_W, BIT_W,
RRA, RLA, ISC, SLO, SRE,
ALR, ARR, ANC, SBX, LXA, XAA,
AHX, SHX, SHY, LAS, TAS
AHX, SHX, SHY, LAS, TAS,
HuSAX, SAY,
TCD, TDC, TSC, TCS,
SEP, REP,
)
// If any of these opcodes is used on a variable
// then it's too hard to assign that variable to a register.
// Also, LDY prevents assigning a variable to X and LDX and LAX prevent assigning a variable to Y.
private val opcodesThatCannotBeUsedWithIndexRegistersAsParameters = Set(
LDX_W, LDY_W, LDA_W,
BIT, CPX, CPY, STY,
BIT_W, CPX_W, CPY_W, STY_W,
EOR, ORA, AND, ADC, SBC, CMP,
EOR_W, ORA_W, AND_W, ADC_W, SBC_W, CMP_W,
ROL, ROR, LSR, ASL, STX,
ROL_W, ROR_W, LSR_W, ASL_W, STX_W,
INC_W, DEC_W,
SAX, SLO, SRE, ISC, DCP, RLA, RRA,
AHX, SHY, SHX, LAS, TAS,
TRB, TSB)
TRB, TSB,
ASR,
)
private val opcodesThatCannotBeUsedWithAccumulatorAsParameter = Set(
BIT, CPX, CPY,
BIT_W, CPX_W, CPY_W,
EOR, ORA, AND, ADC, SBC, CMP, STA,
EOR_W, ORA_W, AND_W, ADC_W, SBC_W, CMP_W, STA_W,
INC_W, DEC_W,
SAX, SLO, SRE, ISC, DCP, RLA, RRA,
AHX, SHY, SHX, LAS, TAS,
TRB, TSB)
TRB, TSB,
ASR,
)
private val opcodesCommutative = Set(AND, ORA, EOR, ADC)
private val opcodesIdentityTable = Set(AND, ORA, EOR, CMP, ADC, SBC)
private val LdxAddrModes = Set(ZeroPage, Absolute, Immediate, AbsoluteY, ZeroPageY)
private val LdyAddrModes = Set(ZeroPage, Absolute, Immediate, AbsoluteX, ZeroPageX)
private val LdzAddrModes = Set(Absolute, Immediate, AbsoluteX)
private val CpxyzAddrModes = Set(Absolute, Immediate, ZeroPage)
override def name = "Allocating variables to index registers"
@ -97,13 +135,20 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
val importances = ReverseFlowAnalyzer.analyze(f, code)
val blastProcessing = options.flag(CompilationFlag.OptimizeForSonicSpeed)
val identityArray = f.environment.maybeGet[ThingInMemory]("identity$").map(MemoryAddressConstant).getOrElse(Constant.Zero)
val izIsAlwaysZero = !options.flag(CompilationFlag.Emit65CE02Opcodes)
val features = Features(
blastProcessing =options.flag(CompilationFlag.OptimizeForSonicSpeed),
izIsAlwaysZero = !options.flag(CompilationFlag.Emit65CE02Opcodes),
indexRegisterTransfers = options.flag(CompilationFlag.EmitEmulation65816Opcodes),
identityArray = f.environment.maybeGet[ThingInMemory]("identity$").map(MemoryAddressConstant).getOrElse(Constant.Zero)
)
val xCandidates = variablesWithLifetimes.filter {
case (vName, range) =>
importances(range.start).x != Important
}.flatMap {
case (vName, range) =>
canBeInlined(Some(vName), None, blastProcessing, code.zip(importances).slice(range.start, range.end)).map { score =>
canBeInlined(Some(vName), None, None, features, code.zip(importances).slice(range.start, range.end)).map { score =>
(vName, range, if (variablesWithRegisterHint(vName)) score + 16 else score)
}
}
@ -113,7 +158,17 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
importances(range.start).y != Important
}.flatMap {
case (vName, range) =>
canBeInlined(None, Some(vName), blastProcessing, code.zip(importances).slice(range.start, range.end)).map { score =>
canBeInlined(None, Some(vName), None, features, code.zip(importances).slice(range.start, range.end)).map { score =>
(vName, range, if (variablesWithRegisterHint(vName)) score + 16 else score)
}
}
val zCandidates = if (izIsAlwaysZero) Nil else variablesWithLifetimes.filter {
case (vName, range) =>
importances(range.start).iz != Important
}.flatMap {
case (vName, range) =>
canBeInlined(None, None, Some(vName), features, code.zip(importances).slice(range.start, range.end)).map { score =>
(vName, range, if (variablesWithRegisterHint(vName)) score + 16 else score)
}
}
@ -134,27 +189,33 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
}
// println(s"X: $xCandidates")
// println(s"Y: $yCandidates")
// println(s"Z: $zCandidates")
// println(s"A: $aCandidates")
val xCandidateSets = NonOverlappingIntervals.apply[(String, Range, Int)](xCandidates, _._2.start, _._2.end)
val yCandidateSets = NonOverlappingIntervals.apply[(String, Range, Int)](yCandidates, _._2.start, _._2.end)
val zCandidateSets = NonOverlappingIntervals.apply[(String, Range, Int)](zCandidates, _._2.start, _._2.end)
val aCandidateSets = NonOverlappingIntervals.apply[(String, Range, Int)](aCandidates, _._2.start, _._2.end)
val variants = for {
vx <- xCandidateSets.par
vy <- yCandidateSets
vz <- zCandidateSets
va <- aCandidateSets
if (vx & vz).isEmpty
if (vz & vy).isEmpty
if (va & vz).isEmpty
if (vx & vy).isEmpty
if (vx & va).isEmpty
if (va & vy).isEmpty
score = vx.toSeq.map(_._3).sum + vy.toSeq.map(_._3).sum + va.toSeq.map(_._3).sum
} yield (score, vx, vy, va)
score = vx.toSeq.map(_._3).sum + vy.toSeq.map(_._3).sum + va.toSeq.map(_._3).sum + vz.toSeq.map(_._3).sum
} yield (score, vx, vy, vz, va)
if (variants.isEmpty) {
return code
}
val (_, bestXs, bestYs, bestAs) = variants.maxBy(_._1)
val (_, bestXs, bestYs, bestZs, bestAs) = variants.maxBy(_._1)
def reportOptimizedBlock[T](oldCode: List[(AssemblyLine, T)], newCode: List[AssemblyLine]): Unit = {
oldCode.foreach(l => ErrorReporting.trace(l._1.toString))
@ -162,9 +223,10 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
newCode.foreach(l => ErrorReporting.trace(l.toString))
}
if (bestXs.nonEmpty || bestYs.nonEmpty || bestAs.nonEmpty) {
if (bestXs.nonEmpty || bestYs.nonEmpty || bestZs.nonEmpty || bestAs.nonEmpty) {
bestXs.foreach(v => f.environment.removeVariable(v._1))
bestYs.foreach(v => f.environment.removeVariable(v._1))
bestZs.foreach(v => f.environment.removeVariable(v._1))
bestAs.foreach(v => f.environment.removeVariable(v._1))
val output = ListBuffer[AssemblyLine]()
var i = 0
@ -174,7 +236,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
case (v, range, _) =>
ErrorReporting.debug(s"Inlining $v to register X")
val oldCode = code.zip(importances).slice(range.start, range.end)
val newCode = inlineVars(Some(v), None, None, identityArray, oldCode)
val newCode = inlineVars(Some(v), None, None, None, features, oldCode)
reportOptimizedBlock(oldCode, newCode)
output ++= newCode
i = range.end
@ -185,7 +247,19 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
case (v, range, _) =>
ErrorReporting.debug(s"Inlining $v to register Y")
val oldCode = code.zip(importances).slice(range.start, range.end)
val newCode = inlineVars(None, Some(v), None, identityArray, oldCode)
val newCode = inlineVars(None, Some(v), None, None, features, oldCode)
reportOptimizedBlock(oldCode, newCode)
output ++= newCode
i = range.end
done = true
}
}
if (!done) {
bestZs.find(_._2.start == i).foreach {
case (v, range, _) =>
ErrorReporting.debug(s"Inlining $v to register Z")
val oldCode = code.zip(importances).slice(range.start, range.end)
val newCode = inlineVars(None, None, Some(v), None, features, oldCode)
reportOptimizedBlock(oldCode, newCode)
output ++= newCode
i = range.end
@ -197,7 +271,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
case (v, range, _) =>
ErrorReporting.debug(s"Inlining $v to register A")
val oldCode = code.zip(importances).slice(range.start, range.end)
val newCode = inlineVars(None, None, Some(v), identityArray, oldCode)
val newCode = inlineVars(None, None, None, Some(v), features, oldCode)
reportOptimizedBlock(oldCode, newCode)
output ++= newCode
i = range.end
@ -215,28 +289,42 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
}
}
def canBeInlined(xCandidate: Option[String], yCandidate: Option[String], blastProcessing: Boolean, lines: List[(AssemblyLine, CpuImportance)]): Option[Int] = {
// TODO: STA has different flag behaviour than TAX, keep it in mind!
def canBeInlined(xCandidate: Option[String], yCandidate: Option[String], zCandidate: Option[String], features: Features, lines: List[(AssemblyLine, CpuImportance)]): Option[Int] = {
val vx = xCandidate.getOrElse("-")
val vy = yCandidate.getOrElse("-")
val vz = zCandidate.getOrElse("-")
lines match {
case (AssemblyLine(_, Immediate, SubbyteConstant(MemoryAddressConstant(th), _), _), _) :: xs
if th.name == vx || th.name == vy =>
if th.name == vx || th.name == vy || th.name == vz =>
// if an address of a variable is used, then that variable cannot be assigned to a register
None
case (AssemblyLine(_, Immediate, HalfWordConstant(MemoryAddressConstant(th), _), _), _) :: xs
if th.name == vx || th.name == vy =>
if th.name == vx || th.name == vy || th.name == vz =>
// if an address of a variable is used, then that variable cannot be assigned to a register
None
case (AssemblyLine(_, AbsoluteX | AbsoluteY | ZeroPageX | ZeroPageY | IndexedY | IndexedX | ZeroPageIndirect | Indirect | AbsoluteIndexedX, MemoryAddressConstant(th), _), _) :: xs =>
case (AssemblyLine(_, AbsoluteX | AbsoluteY | LongAbsoluteX |
ZeroPageX | ZeroPageY |
IndexedY | IndexedX | IndexedZ |
LongIndexedY | LongIndexedZ |
Indirect | LongIndirect |
AbsoluteIndexedX, MemoryAddressConstant(th), _), _) :: xs =>
// if a variable is used as an array or a pointer, then it cannot be assigned to a register
if (th.name == vx || th.name == vy) {
if (th.name == vx || th.name == vy || th.name == vz) {
None
} else {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
case (AssemblyLine(STY | LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs if th.name == vx =>
if (features.indexRegisterTransfers) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 2)
else None
case (AssemblyLine(STX | LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs if th.name == vy =>
if (features.indexRegisterTransfers) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 2)
else None
case (AssemblyLine(opcode, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vx && (opcode == LDY || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
// if a variable is used by some opcodes, then it cannot be assigned to a register
@ -247,15 +335,20 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
// if a variable is used by some opcodes, then it cannot be assigned to a register
None
case (AssemblyLine(opcode, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vz && (opcode == LDZ || opcodesThatCannotBeUsedWithIndexRegistersAsParameters(opcode)) =>
// if a variable is used by some opcodes, then it cannot be assigned to a register
None
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), elidable), imp) :: xs
if xCandidate.isDefined =>
// if a register is populated with a different variable, then this variable cannot be assigned to that register
// removing LDX saves 3 cycles
if (elidable && th.name == vx) {
if (imp.z == Unimportant && imp.n == Unimportant) {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 3)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 3)
} else {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 1)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 1)
}
} else {
None
@ -266,7 +359,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
// LAX = LDX-LDA, and since LDX simplifies to nothing and LDA simplifies to TXA,
// LAX simplifies to TXA, saving two bytes
if (elidable && th.name == vx) {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 2)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 2)
} else {
None
}
@ -277,9 +370,20 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
// sometimes that LDX has to be converted into CPX#0
if (elidable && th.name == vy) {
if (imp.z == Unimportant && imp.n == Unimportant) {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 3)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 3)
} else {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 1)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 1)
}
} else {
None
}
case (AssemblyLine(LDZ, Absolute | ZeroPage, MemoryAddressConstant(th), elidable), imp) :: xs if zCandidate.isDefined =>
if (elidable && th.name == vz) {
if (imp.z == Unimportant && imp.n == Unimportant) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 3)
} else {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 1)
}
} else {
None
@ -293,33 +397,46 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
// if a register is populated with something else than a variable, then no variable cannot be assigned to that register
None
case (AssemblyLine(LDZ, _, _, _), _) :: xs if zCandidate.isDefined =>
// if a register is populated with something else than a variable, then no variable cannot be assigned to that register
None
case (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidable),_) :: xs
if opcodesIdentityTable(op) =>
if (th.name == vx || th.name == vy) {
if (elidable) canBeInlined(xCandidate, yCandidate, blastProcessing, xs)
if (elidable) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
else None
} else canBeInlined(xCandidate, yCandidate, blastProcessing, xs)
} else {
if (th.name == vz) None
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
case (AssemblyLine(LDA, _, _, elidable),_) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidable2),_) :: xs
if opcodesCommutative(op) =>
if (th.name == vx || th.name == vy) {
if (elidable && elidable2) canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 2)
if (elidable && elidable2) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 2)
else None
} else canBeInlined(xCandidate, yCandidate, blastProcessing, xs)
} else {
if (th.name == vz) None
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
case (AssemblyLine(LDA, _, _, elidable),_) :: (AssemblyLine(CLC, _, _, _),_) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), elidable2),_) :: xs
if opcodesCommutative(op) =>
if (th.name == vx || th.name == vy) {
if (elidable && elidable2) canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 2)
if (elidable && elidable2) canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 2)
else None
} else canBeInlined(xCandidate, yCandidate, blastProcessing, xs)
} else {
if (th.name == vz) None
else canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), elidable), _) :: (AssemblyLine(TAX, _, _, elidable2), _) :: xs
if xCandidate.isDefined =>
// a variable cannot be inlined if there is TAX not after LDA of that variable
// but LDA-TAX can be simplified to TXA
if (elidable && elidable2 && th.name == vx) {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 3)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 3)
} else {
None
}
@ -329,21 +446,43 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
// a variable cannot be inlined if there is TAY not after LDA of that variable
// but LDA-TAY can be simplified to TYA
if (elidable && elidable2 && th.name == vy) {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 3)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 3)
} else {
None
}
case (AssemblyLine(LDA | STA | INC | DEC | STZ, Absolute | ZeroPage, MemoryAddressConstant(th), elidable), _) :: xs =>
// changing LDA->TXA, STA->TAX, INC->INX, DEC->DEX, STZ->LDA saves 2 bytes
if (th.name == vy || th.name == vx) {
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), elidable), _) :: (AssemblyLine(TAZ, _, _, elidable2), _) :: xs
if zCandidate.isDefined =>
// a variable cannot be inlined if there is TAZ not after LDA of that variable
// but LDA-TAZ can be simplified to TZA
if (elidable && elidable2 && th.name == vy) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 3)
} else {
None
}
case (AssemblyLine(LDA | STA | INC | DEC, Absolute | ZeroPage, MemoryAddressConstant(th), elidable), _) :: xs =>
// changing LDA->TXA, STA->TAX, INC->INX, DEC->DEX saves 2 bytes
if (th.name == vy || th.name == vx || th.name == vz) {
if (elidable) {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs).map(_ + 2)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 2)
} else {
None
}
} else {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), elidable), _) :: xs =>
// changing STZ->LDX saves 2 bytes
if (th.name == vy || th.name == vx) {
if (elidable && features.izIsAlwaysZero) {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs).map(_ + 2)
} else {
None
}
} else {
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
case (AssemblyLine(TAX, _, _, _), _) :: xs if xCandidate.isDefined =>
@ -354,17 +493,23 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
// a variable cannot be inlined if there is TAY not after LDA of that variable
None
case (AssemblyLine(TAZ, _, _, _), _) :: xs if zCandidate.isDefined =>
// a variable cannot be inlined if there is TAZ not after LDA of that variable
None
case (AssemblyLine(LABEL, _, _, _), _) :: xs =>
// labels always end the initial section
canBeInlined(xCandidate, yCandidate, blastProcessing, xs)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
case (x, _) :: xs =>
if (xCandidate.isDefined && opcodesThatAlwaysPrecludeXAllocation(x.opcode)) {
None
} else if (yCandidate.isDefined && opcodesThatAlwaysPrecludeYAllocation(x.opcode)) {
None
} else if (zCandidate.isDefined && opcodesThatAlwaysPrecludeZAllocation(x.opcode)) {
None
} else {
canBeInlined(xCandidate, yCandidate, blastProcessing, xs)
canBeInlined(xCandidate, yCandidate, zCandidate, features, xs)
}
case Nil => Some(0)
@ -397,7 +542,7 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
// if an address of a variable is used, then that variable cannot be assigned to a register
None
case (AssemblyLine(_, AbsoluteX | AbsoluteY | ZeroPageX | ZeroPageY | IndexedY | IndexedX | ZeroPageIndirect | Indirect | AbsoluteIndexedX, MemoryAddressConstant(th), _),_) :: xs
case (AssemblyLine(_, AbsoluteX | AbsoluteY | ZeroPageX | ZeroPageY | IndexedY | IndexedX | IndexedZ | Indirect | AbsoluteIndexedX, MemoryAddressConstant(th), _),_) :: xs
if th.name == candidate =>
// if a variable is used as an array or a pointer, then it cannot be assigned to a register
None
@ -508,178 +653,237 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
}
}
def inlineVars(xCandidate: Option[String], yCandidate: Option[String], aCandidate: Option[String], identityArray: Constant, lines: List[(AssemblyLine, CpuImportance)]): List[AssemblyLine] = {
def inlineVars(xCandidate: Option[String], yCandidate: Option[String], zCandidate: Option[String], aCandidate: Option[String], features: Features, lines: List[(AssemblyLine, CpuImportance)]): List[AssemblyLine] = {
val vx = xCandidate.getOrElse("-")
val vy = yCandidate.getOrElse("-")
val vz = zCandidate.getOrElse("-")
val va = aCandidate.getOrElse("-")
lines match {
case (AssemblyLine(INC, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vx =>
AssemblyLine.implied(INX) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(INX) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(INC, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vy =>
AssemblyLine.implied(INY) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(INY) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(INC, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vz =>
AssemblyLine.implied(INZ) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(DEC, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vx =>
AssemblyLine.implied(DEX) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(DEX) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(DEC, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vy =>
AssemblyLine.implied(DEY) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(DEY) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(DEC, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vz =>
AssemblyLine.implied(DEZ) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(opcode@(DEC | INC | ROL | ROR | ASL | LSR), Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == va =>
AssemblyLine.implied(opcode) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(opcode) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _), imp) :: xs
if th.name == vx =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
AssemblyLine.immediate(CPX, 0) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.immediate(CPX, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
}
case (AssemblyLine(LAX, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vx =>
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (l@AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if opcodesIdentityTable(op) && th.name == vx =>
l.copy(addrMode = AbsoluteX, parameter = identityArray) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
l.copy(addrMode = AbsoluteX, parameter = features.identityArray) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (l@AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if opcodesIdentityTable(op) && th.name == vy =>
l.copy(addrMode = AbsoluteY, parameter = identityArray) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
l.copy(addrMode = AbsoluteY, parameter = features.identityArray) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (l@AssemblyLine(LDA, _, _, _), _) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if opcodesCommutative(op) && th.name == va =>
l.copy(opcode = op) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
l.copy(opcode = op) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (l@AssemblyLine(LDA, _, _, _), _) :: (clc@AssemblyLine(CLC, _, _, _), _) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if opcodesCommutative(op) && th.name == va =>
l.copy(opcode = op) :: clc :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
l.copy(opcode = op) :: clc :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (l@AssemblyLine(LDA, _, _, _), _) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if opcodesCommutative(op) && th.name == vx =>
AssemblyLine.implied(TXA) :: l.copy(opcode = op) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TXA) :: l.copy(opcode = op) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (l@AssemblyLine(LDA, _, _, _), _) :: (clc@AssemblyLine(CLC, _, _, _), _) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if opcodesCommutative(op) && th.name == vx =>
AssemblyLine.implied(TXA) :: l.copy(opcode = op) :: clc :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TXA) :: l.copy(opcode = op) :: clc :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (l@AssemblyLine(LDA, _, _, _), _) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if opcodesCommutative(op) && th.name == vy =>
AssemblyLine.implied(TYA) :: l.copy(opcode = op) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TYA) :: l.copy(opcode = op) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (l@AssemblyLine(LDA, _, _, _), _) :: (clc@AssemblyLine(CLC, _, _, _), _) :: (AssemblyLine(op, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if opcodesCommutative(op) && th.name == vy =>
AssemblyLine.implied(TYA) :: l.copy(opcode = op) :: clc :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TYA) :: l.copy(opcode = op) :: clc :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA | STA, Absolute | ZeroPage, MemoryAddressConstant(th), _), imp) :: xs
if th.name == va =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
AssemblyLine.immediate(CMP, 0) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.immediate(CMP, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
}
case (AssemblyLine(LAX, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == va =>
AssemblyLine.implied(TAX) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TAX) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _), imp) :: xs
if th.name == vy =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
AssemblyLine.immediate(CPY, 0) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.immediate(CPY, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
}
case (AssemblyLine(LDZ, Absolute | ZeroPage, MemoryAddressConstant(th), _), imp) :: xs
if th.name == vz =>
if (imp.z == Unimportant && imp.n == Unimportant) {
inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
} else {
AssemblyLine.immediate(CPZ, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
}
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), true), _) :: (AssemblyLine(TAX, _, _, true), _) :: xs
if th.name == vx =>
// these TXA's may get optimized away by a different optimization
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), true), _) :: (AssemblyLine(TAY, _, _, true), _) :: xs
if th.name == vy =>
// these TYA's may get optimized away by a different optimization
AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), true), _) :: (AssemblyLine(TAZ, _, _, true), _) :: xs
if th.name == vz =>
// these TZA's may get optimized away by a different optimization
AssemblyLine.implied(TZA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), true), _) :: (AssemblyLine(TXA, _, _, true), _) :: xs
if th.name == va =>
// these TAX's may get optimized away by a different optimization
AssemblyLine.implied(TAX) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TAX) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), true), _) :: (AssemblyLine(TYA, _, _, true), _) :: xs
if th.name == va =>
// these TAY's may get optimized away by a different optimization
AssemblyLine.implied(TAY) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TAY) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, am, param, true), _) :: (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), true), _) :: xs
if th.name == vx && LdxAddrModes(am) =>
// these TXA's may get optimized away by a different optimization
AssemblyLine(LDX, am, param) :: AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine(LDX, am, param) :: AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, am, param, true), _) :: (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), true), _) :: xs
if th.name == vy && LdyAddrModes(am) =>
// these TYA's may get optimized away by a different optimization
AssemblyLine(LDY, am, param) :: AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine(LDY, am, param) :: AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, am, param, true), _) :: (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), true), _) :: xs
if th.name == vz && LdzAddrModes(am) =>
// these TZA's may get optimized away by a different optimization
AssemblyLine(LDZ, am, param) :: AssemblyLine.implied(TZA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: (AssemblyLine(CMP, am, param, true), _) :: xs
if th.name == vx && doesntUseXOrY(am) =>
if th.name == vx && CpxyzAddrModes(am) =>
// ditto
AssemblyLine.implied(TXA) :: AssemblyLine(CPX, am, param) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TXA) :: AssemblyLine(CPX, am, param) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: (AssemblyLine(CMP, am, param, true), _) :: xs
if th.name == vy && doesntUseXOrY(am) =>
if th.name == vy && CpxyzAddrModes(am) =>
// ditto
AssemblyLine.implied(TYA) :: AssemblyLine(CPY, am, param) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TYA) :: AssemblyLine(CPY, am, param) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: (AssemblyLine(CMP, am, param, true), _) :: xs
if th.name == vy && CpxyzAddrModes(am) =>
// ditto
AssemblyLine.implied(TZA) :: AssemblyLine(CPZ, am, param) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vx =>
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vy =>
AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vx =>
AssemblyLine.implied(TXY) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vy =>
AssemblyLine.implied(TYX) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDA, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vz =>
AssemblyLine.implied(TZA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDX, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == va =>
AssemblyLine.implied(TAX) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TAX) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(LDY, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == va =>
AssemblyLine.implied(TAY) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TAY) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vx =>
AssemblyLine.implied(TAX) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TAX) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vy =>
AssemblyLine.implied(TAY) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TAY) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(STA, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vz =>
AssemblyLine.implied(TAZ) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(STX, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == va =>
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TXA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(STY, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == va =>
AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.implied(TYA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(STX, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vy =>
AssemblyLine.implied(TXY) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(STY, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vx =>
AssemblyLine.implied(TYX) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vx =>
AssemblyLine.immediate(LDX, 0) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
if (features.izIsAlwaysZero) AssemblyLine.immediate(LDX, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
else ErrorReporting.fatal("Unexpected STZ")
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == vy =>
AssemblyLine.immediate(LDY, 0) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
if (features.izIsAlwaysZero) AssemblyLine.immediate(LDY, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
else ErrorReporting.fatal("Unexpected STZ")
case (AssemblyLine(STZ, Absolute | ZeroPage, MemoryAddressConstant(th), _), _) :: xs
if th.name == va =>
AssemblyLine.immediate(LDA, 0) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
if (features.izIsAlwaysZero) AssemblyLine.immediate(LDA, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
else AssemblyLine.implied(TZA) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(TAX, _, _, _), _) :: xs if xCandidate.isDefined =>
ErrorReporting.fatal("Unexpected TAX")
@ -687,20 +891,26 @@ object VariableToRegisterOptimization extends AssemblyOptimization {
case (AssemblyLine(TAY, _, _, _), _) :: xs if yCandidate.isDefined =>
ErrorReporting.fatal("Unexpected TAY")
case (AssemblyLine(TAZ, _, _, _), _) :: xs if zCandidate.isDefined =>
ErrorReporting.fatal("Unexpected TAZ")
case (AssemblyLine(TXA, _, _, _), _) :: xs if aCandidate.isDefined =>
AssemblyLine.immediate(CPX, 0) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.immediate(CPX, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (AssemblyLine(TYA, _, _, _), _) :: xs if aCandidate.isDefined =>
AssemblyLine.immediate(CPY, 0) :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
AssemblyLine.immediate(CPY, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (x, _) :: xs => x :: inlineVars(xCandidate, yCandidate, aCandidate, identityArray, xs)
case (AssemblyLine(TZA, _, _, _), _) :: xs if aCandidate.isDefined =>
AssemblyLine.immediate(CPZ, 0) :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case (x, _) :: xs => x :: inlineVars(xCandidate, yCandidate, zCandidate, aCandidate, features, xs)
case Nil => Nil
}
}
def doesntUseXOrY(am: AddrMode.Value): Boolean = am match {
case Immediate | ZeroPage | Absolute | Relative | Indirect | ZeroPageIndirect => true
case Immediate | WordImmediate | ZeroPage | Absolute | LongAbsolute | Relative | LongRelative | Indirect | LongIndirect | Stack | IndexedZ => true
case _ => false
}
}

View File

@ -258,6 +258,14 @@ object BuiltIns {
case Some(NumericConstant(0, _)) =>
Nil
case Some(NumericConstant(shift, _)) if shift > 0 =>
if (ctx.options.flags(CompilationFlag.EmitNative65816Opcodes)) {
targetBytes match {
case List(List(AssemblyLine(STA, a1, l, _)), List(AssemblyLine(STA, a2, h, _))) =>
if (a1 == a2 && l.+(1).quickSimplify == h) {
return List(AssemblyLine.accu16) ++ List.fill(shift.toInt)(AssemblyLine(if (aslRatherThanLsr) ASL_W else LSR_W, a1, l)) ++ List(AssemblyLine.accu8)
}
}
}
List.fill(shift.toInt)(if (aslRatherThanLsr) {
staTo(ASL, lo) ++ targetBytes.tail.flatMap { b => staTo(ROL, b) }
} else {
@ -643,15 +651,79 @@ object BuiltIns {
case Some(NumericConstant(0, _)) =>
return Nil
case Some(NumericConstant(1, _)) if canUseIncDec && !subtract =>
if (ctx.options.flags(CompilationFlag.Emit65CE02Opcodes)) {
targetBytes match {
case List(List(AssemblyLine(STA, ZeroPage, l, _)), List(AssemblyLine(STA, ZeroPage, h, _))) =>
if (l.+(1).quickSimplify == h) {
return List(AssemblyLine.zeropage(INC_W, l))
}
}
}
if (ctx.options.flags(CompilationFlag.EmitNative65816Opcodes)) {
targetBytes match {
case List(List(AssemblyLine(STA, a1@(ZeroPage | Absolute | ZeroPageX | AbsoluteX), l, _)), List(AssemblyLine(STA, a2, h, _))) =>
if (a1 == a2 && l.+(1).quickSimplify == h) {
return List(AssemblyLine.accu16, AssemblyLine(INC_W, a1, l), AssemblyLine.accu8)
}
}
}
val label = MfCompiler.nextLabel("in")
return staTo(INC, targetBytes.head) ++ targetBytes.tail.flatMap(l => AssemblyLine.relative(BNE, label)::staTo(INC, l)) :+ AssemblyLine.label(label)
case Some(NumericConstant(-1, _)) if canUseIncDec && subtract =>
if (ctx.options.flags(CompilationFlag.Emit65CE02Opcodes)) {
targetBytes match {
case List(List(AssemblyLine(STA, ZeroPage, l, _)), List(AssemblyLine(STA, ZeroPage, h, _))) =>
if (l.+(1).quickSimplify == h) {
return List(AssemblyLine.zeropage(INC_W, l))
}
}
}
if (ctx.options.flags(CompilationFlag.EmitNative65816Opcodes)) {
targetBytes match {
case List(List(AssemblyLine(STA, a1@(ZeroPage | Absolute | ZeroPageX | AbsoluteX), l, _)), List(AssemblyLine(STA, a2, h, _))) =>
if (a1 == a2 && l.+(1).quickSimplify == h) {
return List(AssemblyLine.accu16, AssemblyLine(INC_W, a1, l), AssemblyLine.accu8)
}
}
}
val label = MfCompiler.nextLabel("in")
return staTo(INC, targetBytes.head) ++ targetBytes.tail.flatMap(l => AssemblyLine.relative(BNE, label)::staTo(INC, l)) :+ AssemblyLine.label(label)
case Some(NumericConstant(1, _)) if canUseIncDec && subtract =>
if (ctx.options.flags(CompilationFlag.Emit65CE02Opcodes)) {
targetBytes match {
case List(List(AssemblyLine(STA, ZeroPage, l, _)), List(AssemblyLine(STA, ZeroPage, h, _))) =>
if (l.+(1).quickSimplify == h) {
return List(AssemblyLine.zeropage(DEC_W, l))
}
}
}
if (ctx.options.flags(CompilationFlag.EmitNative65816Opcodes)) {
targetBytes match {
case List(List(AssemblyLine(STA, a1@(ZeroPage | Absolute | ZeroPageX | AbsoluteX), l, _)), List(AssemblyLine(STA, a2, h, _))) =>
if (a1 == a2 && l.+(1).quickSimplify == h) {
return List(AssemblyLine.accu16, AssemblyLine(DEC_W, a1, l), AssemblyLine.accu8)
}
}
}
val label = MfCompiler.nextLabel("de")
return doDec(targetBytes)
case Some(NumericConstant(-1, _)) if canUseIncDec && !subtract =>
if (ctx.options.flags(CompilationFlag.Emit65CE02Opcodes)) {
targetBytes match {
case List(List(AssemblyLine(STA, ZeroPage, l, _)), List(AssemblyLine(STA, ZeroPage, h, _))) =>
if (l.+(1).quickSimplify == h) {
return List(AssemblyLine.zeropage(DEC_W, l))
}
}
}
if (ctx.options.flags(CompilationFlag.EmitNative65816Opcodes)) {
targetBytes match {
case List(List(AssemblyLine(STA, a1@(ZeroPage | Absolute | ZeroPageX | AbsoluteX), l, _)), List(AssemblyLine(STA, a2, h, _))) =>
if (a1 == a2 && l.+(1).quickSimplify == h) {
return List(AssemblyLine.accu16, AssemblyLine(DEC_W, a1, l), AssemblyLine.accu8)
}
}
}
val label = MfCompiler.nextLabel("de")
return doDec(targetBytes)
case Some(constant) =>
@ -719,6 +791,32 @@ object BuiltIns {
}
}
val addendByteRead = addendByteRead0 ++ List.fill((targetSize - addendByteRead0.size) max 0)(List(AssemblyLine.immediate(LDA, 0)))
if (ctx.options.flags(CompilationFlag.EmitNative65816Opcodes)) {
(removeTsx(targetBytes), removeTsx(addendByteRead)) match {
case (List(List(AssemblyLine(STA, ta1, tl, _)), List(AssemblyLine(STA, ta2, th, _))), List(List(AssemblyLine(LDA, Immediate, al, _)), List(AssemblyLine(LDA, Immediate, ah, _)))) =>
if (ta1 == ta2 && tl.+(1).quickSimplify == th) {
return wrapInSedCldIfNeeded(decimal, List(
AssemblyLine.implied(if(subtract) SEC else CLC),
AssemblyLine.accu16,
AssemblyLine(LDA_W, ta1, tl),
AssemblyLine(if(subtract) SBC_W else ADC_W, WordImmediate, ah.asl(8).+(al).quickSimplify),
AssemblyLine(STA_W, ta1, tl),
AssemblyLine.accu8))
}
case (List(List(AssemblyLine(STA, ta1, tl, _)), List(AssemblyLine(STA, ta2, th, _))), List(List(AssemblyLine(LDA, aa1, al, _)), List(AssemblyLine(LDA, aa2, ah, _)))) =>
if (ta1 == ta2 && aa1 == aa2 && tl.+(1).quickSimplify == th && al.+(1).quickSimplify == ah) {
return wrapInSedCldIfNeeded(decimal, List(
AssemblyLine.accu16,
AssemblyLine.implied(if(subtract) SEC else CLC),
AssemblyLine(LDA_W, ta1, tl),
AssemblyLine(if(subtract) SBC_W else ADC_W, aa1, al),
AssemblyLine(STA_W, ta1, tl),
AssemblyLine.accu8))
}
case _ =>
}
}
val buffer = mutable.ListBuffer[AssemblyLine]()
buffer ++= calculateRhs
buffer += AssemblyLine.implied(if (subtract) SEC else CLC)
@ -819,6 +917,29 @@ object BuiltIns {
})
}
}
if (ctx.options.flags(CompilationFlag.EmitNative65816Opcodes)) {
(removeTsx(targetBytes), removeTsx(addendByteRead)) match {
case (List(List(AssemblyLine(STA, ta1, tl, _)), List(AssemblyLine(STA, ta2, th, _))), List(List(AssemblyLine(LDA, Immediate, al, _)), List(AssemblyLine(LDA, Immediate, ah, _)))) =>
if (ta1 == ta2 && tl.+(1).quickSimplify == th) {
return List(
AssemblyLine.accu16,
AssemblyLine(LDA_W, ta1, tl),
AssemblyLine(Opcode.widen(operation).get, WordImmediate, ah.asl(8).+(al).quickSimplify),
AssemblyLine(STA_W, ta1, tl),
AssemblyLine.accu8)
}
case (List(List(AssemblyLine(STA, ta1, tl, _)), List(AssemblyLine(STA, ta2, th, _))), List(List(AssemblyLine(LDA, aa1, al, _)), List(AssemblyLine(LDA, aa2, ah, _)))) =>
if (ta1 == ta2 && aa1 == aa2 && tl.+(1).quickSimplify == th && al.+(1).quickSimplify == ah) {
return List(
AssemblyLine.accu16,
AssemblyLine(LDA_W, ta1, tl),
AssemblyLine(Opcode.widen(operation).get, aa1, al),
AssemblyLine(STA_W, ta1, tl),
AssemblyLine.accu8)
}
case _ =>
}
}
val AllOnes = (1L << (8 * targetSize)) - 1
(operation, env.eval(param)) match {
case (EOR, Some(NumericConstant(0, _)))
@ -880,4 +1001,10 @@ object BuiltIns {
???
}
}
private def removeTsx(codes: List[List[AssemblyLine]]): List[List[AssemblyLine]] = codes.map {
case List(AssemblyLine(TSX, _, _, _), AssemblyLine(op, AbsoluteX, NumericConstant(nn, _), _)) if nn >= 0x100 && nn <= 0x1ff =>
List(AssemblyLine(op, Stack, NumericConstant(nn & 0xff, 1)))
case x => x
}
}

View File

@ -87,6 +87,11 @@ object ExpressionCompiler {
def compileConstant(ctx: CompilationContext, expr: Constant, target: Variable): List[AssemblyLine] = {
target match {
case RegisterVariable(Register.A, _) => List(AssemblyLine(LDA, Immediate, expr))
case RegisterVariable(Register.AW, _) =>
List(
AssemblyLine.accu16,
AssemblyLine(LDA_W, WordImmediate, expr),
AssemblyLine.accu8)
case RegisterVariable(Register.X, _) => List(AssemblyLine(LDX, Immediate, expr))
case RegisterVariable(Register.Y, _) => List(AssemblyLine(LDY, Immediate, expr))
case RegisterVariable(Register.AX, _) => List(
@ -221,7 +226,12 @@ object ExpressionCompiler {
case 1 =>
v match {
case mv: VariableInMemory => AssemblyLine.variable(ctx, store, mv)
case sv@StackVariable(_, _, offset) => AssemblyLine.implied(transferToA) :: AssemblyLine.implied(TSX) :: AssemblyLine.absoluteX(STA, offset + ctx.extraStackOffset) :: Nil
case sv@StackVariable(_, _, offset) =>
if (ctx.options.flags(CompilationFlag.EmitEmulation65816Opcodes)) {
AssemblyLine.implied(transferToA) :: AssemblyLine.stackRelative(STA, offset + ctx.extraStackOffset) :: Nil
} else {
AssemblyLine.implied(transferToA) :: AssemblyLine.implied(TSX) :: AssemblyLine.absoluteX(STA, offset + ctx.extraStackOffset) :: Nil
}
}
case s if s > 1 =>
v match {
@ -373,6 +383,18 @@ object ExpressionCompiler {
case source: VariableInMemory =>
target match {
case RegisterVariable(Register.A, _) => AssemblyLine.variable(ctx, LDA, source)
case RegisterVariable(Register.AW, _) =>
exprType.size match {
case 1 => if (exprType.isSigned) {
AssemblyLine.variable(ctx, LDA, source) ++ List(
AssemblyLine.implied(PHA)) ++ signExtendA() ++ List(
AssemblyLine.implied(XBA),
AssemblyLine.implied(PLA))
} else List(AssemblyLine.immediate(LDX, 0), AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source) :+ AssemblyLine.immediate(LDX, 0)
case 2 =>
// TODO: use LDA_W
AssemblyLine.variable(ctx, LDA, source, 1) ++ List(AssemblyLine.implied(XBA)) ++ AssemblyLine.variable(ctx, LDA, source)
}
case RegisterVariable(Register.X, _) => AssemblyLine.variable(ctx, LDX, source)
case RegisterVariable(Register.Y, _) => AssemblyLine.variable(ctx, LDY, source)
case RegisterVariable(Register.AX, _) =>
@ -1021,13 +1043,13 @@ object ExpressionCompiler {
// TODO: fix
case _ => Nil
}
secondViaMemory ++ thirdViaRegisters :+ AssemblyLine.absolute(JSR, function)
secondViaMemory ++ thirdViaRegisters :+ AssemblyLine.absoluteOrLongAbsolute(JSR, function, ctx.options)
case NormalParamSignature(paramVars) =>
params.zip(paramVars).flatMap {
case (paramExpr, paramVar) =>
val callCtx = callingContext(ctx, paramVar)
compileAssignment(callCtx, paramExpr, VariableExpression(paramVar.name))
} ++ List(AssemblyLine.absolute(JSR, function))
} ++ List(AssemblyLine.absoluteOrLongAbsolute(JSR, function, ctx.options))
}
result
}
@ -1076,6 +1098,7 @@ object ExpressionCompiler {
exprTypeAndVariable.fold(noop) {
case (VoidType, _) => ???
case (_, RegisterVariable(Register.A, _)) => noop
case (_, RegisterVariable(Register.AW, _)) => List(AssemblyLine.implied(XBA), AssemblyLine.implied(TAX), AssemblyLine.implied(XBA))
case (_, RegisterVariable(Register.X, _)) => List(AssemblyLine.implied(TAX))
case (_, RegisterVariable(Register.Y, _)) => List(AssemblyLine.implied(TAY))
case (_, RegisterVariable(Register.AX, _)) =>
@ -1083,7 +1106,9 @@ object ExpressionCompiler {
noop
case (_, RegisterVariable(Register.XA, _)) =>
// TODO: sign extension
if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) {
if (ctx.options.flag(CompilationFlag.EmitHudsonOpcodes)) {
List(AssemblyLine.implied(HuSAX))
} else if (ctx.options.flag(CompilationFlag.EmitCmosOpcodes)) {
List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(PHX),
@ -1105,11 +1130,17 @@ object ExpressionCompiler {
AssemblyLine.implied(TXA))
case (_, RegisterVariable(Register.AY, _)) =>
// TODO: sign extension
List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(TXA),
AssemblyLine.implied(TAY),
AssemblyLine.implied(PLA))
if (ctx.options.flag(CompilationFlag.EmitHudsonOpcodes)) {
List(AssemblyLine.implied(SXY))
} else if (ctx.options.flag(CompilationFlag.EmitEmulation65816Opcodes)) {
List(AssemblyLine.implied(TXY))
} else {
List(
AssemblyLine.implied(PHA),
AssemblyLine.implied(TXA),
AssemblyLine.implied(TAY),
AssemblyLine.implied(PLA))
}
case (t, v: VariableInMemory) => t.size match {
case 1 => v.typ.size match {
case 1 =>
@ -1230,7 +1261,7 @@ object ExpressionCompiler {
if (i < arrayLength) return Nil
if (i >= arrayLength) return List(
AssemblyLine.implied(PHP),
AssemblyLine.absolute(JSR, ctx.env.get[ThingInMemory]("_panic")))
AssemblyLine.absoluteOrLongAbsolute(JSR, ctx.env.get[ThingInMemory]("_panic"), ctx.options))
}
case _ =>
}
@ -1245,7 +1276,7 @@ object ExpressionCompiler {
AssemblyLine.implied(PHP),
AssemblyLine.immediate(compare, arrayLength),
AssemblyLine.relative(BCC, label),
AssemblyLine.absolute(JSR, ctx.env.get[ThingInMemory]("_panic")),
AssemblyLine.absoluteOrLongAbsolute(JSR, ctx.env.get[ThingInMemory]("_panic"), ctx.options),
AssemblyLine.label(label),
AssemblyLine.implied(PLP))
} else {

View File

@ -69,7 +69,7 @@ object StatementCompiler {
val actualAddrMode = a match {
case Absolute if OpcodeClasses.ShortBranching(o) => Relative
case IndexedX if o == JMP => AbsoluteIndexedX
case Indirect if o != JMP => ZeroPageIndirect
case Indirect if o != JMP => IndexedZ
case _ => a
}
List(AssemblyLine(o, actualAddrMode, c, e))

View File

@ -1,7 +1,7 @@
package millfork.env
import millfork.{CompilationFlag, CompilationOptions}
import millfork.assembly.Opcode
import millfork.error.ErrorReporting
import millfork.node._
sealed trait Thing {
@ -79,6 +79,12 @@ sealed trait TypedThing extends Thing {
sealed trait ThingInMemory extends Thing {
def toAddress: Constant
var farFlag: Option[Boolean] = None
var declaredBank: Option[Int] = None
def isFar(compilationOptions: CompilationOptions): Boolean
def bank(compilationOptions: CompilationOptions): Int
}
sealed trait PreallocableThing extends ThingInMemory {
@ -91,6 +97,12 @@ sealed trait PreallocableThing extends ThingInMemory {
case class Label(name: String) extends ThingInMemory {
override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this)
override def isFar(compilationOptions: CompilationOptions): Boolean =
compilationOptions.flag(CompilationFlag.LargeCode) && farFlag.getOrElse(true)
override def bank(compilationOptions: CompilationOptions): Int =
declaredBank.getOrElse(compilationOptions.platform.defaultCodeBank)
}
sealed trait Variable extends TypedThing with VariableLikeThing
@ -100,8 +112,13 @@ case class BlackHole(typ: Type) extends Variable {
}
sealed trait VariableInMemory extends Variable with ThingInMemory with IndexableThing {
def zeropage: Boolean
override def isFar(compilationOptions: CompilationOptions): Boolean =
!zeropage && farFlag.getOrElse(false)
override def bank(compilationOptions: CompilationOptions): Int =
declaredBank.getOrElse(0)
}
case class RegisterVariable(register: Register.Value, typ: Type) extends Variable {
@ -156,14 +173,26 @@ case class UninitializedArray(name: String, sizeInBytes: Int) extends MfArray wi
override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this)
override def alloc = VariableAllocationMethod.Static
override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false)
override def bank(compilationOptions: CompilationOptions): Int = declaredBank.getOrElse(0)
}
case class RelativeArray(name: String, address: Constant, sizeInBytes: Int) extends MfArray {
override def toAddress: Constant = address
override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false)
override def bank(compilationOptions: CompilationOptions): Int = declaredBank.getOrElse(0)
}
case class InitializedArray(name: String, address: Option[Constant], contents: List[Constant]) extends MfArray with PreallocableThing {
override def shouldGenerate = true
override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false)
override def bank(compilationOptions: CompilationOptions): Int = declaredBank.getOrElse(0)
}
case class RelativeVariable(name: String, address: Constant, typ: Type, zeropage: Boolean) extends VariableInMemory {
@ -198,6 +227,12 @@ case class MacroFunction(name: String,
sealed trait FunctionInMemory extends MangledFunction with ThingInMemory {
def environment: Environment
override def isFar(compilationOptions: CompilationOptions): Boolean =
compilationOptions.flag(CompilationFlag.LargeCode) && farFlag.getOrElse(true)
override def bank(compilationOptions: CompilationOptions): Int =
declaredBank.getOrElse(compilationOptions.platform.defaultCodeBank)
}
case class ExternFunction(name: String,

View File

@ -61,7 +61,7 @@ case class HalfWordExpression(expression: Expression, hiByte: Boolean) extends E
}
object Register extends Enumeration {
val A, X, Y, AX, AY, YA, XA, XY, YX = Value
val A, X, Y, AX, AY, YA, XA, XY, YX, AW = Value
}
//case class Indexing(child: Expression, register: Register.Value) extends Expression

View File

@ -1,6 +1,6 @@
package millfork.output
import millfork.assembly.opt.{AssemblyOptimization, JumpShortening}
import millfork.assembly.opt.{AssemblyOptimization, HudsonOptimizations, JumpShortening}
import millfork.assembly.{AddrMode, AssemblyLine, Opcode}
import millfork.compiler.{CompilationContext, MfCompiler}
import millfork.env._
@ -116,6 +116,16 @@ class Assembler(private val program: Program, private val rootEnv: Environment)
}
}
def extractBank(c: Constant, options: CompilationOptions): Byte = {
c.quickSimplify match {
case NumericConstant(nn, _) => nn.>>(16).toInt.&(0xff).toByte
case MemoryAddressConstant(th) => th.bank(options).toByte
case CompoundConstant(MathOperator.Plus, a, b) => (extractBank(a, options) + extractBank(b, options)).toByte
case CompoundConstant(MathOperator.Minus, a, b) => (extractBank(a, options) - extractBank(b, options)).toByte
case _ => ErrorReporting.fatal("Failed to extract bank number from constant " + c)
}
}
private def parseNormalToDecimalValue(a: Long): Long = {
if (a < 0) -parseNormalToDecimalValue(-a)
var x = a
@ -314,7 +324,7 @@ class Assembler(private val program: Program, private val rootEnv: Environment)
ErrorReporting.debug("Compiling: " + f.name, f.position)
val unoptimized =
MfCompiler.compile(CompilationContext(env = f.environment, function = f, extraStackOffset = 0, options = options)).flatMap {
case AssemblyLine(Opcode.JSR, _, p, true) if inlinedFunctions.contains(p.toString) =>
case AssemblyLine(Opcode.JSR, AddrMode.Absolute | AddrMode.LongAbsolute, p, true) if inlinedFunctions.contains(p.toString) =>
val labelPrefix = MfCompiler.nextLabel("ai")
inlinedFunctions(p.toString).map{
case line@AssemblyLine(_, _, MemoryAddressConstant(Label(label)), _) =>
@ -336,7 +346,10 @@ class Assembler(private val program: Program, private val rootEnv: Environment)
val code = optimizations.foldLeft(unoptimized) { (c, opt) =>
opt.optimize(f, c, options)
}
if (optimizations.nonEmpty) JumpShortening(f, JumpShortening(f, code, options), options)
if (optimizations.nonEmpty) {
val finalCode = if (options.flag(CompilationFlag.EmitHudsonOpcodes)) HudsonOptimizations.removeLoadZero(code) else code
JumpShortening(f, JumpShortening(f, finalCode, options), options)
}
else code
}
@ -361,14 +374,19 @@ class Assembler(private val program: Program, private val rootEnv: Environment)
writeByte(0, index, Assembler.opcodeFor(op, Relative, options))
writeByte(0, index + 1, param - (index + 2))
index += 2
case AssemblyLine(op, am@(Immediate | ZeroPage | ZeroPageX | ZeroPageY | IndexedY | IndexedX | ZeroPageIndirect), param, _) =>
case AssemblyLine(op, am@(Immediate | ZeroPage | ZeroPageX | ZeroPageY | IndexedY | IndexedX | IndexedZ | LongIndexedY | LongIndexedZ | Stack), param, _) =>
writeByte(0, index, Assembler.opcodeFor(op, am, options))
writeByte(0, index + 1, param)
index += 2
case AssemblyLine(op, am@(Absolute | AbsoluteY | AbsoluteX | Indirect | AbsoluteIndexedX), param, _) =>
case AssemblyLine(op, am@(WordImmediate | Absolute | AbsoluteY | AbsoluteX | Indirect | AbsoluteIndexedX), param, _) =>
writeByte(0, index, Assembler.opcodeFor(op, am, options))
writeWord(0, index + 1, param)
index += 3
case AssemblyLine(op, am@(LongAbsolute | LongAbsoluteX | LongIndirect), param, _) =>
writeByte(0, index, Assembler.opcodeFor(op, am, options))
writeWord(0, index + 1, param)
writeByte(0, index + 3, extractBank(param, options))
index += 4
}
}
index
@ -379,44 +397,66 @@ object Assembler {
val opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
val illegalOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
val cmosOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
val cmosNopOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
val ce02Opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
val hudsonOpcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
val emulation65816Opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
val native65816Opcodes = mutable.Map[(Opcode.Value, AddrMode.Value), Byte]()
def opcodeFor(opcode: Opcode.Value, addrMode: AddrMode.Value, options: CompilationOptions): Byte = {
val key = opcode -> addrMode
opcodes.get(key) match {
case Some(v) => v
case None =>
illegalOpcodes.get(key) match {
case Some(v) =>
if (options.flag(CompilationFlag.EmitIllegals)) v
else ErrorReporting.fatal("Cannot assemble an illegal opcode " + key)
case None =>
cmosOpcodes.get(key) match {
case Some(v) =>
if (options.flag(CompilationFlag.EmitCmosOpcodes)) v
else ErrorReporting.fatal("Cannot assemble a CMOS opcode " + key)
case None =>
ErrorReporting.fatal("Cannot assemble an unknown opcode " + key)
}
}
}
opcodes.get(key).foreach(return _)
if (options.flag(CompilationFlag.EmitIllegals)) illegalOpcodes.get(key).foreach(return _)
if (options.flag(CompilationFlag.EmitCmosOpcodes)) cmosOpcodes.get(key).foreach(return _)
if (options.flag(CompilationFlag.EmitCmosNopOpcodes)) cmosNopOpcodes.get(key).foreach(return _)
if (options.flag(CompilationFlag.Emit65CE02Opcodes)) ce02Opcodes.get(key).foreach(return _)
if (options.flag(CompilationFlag.EmitHudsonOpcodes)) hudsonOpcodes.get(key).foreach(return _)
if (options.flag(CompilationFlag.EmitEmulation65816Opcodes)) emulation65816Opcodes.get(key).foreach(return _)
if (options.flag(CompilationFlag.EmitNative65816Opcodes)) native65816Opcodes.get(key).foreach(return _)
ErrorReporting.fatal("Cannot assemble an unknown opcode " + key)
}
private def op(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
if (x < 0 || x > 0xff) ???
if (x < 0 || x > 0xff) ErrorReporting.fatal("Invalid code for" + (op -> am))
opcodes(op -> am) = x.toByte
if (am == AddrMode.Relative) opcodes(op -> AddrMode.Immediate) = x.toByte
}
private def cm(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
if (x < 0 || x > 0xff) ???
if (x < 0 || x > 0xff) ErrorReporting.fatal("Invalid code for" + (op -> am))
cmosOpcodes(op -> am) = x.toByte
}
private def cn(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
if (x < 0 || x > 0xff) ErrorReporting.fatal("Invalid code for" + (op -> am))
cmosNopOpcodes(op -> am) = x.toByte
}
private def il(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
if (x < 0 || x > 0xff) ???
if (x < 0 || x > 0xff) ErrorReporting.fatal("Invalid code for" + (op -> am))
illegalOpcodes(op -> am) = x.toByte
}
private def hu(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
if (x < 0 || x > 0xff) ErrorReporting.fatal("Invalid code for" + (op -> am))
hudsonOpcodes(op -> am) = x.toByte
}
private def ce(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
if (x < 0 || x > 0xff) ErrorReporting.fatal("Invalid code for" + (op -> am))
ce02Opcodes(op -> am) = x.toByte
}
private def em(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
if (x < 0 || x > 0xff) ErrorReporting.fatal("Invalid code for" + (op -> am))
emulation65816Opcodes(op -> am) = x.toByte
}
private def na(op: Opcode.Value, am: AddrMode.Value, x: Int): Unit = {
if (x < 0 || x > 0xff) ErrorReporting.fatal("Invalid code for" + (op -> am))
native65816Opcodes(op -> am) = x.toByte
}
def getStandardLegalOpcodes: Set[Int] = opcodes.values.map(_ & 0xff).toSet
import AddrMode._
@ -617,6 +657,7 @@ object Assembler {
il(AHX, AbsoluteY, 0x9F)
il(SAX, IndexedX, 0x83)
il(AHX, IndexedY, 0x93)
il(SHY, AbsoluteX, 0x9C)
il(ANC, Immediate, 0x0B)
il(ALR, Immediate, 0x4B)
@ -679,10 +720,10 @@ object Assembler {
il(NOP, Absolute, 0x5C)
il(NOP, AbsoluteX, 0x1C)
cm(NOP, Immediate, 0x02)
cm(NOP, ZeroPage, 0x44)
cm(NOP, ZeroPageX, 0x54)
cm(NOP, Absolute, 0x5C)
cn(NOP, Immediate, 0x02)
cn(NOP, ZeroPage, 0x44)
cn(NOP, ZeroPageX, 0x54)
cn(NOP, Absolute, 0x5C)
cm(STZ, ZeroPage, 0x64)
cm(STZ, ZeroPageX, 0x74)
@ -694,14 +735,14 @@ object Assembler {
cm(PLX, Implied, 0xFA)
cm(PLY, Implied, 0x7A)
cm(ORA, ZeroPageIndirect, 0x12)
cm(AND, ZeroPageIndirect, 0x32)
cm(EOR, ZeroPageIndirect, 0x52)
cm(ADC, ZeroPageIndirect, 0x72)
cm(STA, ZeroPageIndirect, 0x92)
cm(LDA, ZeroPageIndirect, 0xB2)
cm(CMP, ZeroPageIndirect, 0xD2)
cm(SBC, ZeroPageIndirect, 0xF2)
cm(ORA, IndexedZ, 0x12)
cm(AND, IndexedZ, 0x32)
cm(EOR, IndexedZ, 0x52)
cm(ADC, IndexedZ, 0x72)
cm(STA, IndexedZ, 0x92)
cm(LDA, IndexedZ, 0xB2)
cm(CMP, IndexedZ, 0xD2)
cm(SBC, IndexedZ, 0xF2)
cm(TSB, ZeroPage, 0x04)
cm(TSB, Absolute, 0x0C)
@ -717,4 +758,119 @@ object Assembler {
cm(WAI, Implied, 0xCB)
cm(STP, Implied, 0xDB)
ce(CPZ, Immediate, 0xC2)
ce(CPZ, ZeroPage, 0xD4)
ce(CPZ, Absolute, 0xDC)
ce(DEZ, Implied, 0x3B)
ce(INZ, Implied,0x1B )
ce(DEC_W, ZeroPage, 0xC3)
ce(INC_W, ZeroPage, 0xE3)
ce(ASL_W, Absolute, 0xCB)
// TODO: or is it ROL_W?
ce(ROR_W, Absolute, 0xEB)
ce(ASR, Implied, 0x43)
ce(ASR, ZeroPage, 0x44)
ce(ASR, ZeroPageX, 0x54)
ce(LDZ, Immediate, 0xA3)
ce(LDZ, Absolute, 0xAB)
ce(LDZ, AbsoluteX, 0xBB)
ce(TAB, Implied, 0x5B)
ce(TBA, Implied, 0x7B)
ce(TAZ, Implied, 0x4B)
ce(TZA, Implied, 0x6B)
ce(TSY, Implied, 0x0B)
ce(TYS, Implied, 0x2B)
ce(PHW, WordImmediate, 0xF4)
ce(PHW, Absolute, 0xFC)
ce(PHZ, Implied, 0xDB)
ce(PLZ, Implied, 0xFB)
// ce(CLE, Implied, )
// ce(SEE, Implied, )
// ce(BSR, , )
hu(CLY, Implied, 0xC2)
hu(CLX, Implied, 0x82)
hu(CLA, Implied, 0x62)
hu(CSH, Implied, 0xD4)
hu(CSL, Implied, 0x54)
hu(HuSAX, Implied, 0x22)
hu(SAY, Implied, 0x42)
hu(SXY, Implied, 0x02)
hu(TAM, Immediate, 0x53)
hu(TMA, Immediate, 0x43)
em(ORA, Stack, 0x03)
em(ORA, IndexedSY, 0x13)
na(ORA, LongIndexedZ, 0x07)
na(ORA, LongIndexedY, 0x17)
na(ORA, LongAbsolute, 0x0F)
na(ORA, LongAbsoluteX, 0x1F)
em(AND, Stack, 0x23)
em(AND, IndexedSY, 0x33)
na(AND, LongIndexedZ, 0x27)
na(AND, LongIndexedY, 0x37)
na(AND, LongAbsolute, 0x2F)
na(AND, LongAbsoluteX, 0x3F)
em(EOR, Stack, 0x43)
em(EOR, IndexedSY, 0x53)
na(EOR, LongIndexedZ, 0x47)
na(EOR, LongIndexedY, 0x57)
na(EOR, LongAbsolute, 0x4F)
na(EOR, LongAbsoluteX, 0x5F)
em(ADC, Stack, 0x63)
em(ADC, IndexedSY, 0x73)
na(ADC, LongIndexedZ, 0x67)
na(ADC, LongIndexedY, 0x77)
na(ADC, LongAbsolute, 0x6F)
na(ADC, LongAbsoluteX, 0x7F)
em(STA, Stack, 0x83)
em(STA, IndexedSY, 0x93)
na(STA, LongIndexedZ, 0x87)
na(STA, LongIndexedY, 0x97)
na(STA, LongAbsolute, 0x8F)
na(STA, LongAbsoluteX, 0x9F)
em(LDA, Stack, 0xA3)
em(LDA, IndexedSY, 0xB3)
na(LDA, LongIndexedZ, 0xA7)
na(LDA, LongIndexedY, 0xB7)
na(LDA, LongAbsolute, 0xAF)
na(LDA, LongAbsoluteX, 0xBF)
em(CMP, Stack, 0xA3)
em(CMP, IndexedSY, 0xB3)
na(CMP, LongIndexedZ, 0xA7)
na(CMP, LongIndexedY, 0xB7)
na(CMP, LongAbsolute, 0xAF)
na(CMP, LongAbsoluteX, 0xBF)
em(COP, Immediate, 0x02)
em(XBA, Implied, 0xEB)
em(TXY, Implied, 0x9B)
em(TYX, Implied, 0xBB)
na(RTL, Implied, 0x6B)
na(JMP, LongAbsolute, 0x5C)
na(JMP, LongIndirect, 0x7C)
na(BRL, LongRelative, 0x82)
em(PHD, Implied, 0x0B)
em(PLD, Implied, 0x2B)
em(PHB, Implied, 0x8B)
em(PLB, Implied, 0xAB)
em(PHK, Implied, 0x4B)
na(REP, Immediate, 0xC2)
na(SEP, Immediate, 0xE2)
na(XCE, Implied, 0xFB)
na(TCD, Implied, 0x5B)
na(TDC, Implied, 0x7B)
na(TSC, Implied, 0x3B)
na(TCS, Implied, 0x1B)
for {
((narrow, am), code) <- emulation65816Opcodes ++ opcodes ++ cmosOpcodes ++ native65816Opcodes
wide <- Opcode.widen(narrow)
} na(wide, if (am == Immediate) WordImmediate else am, code & 0xff)
}

View File

@ -76,12 +76,13 @@ object InliningCalculator {
case _ => Nil
}
private val badOpcodes = Set(RTI, RTS, JSR, BRK) ++ OpcodeClasses.ChangesStack
private val badOpcodes = Set(RTI, RTS, JSR, BRK, RTL, BSR) ++ OpcodeClasses.ChangesStack
private val jumpingRelatedOpcodes = Set(LABEL, JMP) ++ OpcodeClasses.ShortBranching
def codeForInlining(fname: String, code: List[AssemblyLine]): Option[List[AssemblyLine]] = {
if (code.isEmpty) return None
if (code.last.opcode != RTS) return None
val lastOpcode = code.last.opcode
if (lastOpcode != RTS && lastOpcode != RTL) return None
var result = code.init
while (result.nonEmpty && OpcodeClasses.NoopDiscardsFlags(result.last.opcode)) {
result = result.init

View File

@ -88,7 +88,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
for {
p <- position()
minus <- "-".!.?
_ <- P("0x" | "$") ~/ Pass
_ <- P("0x" | "0X" | "$") ~/ Pass
s <- CharsWhileIn("1234567890abcdefABCDEF", min = 1).!.opaque("<hex digits>")
} yield {
val abs = Integer.parseInt(s, 16)
@ -96,7 +96,31 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
LiteralExpression(value, size(value, s.length > 2, s.length > 4)).pos(p)
}
val literalAtom: P[LiteralExpression] = binaryAtom | hexAtom | decimalAtom
val octalAtom: P[LiteralExpression] =
for {
p <- position()
minus <- "-".!.?
_ <- P("0o" | "0O") ~/ Pass
s <- CharsWhileIn("01234567", min = 1).!.opaque("<octal digits>")
} yield {
val abs = Integer.parseInt(s, 8)
val value = sign(abs, minus.isDefined)
LiteralExpression(value, size(value, s.length > 3, s.length > 6)).pos(p)
}
val quaternaryAtom: P[LiteralExpression] =
for {
p <- position()
minus <- "-".!.?
_ <- P("0q" | "0Q") ~/ Pass
s <- CharsWhileIn("0123", min = 1).!.opaque("<quaternary digits>")
} yield {
val abs = Integer.parseInt(s, 4)
val value = sign(abs, minus.isDefined)
LiteralExpression(value, size(value, s.length > 4, s.length > 8)).pos(p)
}
val literalAtom: P[LiteralExpression] = binaryAtom | hexAtom | octalAtom | quaternaryAtom | decimalAtom
val atom: P[Expression] = P(literalAtom | (position() ~ identifier).map { case (p, i) => VariableExpression(i).pos(p) })
@ -307,13 +331,19 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
val commaX = HWS ~ "," ~ HWS ~ ("X" | "x") ~ HWS
val commaY = HWS ~ "," ~ HWS ~ ("Y" | "y") ~ HWS
val commaZ = HWS ~ "," ~ HWS ~ ("Z" | "z") ~ HWS
val commaS = HWS ~ "," ~ HWS ~ ("S" | "s") ~ HWS
def asmParameter: P[(AddrMode.Value, Expression)] = {
(SWS ~ (
("##" ~ asmExpression).map(AddrMode.WordImmediate -> _) |
("#" ~ asmExpression).map(AddrMode.Immediate -> _) |
("(" ~ HWS ~ asmExpression ~ HWS ~ ")" ~ commaY).map(AddrMode.IndexedY -> _) |
("(" ~ HWS ~ asmExpression ~ commaS ~ ")" ~ commaY).map(AddrMode.IndexedSY -> _) |
("(" ~ HWS ~ asmExpression ~ HWS ~ ")" ~ commaZ).map(AddrMode.IndexedZ -> _) |
("(" ~ HWS ~ asmExpression ~ commaX ~ ")").map(AddrMode.IndexedX -> _) |
("(" ~ HWS ~ asmExpression ~ HWS ~ ")").map(AddrMode.Indirect -> _) |
(asmExpression ~ commaS).map(AddrMode.Stack -> _) |
(asmExpression ~ commaX).map(AddrMode.AbsoluteX -> _) |
(asmExpression ~ commaY).map(AddrMode.AbsoluteY -> _) |
asmExpression.map(AddrMode.Absolute -> _)
@ -325,7 +355,13 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o
def asmInstruction: P[ExecutableStatement] = {
val lineParser: P[(Boolean, Opcode.Value, (AddrMode.Value, Expression))] = !"}" ~ elidable ~/ asmOpcode ~/ asmParameter
lineParser.map { case (elid, op, param) =>
AssemblyStatement(op, param._1, param._2, elid)
(op, param._1) match {
case (Opcode.SAX, AddrMode.Implied) => AssemblyStatement(Opcode.HuSAX, param._1, param._2, elid)
case (Opcode.SBX, AddrMode.Immediate) => AssemblyStatement(Opcode.SBX, param._1, param._2, elid)
case (Opcode.SAY, AddrMode.AbsoluteX) => AssemblyStatement(Opcode.SHY, param._1, param._2, elid)
case (Opcode.SBX, _) => AssemblyStatement(Opcode.SAX, param._1, param._2, elid)
case _ => AssemblyStatement(op, param._1, param._2, elid)
}
}
}

View File

@ -1,6 +1,6 @@
package millfork.test
import millfork.test.emu.{EmuBenchmarkRun, EmuUnoptimizedRun}
import millfork.test.emu.{EmuBenchmarkRun, EmuCmosBenchmarkRun}
import org.scalatest.{FunSuite, Matchers}
/**
@ -9,7 +9,7 @@ import org.scalatest.{FunSuite, Matchers}
class StackVarSuite extends FunSuite with Matchers {
test("Basic stack assignment") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| byte output @$c000
| void main () {
| stack byte a
@ -23,7 +23,7 @@ class StackVarSuite extends FunSuite with Matchers {
}
test("Stack byte addition") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| byte output @$c000
| void main () {
| stack byte a
@ -41,7 +41,7 @@ class StackVarSuite extends FunSuite with Matchers {
}
test("Complex expressions involving stack variables") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| byte output @$c000
| void main () {
| stack byte a
@ -74,7 +74,7 @@ class StackVarSuite extends FunSuite with Matchers {
// }
test("Stack word addition") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| word output @$c000
| void main () {
| stack word a
@ -92,7 +92,7 @@ class StackVarSuite extends FunSuite with Matchers {
}
test("Recursion") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| array output [6] @$c000
| byte fails @$c010
| void main () {
@ -129,7 +129,7 @@ class StackVarSuite extends FunSuite with Matchers {
test("Indexing") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| array output [200] @$c000
| void main () {
| stack byte a

View File

@ -1,5 +1,5 @@
package millfork.test
import millfork.test.emu.EmuBenchmarkRun
import millfork.test.emu.{EmuBenchmarkRun, EmuCmosBenchmarkRun}
import org.scalatest.{FunSuite, Matchers}
/**
@ -8,7 +8,7 @@ import org.scalatest.{FunSuite, Matchers}
class WordMathSuite extends FunSuite with Matchers {
test("Word addition") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| word output @$c000
| word a
| void main () {
@ -20,7 +20,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word subtraction") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| word output @$c000
| word a
| void main () {
@ -32,7 +32,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Word subtraction 2") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| word output @$c000
| word a
| void main () {
@ -44,7 +44,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Byte-to-word addition") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| word output @$c000
| word pair
| void main () {
@ -57,7 +57,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Literal addition") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| word output @$c000
| void main () {
| output = 640
@ -67,7 +67,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("Array element addition") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| word output @$c000
| word pair
| array b[2]
@ -84,7 +84,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("nesdev.com example") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| byte output @$c000
| array map [256] @$c300
| array b[2]
@ -102,7 +102,7 @@ class WordMathSuite extends FunSuite with Matchers {
}
test("hi()/lo()") {
EmuBenchmarkRun("""
EmuCmosBenchmarkRun("""
| array output [7] @$c000
| void main () {
| output[0] = lo(33)

View File

@ -7,9 +7,18 @@ import millfork.output.MemoryBank
*/
object EmuCmosBenchmarkRun {
def apply(source:String)(verifier: MemoryBank=>Unit) = {
println(f"Compiling for NMOS (unoptimized)")
val (Timings(_, t0), m0) = EmuUnoptimizedRun.apply2(source)
println(f"Compiling for NMOS")
val (Timings(_, t1), m1) = EmuOptimizedRun.apply2(source)
println(f"Compiling for CMOS")
val (Timings(_, t2), m2) = EmuOptimizedCmosRun.apply2(source)
println(f"Compiling for HuC6280")
EmuOptimizedHudsonRun.apply2(source)
println(f"Compiling for 65CE02")
EmuOptimized65CE02Run.apply2(source)
println(f"Compiling for 65816")
EmuOptimized65816Run.apply2(source)
println(f"Before optimization: $t0%7d")
println(f"After NMOS optimization: $t1%7d")
println(f"After CMOS optimization: $t2%7d")

View File

@ -0,0 +1,21 @@
package millfork.test.emu
import millfork.assembly.opt.{CmosOptimizations, SixteenOptimizations}
import millfork.{Cpu, OptimizationPresets}
/**
* @author Karol Stasiak
*/
object EmuOptimized65816Run extends EmuRun(
Cpu.Sixteen,
OptimizationPresets.NodeOpt,
OptimizationPresets.AssOpt ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ SixteenOptimizations.All ++ OptimizationPresets.Good,
false)

View File

@ -0,0 +1,19 @@
package millfork.test.emu
import millfork.assembly.opt.{CE02Optimizations, CmosOptimizations}
import millfork.{Cpu, OptimizationPresets}
/**
* @author Karol Stasiak
*/
object EmuOptimized65CE02Run extends EmuRun(
Cpu.CE02,
OptimizationPresets.NodeOpt,
OptimizationPresets.AssOpt ++
CmosOptimizations.All ++ CE02Optimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ CE02Optimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ CE02Optimizations.All ++ OptimizationPresets.Good,
false)

View File

@ -0,0 +1,19 @@
package millfork.test.emu
import millfork.assembly.opt.{CmosOptimizations, HudsonOptimizations}
import millfork.{Cpu, OptimizationPresets}
/**
* @author Karol Stasiak
*/
object EmuOptimizedHudsonRun extends EmuRun(
Cpu.HuC6280,
OptimizationPresets.NodeOpt,
OptimizationPresets.AssOpt ++
CmosOptimizations.All ++ HudsonOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ HudsonOptimizations.All ++ OptimizationPresets.Good ++
CmosOptimizations.All ++ HudsonOptimizations.All ++ OptimizationPresets.Good,
false)

View File

@ -97,7 +97,10 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
CompilationFlag.DetailedFlowAnalysis -> quantum,
CompilationFlag.InlineFunctions -> this.inline,
CompilationFlag.CompactReturnDispatchParams -> true,
CompilationFlag.EmitCmosOpcodes -> (platform.cpu == millfork.Cpu.Cmos),
CompilationFlag.EmitCmosOpcodes -> millfork.Cpu.CmosCompatible.contains(platform.cpu),
CompilationFlag.EmitEmulation65816Opcodes -> (platform.cpu == millfork.Cpu.Sixteen),
CompilationFlag.Emit65CE02Opcodes -> (platform.cpu == millfork.Cpu.CE02),
CompilationFlag.EmitHudsonOpcodes -> (platform.cpu == millfork.Cpu.HuC6280),
CompilationFlag.OptimizeForSonicSpeed -> blastProcessing
// CompilationFlag.CheckIndexOutOfBounds -> true,
))
@ -167,8 +170,11 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization],
case millfork.Cpu.Mos =>
ErrorReporting.fatal("There's no NMOS emulator with decimal mode support")
Timings(-1, -1) -> memoryBank
case _ =>
case millfork.Cpu.StrictMos | millfork.Cpu.StrictRicoh =>
runViaSymon(memoryBank, platform.org, CpuBehavior.NMOS_6502)
case _ =>
ErrorReporting.trace("No emulation support for " + platform.cpu)
Timings(-1, -1) -> memoryBank
}
case f: Failure[_, _] =>
println(f)

View File

@ -10,8 +10,9 @@ class SymonTestRam(mem: MemoryBank) extends Device(0x0000, 0xffff, "RAM") {
mem.readable(1) = true
mem.readable(2) = true
mem.readable(0x23) = true
(0x100 to 0x1ff).foreach { stack =>
(0 to 0x1ff).foreach { stack =>
mem.writeable(stack) = true
mem.readable(stack) = true
}