From 3852b2dbe965f11717880ce6e9d82cf6130faaf0 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Fri, 14 Jun 2019 11:39:11 +0200 Subject: [PATCH] More label file formats --- .gitignore | 2 + CHANGELOG.md | 2 + docs/api/command-line.md | 15 +++- docs/api/custom-platform.md | 13 ++++ docs/api/getting-started.md | 29 +++---- include/c128.ini | 1 + include/c16.ini | 1 + include/c64.ini | 1 + include/c64_crt16k.ini | 1 + include/c64_crt8k.ini | 1 + include/c64_scpu.ini | 1 + include/c64_scpu16.ini | 1 + include/gb_small.ini | 1 + include/lunix.ini | 1 + include/nes_mmc4.ini | 11 +++ include/nes_small.ini | 2 + include/pet.ini | 1 + include/plus4.ini | 1 + include/vic20.ini | 1 + include/vic20_3k.ini | 1 + include/vic20_8k.ini | 1 + include/vic20_a000.ini | 1 + src/main/scala/millfork/Context.scala | 2 + .../scala/millfork/DebugOutputFormat.scala | 77 +++++++++++++++++++ src/main/scala/millfork/Main.scala | 42 ++++++---- src/main/scala/millfork/Platform.scala | 7 ++ .../assembly/AssemblyOptimization.scala | 2 +- .../opt/RuleBasedAssemblyOptimization.scala | 10 +-- src/main/scala/millfork/env/Environment.scala | 15 ++-- .../millfork/output/AbstractAssembler.scala | 57 +++++++------- .../millfork/output/CompiledMemory.scala | 6 +- .../scala/millfork/output/MosAssembler.scala | 9 ++- .../scala/millfork/output/Z80Assembler.scala | 5 +- .../output/Z80ToX86Crossassembler.scala | 5 +- .../scala/millfork/test/emu/EmuI86Run.scala | 4 +- .../scala/millfork/test/emu/EmuPlatform.scala | 3 +- src/test/scala/millfork/test/emu/EmuRun.scala | 2 +- .../scala/millfork/test/emu/EmuZ80Run.scala | 2 +- 38 files changed, 253 insertions(+), 84 deletions(-) create mode 100644 src/main/scala/millfork/DebugOutputFormat.scala diff --git a/.gitignore b/.gitignore index e718206f..d978f065 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ examples/lunix/ *.asm *.lbl *.nl +*.fns +*.sym *.deb *.xex *.nes diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a1b5da..017b1f94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ * Support for Intel 8085, together with illegal instructions. +* More label file formats. + * Added `memory_barrier` macro. * Added `random` module. diff --git a/docs/api/command-line.md b/docs/api/command-line.md index c7d83f0b..63c6ef89 100644 --- a/docs/api/command-line.md +++ b/docs/api/command-line.md @@ -28,7 +28,20 @@ no extension for BBC micro program file, * `-s` – Generate also the assembly output. It is not compatible with any assembler, but it serves purely informational purpose. The file has the same nam as the output file and the extension is `.asm`. -* `-g` – Generate also the label file. The label file contains labels with their addresses, with duplicates removed. It can be loaded into the monitor of the Vice emulator for debugging purposes. The file has the same name as the output file and the extension is `.lbl`. +* `-g` – Generate also the label file. The label file contains labels with their addresses, with duplicates removed. +It can be loaded into the monitor of the emulator for debugging purposes. +The file has the same name as the output file. +The extension and the file format are platform-dependent. + +* `-G ` – The same as `-g`, but with the specified format: + + * `-G vice` – format compatible with the Vice emulator. The extension is `.lbl`. + + * `-G nesasm` – format used by the NESASM assembler. The extension is `.fns`. + + * `-G sym` – format used by the WLA DX assembler. The extension is `.sym`. + + * `-G fceux` – multi-file format used by the FCEUX emulator. The extension is `.nl`. * `-I ;` – The include directories. Those directories are searched for modules and platform definitions. diff --git a/docs/api/custom-platform.md b/docs/api/custom-platform.md index 2d13c1f4..74068aae 100644 --- a/docs/api/custom-platform.md +++ b/docs/api/custom-platform.md @@ -141,6 +141,9 @@ Default: the same as `segment_NAME_end`. * `segment_NAME_datastart` – the first address used for non-zeropage variables, or `after_code` if the variables should be allocated after the code. Default: `after_code`. +* `segment_NAME_bank` – the bank number the segment belongs to. Default: `0`. +For better debugging on NES, RAM segments should use bank number `$ff`. + #### `[output]` section * `style` – how multi-segment programs should be output: @@ -178,3 +181,13 @@ Default: `after_code`. * `bbc_inf` – should the `.inf` file with file metadata for BBC Micro be created * `gb_checksum` – should the main output file be patched with Game Boy-compatible checksums + +* `labels` – format of the label file: + + * `vice` (the default) – format compatible with the Vice emulator. The extension is `.lbl`. + + * `nesasm` – format used by the NESASM assembler. The extension is `.fns`. + + * `sym` – format used by the WLA/DX assembler. The extension is `.sym`. + + * `fceux` – multi-file format used by the FCEUX emulator. The extension is `.nl`. diff --git a/docs/api/getting-started.md b/docs/api/getting-started.md index 1510edf5..f61ad078 100644 --- a/docs/api/getting-started.md +++ b/docs/api/getting-started.md @@ -31,19 +31,20 @@ x64 hello_world.prg The following options are obligatory when compiling your sources: -* `-o FILENAME` – specifies the base name for your output file, an appropriate file extension will be appended -(`prg` for Commodore, -`xex` for Atari computers, -`a2` for Apple, -`asm` for assembly output, -`lbl` for label file, -`inf` for BBC file metadata, -`dsk` for PC-88, -`tap` for ZX Spectrum, -`rom` for MSX cartridges, -`com` for CP/M, -`nes` for Famicom, -`bin` for Atari 2600) +* `-o FILENAME` – specifies the base name for your output file, an appropriate file extension will be appended: +`prg` for Commodore; +`crt` for Commodore cartridges; +`xex` for Atari computers; +`a2` for Apple; +`dsk` for PC-88; +`tap` for ZX Spectrum; +`rom` for MSX cartridges; +`com` for CP/M; +`nes` for Famicom; +`bin` for Atari 2600; +`inf` for BBC file metadata; +`asm` for assembly output; +`lbl`, `nl`, `fns`, or `sym` for label file * `-t PLATFORM` – specifies the target platform. Each platform is defined in an `.ini` file in the include directory. @@ -62,7 +63,7 @@ You may be also interested in the following: * `-fsource-in-asm` – show original Millfork source in the assembly output -* `-g` – additionally generate a label file, in format compatible with VICE emulator +* `-g` – additionally generate a label file * `-r PROGRAM` – automatically launch given program after successful compilation diff --git a/include/c128.ini b/include/c128.ini index 0b18dd34..520d8fe3 100644 --- a/include/c128.ini +++ b/include/c128.ini @@ -25,5 +25,6 @@ HAS_BITMAP_MODE=1 style=single format=startaddr,allocated extension=prg +labels=vice diff --git a/include/c16.ini b/include/c16.ini index c64ab1ee..9478db2f 100644 --- a/include/c16.ini +++ b/include/c16.ini @@ -24,5 +24,6 @@ HAS_BITMAP_MODE=1 style=single format=startaddr,allocated extension=prg +labels=vice diff --git a/include/c64.ini b/include/c64.ini index 5395ed3a..0f7b243a 100644 --- a/include/c64.ini +++ b/include/c64.ini @@ -43,5 +43,6 @@ style=single format=startaddr,allocated ; default output file extension extension=prg +labels=vice diff --git a/include/c64_crt16k.ini b/include/c64_crt16k.ini index 94c1d508..7ad6de99 100644 --- a/include/c64_crt16k.ini +++ b/include/c64_crt16k.ini @@ -37,5 +37,6 @@ format =$43,$36,$34,$20,$43,$41,$52,$54,$52,$49,$44,$47,$45,$20,$20,$20, \ $43,$48,$49,$50, 0,0,$40,$10, 0,0, 0,0, $80,$00, $40,$00, \ prgrom:$8000:$bfff extension=crt +labels=vice diff --git a/include/c64_crt8k.ini b/include/c64_crt8k.ini index 5495790e..9d79b44b 100644 --- a/include/c64_crt8k.ini +++ b/include/c64_crt8k.ini @@ -37,5 +37,6 @@ format =$43,$36,$34,$20,$43,$41,$52,$54,$52,$49,$44,$47,$45,$20,$20,$20, \ $43,$48,$49,$50, 0,0,$20,$10, 0,0, 0,0, $80,$00, $20,$00, \ prgrom:$8000:$9fff extension=crt +labels=vice diff --git a/include/c64_scpu.ini b/include/c64_scpu.ini index f1b57614..db8b86ec 100644 --- a/include/c64_scpu.ini +++ b/include/c64_scpu.ini @@ -27,5 +27,6 @@ HAS_BITMAP_MODE=1 style=single format=startaddr,allocated extension=prg +labels=vice diff --git a/include/c64_scpu16.ini b/include/c64_scpu16.ini index 02501682..2fa6856a 100644 --- a/include/c64_scpu16.ini +++ b/include/c64_scpu16.ini @@ -29,5 +29,6 @@ HAS_BITMAP_MODE=1 style=single format=startaddr,allocated extension=prg +labels=vice diff --git a/include/gb_small.ini b/include/gb_small.ini index 8d9bb012..0cbbe8f4 100644 --- a/include/gb_small.ini +++ b/include/gb_small.ini @@ -30,5 +30,6 @@ style=single format=rom:0:$7fff gb_checksum=true extension=gb +labels=sym diff --git a/include/lunix.ini b/include/lunix.ini index 8627bde1..f82677b2 100644 --- a/include/lunix.ini +++ b/include/lunix.ini @@ -30,5 +30,6 @@ HAS_BITMAP_MODE=1 style=lunix format=$ff,$fe,0,21,pagecount,startpage,allocated extension=prg +labels=vice diff --git a/include/nes_mmc4.ini b/include/nes_mmc4.ini index 70c6cc99..0d38356f 100644 --- a/include/nes_mmc4.ini +++ b/include/nes_mmc4.ini @@ -21,33 +21,43 @@ default_code_segment=prgrom7 segment_default_start=$200 segment_default_end=$7ff +segment_default_bank=$ff segment_ram_start=$6000 segment_ram_end=$7fff +segment_ram_bank=$ff segment_prgrom7_start=$c000 segment_prgrom7_end=$ffff +segment_prgrom7_bank=7 segment_prgrom0_start=$8000 segment_prgrom0_end=$bfff +segment_prgrom0_bank=0 segment_prgrom1_start=$8000 segment_prgrom1_end=$bfff +segment_prgrom1_bank=1 segment_prgrom2_start=$8000 segment_prgrom2_end=$bfff +segment_prgrom2_bank=2 segment_prgrom3_start=$8000 segment_prgrom3_end=$bfff +segment_prgrom3_bank=3 segment_prgrom4_start=$8000 segment_prgrom4_end=$bfff +segment_prgrom4_bank=4 segment_prgrom5_start=$8000 segment_prgrom5_end=$bfff +segment_prgrom5_bank=5 segment_prgrom6_start=$8000 segment_prgrom6_end=$bfff +segment_prgrom6_bank=6 segment_chrrom0_start=$0000 segment_chrrom0_end=$ffff @@ -67,5 +77,6 @@ style=single format=$4E,$45,$53,$1A, 8,16,$A0,8, 0,0,$07,0, 2,0,0,0, prgrom0:$8000:$bfff,prgrom1:$8000:$bfff,prgrom2:$8000:$bfff,prgrom3:$8000:$bfff,prgrom4:$8000:$bfff,prgrom5:$8000:$bfff,prgrom6:$8000:$bfff,prgrom7:$c000:$ffff,chrrom0:$0000:$ffff,chrrom1:$0000:$ffff extension=nes +labels=nesasm diff --git a/include/nes_small.ini b/include/nes_small.ini index c53ad35f..b054316c 100644 --- a/include/nes_small.ini +++ b/include/nes_small.ini @@ -17,6 +17,7 @@ default_code_segment=prgrom segment_default_start=$200 segment_default_end=$7ff +segment_default_bank=$ff segment_prgrom_start=$8000 segment_prgrom_end=$ffff @@ -35,5 +36,6 @@ HAS_BITMAP_MODE=0 style=single format=$4E,$45,$53,$1A, 2,1,0,0, 0,0,0,0, 0,0,0,0, prgrom:$8000:$ffff, chrrom:$0000:$1fff extension=nes +labels=nesasm diff --git a/include/pet.ini b/include/pet.ini index f39b40a2..fa08b27f 100644 --- a/include/pet.ini +++ b/include/pet.ini @@ -23,5 +23,6 @@ HAS_BITMAP_MODE=0 style=single format=startaddr,allocated extension=prg +labels=vice diff --git a/include/plus4.ini b/include/plus4.ini index 61b05ee5..ffb42e93 100644 --- a/include/plus4.ini +++ b/include/plus4.ini @@ -24,5 +24,6 @@ HAS_BITMAP_MODE=1 style=per_bank format=startaddr,allocated extension=prg +labels=vice diff --git a/include/vic20.ini b/include/vic20.ini index d1acb84a..9198fdcf 100644 --- a/include/vic20.ini +++ b/include/vic20.ini @@ -23,5 +23,6 @@ HAS_BITMAP_MODE=1 style=single format=startaddr,allocated extension=prg +labels=vice diff --git a/include/vic20_3k.ini b/include/vic20_3k.ini index 84a7f61d..6d83cfe2 100644 --- a/include/vic20_3k.ini +++ b/include/vic20_3k.ini @@ -23,5 +23,6 @@ HAS_BITMAP_MODE=1 style=single format=startaddr,allocated extension=prg +labels=vice diff --git a/include/vic20_8k.ini b/include/vic20_8k.ini index 65fe5ddd..6fb71d38 100644 --- a/include/vic20_8k.ini +++ b/include/vic20_8k.ini @@ -23,5 +23,6 @@ HAS_BITMAP_MODE=1 style=single format=startaddr,allocated extension=prg +labels=vice diff --git a/include/vic20_a000.ini b/include/vic20_a000.ini index 52d160e5..e1133a61 100644 --- a/include/vic20_a000.ini +++ b/include/vic20_a000.ini @@ -30,5 +30,6 @@ HAS_BITMAP_MODE=1 style=single format=$00,$a0,prgrom:$a000:$bfff extension=crt +labels=vice diff --git a/src/main/scala/millfork/Context.scala b/src/main/scala/millfork/Context.scala index b7b09118..d24d1147 100644 --- a/src/main/scala/millfork/Context.scala +++ b/src/main/scala/millfork/Context.scala @@ -15,7 +15,9 @@ case class Context(errorReporting: Logger, platform: Option[String] = None, outputAssembly: Boolean = false, outputLabels: Boolean = false, + outputLabelsFormatOverride: Option[DebugOutputFormat] = None, includePath: List[String] = Nil, + extraIncludePath: Seq[String] = IndexedSeq(), flags: Map[CompilationFlag.Value, Boolean] = Map(), features: Map[String, Long] = Map(), verbosity: Option[Int] = None) { diff --git a/src/main/scala/millfork/DebugOutputFormat.scala b/src/main/scala/millfork/DebugOutputFormat.scala new file mode 100644 index 00000000..3ed4ea84 --- /dev/null +++ b/src/main/scala/millfork/DebugOutputFormat.scala @@ -0,0 +1,77 @@ +package millfork + +/** + * @author Karol Stasiak + */ + +object DebugOutputFormat { + val map: Map[String, DebugOutputFormat] = Map( + "vice" -> ViceDebugOutputFormat, + "nesasm" -> NesasmDebugOutputFormat, + "fns" -> NesasmDebugOutputFormat, + "fceux" -> FceuxDebugOutputFormat, + "nl" -> FceuxDebugOutputFormat, + "sym" -> SymDebugOutputFormat) +} + +sealed trait DebugOutputFormat extends Function[(String, (Int, Int)), String] { + + def apply(labelAndValue: (String, (Int, Int))): String = formatLine(labelAndValue._1, labelAndValue._2._1, labelAndValue._2._2) + + def formatLine(label: String, bank: Int, value: Int): String + + def fileExtension(bank: Int): String + + def filePerBank: Boolean + + def addOutputExtension: Boolean +} + +object ViceDebugOutputFormat extends DebugOutputFormat { + override def formatLine(label: String, bank: Int, value: Int): String = { + val normalized = label.replace('$', '_').replace('.', '_') + s"al ${value.toHexString} .$normalized" + } + + override def fileExtension(bank: Int): String = ".lbl" + + override def filePerBank: Boolean = false + + override def addOutputExtension: Boolean = false +} + +object NesasmDebugOutputFormat extends DebugOutputFormat { + override def formatLine(label: String, bank: Int, value: Int): String = { + label + " = $" + value.toHexString + } + + override def fileExtension(bank: Int): String = ".fns" + + override def filePerBank: Boolean = false + + override def addOutputExtension: Boolean = false +} + +object SymDebugOutputFormat extends DebugOutputFormat { + override def formatLine(label: String, bank: Int, value: Int): String = { + f"$bank%02x:$value%04x $label%s" + } + + override def fileExtension(bank: Int): String = ".sym" + + override def filePerBank: Boolean = false + + override def addOutputExtension: Boolean = false +} + +object FceuxDebugOutputFormat extends DebugOutputFormat { + override def formatLine(label: String, bank: Int, value: Int): String = { + f"$$$value%04x#$label%s#" + } + + override def fileExtension(bank: Int): String = if (bank == 0xff) ".ram.nl" else s".$bank.nl" + + override def filePerBank: Boolean = true + + override def addOutputExtension: Boolean = true +} diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index ca91bbe0..11ca3da5 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -67,7 +67,6 @@ object Main { val output = c.outputFileName.getOrElse("a") val assOutput = output + ".asm" - val labelOutput = output + ".lbl" // val prgOutputs = (platform.outputStyle match { // case OutputStyle.Single => List("default") // case OutputStyle.PerBank => platform.bankNumbers.keys.toList @@ -91,17 +90,27 @@ object Main { Files.write(path, result.asm.mkString("\n").getBytes(StandardCharsets.UTF_8)) } if (c.outputLabels) { - val path = Paths.get(labelOutput) - errorReporting.debug("Writing labels to " + path.toAbsolutePath) - Files.write(path, result.labels.sortWith { (a, b) => - val aLocal = a._1.head == '.' - val bLocal = b._1.head == '.' - if (aLocal == bLocal) a._1 < b._1 - else b._1 < a._1 - }.groupBy(_._2).values.map(_.head).toSeq.sortBy(_._2).map { case (l, a) => - val normalized = l.replace('$', '_').replace('.', '_') - s"al ${a.toHexString} .$normalized" - }.mkString("\n").getBytes(StandardCharsets.UTF_8)) + def labelUnimportance(l: String): Int = { + if (l.startsWith(".")) 8 + else if (l.startsWith("__")) 7 + else 0 + } + val sortedLabels = result.labels.groupBy(_._2).values.map(_.minBy(a => labelUnimportance(a._1) -> a._1)).toSeq.sortBy(_._2) + val format = c.outputLabelsFormatOverride.getOrElse(platform.outputLabelsFormat) + val basename = if (format.addOutputExtension) output + platform.fileExtension else output + if (format.filePerBank) { + sortedLabels.groupBy(_._2._1).foreach{ case (bank, labels) => + val labelOutput = basename + format.fileExtension(bank) + val path = Paths.get(labelOutput) + errorReporting.debug("Writing labels to " + path.toAbsolutePath) + Files.write(path, labels.map(format).mkString("\n").getBytes(StandardCharsets.UTF_8)) + } + } else { + val labelOutput = basename + format.fileExtension(0) + val path = Paths.get(labelOutput) + errorReporting.debug("Writing labels to " + path.toAbsolutePath) + Files.write(path, sortedLabels.map(format).mkString("\n").getBytes(StandardCharsets.UTF_8)) + } } val defaultPrgOutput = if (output.endsWith(platform.fileExtension)) output else output + platform.fileExtension result.code.foreach{ @@ -336,7 +345,14 @@ object Main { flag("-g").action { c => c.copy(outputLabels = true) - }.description("Generate also the label file.") + }.description("Generate also the label file in the default format.") + + parameter("-G").placeholder("").action { (p, c) => + val f = DebugOutputFormat.map.getOrElse( + p.toLowerCase(Locale.ROOT), + errorReporting.fatal("Invalid label file format: " + p)) + c.copy(outputLabels = true, outputLabelsFormatOverride = Some(f)) + }.description("Generate also the label file in the given format. Available options: vice, nesasm, sym.") parameter("-t", "--target").placeholder("").action { (p, c) => assertNone(c.platform, "Platform already defined") diff --git a/src/main/scala/millfork/Platform.scala b/src/main/scala/millfork/Platform.scala index cf5ccb5f..fbb99dab 100644 --- a/src/main/scala/millfork/Platform.scala +++ b/src/main/scala/millfork/Platform.scala @@ -3,6 +3,7 @@ package millfork import java.io.{File, StringReader} import java.nio.charset.StandardCharsets import java.nio.file.{Files, Paths} +import java.util.Locale import millfork.error.Logger import millfork.output._ @@ -34,6 +35,7 @@ class Platform( val generateGameBoyChecksums: Boolean, val bankNumbers: Map[String, Int], val defaultCodeBank: String, + val outputLabelsFormat: DebugOutputFormat, val outputStyle: OutputStyle.Value ) { def hasZeroPage: Boolean = cpuFamily == CpuFamily.M6502 @@ -201,6 +203,10 @@ object Platform { case "lunix" => OutputStyle.LUnix case x => log.fatal(s"Invalid output style: `$x`") } + val debugOutputFormatName = os.get(classOf[String], "labels", "vice") + val debugOutputFormat = DebugOutputFormat.map.getOrElse( + debugOutputFormatName.toLowerCase(Locale.ROOT), + log.fatal(s"Invalid label file format: `$debugOutputFormatName`")) val builtInFeatures = builtInCpuFeatures(cpu) @@ -232,6 +238,7 @@ object Platform { generateGameBoyChecksums, bankNumbers, defaultCodeBank, + debugOutputFormat, outputStyle) } diff --git a/src/main/scala/millfork/assembly/AssemblyOptimization.scala b/src/main/scala/millfork/assembly/AssemblyOptimization.scala index 11a85090..36dc9916 100644 --- a/src/main/scala/millfork/assembly/AssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/AssemblyOptimization.scala @@ -10,7 +10,7 @@ import millfork.node.NiceFunctionProperty * @author Karol Stasiak */ case class OptimizationContext(options: CompilationOptions, - labelMap: Map[String, Int], + labelMap: Map[String, (Int, Int)], zreg: Option[ThingInMemory], niceFunctionProperties: Set[(NiceFunctionProperty, String)]) { @inline diff --git a/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala b/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala index cacb2bde..04130854 100644 --- a/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala +++ b/src/main/scala/millfork/assembly/mos/opt/RuleBasedAssemblyOptimization.scala @@ -102,10 +102,10 @@ class RuleBasedAssemblyOptimization(val name: String, val needsFlowInfo: FlowInf } class AssemblyMatchingContext(val compilationOptions: CompilationOptions, - val labelMap: Map[String, Int], + val labelMap: Map[String, (Int, Int)], val zeropageRegister: Option[ThingInMemory], val niceFunctionProperties: Set[(NiceFunctionProperty, String)], - val labeUseCount: String => Int) { + val labelUseCount: String => Int) { @inline def log: Logger = compilationOptions.log @inline @@ -222,7 +222,7 @@ class AssemblyMatchingContext(val compilationOptions: CompilationOptions, } // if a jump leads inside the block, then it's internal // if a jump leads outside the block, then it's external - jumps == labels && labels.forall(l => labeUseCount(l) <= 1) + jumps == labels && labels.forall(l => labelUseCount(l) <= 1) } def zreg(i: Int): Constant = { @@ -1530,10 +1530,10 @@ case object IsZeroPage extends AssemblyLinePattern { ISC | DCP | LAX | SAX | RLA | RRA | SLO | SRE, AddrMode.Absolute, p, Elidability.Elidable, _) => p match { case NumericConstant(n, _) => n <= 255 - case MemoryAddressConstant(th) => ctx.labelMap.getOrElse(th.name, 0x800) < 0x100 + case MemoryAddressConstant(th) => ctx.labelMap.getOrElse(th.name, 0 -> 0x800)._2 < 0x100 case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), - NumericConstant(n, _)) => ctx.labelMap.getOrElse(th.name, 0x800) + n < 0x100 + NumericConstant(n, _)) => ctx.labelMap.getOrElse(th.name, 0 -> 0x800)._2 + n < 0x100 case _ => false } case _ => false diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index e7d9bece..6078612b 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -110,7 +110,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa callGraph: CallGraph, allocators: Map[String, VariableAllocator], options: CompilationOptions, - onEachVariable: (String, Int) => Unit, + onEachVariable: (String, (Int, Int)) => Unit, pass: Int, forZpOnly: Boolean): Unit = { if (forZpOnly && !options.platform.hasZeroPage) { @@ -155,14 +155,15 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa } } else GlobalVertex val bank = m.bank(options) + val bank0 = mem.banks(bank) m.alloc match { case VariableAllocationMethod.None => Nil case VariableAllocationMethod.Zeropage => if (forZpOnly || !options.platform.hasZeroPage) { val addr = - allocators(bank).allocateBytes(mem.banks(bank), callGraph, vertex, options, m.sizeInBytes, initialized = false, writeable = true, location = AllocationLocation.Zeropage, alignment = m.alignment) - onEachVariable(m.name, addr) + allocators(bank).allocateBytes(bank0, callGraph, vertex, options, m.sizeInBytes, initialized = false, writeable = true, location = AllocationLocation.Zeropage, alignment = m.alignment) + onEachVariable(m.name, bank0.index -> addr) List( ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p) ) @@ -175,10 +176,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa val graveName = m.name.stripPrefix(prefix) + "`" if (forZpOnly) { if (bank == "default") { - allocators(bank).tryAllocateZeropageBytes(mem.banks(bank), callGraph, vertex, options, m.sizeInBytes, alignment = m.alignment) match { + allocators(bank).tryAllocateZeropageBytes(bank0, callGraph, vertex, options, m.sizeInBytes, alignment = m.alignment) match { case None => Nil case Some(addr) => - onEachVariable(m.name, addr) + onEachVariable(m.name, bank0.index -> addr) List( ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p) ) @@ -187,8 +188,8 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa } else if (things.contains(graveName)) { Nil } else { - val addr = allocators(bank).allocateBytes(mem.banks(bank), callGraph, vertex, options, m.sizeInBytes, initialized = false, writeable = true, location = AllocationLocation.Either, alignment = m.alignment) - onEachVariable(m.name, addr) + val addr = allocators(bank).allocateBytes(bank0, callGraph, vertex, options, m.sizeInBytes, initialized = false, writeable = true, location = AllocationLocation.Either, alignment = m.alignment) + onEachVariable(m.name, bank0.index -> addr) List( ConstantThing(graveName, NumericConstant(addr, 2), p) ) diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index 57699a2d..06fc195b 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -16,7 +16,7 @@ import millfork.node.NiceFunctionProperty.IsLeaf * @author Karol Stasiak */ -case class AssemblerOutput(code: Map[String, Array[Byte]], asm: Array[String], labels: List[(String, Int)]) +case class AssemblerOutput(code: Map[String, Array[Byte]], asm: Array[String], labels: List[(String, (Int, Int))]) abstract class AbstractAssembler[T <: AbstractCode](private val program: Program, private val rootEnv: Environment, @@ -30,8 +30,8 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program var initializedVariablesSize: Int = 0 protected val log: Logger = rootEnv.log - val mem = new CompiledMemory(platform.bankNumbers.keys.toList) - val labelMap: mutable.Map[String, Int] = mutable.Map() + val mem = new CompiledMemory(platform.bankNumbers.toList) + val labelMap: mutable.Map[String, (Int, Int)] = mutable.Map() private val bytesToWriteLater = mutable.ListBuffer[(String, Int, Constant)]() private val wordsToWriteLater = mutable.ListBuffer[(String, Int, Constant)]() @@ -97,9 +97,9 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program } case MemoryAddressConstant(th) => try { - if (labelMap.contains(th.name)) return labelMap(th.name) - if (labelMap.contains(th.name + "`")) return labelMap(th.name) - if (labelMap.contains(th.name + ".addr")) return labelMap.getOrElse[Int](th.name, labelMap(th.name + ".array")) + if (labelMap.contains(th.name)) return labelMap(th.name)._2 + if (labelMap.contains(th.name + "`")) return labelMap(th.name)._2 + if (labelMap.contains(th.name + ".addr")) return labelMap.getOrElse[(Int, Int)](th.name, labelMap(th.name + ".array"))._2 val x1 = env.maybeGet[ConstantThing](th.name).map(_.value) val x2 = env.maybeGet[ConstantThing](th.name + "`").map(_.value) val x3 = env.maybeGet[NormalFunction](th.name).flatMap(_.address) @@ -121,7 +121,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program log.fatal("Stack overflow " + c) } case UnexpandedConstant(name, _) => - if (labelMap.contains(name)) labelMap(name) + if (labelMap.contains(name)) labelMap(name)._2 else ??? case SubbyteConstant(cc, i) => deepConstResolve(cc).>>>(i * 8).&(0xff) case CompoundConstant(operator, lc, rc) => @@ -286,7 +286,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program val index = f.address.get.asInstanceOf[NumericConstant].value.toInt compiledFunctions(f.name) match { case NormalCompiledFunction(_, functionCode, _, _) => - labelMap(f.name) = index + labelMap(f.name) = bank0.index -> index val end = outputFunction(bank, functionCode, index, assembly, options) for (i <- index until end) { bank0.occupied(index) = true @@ -308,14 +308,16 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program // already done before case (name, NormalCompiledFunction(bank, functionCode, false, alignment)) => val size = functionCode.map(_.sizeInBytes).sum - val index = codeAllocators(bank).allocateBytes(mem.banks(bank), options, size, initialized = true, writeable = false, location = AllocationLocation.High, alignment = alignment) - labelMap(name) = index + val bank0 = mem.banks(bank) + val index = codeAllocators(bank).allocateBytes(bank0, options, size, initialized = true, writeable = false, location = AllocationLocation.High, alignment = alignment) + labelMap(name) = bank0.index -> index justAfterCode += bank -> outputFunction(bank, functionCode, index, assembly, options) case _ => } sortedCompilerFunctions.foreach { case (name, RedirectedFunction(_, target, offset)) => - labelMap(name) = labelMap(target) + offset + val tuple = labelMap(target) + labelMap(name) = tuple._1 -> (tuple._2 + offset) case _ => } @@ -332,7 +334,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program if (bank != "default") ??? val bank0 = mem.banks(bank) var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size + 1, initialized = true, writeable = false, location = AllocationLocation.High, alignment = m.alignment) - labelMap(name) = index + 1 + labelMap(name) = bank0.index -> (index + 1) val altName = m.name.stripPrefix(env.prefix) + "`" val thing = if (name.endsWith(".addr")) env.get[ThingInMemory](name.stripSuffix(".addr")) else env.get[ThingInMemory](name + ".array") env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer")) @@ -363,7 +365,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program val bank = thing.bank(options) val bank0 = mem.banks(bank) var index = codeAllocators(bank).allocateBytes(bank0, options, items.size, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment) - labelMap(name) = index + labelMap(name) = bank0.index -> index assembly.append("* = $" + index.toHexString) assembly.append(name) for (item <- items) { @@ -385,7 +387,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program val bank = m.bank(options) val bank0 = mem.banks(bank) var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size, initialized = true, writeable = true, location = AllocationLocation.High, alignment = alignment) - labelMap(name) = index + labelMap(name) = bank0.index -> index val altName = m.name.stripPrefix(env.prefix) + "`" env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer")) assembly.append("* = $" + index.toHexString) @@ -414,20 +416,21 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, 2, forZpOnly = false) env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, 3, forZpOnly = false) + val defaultBank = mem.banks("default").index if (platform.freeZpPointers.nonEmpty) { val zpUsageOffset = platform.freeZpPointers.min val zeropageOccupation = zpOccupied.slice(zpUsageOffset, platform.freeZpPointers.max + 2) - labelMap += "__zeropage_usage" -> (zeropageOccupation.lastIndexOf(true) - zeropageOccupation.indexOf(true) + 1) - labelMap += "__zeropage_first" -> (zpUsageOffset + (zeropageOccupation.indexOf(true) max 0)) - labelMap += "__zeropage_last" -> (zpUsageOffset + (zeropageOccupation.lastIndexOf(true) max 0)) - labelMap += "__zeropage_end" -> (zpUsageOffset + zeropageOccupation.lastIndexOf(true) + 1) + labelMap += "__zeropage_usage" -> (defaultBank, zeropageOccupation.lastIndexOf(true) - zeropageOccupation.indexOf(true) + 1) + labelMap += "__zeropage_first" -> (defaultBank, zpUsageOffset + (zeropageOccupation.indexOf(true) max 0)) + labelMap += "__zeropage_last" -> (defaultBank, zpUsageOffset + (zeropageOccupation.lastIndexOf(true) max 0)) + labelMap += "__zeropage_end" -> (defaultBank, zpUsageOffset + zeropageOccupation.lastIndexOf(true) + 1) } else { - labelMap += "__zeropage_usage" -> 0 - labelMap += "__zeropage_first" -> 3 - labelMap += "__zeropage_last" -> 2 - labelMap += "__zeropage_end" -> 3 + labelMap += "__zeropage_usage" -> (defaultBank -> 0) + labelMap += "__zeropage_first" -> (defaultBank -> 3) + labelMap += "__zeropage_last" -> (defaultBank -> 2) + labelMap += "__zeropage_end" -> (defaultBank -> 3) } - labelMap += "__heap_start" -> variableAllocators("default").heapStart + labelMap += "__heap_start" -> (defaultBank -> variableAllocators("default").heapStart) env = rootEnv.allThings @@ -449,10 +452,10 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program mem.banks(bank).end = end } - labelMap.toList.sorted.foreach { case (l, v) => + labelMap.toList.sorted.foreach { case (l, (_, v)) => assembly += f"$l%-30s = $$$v%04X" } - labelMap.toList.sortBy { case (a, b) => b -> a }.foreach { case (l, v) => + labelMap.toList.sortBy { case (a, (_, v)) => v -> a }.foreach { case (l, (_, v)) => assembly += f" ; $$$v%04X = $l%s" } @@ -464,13 +467,13 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program AssemblerOutput(code, assembly.toArray, labelMap.toList) } - def injectLabels(labelMap: Map[String, Int], code: List[T]): List[T] + def injectLabels(labelMap: Map[String, (Int, Int)], code: List[T]): List[T] private def compileFunction(f: NormalFunction, optimizations: Seq[AssemblyOptimization[T]], options: CompilationOptions, inlinedFunctions: Map[String, List[T]], - labelMap: Map[String, Int], + labelMap: Map[String, (Int, Int)], niceFunctionProperties: Set[(NiceFunctionProperty, String)]): List[T] = { log.debug("Compiling: " + f.name, f.position) val unoptimized: List[T] = diff --git a/src/main/scala/millfork/output/CompiledMemory.scala b/src/main/scala/millfork/output/CompiledMemory.scala index 44ee3242..cb105f4e 100644 --- a/src/main/scala/millfork/output/CompiledMemory.scala +++ b/src/main/scala/millfork/output/CompiledMemory.scala @@ -5,12 +5,12 @@ import scala.collection.mutable /** * @author Karol Stasiak */ -class CompiledMemory(bankNames: List[String]) { +class CompiledMemory(bankNames: List[(String, Int)]) { var programName = "MILLFORK" - val banks: mutable.Map[String, MemoryBank] = mutable.Map(bankNames.map(_ -> new MemoryBank): _*) + val banks: mutable.Map[String, MemoryBank] = mutable.Map(bankNames.map(p => p._1 -> new MemoryBank(p._2)): _*) } -class MemoryBank { +class MemoryBank(val index: Int) { def readByte(addr: Int): Int = output(addr) & 0xff def readWord(addr: Int): Int = readByte(addr) + (readByte(addr + 1) << 8) diff --git a/src/main/scala/millfork/output/MosAssembler.scala b/src/main/scala/millfork/output/MosAssembler.scala index 1d313655..19de7b92 100644 --- a/src/main/scala/millfork/output/MosAssembler.scala +++ b/src/main/scala/millfork/output/MosAssembler.scala @@ -39,7 +39,8 @@ class MosAssembler(program: Program, case AssemblyLine0(BYTE, _, _) => log.fatal("BYTE opcode failure") case AssemblyLine0(_, RawByte, _) => log.fatal("BYTE opcode failure") case AssemblyLine0(LABEL, _, MemoryAddressConstant(Label(labelName))) => - labelMap(labelName) = index + val bank0 = mem.banks(bank) + labelMap(labelName) = bank0.index -> index index case AssemblyLine0(_, DoesNotExist, _) => index @@ -66,7 +67,7 @@ class MosAssembler(program: Program, } } - override def injectLabels(labelMap: Map[String, Int], code: List[AssemblyLine]): List[AssemblyLine] = { + override def injectLabels(labelMap: Map[String, (Int, Int)], code: List[AssemblyLine]): List[AssemblyLine] = { import Opcode._ code.map { case l@AssemblyLine(LDA | STA | CMP | @@ -79,10 +80,10 @@ class MosAssembler(program: Program, ISC | DCP | LAX | SAX | RLA | RRA | SLO | SRE, AddrMode.Absolute, p, Elidability.Elidable, _) => p match { case NumericConstant(n, _) => if (n <= 255) l.copy(addrMode = AddrMode.ZeroPage) else l - case MemoryAddressConstant(th) => if (labelMap.getOrElse(th.name, 0x800) < 0x100) l.copy(addrMode = AddrMode.ZeroPage) else l + case MemoryAddressConstant(th) => if (labelMap.getOrElse(th.name, 0 -> 0x800)._2 < 0x100) l.copy(addrMode = AddrMode.ZeroPage) else l case CompoundConstant(MathOperator.Plus, MemoryAddressConstant(th), - NumericConstant(n, _)) => if (labelMap.getOrElse(th.name, 0x800) + n < 0x100) l.copy(addrMode = AddrMode.ZeroPage) else l + NumericConstant(n, _)) => if (labelMap.getOrElse(th.name, 0 -> 0x800)._2 < 0x100) l.copy(addrMode = AddrMode.ZeroPage) else l case _ => l } diff --git a/src/main/scala/millfork/output/Z80Assembler.scala b/src/main/scala/millfork/output/Z80Assembler.scala index 6f062496..5d36cd30 100644 --- a/src/main/scala/millfork/output/Z80Assembler.scala +++ b/src/main/scala/millfork/output/Z80Assembler.scala @@ -73,7 +73,8 @@ class Z80Assembler(program: Program, try { instr match { case ZLine0(LABEL, NoRegisters, MemoryAddressConstant(Label(labelName))) => - labelMap(labelName) = index + val bank0 = mem.banks(bank) + labelMap(labelName) = bank0.index -> index index case ZLine0(BYTE, NoRegisters, param) => writeByte(bank, index, param) @@ -688,7 +689,7 @@ class Z80Assembler(program: Program, } } - override def injectLabels(labelMap: Map[String, Int], code: List[ZLine]): List[ZLine] = code // TODO + override def injectLabels(labelMap: Map[String, (Int, Int)], code: List[ZLine]): List[ZLine] = code // TODO override def quickSimplify(code: List[ZLine]): List[ZLine] = code.map(a => a.copy(parameter = a.parameter.quickSimplify)) diff --git a/src/main/scala/millfork/output/Z80ToX86Crossassembler.scala b/src/main/scala/millfork/output/Z80ToX86Crossassembler.scala index 7c753d51..7f61a0b8 100644 --- a/src/main/scala/millfork/output/Z80ToX86Crossassembler.scala +++ b/src/main/scala/millfork/output/Z80ToX86Crossassembler.scala @@ -22,7 +22,7 @@ class Z80ToX86Crossassembler(program: Program, } else code } - override def injectLabels(labelMap: Map[String, Int], code: List[ZLine]): List[ZLine] = code // TODO + override def injectLabels(labelMap: Map[String, (Int, Int)], code: List[ZLine]): List[ZLine] = code // TODO override def quickSimplify(code: List[ZLine]): List[ZLine] = code.map(a => a.copy(parameter = a.parameter.quickSimplify)) @@ -74,7 +74,8 @@ class Z80ToX86Crossassembler(program: Program, import Z80ToX86Crossassembler._ instr match { case ZLine0(LABEL, NoRegisters, MemoryAddressConstant(Label(labelName))) => - labelMap(labelName) = index + val bank0 = mem.banks(bank) + labelMap(labelName) = bank0.index -> index index case ZLine0(BYTE, NoRegisters, param) => writeByte(bank, index, param) diff --git a/src/test/scala/millfork/test/emu/EmuI86Run.scala b/src/test/scala/millfork/test/emu/EmuI86Run.scala index ee983f26..7fadfcc2 100644 --- a/src/test/scala/millfork/test/emu/EmuI86Run.scala +++ b/src/test/scala/millfork/test/emu/EmuI86Run.scala @@ -74,7 +74,7 @@ class EmuI86Run(nodeOptimizations: List[NodeOptimization], assemblyOptimizations } def apply2(source: String): (Timings, MemoryBank) = { - if (!Settings.enableIntel8086Tests) return Timings(-1, -1) -> new MemoryBank() + if (!Settings.enableIntel8086Tests) return Timings(-1, -1) -> new MemoryBank(0) Console.out.flush() Console.err.flush() val log = TestErrorReporting.log @@ -139,7 +139,7 @@ class EmuI86Run(nodeOptimizations: List[NodeOptimization], assemblyOptimizations println(";;; compiled: -----------------") output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("////; DISCARD_")).foreach(println) println(";;; ---------------------------") - assembler.labelMap.foreach { case (l, addr) => println(f"$l%-15s $$$addr%04x") } + assembler.labelMap.foreach { case (l, (_, addr)) => println(f"$l%-15s $$$addr%04x") } val optimizedSize = assembler.mem.banks("default").initialized.count(identity).toLong if (unoptimizedSize == optimizedSize) { diff --git a/src/test/scala/millfork/test/emu/EmuPlatform.scala b/src/test/scala/millfork/test/emu/EmuPlatform.scala index 925b6211..7abfba3a 100644 --- a/src/test/scala/millfork/test/emu/EmuPlatform.scala +++ b/src/test/scala/millfork/test/emu/EmuPlatform.scala @@ -2,7 +2,7 @@ package millfork.test.emu import millfork.output.{AfterCodeByteAllocator, CurrentBankFragmentOutput, UpwardByteAllocator, VariableAllocator} import millfork.parser.TextCodec -import millfork.{Cpu, CpuFamily, OutputStyle, Platform} +import millfork.{Cpu, CpuFamily, OutputStyle, Platform, ViceDebugOutputFormat} /** * @author Karol Stasiak @@ -29,6 +29,7 @@ object EmuPlatform { false, Map("default" -> 0), "default", + ViceDebugOutputFormat, OutputStyle.Single ) } diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index c0bb3a32..555f273c 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -212,7 +212,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], println(";;; compiled: -----------------") output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("; DISCARD_")).foreach(println) println(";;; ---------------------------") - assembler.labelMap.foreach { case (l, addr) => println(f"$l%-15s $$$addr%04x") } + assembler.labelMap.foreach { case (l, (_, addr)) => println(f"$l%-15s $$$addr%04x") } val optimizedSize = assembler.mem.banks("default").initialized.count(identity).toLong if (unoptimizedSize == optimizedSize) { diff --git a/src/test/scala/millfork/test/emu/EmuZ80Run.scala b/src/test/scala/millfork/test/emu/EmuZ80Run.scala index ae03257d..23e6a6f8 100644 --- a/src/test/scala/millfork/test/emu/EmuZ80Run.scala +++ b/src/test/scala/millfork/test/emu/EmuZ80Run.scala @@ -138,7 +138,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio println(";;; compiled: -----------------") output.asm.takeWhile(s => !(s.startsWith(".") && s.contains("= $"))).filterNot(_.contains("////; DISCARD_")).foreach(println) println(";;; ---------------------------") - assembler.labelMap.foreach { case (l, addr) => println(f"$l%-15s $$$addr%04x") } + assembler.labelMap.foreach { case (l, (_, addr)) => println(f"$l%-15s $$$addr%04x") } val optimizedSize = assembler.mem.banks("default").initialized.count(identity).toLong if (unoptimizedSize == optimizedSize) {