From 8a347e5058f8f706942559231bbc029322b5605f Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Thu, 15 Mar 2018 23:09:19 +0100 Subject: [PATCH] Preliminary segment support; C16/+4 fixes --- CHANGELOG.md | 2 + doc/api/target-platforms.md | 35 ++++-- doc/lang/functions.md | 6 +- doc/lang/syntax.md | 10 +- include/a8.ini | 11 +- include/apple2.ini | 9 +- include/c128.ini | 9 +- include/c16.ini | 8 +- include/c264_hardware.mfk | 2 +- include/c264_ted.mfk | 4 +- include/c64.ini | 17 +-- include/c64_scpu.ini | 9 +- include/c64_scpu16.ini | 8 +- include/pet.ini | 8 +- include/plus4.ini | 8 +- include/vic20.ini | 8 +- include/vic20_3k.ini | 8 +- include/vic20_8k.ini | 8 +- src/main/scala/millfork/Main.scala | 32 +++-- src/main/scala/millfork/Platform.scala | 112 ++++++++++++------ .../compiler/ExpressionCompiler.scala | 2 +- .../millfork/compiler/ReturnDispatch.scala | 9 +- src/main/scala/millfork/env/Environment.scala | 94 +++++++++------ src/main/scala/millfork/env/Thing.scala | 38 +++--- src/main/scala/millfork/node/Node.scala | 3 + .../scala/millfork/output/Assembler.scala | 110 +++++++++-------- .../millfork/output/CompiledMemory.scala | 4 +- .../millfork/output/OutputPackager.scala | 18 +-- src/main/scala/millfork/parser/MfParser.scala | 12 +- .../scala/millfork/test/emu/EmuPlatform.scala | 11 +- src/test/scala/millfork/test/emu/EmuRun.scala | 12 +- 31 files changed, 377 insertions(+), 250 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29031ae6..0b750dff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ * **Breaking change!** Renamed `inline` to `macro`. +* **Breaking change!** Added support for memory segments. Changed the platform definition file syntax. + * 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. diff --git a/doc/api/target-platforms.md b/doc/api/target-platforms.md index 75f2c892..00878a7f 100644 --- a/doc/api/target-platforms.md +++ b/doc/api/target-platforms.md @@ -96,30 +96,49 @@ Every platform is defined in an `.ini` file with an appropriate name. #### `[allocation]` section -* `main_org` – the address for the `main` function; all the other functions will be placed after it - * `zp_pointers` – either a list of comma separated zeropage addresses that can be used by the program as zeropage pointers, or `all` for all. Each value should be the address of the first of two free bytes in the zeropage. -* `himem_style` – not yet supported +* `segments` – a comma-separated list of segment names. +A segment named `default` is always required. +Default: `default`. In all options below, `NAME` refers to a segment name. -* `himem_start` – the first address used for non-zeropage variables, or `after_code` if the variables should be allocated after the code +* `default_code_segment` – the default segment for code and initialized arrays. +Note that the default segment for uninitialized arrays and variables is always `default`. +Default: `default` -* `himem_end` – the last address available for non-zeropage variables +* `segment_NAME_start` – the first address used for automatic allocation in the segment. +Note that the `default` segment shouldn't start before $200, as the $0-$1FF range is reserved for the zeropage and the stack. +The `main` function will be placed as close to the beginning of its segment as possible, but not necessarily at `segment_NAME_start` + +* `segment_NAME_end` – the last address in the segment + +* `segment_NAME_codeend` – the last address in the segment for code and initialized arrays. +Only uninitialized variables are allowed between `segment_NAME_codeend` and `segment_NAME_end`. +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`. #### `[output]` section -* `style` – not yet supported +* `style` – how multi-segment programs should be output: + + * `single` – output a single file, based mostly, but not necessarily only on data in the `default` segment (the default) + + * `per_segment` – generate a separate file with each segment * `format` – output file format; a comma-separated list of tokens: * literal byte values - * `startaddr` – little-endian 16-bit address of the first used byte of the compiled output + * `startaddr` – little-endian 16-bit address of the first used byte of the compiled output (not necessarily the segment start) - * `endaddr` – little-endian 16-bit address of the last used byte of the compiled output + * `endaddr` – little-endian 16-bit address of the last used byte of the compiled output (usually not the segment end) * `allocated` – all used bytes * `:` - inclusive range of bytes + * `::` - inclusive range of bytes in a given segment + * `extension` – target file extension, with or without the dot \ No newline at end of file diff --git a/doc/lang/functions.md b/doc/lang/functions.md index 6b2d231b..afcc8eb7 100644 --- a/doc/lang/functions.md +++ b/doc/lang/functions.md @@ -2,9 +2,11 @@ Syntax: -`[] ( ) [@
] { }` +`[segment ()] [] ( ) [@
] { }` -`asm ( ) @
extern` +`[segment ()] asm ( ) @
extern` + +* ``: segment name; if absent, then defaults to `default_code_segment` as defined for the platform * ``: zero or more of the following: diff --git a/doc/lang/syntax.md b/doc/lang/syntax.md index 9e38f268..ddea4b80 100644 --- a/doc/lang/syntax.md +++ b/doc/lang/syntax.md @@ -18,7 +18,9 @@ or a top level of a function (*local* variables). Syntax: -`[] [@
] [= ]` +`[segment()] [] [@
] [= ]` + +* ``: segment name; if absent, then defaults to `default`. * `` can be only specified for local variables. It can be either `stack`, `static`, `register` or nothing. `register` is only a hint for the optimizer. @@ -44,7 +46,11 @@ An array is a continuous sequence of bytes in memory. Syntax: -`array [[]] [@
] [= ]` +`[segment()] array [[]] [@
] [= ]` + +* ``: segment name; if absent, +then defaults to `default_code_segment` as defined for the platform if the array has initial values, +or to `default` if it doesn't. TODO diff --git a/include/a8.ini b/include/a8.ini index c6d14bd1..75c635fe 100644 --- a/include/a8.ini +++ b/include/a8.ini @@ -4,18 +4,15 @@ modules=a8_kernel,default_panic [allocation] -main_org=$2000 ; TODO zp_pointers=$80,$82,$84,$86,$88,$8a,$8c,$8e,$90,$92,$94,$96,$98,$9a,$9c,$9e,$a0,$a2,$a4 -;TODO -himem_style=per_bank -himem_start=after_code -;TODO -himem_end=$3FFF +segment_default_start=$2000 +; TODO +segment_default_end=$3fff [output] ;TODO -style=per_bank +style=single format=$FF,$FF,$E0,$02,$E1,$02,startaddr,startaddr,endaddr,allocated extension=xex diff --git a/include/apple2.ini b/include/apple2.ini index c8c79426..7079822f 100644 --- a/include/apple2.ini +++ b/include/apple2.ini @@ -4,17 +4,14 @@ modules=apple2_kernel,default_panic [allocation] -main_org=$0C00 ; TODO zp_pointers=6,8,$EB,$ED,$FA,$FC -;TODO -himem_style=per_bank -himem_start=after_code -himem_end=$95FF +segment_default_start=$0C00 +segment_default_end=$95FF [output] ;TODO -style=per_bank +style=single format=allocated extension=a2 diff --git a/include/c128.ini b/include/c128.ini index 277d8151..cc588250 100644 --- a/include/c128.ini +++ b/include/c128.ini @@ -4,16 +4,13 @@ modules=c128_hardware,loader_1c01,c128_kernal,default_panic [allocation] -main_org=$1C0D ; TODO zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$45,$47,$4B -himem_style=per_bank -himem_start=after_code -; TODO -himem_end=$FEFF +segment_default_start=$1C0D +segment_default_end=$FEFF [output] -style=per_bank +style=single format=startaddr,allocated extension=prg diff --git a/include/c16.ini b/include/c16.ini index a5dd1173..40b2011d 100644 --- a/include/c16.ini +++ b/include/c16.ini @@ -4,15 +4,13 @@ modules=loader_1001,c264_kernal,c264_hardware,default_panic [allocation] -main_org=$100D ; TODO zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B -himem_style=per_bank -himem_start=after_code -himem_end=$3FFF +segment_default_start=$100D +segment_default_end=$3FFF [output] -style=per_bank +style=single format=startaddr,allocated extension=prg diff --git a/include/c264_hardware.mfk b/include/c264_hardware.mfk index b83cf6e6..e66fc411 100644 --- a/include/c264_hardware.mfk +++ b/include/c264_hardware.mfk @@ -1 +1 @@ -import c16_ted \ No newline at end of file +import c264_ted \ No newline at end of file diff --git a/include/c264_ted.mfk b/include/c264_ted.mfk index 580b08f9..c41e7692 100644 --- a/include/c264_ted.mfk +++ b/include/c264_ted.mfk @@ -13,8 +13,8 @@ const byte light_red = $32 const byte dark_grey = $21 const byte dark_gray = $21 const byte medium_grey = $31 -const byte medium gray = $31 +const byte medium_gray = $31 const byte light_green = $55 const byte light_blue = $36 const byte light_grey = $41 -const byte light_gray = $41 \ No newline at end of file +const byte light_gray = $41 diff --git a/include/c64.ini b/include/c64.ini index beb062f0..d2aaba0f 100644 --- a/include/c64.ini +++ b/include/c64.ini @@ -7,22 +7,23 @@ arch=nmos ; modules to load modules=c64_hardware,loader_0801,c64_kernal,c64_panic,stdlib ; optionally: default flags -emit_illegals=false +emit_illegals=true [allocation] -; where the main function should be allocated, also the start of bank 0 -main_org=$80D ; list of free zp pointer locations (these assume that some BASIC routines will keep working) zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$45,$47,$4B -; where to allocate non-zp variables -himem_style=per_bank -himem_start=after_code -himem_end=$9FFF +segments=default +default_code_segment=default +segment_default_start=$80D +segment_default_codeend=$9fff +segment_default_datastart=after_code +segment_default_end=$cfff + [output] ; how the banks are laid out in the output files; so far, there is no bank support in the compiler yet -style=per_bank +style=single ; output file format ; startaddr - little-endian address of the first used byte in the bank ; endaddr - little-endian address of the last used byte in the bank diff --git a/include/c64_scpu.ini b/include/c64_scpu.ini index 4029e560..3cc45d25 100644 --- a/include/c64_scpu.ini +++ b/include/c64_scpu.ini @@ -7,14 +7,13 @@ 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 +segment_default_start=$80D +segment_default_codeend=$9fff +segment_default_end=$cfff [output] -style=per_bank +style=single format=startaddr,allocated extension=prg diff --git a/include/c64_scpu16.ini b/include/c64_scpu16.ini index 9a23e400..9370f8b4 100644 --- a/include/c64_scpu16.ini +++ b/include/c64_scpu16.ini @@ -10,12 +10,12 @@ emit_65816=native [allocation] main_org=$811 zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$45,$47,$4B -himem_style=per_bank -himem_start=after_code -himem_end=$9FFF +segment_default_start=$80D +segment_default_codeend=$9fff +segment_default_end=$cfff [output] -style=per_bank +style=single format=startaddr,allocated extension=prg diff --git a/include/pet.ini b/include/pet.ini index 0ca739d8..8bdf03d9 100644 --- a/include/pet.ini +++ b/include/pet.ini @@ -4,15 +4,13 @@ modules=loader_0401,pet_kernal,default_panic [allocation] -main_org=$40D ; TODO zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B -himem_style=per_bank -himem_start=after_code -himem_end=$FFF +segment_default_start=$40D +segment_default_end=$FFF [output] -style=per_bank +style=single format=startaddr,allocated extension=prg diff --git a/include/plus4.ini b/include/plus4.ini index 49f7b415..eced39e2 100644 --- a/include/plus4.ini +++ b/include/plus4.ini @@ -1,15 +1,13 @@ [compilation] arch=nmos -modules=c264_loader,c264_kernal,c264_hardware,default_panic +modules=loader_1001,c264_kernal,c264_hardware,default_panic [allocation] -main_org=$100D ; TODO zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B -himem_style=per_bank -himem_start=after_code -himem_end=$7FFF +segment_default_start=$100D +segment_default_end=$3FFF [output] style=per_bank diff --git a/include/vic20.ini b/include/vic20.ini index c2604ea1..7f08a624 100644 --- a/include/vic20.ini +++ b/include/vic20.ini @@ -4,15 +4,13 @@ modules=loader_1001,vic20_kernal,default_panic [allocation] -main_org=$100D ; TODO zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B -himem_style=per_bank -himem_start=after_code -himem_end=$1CFF +segment_default_start=$100D +segment_default_end=$1CFF [output] -style=per_bank +style=single format=startaddr,allocated extension=prg diff --git a/include/vic20_3k.ini b/include/vic20_3k.ini index 66aee5e1..1a78a95f 100644 --- a/include/vic20_3k.ini +++ b/include/vic20_3k.ini @@ -4,15 +4,13 @@ modules=loader_0401,vic20_kernal,default_panic [allocation] -main_org=$40D ; TODO zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B -himem_style=per_bank -himem_start=after_code -himem_end=$1CFF +segment_default_start=$40D +segment_default_end=$1CFF [output] -style=per_bank +style=single format=startaddr,allocated extension=prg diff --git a/include/vic20_8k.ini b/include/vic20_8k.ini index 9b029e80..4529c0f2 100644 --- a/include/vic20_8k.ini +++ b/include/vic20_8k.ini @@ -4,15 +4,13 @@ modules=loader_1201,vic20_kernal,default_panic [allocation] -main_org=$120D ; TODO zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B -himem_style=per_bank -himem_start=after_code -himem_end=$1FFF +segment_default_start=$120D +segment_default_end=$1FFF [output] -style=per_bank +style=single format=startaddr,allocated extension=prg diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index d3259f8f..3144fe42 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -73,7 +73,16 @@ object Main { val output = c.outputFileName.getOrElse("a") val assOutput = output + ".asm" val labelOutput = output + ".lbl" - val prgOutput = if (!output.endsWith(platform.fileExtension)) output + platform.fileExtension else output +// val prgOutputs = (platform.outputStyle match { +// case OutputStyle.Single => List("default") +// case OutputStyle.PerBank => platform.bankNumbers.keys.toList +// }).map(bankName => bankName -> { +// if (bankName == "default") { +// if (output.endsWith(platform.fileExtension)) output else output + platform.fileExtension +// } else { +// s"${output.stripSuffix(platform.fileExtension)}.$bankName${platform.fileExtension}" +// } +// }).toMap val unoptimized = new SourceLoadingQueue( initialFilenames = c.inputFileNames, @@ -114,7 +123,7 @@ object Main { } // compile - val assembler = new Assembler(program, env) + val assembler = new Assembler(program, env, platform) val result = assembler.assemble(callGraph, assemblyOptimizations, options) ErrorReporting.assertNoErrors("Codegen failed") ErrorReporting.debug(f"Unoptimized code size: ${assembler.unoptimizedCodeSize}%5d B") @@ -140,13 +149,22 @@ object Main { s"al ${a.toHexString} .$normalized" }.mkString("\n").getBytes(StandardCharsets.UTF_8)) } - val path = Paths.get(prgOutput) - ErrorReporting.debug("Writing output to " + path.toAbsolutePath) - ErrorReporting.debug(s"Total output size: ${result.code.length} bytes") - Files.write(path, result.code) + val defaultPrgOutput = if (output.endsWith(platform.fileExtension)) output else output + platform.fileExtension + result.code.foreach{ + case (bankName, code) => + val prgOutput = if (bankName == "default") { + defaultPrgOutput + } else { + s"${output.stripSuffix(platform.fileExtension)}.$bankName${platform.fileExtension}" + } + val path = Paths.get(prgOutput) + ErrorReporting.debug("Writing output to " + path.toAbsolutePath) + ErrorReporting.debug(s"Total output size: ${code.length} bytes") + Files.write(path, code) + } ErrorReporting.debug(s"Total time: ${Math.round((System.nanoTime() - startTime)/1e6)} ms") c.runFileName.foreach(program => - new ProcessBuilder(program, path.toAbsolutePath.toString).start() + new ProcessBuilder(program, Paths.get(defaultPrgOutput).toAbsolutePath.toString).start() ) } diff --git a/src/main/scala/millfork/Platform.scala b/src/main/scala/millfork/Platform.scala index 73a515b8..96357247 100644 --- a/src/main/scala/millfork/Platform.scala +++ b/src/main/scala/millfork/Platform.scala @@ -12,32 +12,25 @@ import org.apache.commons.configuration2.INIConfiguration * @author Karol Stasiak */ +object OutputStyle extends Enumeration { + val Single, PerBank = Value +} + class Platform( val cpu: Cpu.Value, val flagOverrides: Map[CompilationFlag.Value, Boolean], val startingModules: List[String], val outputPackager: OutputPackager, - val codeAllocator: UpwardByteAllocator, - val variableAllocator: VariableAllocator, + val codeAllocators: Map[String, UpwardByteAllocator], + val variableAllocators: Map[String, VariableAllocator], val fileExtension: String, - var defaultCodeBank: Int = 0, + val bankNumbers: Map[String, Int], + val defaultCodeBank: String, + val outputStyle: OutputStyle.Value ) object Platform { - val C64 = new Platform( - Cpu.Mos, - Map(), - List("c64_hardware", "c64_loader"), - SequenceOutput(List(StartAddressOutput, AllocatedDataOutput)), - new UpwardByteAllocator(0x80D, 0xA000), - new VariableAllocator( - List(0xC1, 0xC3, 0xFB, 0xFD, 0x39, 0x3B, 0x3D, 0x43, 0x4B), - new AfterCodeByteAllocator(0xA000) - ), - ".prg" - ) - def lookupPlatformFile(includePath: List[String], platformName: String): Platform = { includePath.foreach { dir => val file = Paths.get(dir, platformName + ".ini").toFile @@ -79,7 +72,7 @@ object Platform { }).toMap ++ CompilationFlag.fromString.flatMap { case (k, f) => val value = cs.get(classOf[String], k, "") value.toLowerCase match { - case "" => None + case "" | null => None case "false" | "off" | "no" | "0" => Some(f -> false) case "true" | "on" | "yes" | "1" => Some(f -> true) case _ => @@ -90,23 +83,66 @@ object Platform { val startingModules = cs.get(classOf[String], "modules", "").split("[, ]+").filter(_.nonEmpty).toList val as = conf.getSection("allocation") - val org = as.get(classOf[String], "main_org", "") match { - case "" => ErrorReporting.fatal(s"Undefined main_org") - case m => parseNumber(m) + + val banks = as.get(classOf[String], "segments", "default").split("[, ]+").filter(_.nonEmpty).toList + if (!banks.contains("default")) { + ErrorReporting.error("A segment named `default` is required") } + if (banks.toSet.size != banks.length) { + ErrorReporting.error("Duplicate segment name") + } + val BankRegex = """\A[A-Za-z0-9_]+\z""".r + banks.foreach { + case BankRegex(_*) => // ok + case b => ErrorReporting.error(s"Invalid segment name: `$b`") + } + + val bankStarts = banks.map(b => b -> (as.get(classOf[String], s"segment_${b}_start") match { + case "" | null => ErrorReporting.error(s"Undefined segment_${b}_start"); 0 + case x => parseNumber(x) + })).toMap + val bankDataStarts = banks.map(b => b -> (as.get(classOf[String], s"segment_${b}_datastart", "after_code") match { + case "" | "after_code" => None + case x => Some(parseNumber(x)) + })).toMap + val bankEnds = banks.map(b => b -> (as.get(classOf[String], s"segment_${b}_end") match { + case "" | null => ErrorReporting.error(s"Undefined segment_${b}_end"); 0xffff + case x => parseNumber(x) + })).toMap + val bankCodeEnds = banks.map(b => b -> (as.get(classOf[String], s"segment_${b}_codeend", "") match { + case "" => bankEnds(b) + case x => parseNumber(x) + })).toMap + val defaultCodeBank = as.get(classOf[String], "default_code_segment") match { + case "" | null => "default" + case x => x + } + // used by 65816: + val bankNumbers = banks.map(b => b -> (as.get(classOf[String], s"segment_${b}_bank", "00") match { + case "" => 0 + case x => parseNumber(x) + })).toMap + + // TODO: validate stuff + banks.foreach(b => { + if (bankNumbers(b) < 0 || bankNumbers(b) > 255) ErrorReporting.error(s"Segment $b has invalid bank") + if (bankStarts(b) >= bankCodeEnds(b)) ErrorReporting.error(s"Segment $b has invalid range") + if (bankCodeEnds(b) > bankEnds(b)) ErrorReporting.error(s"Segment $b has invalid range") + if (bankStarts(b) >= bankEnds(b)) ErrorReporting.error(s"Segment $b has invalid range") + bankDataStarts(b).foreach(dataStarts => if (dataStarts >= bankEnds(b)) ErrorReporting.error(s"Segment $b has invalid range")) + }) + val freePointers = as.get(classOf[String], "zp_pointers", "all") match { case "all" => List.tabulate(128)(_ * 2) case xs => xs.split("[, ]+").map(parseNumber).toList } - val himemEnd = as.get(classOf[String], "himem_end", "") match { - case "" => ErrorReporting.fatal(s"Undefined himem_end") - case end => parseNumber(end) + 1 - } - val byteAllocator = as.get(classOf[String], "himem_start", "") match { - case "" => ErrorReporting.fatal(s"Undefined himem_start") - case "after_code" => new AfterCodeByteAllocator(himemEnd) - case start => new UpwardByteAllocator(parseNumber(start), himemEnd) - } + + val codeAllocators = banks.map(b => b -> new UpwardByteAllocator(bankStarts(b), bankCodeEnds(b))) + val variableAllocators = banks.map(b => b -> new VariableAllocator( + if (b == "default") freePointers else Nil, bankDataStarts(b) match { + case None => new AfterCodeByteAllocator(bankEnds(b)) + case Some(start) => new UpwardByteAllocator(start, bankEnds(b)) + })) val os = conf.getSection("output") val outputPackager = SequenceOutput(os.get(classOf[String], "format", "").split("[, ]+").filter(_.nonEmpty).map { @@ -114,18 +150,26 @@ object Platform { case "endaddr" => EndAddressOutput case "allocated" => AllocatedDataOutput case n => n.split(":").filter(_.nonEmpty) match { - case Array(b, s, e) => BankFragmentOutput(parseNumber(b), parseNumber(s), parseNumber(e)) + case Array(b, s, e) => BankFragmentOutput(b, parseNumber(s), parseNumber(e)) case Array(s, e) => CurrentBankFragmentOutput(parseNumber(s), parseNumber(e)) case Array(b) => ConstOutput(parseNumber(b).toByte) case x => ErrorReporting.fatal(s"Invalid output format: `$x`") } }.toList) - var fileExtension = os.get(classOf[String], "extension", ".bin") + val fileExtension = os.get(classOf[String], "extension", ".bin") + val outputStyle = os.get(classOf[String], "style", "single") match { + case "" | "single" => OutputStyle.Single + case "per_bank" | "per_segment" => OutputStyle.PerBank + case x => ErrorReporting.fatal(s"Invalid output style: `$x`") + } new Platform(cpu, flagOverrides, startingModules, outputPackager, - new UpwardByteAllocator(org, 0xffff), - new VariableAllocator(freePointers, byteAllocator), - if (fileExtension.startsWith(".")) fileExtension else "." + fileExtension) + codeAllocators.toMap, + variableAllocators.toMap, + if (fileExtension.startsWith(".")) fileExtension else "." + fileExtension, + bankNumbers, + defaultCodeBank, + outputStyle) } def parseNumber(s: String): Int = { diff --git a/src/main/scala/millfork/compiler/ExpressionCompiler.scala b/src/main/scala/millfork/compiler/ExpressionCompiler.scala index 53481f28..8a5c0560 100644 --- a/src/main/scala/millfork/compiler/ExpressionCompiler.scala +++ b/src/main/scala/millfork/compiler/ExpressionCompiler.scala @@ -386,7 +386,7 @@ object ExpressionCompiler { def callingContext(ctx: CompilationContext, v: MemoryVariable): CompilationContext = { val result = new Environment(Some(ctx.env), "") - result.registerVariable(VariableDeclarationStatement(v.name, v.typ.name, stack = false, global = false, constant = false, volatile = false, register = false, initialValue = None, address = None), ctx.options) + result.registerVariable(VariableDeclarationStatement(v.name, v.typ.name, stack = false, global = false, constant = false, volatile = false, register = false, initialValue = None, address = None, bank = v.declaredBank), ctx.options) ctx.copy(env = result) } diff --git a/src/main/scala/millfork/compiler/ReturnDispatch.scala b/src/main/scala/millfork/compiler/ReturnDispatch.scala index 90932ad9..841eb489 100644 --- a/src/main/scala/millfork/compiler/ReturnDispatch.scala +++ b/src/main/scala/millfork/compiler/ReturnDispatch.scala @@ -126,7 +126,8 @@ object ReturnDispatch { val paramArrays = stmt.params.indices.map { ix => val a = InitializedArray(label + "$" + ix + ".array", None, (paramMins(ix) to paramMaxes(ix)).map { key => map(key)._2.lift(ix).getOrElse(Constant.Zero) - }.toList) + }.toList, + ctx.function.declaredBank) env.registerUnnamedArray(a) a } @@ -146,7 +147,7 @@ object ReturnDispatch { } if (useJmpaix) { - val jumpTable = InitializedArray(label + "$jt.array", None, (actualMin to actualMax).flatMap(i => List(map(i)._1.loByte, map(i)._1.hiByte)).toList) + val jumpTable = InitializedArray(label + "$jt.array", None, (actualMin to actualMax).flatMap(i => List(map(i)._1.loByte, map(i)._1.hiByte)).toList, ctx.function.declaredBank) env.registerUnnamedArray(jumpTable) if (copyParams.isEmpty) { val loadIndex = ExpressionCompiler.compile(ctx, stmt.indexer, Some(b -> RegisterVariable(Register.A, b)), BranchSpec.None) @@ -161,8 +162,8 @@ object ReturnDispatch { } } else { val loadIndex = ExpressionCompiler.compile(ctx, stmt.indexer, Some(b -> RegisterVariable(Register.X, b)), BranchSpec.None) - val jumpTableLo = InitializedArray(label + "$jl.array", None, (actualMin to actualMax).map(i => (map(i)._1 - 1).loByte).toList) - val jumpTableHi = InitializedArray(label + "$jh.array", None, (actualMin to actualMax).map(i => (map(i)._1 - 1).hiByte).toList) + val jumpTableLo = InitializedArray(label + "$jl.array", None, (actualMin to actualMax).map(i => (map(i)._1 - 1).loByte).toList, ctx.function.declaredBank) + val jumpTableHi = InitializedArray(label + "$jh.array", None, (actualMin to actualMax).map(i => (map(i)._1 - 1).hiByte).toList, ctx.function.declaredBank) env.registerUnnamedArray(jumpTableLo) env.registerUnnamedArray(jumpTableHi) loadIndex ++ copyParams ++ List( diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 1d833813..10d55bed 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -23,7 +23,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { private val relVarId = new AtomicLong def genRelativeVariable(constant: Constant, typ: Type, zeropage: Boolean): RelativeVariable = { - val variable = RelativeVariable(".rv__" + relVarId.incrementAndGet().formatted("%06d"), constant, typ, zeropage = zeropage) + val variable = RelativeVariable(".rv__" + relVarId.incrementAndGet().formatted("%06d"), constant, typ, zeropage = zeropage, declaredBank = None /*TODO*/) addThing(variable, None) variable } @@ -68,19 +68,19 @@ class Environment(val parent: Option[Environment], val prefix: String) { case _ => Nil }.toList - def getAllFixedAddressObjects: List[(Int, Int)] = { + def getAllFixedAddressObjects: List[(String, Int, Int)] = { things.values.flatMap { - case RelativeArray(_, NumericConstant(addr, _), size) => - List(addr.toInt -> size) - case RelativeVariable(_, NumericConstant(addr, _), typ, _) => - List(addr.toInt -> typ.size) + case RelativeArray(_, NumericConstant(addr, _), size, declaredBank) => + List((declaredBank.getOrElse("default"), addr.toInt, size)) + case RelativeVariable(_, NumericConstant(addr, _), typ, _, declaredBank) => + List((declaredBank.getOrElse("default"), addr.toInt, typ.size)) case f: NormalFunction => f.environment.getAllFixedAddressObjects case _ => Nil }.toList } - def allocateVariables(nf: Option[NormalFunction], mem: MemoryBank, callGraph: CallGraph, allocator: VariableAllocator, options: CompilationOptions, onEachVariable: (String, Int) => Unit): Unit = { + def allocateVariables(nf: Option[NormalFunction], mem: CompiledMemory, callGraph: CallGraph, allocators: Map[String, VariableAllocator], options: CompilationOptions, onEachVariable: (String, Int) => Unit): Unit = { val b = get[Type]("byte") val p = get[Type]("pointer") val params = nf.fold(List[String]()) { f => @@ -104,6 +104,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } } } else GlobalVertex + val bank = m.bank(options) m.alloc match { case VariableAllocationMethod.None => Nil @@ -111,7 +112,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { m.sizeInBytes match { case 2 => val addr = - allocator.allocatePointer(mem, callGraph, vertex) + allocators(bank).allocatePointer(mem.banks(bank), callGraph, vertex) onEachVariable(m.name, addr) List( ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p) @@ -125,13 +126,13 @@ class Environment(val parent: Option[Environment], val prefix: String) { case 0 => Nil case 2 => val addr = - allocator.allocateBytes(mem, callGraph, vertex, options, 2, initialized = false, writeable = true) + allocators(bank).allocateBytes(mem.banks(bank), callGraph, vertex, options, 2, initialized = false, writeable = true) onEachVariable(m.name, addr) List( ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p) ) case count => - val addr = allocator.allocateBytes(mem, callGraph, vertex, options, count, initialized = false, writeable = true) + val addr = allocators(bank).allocateBytes(mem.banks(bank), callGraph, vertex, options, count, initialized = false, writeable = true) onEachVariable(m.name, addr) List( ConstantThing(m.name.stripPrefix(prefix) + "`", NumericConstant(addr, 2), p) @@ -139,7 +140,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { } } case f: NormalFunction => - f.environment.allocateVariables(Some(f), mem, callGraph, allocator, options, onEachVariable) + f.environment.allocateVariables(Some(f), mem, callGraph, allocators, options, onEachVariable) Nil case _ => Nil }.toList @@ -200,9 +201,9 @@ class Environment(val parent: Option[Environment], val prefix: String) { InitializedMemoryVariable UninitializedMemoryVariable getArrayOrPointer(name) match { - case th@InitializedArray(_, _, cs) => ConstantPointy(th.toAddress, Some(cs.length)) - case th@UninitializedArray(_, size) => ConstantPointy(th.toAddress, Some(size)) - case th@RelativeArray(_, _, size) => ConstantPointy(th.toAddress, Some(size)) + case th@InitializedArray(_, _, cs, _) => ConstantPointy(th.toAddress, Some(cs.length)) + case th@UninitializedArray(_, size, _) => ConstantPointy(th.toAddress, Some(size)) + case th@RelativeArray(_, _, size, _) => ConstantPointy(th.toAddress, Some(size)) case ConstantThing(_, value, typ) if typ.size <= 2 => ConstantPointy(value, None) case th:VariableInMemory => VariablePointy(th.toAddress) case _ => @@ -480,7 +481,8 @@ class Environment(val parent: Option[Environment], val prefix: String) { resultType, params, addr, - env + env, + stmt.bank ) addThing(mangled, stmt.position) registerAddressConstant(mangled, stmt.position) @@ -498,12 +500,15 @@ class Environment(val parent: Option[Environment], val prefix: String) { } val needsExtraRTS = !stmt.isMacro && !stmt.assembly && (statements.isEmpty || !statements.last.isInstanceOf[ReturnStatement]) if (stmt.isMacro) { + if (stmt.bank.isDefined) { + ErrorReporting.error("Macro functions cannot be in a defined segment", stmt.position) + } val mangled = MacroFunction( name, resultType, params, env, - executableStatements ++ (if (needsExtraRTS) List(AssemblyStatement.implied(Opcode.RTS, elidable = true)) else Nil), + executableStatements ++ (if (needsExtraRTS) List(AssemblyStatement.implied(Opcode.RTS, elidable = true)) else Nil) ) addThing(mangled, stmt.position) } else { @@ -522,7 +527,8 @@ class Environment(val parent: Option[Environment], val prefix: String) { interrupt = stmt.interrupt, kernalInterrupt = stmt.kernalInterrupt, reentrant = stmt.reentrant, - position = stmt.position + position = stmt.position, + declaredBank = stmt.bank ) addThing(mangled, stmt.position) registerAddressConstant(mangled, stmt.position) @@ -544,13 +550,13 @@ class Environment(val parent: Option[Environment], val prefix: String) { stmt.assemblyParamPassingConvention match { case ByVariable(name) => val zp = typ.name == "pointer" // TODO - val v = UninitializedMemoryVariable(prefix + name, typ, if (zp) VariableAllocationMethod.Zeropage else VariableAllocationMethod.Auto) + val v = UninitializedMemoryVariable(prefix + name, typ, if (zp) VariableAllocationMethod.Zeropage else VariableAllocationMethod.Auto, None) addThing(v, stmt.position) registerAddressConstant(v, stmt.position) if (typ.size == 2) { val addr = v.toAddress - addThing(RelativeVariable(v.name + ".hi", addr + 1, b, zeropage = zp), stmt.position) - addThing(RelativeVariable(v.name + ".lo", addr, b, zeropage = zp), stmt.position) + addThing(RelativeVariable(v.name + ".hi", addr + 1, b, zeropage = zp, None), stmt.position) + addThing(RelativeVariable(v.name + ".lo", addr, b, zeropage = zp, None), stmt.position) } case ByRegister(_) => () case ByConstant(name) => @@ -558,10 +564,10 @@ class Environment(val parent: Option[Environment], val prefix: String) { addThing(v, stmt.position) case ByReference(name) => val addr = UnexpandedConstant(prefix + name, typ.size) - val v = RelativeVariable(prefix + name, addr, p, zeropage = false) + val v = RelativeVariable(prefix + name, addr, p, zeropage = false, None) addThing(v, stmt.position) - addThing(RelativeVariable(v.name + ".hi", addr + 1, b, zeropage = false), stmt.position) - addThing(RelativeVariable(v.name + ".lo", addr, b, zeropage = false), stmt.position) + addThing(RelativeVariable(v.name + ".hi", addr + 1, b, zeropage = false, None), stmt.position) + addThing(RelativeVariable(v.name + ".lo", addr, b, zeropage = false, None), stmt.position) } } @@ -589,16 +595,19 @@ class Environment(val parent: Option[Environment], val prefix: String) { case NumericConstant(length, _) => if (length > 0xffff || length < 0) ErrorReporting.error(s"Array `${stmt.name}` has invalid length", stmt.position) val array = address match { - case None => UninitializedArray(stmt.name + ".array", length.toInt) - case Some(aa) => RelativeArray(stmt.name + ".array", aa, length.toInt) + case None => UninitializedArray(stmt.name + ".array", length.toInt, + declaredBank = stmt.bank) + case Some(aa) => RelativeArray(stmt.name + ".array", aa, length.toInt, + declaredBank = stmt.bank) } addThing(array, stmt.position) - registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None), stmt.position) + registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None, stmt.bank), stmt.position) val a = address match { case None => array.toAddress case Some(aa) => aa } - addThing(RelativeVariable(stmt.name + ".first", a, b, zeropage = false), stmt.position) + addThing(RelativeVariable(stmt.name + ".first", a, b, zeropage = false, + declaredBank = stmt.bank), stmt.position) addThing(ConstantThing(stmt.name, a, p), stmt.position) addThing(ConstantThing(stmt.name + ".hi", a.hiByte.quickSimplify, b), stmt.position) addThing(ConstantThing(stmt.name + ".lo", a.loByte.quickSimplify, b), stmt.position) @@ -625,14 +634,17 @@ class Environment(val parent: Option[Environment], val prefix: String) { if (length > 0xffff || length < 0) ErrorReporting.error(s"Array `${stmt.name}` has invalid length", stmt.position) val address = stmt.address.map(a => eval(a).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant address", stmt.position))) val data = contents.map(x => eval(x).getOrElse(Constant.error(s"Array `${stmt.name}` has non-constant contents", stmt.position))) - val array = InitializedArray(stmt.name + ".array", address, data) + val array = InitializedArray(stmt.name + ".array", address, data, + declaredBank = stmt.bank) addThing(array, stmt.position) - registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None), stmt.position) + registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None, + declaredBank = stmt.bank), stmt.position) val a = address match { case None => array.toAddress case Some(aa) => aa } - addThing(RelativeVariable(stmt.name + ".first", a, b, zeropage = false), stmt.position) + addThing(RelativeVariable(stmt.name + ".first", a, b, zeropage = false, + declaredBank = stmt.bank), stmt.position) addThing(ConstantThing(stmt.name, a, p), stmt.position) addThing(ConstantThing(stmt.name + ".hi", a.hiByte.quickSimplify, b), stmt.position) addThing(ConstantThing(stmt.name + ".lo", a.loByte.quickSimplify, b), stmt.position) @@ -704,12 +716,14 @@ class Environment(val parent: Option[Environment], val prefix: String) { if (alloc != VariableAllocationMethod.Static && stmt.initialValue.isDefined) { ErrorReporting.error(s"`$name` cannot be preinitialized`", position) } - val v = stmt.initialValue.fold[MemoryVariable](UninitializedMemoryVariable(prefix + name, typ, alloc)){ive => + val v = stmt.initialValue.fold[MemoryVariable](UninitializedMemoryVariable(prefix + name, typ, alloc, + declaredBank = stmt.bank)){ive => if (options.flags(CompilationFlag.ReadOnlyArrays)) { ErrorReporting.warn("Initialized variable in read-only segment", options, position) } val ivc = eval(ive).getOrElse(Constant.error(s"Initial value of `$name` is not a constant", position)) - InitializedMemoryVariable(name, None, typ, ivc) + InitializedMemoryVariable(name, None, typ, ivc, + declaredBank = stmt.bank) } registerAddressConstant(v, stmt.position) (v, v.toAddress) @@ -719,7 +733,8 @@ class Environment(val parent: Option[Environment], val prefix: String) { case NumericConstant(n, _) => n < 0x100 case _ => false } - val v = RelativeVariable(prefix + name, addr, typ, zeropage = zp) + val v = RelativeVariable(prefix + name, addr, typ, zeropage = zp, + declaredBank = stmt.bank) registerAddressConstant(v, stmt.position) (v, addr) }) @@ -728,8 +743,10 @@ class Environment(val parent: Option[Environment], val prefix: String) { addThing(ConstantThing(v.name + "`", addr, b), stmt.position) } if (typ.size == 2) { - addThing(RelativeVariable(prefix + name + ".hi", addr + 1, b, zeropage = v.zeropage), stmt.position) - addThing(RelativeVariable(prefix + name + ".lo", addr, b, zeropage = v.zeropage), stmt.position) + addThing(RelativeVariable(prefix + name + ".hi", addr + 1, b, zeropage = v.zeropage, + declaredBank = stmt.bank), stmt.position) + addThing(RelativeVariable(prefix + name + ".lo", addr, b, zeropage = v.zeropage, + declaredBank = stmt.bank), stmt.position) } } } @@ -771,7 +788,8 @@ class Environment(val parent: Option[Environment], val prefix: String) { def collectDeclarations(program: Program, options: CompilationOptions): Unit = { if (options.flag(CompilationFlag.OptimizeForSonicSpeed)) { - addThing(InitializedArray("identity$", None, List.tabulate(256)(n => NumericConstant(n, 1))), None) + addThing(InitializedArray("identity$", None, List.tabulate(256)(n => NumericConstant(n, 1)), + declaredBank = None), None) } program.declarations.foreach { case f: FunctionDeclarationStatement => registerFunction(f, options) @@ -782,6 +800,7 @@ class Environment(val parent: Option[Environment], val prefix: String) { if (options.flag(CompilationFlag.ZeropagePseudoregister) && !things.contains("__reg")) { registerVariable(VariableDeclarationStatement( name = "__reg", + bank = None, typ = "pointer", global = true, stack = false, @@ -792,7 +811,8 @@ class Environment(val parent: Option[Environment], val prefix: String) { address = None), options) } if (!things.contains("__constant8")) { - things("__constant8") = InitializedArray("__constant8", None, List(NumericConstant(8, 1))) + things("__constant8") = InitializedArray("__constant8", None, List(NumericConstant(8, 1)), + declaredBank = None) } } diff --git a/src/main/scala/millfork/env/Thing.scala b/src/main/scala/millfork/env/Thing.scala index b9fd0bc4..d8b675d5 100644 --- a/src/main/scala/millfork/env/Thing.scala +++ b/src/main/scala/millfork/env/Thing.scala @@ -81,10 +81,10 @@ sealed trait ThingInMemory extends Thing { def toAddress: Constant var farFlag: Option[Boolean] = None - var declaredBank: Option[Int] = None + val declaredBank: Option[String] def isFar(compilationOptions: CompilationOptions): Boolean - def bank(compilationOptions: CompilationOptions): Int + def bank(compilationOptions: CompilationOptions): String } sealed trait PreallocableThing extends ThingInMemory { @@ -101,8 +101,10 @@ case class Label(name: String) extends ThingInMemory { override def isFar(compilationOptions: CompilationOptions): Boolean = compilationOptions.flag(CompilationFlag.LargeCode) && farFlag.getOrElse(true) - override def bank(compilationOptions: CompilationOptions): Int = + override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse(compilationOptions.platform.defaultCodeBank) + + override val declaredBank: Option[String] = None } sealed trait Variable extends TypedThing with VariableLikeThing @@ -117,8 +119,8 @@ sealed trait VariableInMemory extends Variable with ThingInMemory with Indexable override def isFar(compilationOptions: CompilationOptions): Boolean = !zeropage && farFlag.getOrElse(false) - override def bank(compilationOptions: CompilationOptions): Int = - declaredBank.getOrElse(0) + override def bank(compilationOptions: CompilationOptions): String = + declaredBank.getOrElse("default") } case class RegisterVariable(register: Register.Value, typ: Type) extends Variable { @@ -149,7 +151,7 @@ abstract class MemoryVariable extends VariableInMemory { def alloc: VariableAllocationMethod.Value } -case class UninitializedMemoryVariable(name: String, typ: Type, alloc: VariableAllocationMethod.Value) extends MemoryVariable with UninitializedMemory { +case class UninitializedMemoryVariable(name: String, typ: Type, alloc: VariableAllocationMethod.Value, declaredBank: Option[String]) extends MemoryVariable with UninitializedMemory { override def sizeInBytes: Int = typ.size override def zeropage: Boolean = alloc == VariableAllocationMethod.Zeropage @@ -157,7 +159,7 @@ case class UninitializedMemoryVariable(name: String, typ: Type, alloc: VariableA override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this) } -case class InitializedMemoryVariable(name: String, address: Option[Constant], typ: Type, initialValue: Constant) extends MemoryVariable with PreallocableThing { +case class InitializedMemoryVariable(name: String, address: Option[Constant], typ: Type, initialValue: Constant, declaredBank: Option[String]) extends MemoryVariable with PreallocableThing { override def zeropage: Boolean = false override def toAddress: MemoryAddressConstant = MemoryAddressConstant(this) @@ -169,33 +171,33 @@ case class InitializedMemoryVariable(name: String, address: Option[Constant], ty trait MfArray extends ThingInMemory with IndexableThing -case class UninitializedArray(name: String, sizeInBytes: Int) extends MfArray with UninitializedMemory { +case class UninitializedArray(name: String, sizeInBytes: Int, declaredBank: Option[String]) extends MfArray with UninitializedMemory { 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) + override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse("default") } -case class RelativeArray(name: String, address: Constant, sizeInBytes: Int) extends MfArray { +case class RelativeArray(name: String, address: Constant, sizeInBytes: Int, declaredBank: Option[String]) 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) + override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse("default") } -case class InitializedArray(name: String, address: Option[Constant], contents: List[Constant]) extends MfArray with PreallocableThing { +case class InitializedArray(name: String, address: Option[Constant], contents: List[Constant], declaredBank: Option[String]) 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) + override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse(compilationOptions.platform.defaultCodeBank) } -case class RelativeVariable(name: String, address: Constant, typ: Type, zeropage: Boolean) extends VariableInMemory { +case class RelativeVariable(name: String, address: Constant, typ: Type, zeropage: Boolean, declaredBank: Option[String]) extends VariableInMemory { override def toAddress: Constant = address } @@ -231,7 +233,7 @@ sealed trait FunctionInMemory extends MangledFunction with ThingInMemory { override def isFar(compilationOptions: CompilationOptions): Boolean = compilationOptions.flag(CompilationFlag.LargeCode) && farFlag.getOrElse(true) - override def bank(compilationOptions: CompilationOptions): Int = + override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse(compilationOptions.platform.defaultCodeBank) } @@ -239,7 +241,8 @@ case class ExternFunction(name: String, returnType: Type, params: ParamSignature, address: Constant, - environment: Environment) extends FunctionInMemory { + environment: Environment, + declaredBank: Option[String]) extends FunctionInMemory { override def toAddress: Constant = address override def interrupt = false @@ -255,7 +258,8 @@ case class NormalFunction(name: String, interrupt: Boolean, kernalInterrupt: Boolean, reentrant: Boolean, - position: Option[Position]) extends FunctionInMemory with PreallocableThing { + position: Option[Position], + declaredBank: Option[String]) extends FunctionInMemory with PreallocableThing { override def shouldGenerate = true } diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index 75af0773..0dccd979 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -93,6 +93,7 @@ case class TypeDefinitionStatement(name: String, parent: String) extends Declara case class VariableDeclarationStatement(name: String, typ: String, + bank: Option[String], global: Boolean, stack: Boolean, constant: Boolean, @@ -104,6 +105,7 @@ case class VariableDeclarationStatement(name: String, } case class ArrayDeclarationStatement(name: String, + bank: Option[String], length: Option[Expression], address: Option[Expression], elements: Option[List[Expression]]) extends DeclarationStatement { @@ -120,6 +122,7 @@ case class ImportStatement(filename: String) extends DeclarationStatement { case class FunctionDeclarationStatement(name: String, resultType: String, params: List[ParameterDeclaration], + bank: Option[String], address: Option[Expression], statements: Option[List[Statement]], isMacro: Boolean, diff --git a/src/main/scala/millfork/output/Assembler.scala b/src/main/scala/millfork/output/Assembler.scala index 42ab9fc3..f2a6e2ee 100644 --- a/src/main/scala/millfork/output/Assembler.scala +++ b/src/main/scala/millfork/output/Assembler.scala @@ -6,7 +6,7 @@ import millfork.compiler.{CompilationContext, MfCompiler} import millfork.env._ import millfork.error.ErrorReporting import millfork.node.{CallGraph, Program} -import millfork.{CompilationFlag, CompilationOptions, Tarjan} +import millfork._ import scala.collection.mutable @@ -14,41 +14,41 @@ import scala.collection.mutable * @author Karol Stasiak */ -case class AssemblerOutput(code: Array[Byte], asm: Array[String], labels: List[(String, Int)]) +case class AssemblerOutput(code: Map[String, Array[Byte]], asm: Array[String], labels: List[(String, Int)]) -class Assembler(private val program: Program, private val rootEnv: Environment) { +class Assembler(private val program: Program, private val rootEnv: Environment, private val platform: Platform) { private var env = rootEnv.allThings var unoptimizedCodeSize: Int = 0 var optimizedCodeSize: Int = 0 var initializedVariablesSize: Int = 0 - val mem = new CompiledMemory + val mem = new CompiledMemory(platform.bankNumbers.keys.toList) val labelMap: mutable.Map[String, Int] = mutable.Map() - private val bytesToWriteLater = mutable.ListBuffer[(Int, Int, Constant)]() - private val wordsToWriteLater = mutable.ListBuffer[(Int, Int, Constant)]() + private val bytesToWriteLater = mutable.ListBuffer[(String, Int, Constant)]() + private val wordsToWriteLater = mutable.ListBuffer[(String, Int, Constant)]() - def writeByte(bank: Int, addr: Int, value: Byte): Unit = { + def writeByte(bank: String, addr: Int, value: Byte): Unit = { mem.banks(bank).occupied(addr) = true mem.banks(bank).initialized(addr) = true mem.banks(bank).readable(addr) = true mem.banks(bank).output(addr) = value.toByte } - def writeByte(bank: Int, addr: Int, value: Constant): Unit = { + def writeByte(bank: String, addr: Int, value: Constant): Unit = { mem.banks(bank).occupied(addr) = true mem.banks(bank).initialized(addr) = true mem.banks(bank).readable(addr) = true value match { case NumericConstant(x, _) => - if (x > 0xffff) ErrorReporting.error("Byte overflow") - mem.banks(0).output(addr) = x.toByte + if (x > 0xff) ErrorReporting.error("Byte overflow") + mem.banks(bank).output(addr) = x.toByte case _ => bytesToWriteLater += ((bank, addr, value)) } } - def writeWord(bank: Int, addr: Int, value: Constant): Unit = { + def writeWord(bank: String, addr: Int, value: Constant): Unit = { mem.banks(bank).occupied(addr) = true mem.banks(bank).occupied(addr + 1) = true mem.banks(bank).initialized(addr) = true @@ -202,15 +202,15 @@ class Assembler(private val program: Program, private val rootEnv: Environment) } } - val bank0 = mem.banks(0) - env.allPreallocatables.foreach { - case InitializedArray(name, Some(NumericConstant(address, _)), items) => + case thing@InitializedArray(name, Some(NumericConstant(address, _)), items, _) => + val bank = thing.bank(options) + val bank0 = mem.banks(bank) var index = address.toInt assembly.append("* = $" + index.toHexString) assembly.append(name) for (item <- items) { - writeByte(0, index, item) + writeByte(bank, index, item) bank0.occupied(index) = true bank0.initialized(index) = true bank0.writeable(index) = true @@ -221,13 +221,15 @@ class Assembler(private val program: Program, private val rootEnv: Environment) assembly.append(" !byte " + group.mkString(", ")) } initializedVariablesSize += items.length - case InitializedArray(name, Some(_), items) => ??? + case thing@InitializedArray(name, Some(_), items, _) => ??? case f: NormalFunction if f.address.isDefined => + val bank = f.bank(options) + val bank0 = mem.banks(bank) val index = f.address.get.asInstanceOf[NumericConstant].value.toInt val code = compiledFunctions(f.name) if (code.nonEmpty) { labelMap(f.name) = index - val end = outputFunction(code, index, assembly, options) + val end = outputFunction(bank, code, index, assembly, options) for(i <- index until end) { bank0.occupied(index) = true bank0.initialized(index) = true @@ -237,68 +239,77 @@ class Assembler(private val program: Program, private val rootEnv: Environment) case _ => } - val codeAllocator = new VariableAllocator(Nil, platform.codeAllocator) - var justAfterCode = platform.codeAllocator.startAt + val codeAllocators = platform.codeAllocators.mapValues(new VariableAllocator(Nil, _)) + var justAfterCode = platform.codeAllocators.mapValues(a => a.startAt) env.allPreallocatables.foreach { case f: NormalFunction if f.address.isEmpty && f.name == "main" => + val bank = f.bank(options) + val code = compiledFunctions(f.name) if (code.nonEmpty) { val size = code.map(_.sizeInBytes).sum - val index = codeAllocator.allocateBytes(bank0, options, size, initialized = true, writeable = false) + val index = codeAllocators(bank).allocateBytes(mem.banks(bank), options, size, initialized = true, writeable = false) labelMap(f.name) = index - justAfterCode = outputFunction(code, index, assembly, options) + justAfterCode += bank -> outputFunction(bank, code, index, assembly, options) } case _ => } env.allPreallocatables.foreach { case f: NormalFunction if f.address.isEmpty && f.name != "main" => + val bank = f.bank(options) + val bank0 = mem.banks(bank) val code = compiledFunctions(f.name) if (code.nonEmpty) { val size = code.map(_.sizeInBytes).sum - val index = codeAllocator.allocateBytes(bank0, options, size, initialized = true, writeable = false) + val index = codeAllocators(bank).allocateBytes(bank0, options, size, initialized = true, writeable = false) labelMap(f.name) = index - justAfterCode = outputFunction(code, index, assembly, options) + justAfterCode += bank -> outputFunction(bank, code, index, assembly, options) } case _ => } env.allPreallocatables.foreach { - case InitializedArray(name, None, items) => - var index = codeAllocator.allocateBytes(bank0, options, items.size, initialized = true, writeable = true) + case thing@InitializedArray(name, None, items, _) => + val bank = thing.bank(options) + val bank0 = mem.banks(bank) + var index = codeAllocators(bank).allocateBytes(bank0, options, items.size, initialized = true, writeable = true) labelMap(name) = index assembly.append("* = $" + index.toHexString) assembly.append(name) for (item <- items) { - writeByte(0, index, item) + writeByte(bank, index, item) index += 1 } items.grouped(16).foreach {group => assembly.append(" !byte " + group.mkString(", ")) } initializedVariablesSize += items.length - justAfterCode = index - case m@InitializedMemoryVariable(name, None, typ, value) => - var index = codeAllocator.allocateBytes(bank0, options, typ.size, initialized = true, writeable = true) + justAfterCode += bank -> index + case m@InitializedMemoryVariable(name, None, typ, value, _) => + val bank = m.bank(options) + val bank0 = mem.banks(bank) + var index = codeAllocators(bank).allocateBytes(bank0, options, typ.size, initialized = true, writeable = true) labelMap(name) = index val altName = m.name.stripPrefix(env.prefix) + "`" env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer")) assembly.append("* = $" + index.toHexString) assembly.append(name) for (i <- 0 until typ.size) { - writeByte(0, index, value.subbyte(i)) + writeByte(bank, index, value.subbyte(i)) assembly.append(" !byte " + value.subbyte(i).quickSimplify) index += 1 } initializedVariablesSize += typ.size - justAfterCode = index + justAfterCode += bank -> index case _ => } env.getAllFixedAddressObjects.foreach { - case (addr, size) => + case (bank, addr, size) => + val bank0 = mem.banks(bank) for(i <- 0 until size) bank0.occupied(addr + i) = true } - val variableAllocator = platform.variableAllocator - variableAllocator.notifyAboutEndOfCode(justAfterCode) - env.allocateVariables(None, bank0, callGraph, variableAllocator, options, labelMap.put) + val variableAllocators = platform.variableAllocators + variableAllocators.foreach{case (b,a) => a.notifyAboutEndOfCode(justAfterCode(b))} + env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put) env = rootEnv.allThings @@ -327,7 +338,12 @@ class Assembler(private val program: Program, private val rootEnv: Environment) assembly += f" ; $$$v%04X = $l%s" } - AssemblerOutput(platform.outputPackager.packageOutput(mem, 0), assembly.toArray, labelMap.toList) + // TODO: + val code = (platform.outputStyle match { + case OutputStyle.Single => List("default") + case OutputStyle.PerBank => platform.bankNumbers.keys.toList + }).map(b => b -> platform.outputPackager.packageOutput(mem, b)).toMap + AssemblerOutput(code, assembly.toArray, labelMap.toList) } private def compileFunction(f: NormalFunction, optimizations: Seq[AssemblyOptimization], options: CompilationOptions, inlinedFunctions: Map[String, List[AssemblyLine]]): List[AssemblyLine] = { @@ -363,7 +379,7 @@ class Assembler(private val program: Program, private val rootEnv: Environment) else code } - private def outputFunction(code: List[AssemblyLine], startFrom: Int, assOut: mutable.ArrayBuffer[String], options: CompilationOptions): Int = { + private def outputFunction(bank: String, code: List[AssemblyLine], startFrom: Int, assOut: mutable.ArrayBuffer[String], options: CompilationOptions): Int = { var index = startFrom assOut.append("* = $" + startFrom.toHexString) import millfork.assembly.AddrMode._ @@ -378,24 +394,24 @@ class Assembler(private val program: Program, private val rootEnv: Environment) case AssemblyLine(_, DoesNotExist, _, _) => () case AssemblyLine(op, Implied, _, _) => - writeByte(0, index, Assembler.opcodeFor(op, Implied, options)) + writeByte(bank, index, Assembler.opcodeFor(op, Implied, options)) index += 1 case AssemblyLine(op, Relative, param, _) => - writeByte(0, index, Assembler.opcodeFor(op, Relative, options)) - writeByte(0, index + 1, param - (index + 2)) + writeByte(bank, index, Assembler.opcodeFor(op, Relative, options)) + writeByte(bank, index + 1, param - (index + 2)) index += 2 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) + writeByte(bank, index, Assembler.opcodeFor(op, am, options)) + writeByte(bank, index + 1, param) index += 2 case AssemblyLine(op, am@(WordImmediate | Absolute | AbsoluteY | AbsoluteX | Indirect | AbsoluteIndexedX), param, _) => - writeByte(0, index, Assembler.opcodeFor(op, am, options)) - writeWord(0, index + 1, param) + writeByte(bank, index, Assembler.opcodeFor(op, am, options)) + writeWord(bank, 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)) + writeByte(bank, index, Assembler.opcodeFor(op, am, options)) + writeWord(bank, index + 1, param) + writeByte(bank, index + 3, extractBank(param, options)) index += 4 } } diff --git a/src/main/scala/millfork/output/CompiledMemory.scala b/src/main/scala/millfork/output/CompiledMemory.scala index 5877adf4..93f6a75e 100644 --- a/src/main/scala/millfork/output/CompiledMemory.scala +++ b/src/main/scala/millfork/output/CompiledMemory.scala @@ -5,8 +5,8 @@ import scala.collection.mutable /** * @author Karol Stasiak */ -class CompiledMemory { - val banks = mutable.Map(0 -> new MemoryBank) +class CompiledMemory(bankNames: List[String]) { + val banks = mutable.Map(bankNames.map(_ -> new MemoryBank): _*) } class MemoryBank { diff --git a/src/main/scala/millfork/output/OutputPackager.scala b/src/main/scala/millfork/output/OutputPackager.scala index 997d7881..f8c985f1 100644 --- a/src/main/scala/millfork/output/OutputPackager.scala +++ b/src/main/scala/millfork/output/OutputPackager.scala @@ -6,11 +6,11 @@ import java.io.ByteArrayOutputStream * @author Karol Stasiak */ trait OutputPackager { - def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] } case class SequenceOutput(children: List[OutputPackager]) extends OutputPackager { - def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = { + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { val baos = new ByteArrayOutputStream children.foreach { c => val a = c.packageOutput(mem, bank) @@ -21,39 +21,39 @@ case class SequenceOutput(children: List[OutputPackager]) extends OutputPackager } case class ConstOutput(byte: Byte) extends OutputPackager { - def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = Array(byte) + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = Array(byte) } case class CurrentBankFragmentOutput(start: Int, end: Int) extends OutputPackager { - def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = { + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { val b = mem.banks(bank) b.output.slice(start, end + 1) } } -case class BankFragmentOutput(alwaysBank: Int, start: Int, end: Int) extends OutputPackager { - def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = { +case class BankFragmentOutput(alwaysBank: String, start: Int, end: Int) extends OutputPackager { + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { val b = mem.banks(alwaysBank) b.output.slice(start, end + 1) } } object StartAddressOutput extends OutputPackager { - def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = { + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { val b = mem.banks(bank) Array(b.start.toByte, b.start.>>(8).toByte) } } object EndAddressOutput extends OutputPackager { - def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = { + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { val b = mem.banks(bank) Array(b.end.toByte, b.end.>>(8).toByte) } } object AllocatedDataOutput extends OutputPackager { - def packageOutput(mem: CompiledMemory, bank: Int): Array[Byte] = { + def packageOutput(mem: CompiledMemory, bank: String): Array[Byte] = { val b = mem.banks(bank) b.output.slice(b.start, b.end + 1) } diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index e720e808..b48a0ef0 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -140,6 +140,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o def variableDefinition(implicitlyGlobal: Boolean): P[Seq[DeclarationStatement]] = for { p <- position() + bank <- bankDeclaration flags <- flags("const", "static", "volatile", "stack", "register") ~ HWS typ <- identifier ~ SWS name <- identifier ~/ HWS ~/ Pass @@ -148,6 +149,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o _ <- &(EOL) ~/ "" } yield { Seq(VariableDeclarationStatement(name, typ, + bank, global = implicitlyGlobal || flags("static"), stack = flags("stack"), constant = flags("const"), @@ -229,11 +231,12 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o def arrayDefinition: P[Seq[ArrayDeclarationStatement]] = for { p <- position() + bank <- bankDeclaration name <- "array" ~ !letterOrDigit ~/ SWS ~ identifier ~ HWS length <- ("[" ~/ AWS ~/ mlExpression(nonStatementLevel) ~ AWS ~ "]").? ~ HWS addr <- ("@" ~/ HWS ~/ mlExpression(1)).? ~/ HWS contents <- ("=" ~/ HWS ~/ arrayContents).? ~/ HWS - } yield Seq(ArrayDeclarationStatement(name, length, addr, contents).pos(p)) + } yield Seq(ArrayDeclarationStatement(name, bank, length, addr, contents).pos(p)) def tightMlExpression: P[Expression] = P(mlParenExpr | functionCall | mlIndexedExpression | atom) // TODO @@ -463,8 +466,14 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mlExpression(nonStatementLevel) } yield Seq(DoWhileStatement(body.toList, Nil, condition)) + + + + def bankDeclaration: P[Option[String]] = ("segment" ~/ AWS ~/ "(" ~/ AWS ~/ identifier ~/ AWS ~/ ")" ~/ AWS).? + def functionDefinition: P[Seq[DeclarationStatement]] = for { p <- position() + bank <- bankDeclaration flags <- flags("asm", "inline", "interrupt", "macro", "noinline", "reentrant", "kernal_interrupt") ~ HWS returnType <- identifier ~ SWS name <- identifier ~ HWS @@ -510,6 +519,7 @@ case class MfParser(filename: String, input: String, currentDirectory: String, o case None => () } Seq(FunctionDeclarationStatement(name, returnType, params.toList, + bank, addr, statements, flags("macro"), diff --git a/src/test/scala/millfork/test/emu/EmuPlatform.scala b/src/test/scala/millfork/test/emu/EmuPlatform.scala index 6e0083a9..5dbaebc9 100644 --- a/src/test/scala/millfork/test/emu/EmuPlatform.scala +++ b/src/test/scala/millfork/test/emu/EmuPlatform.scala @@ -1,7 +1,7 @@ package millfork.test.emu import millfork.output.{AfterCodeByteAllocator, CurrentBankFragmentOutput, UpwardByteAllocator, VariableAllocator} -import millfork.{Cpu, Platform} +import millfork.{Cpu, OutputStyle, Platform} /** * @author Karol Stasiak @@ -12,8 +12,11 @@ object EmuPlatform { Map(), Nil, CurrentBankFragmentOutput(0, 0xffff), - new UpwardByteAllocator(0x200, 0xb000), - new VariableAllocator((0 until 256 by 2).toList, new AfterCodeByteAllocator(0xff00)), - ".bin" + Map("default" -> new UpwardByteAllocator(0x200, 0xb000)), + Map("default" -> new VariableAllocator((0 until 256 by 2).toList, new AfterCodeByteAllocator(0xff00))), + ".bin", + Map("default" -> 0), + "default", + OutputStyle.Single ) } diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index c018e51d..ea115812 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -147,14 +147,14 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], // compile val env2 = new Environment(None, "") env2.collectDeclarations(program, options) - val assembler = new Assembler(program, env2) + val assembler = new Assembler(program, env2, platform) val output = assembler.assemble(callGraph, assemblyOptimizations, options) 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") } - val optimizedSize = assembler.mem.banks(0).initialized.count(identity).toLong + val optimizedSize = assembler.mem.banks("default").initialized.count(identity).toLong if (unoptimizedSize == optimizedSize) { println(f"Size: $unoptimizedSize%5d B") } else { @@ -165,7 +165,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], ErrorReporting.assertNoErrors("Code generation failed") - val memoryBank = assembler.mem.banks(0) + val memoryBank = assembler.mem.banks("default") if (source.contains("return [")) { for (_ <- 0 until 10; i <- 0xfffe.to(0, -1)) { if (memoryBank.readable(i)) memoryBank.readable(i + 1) = true @@ -173,14 +173,14 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], } platform.cpu match { case millfork.Cpu.Cmos => - runViaSymon(memoryBank, platform.codeAllocator.startAt, CpuBehavior.CMOS_6502) + runViaSymon(memoryBank, platform.codeAllocators("default").startAt, CpuBehavior.CMOS_6502) case millfork.Cpu.Ricoh => - runViaHalfnes(memoryBank, platform.codeAllocator.startAt) + runViaHalfnes(memoryBank, platform.codeAllocators("default").startAt) case millfork.Cpu.Mos => ErrorReporting.fatal("There's no NMOS emulator with decimal mode support") Timings(-1, -1) -> memoryBank case millfork.Cpu.StrictMos | millfork.Cpu.StrictRicoh => - runViaSymon(memoryBank, platform.codeAllocator.startAt, CpuBehavior.NMOS_6502) + runViaSymon(memoryBank, platform.codeAllocators("default").startAt, CpuBehavior.NMOS_6502) case _ => ErrorReporting.trace("No emulation support for " + platform.cpu) Timings(-1, -1) -> memoryBank