1
0
mirror of https://github.com/KarolS/millfork.git synced 2025-01-26 05:30:18 +00:00

Allow initializing writable variables on cartridge targets

This commit is contained in:
Karol Stasiak 2019-06-28 16:28:49 +02:00
parent 7f28a6b10f
commit 674f8d1983
22 changed files with 436 additions and 107 deletions

View File

@ -67,8 +67,6 @@ Default: the same as `encoding`.
* `emit_z80` whether the compiler should emit Zilog Z80 instructions not covered by `emit_x80`, default is `true` on compatible processors and `false` elsewhere
* `ro_arrays` (deprecated) whether the compiler should warn upon array writes, default is `false`
* `prevent_jmp_indirect_bug` whether the compiler should try to avoid the indirect JMP bug,
default is `false` on 65C02-compatible or non-6502 processors and `true` elsewhere
@ -132,6 +130,10 @@ Default: `default`. In all options below, `NAME` refers to a segment name.
Note that the default segment for uninitialized arrays and variables is always `default`.
Default: `default`
* `ram_init_segment` the segment storing a copy of initial values for preinitialized writable arrays and variables.
The segment cannot be `default`. See [the ROM vs RAM guide](./rom-vs-ram.md) for more information.
Default: none.
* `segment_NAME_start` the first address used for automatic allocation in the segment.
Note that on 6502-like targets, 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`

49
docs/api/rom-vs-ram.md Normal file
View File

@ -0,0 +1,49 @@
[< back to index](../index.md)
### ROM vs RAM targets
By default, Millfork assumes that the target platform loads the program into RAM.
Code, read-only data and preinitialized writable data are all mixed together.
The program after loading can modify its contents, including preinitialized variables,
almost immediately and without any extra preparation.
When compiling for a cartridge based target, the preinitialized data cannot be writable,
as they are not loaded into RAM, but stored in ROM.
To make working with preinitialized data easier,
Millfork can create a copy of preinitialized writable date in ROM,
which can then be loaded into RAM when the program starts.
The steps are as follows:
* Add the `ram_init_segment=SEGMENT` option to the `[allocation]` section in your platform definition file,
where `SEGMENT` is the ROM segment that will store the initial values.
* Near the beginning of your program, call the `init_rw_memory` function.
It is imported automatically, you don't need to add any import statements.
If you are targeting both RAM-based and ROM-based platforms, wrap the call in `#if INIT_RW_MEMORY`...`#endif`,
as the `init_rw_memory` function is not available for RAM-based targets. For example:
void main() {
#if INIT_RW_MEMORY
// do a bankswitch to the SEGMENT segment if applicable
init_rw_memory()
#endif
// ... rest of the code
}
If the default implementation of `init_rw_memory` is unsatisfactory for your needs,
consider implementing your own and putting it in the `init_rw_memory` module
in the same directory as your main source file, to override the standard one.
Using the `ram_init_segment` option adds the following restrictions:
* Preinitialized writable variables and arrays can be put only in the `default` segment.
* Preinitialized writable variables and arrays cannot be given a fixed address.
* On 6502-based targets, the zeropage pseudoregister has to have size of at least 4,
unless the size of the `default` segment is 256 bytes or less (out of currently supported targets, only Atari 2600 has such small RAM),
which uses a different, smaller and faster implementation of `init_rw_memory` that doesn't require a zeropage pseudoregister.
You can force the compiler to use this smaller implementation by adding a preprocessor feature `TINY_RW_MEMORY = 1`.
It works only if the total size of writable memory to initialize is 255 bytes or less.

View File

@ -9,6 +9,9 @@ but it may be expanded to support other 6502-based and Z80-based platforms in th
To add a custom platform yourself, see [the custom platform adding guide](./custom-platform.md).
If you are compiling for a cartridge-based target,
you need to take special precautions; see [the ROM vs RAM guide](./rom-vs-ram.md)
## Supported platforms
The following platforms are currently supported:
@ -92,6 +95,3 @@ The compiler emits COM files.
* `dos_com` a COM file for DOS on IBM PC. (very experimental)
The primary and most tested platform is Commodore 64.
Currently, targets that assume that the program will be loaded from disk or tape are better tested.
Cartridge targets may exhibit unexpected bugs.

View File

@ -54,6 +54,9 @@ The following features are defined based on the chosen CPU and compilation optio
`CPUFEATURE_8080`, `CPUFEATURE_8085`, `CPUFEATURE_GAMEBOY`, `CPUFEATURE_Z80`,
`CPUFEATURE_6502_ILLEGALS`, `CPUFEATURE_8085_ILLEGALS`, `CPUFEATURE_Z80_ILLEGALS` 1 if given instruction subset is enabled, 0 otherwise
* `INIT_RW_MEMORY` 1 if the option `ram_init_segment` is defined, 0 otherwise.
See [the ROM vs RAM guide](../api/rom-vs-ram.md) for more information.
* `OPTIMIZE_FOR_SIZE`, `OPTIMIZE_FOR_SPEED`, `OPTIMIZE_INLINE`, `OPTIMIZE_IPO`
1 if given optimization setting is enabled, 0 otherwise
@ -63,6 +66,8 @@ The following features are defined based on the chosen CPU and compilation optio
* `ZPREG_SIZE` size of the pseudoregister in bytes, or 0 on platforms that don't use it
* `TINY_MAIN_RAM` 1 if the main ram is 256 bytes or less, 0 otherwise
* `USES_IX_STACK`, `USES_IY_STACK` 1 if given index register is used as a base pointer for stack-allocated variables, 0 otherwise
* `USES_SHADOW_REGISTERS` 1 if interrupts preserve old registers in the shadow registers, 0 if they do it on stack

View File

@ -4,6 +4,16 @@ Definitions on the following list are frequently provided by the default automat
However, as they are not the part of the standard library, they might not be available on all targets:
#### `void init_rw_memory()`
Initializes all writable arrays and variables with their initial values.
If the preprocessor feature `INIT_RW_MEMORY` is defined and non-zero,
then `init_rw_memory` is available and should be called before accessing any preinitialized writable object.
If the preprocessor feature `INIT_RW_MEMORY` is not defined or is zero,
then `init_rw_memory` is not available.
#### `void putchar(byte char)`
Prints a single character.

View File

@ -3,16 +3,20 @@
import nes_joy1_default
volatile byte initialized
void main() {
init_graphics()
init_input()
init_sound()
ppu_ctrl = %10001000
ppu_mask = %00011110
initialized = 1
while(true){}
}
void nmi() {
if initialized != 1 { return }
scroll_screen()
update_sprite()
react_to_input()
@ -24,7 +28,7 @@ void irq() {
array oam_buffer [256] @$200
byte scroll
byte a
byte a // debouncing
sbyte dx
void init_graphics() {
@ -72,13 +76,13 @@ void load_name_tables() {
p = bg.addr
ppu_set_addr($2400)
for page_count,0,until,4 {
for b,0,until,255 {
for b,0,to,255 {
ppu_write_data(p[b])
}
p.hi += 1
}
for page_count,0,until,4 {
for b,0,until,255 {
for b,0,to,255 {
ppu_write_data(0)
}
}
@ -103,8 +107,11 @@ void update_sprite() {
void react_to_input() {
read_joy()
if input_a != 0 { // A button
reverse_dx()
if a == 0 {
reverse_dx()
}
}
a = input_a
if input_dy < 0 { // Up button
if oam_buffer[0] > 7 { oam_buffer[0] -= 1}
}

View File

@ -1,16 +1,24 @@
// Based upon the example code from NES 101 tutorial by Michael Martin
// compile with -t nes_mmc4
import nes_joy
volatile byte initialized
void main() {
initialized = 0
set_prg_bank(0)
init_rw_memory()
init_graphics()
init_input()
init_sound()
ppu_ctrl = %10001000
ppu_mask = %00011110
initialized = 1
while(true){}
}
void nmi() {
if initialized != 1 { return }
set_prg_bank(1)
scroll_screen()
set_prg_bank(2)
@ -24,22 +32,18 @@ void irq() {
}
array oam_buffer [256] @$200
byte scroll
byte a
byte scroll = 240
byte a = 0 // debouncing
segment(ram) sbyte dx
void init_graphics() {
set_horizontal_mirroring()
set_chr_bank0($06)
set_chr_bank1($1A)
init_sprites()
set_prg_bank(6)
load_palette()
load_name_tables()
init_scrolling()
}
void init_input() {
a = 0
}
void init_sound() {
@ -78,22 +82,18 @@ noinline void load_name_tables() {
p = bg.addr
ppu_set_addr($2400)
for page_count,0,until,4 {
for b,0,until,255 {
for b,0,to,255 {
ppu_write_data(p[b])
}
p.hi += 1
}
for page_count,0,until,4 {
for b,0,until,255 {
for b,0,to,255 {
ppu_write_data(0)
}
}
}
void init_scrolling() {
scroll = 240
}
segment(prgrom2)
noinline void update_sprite() {
ppu_oam_dma_write(oam_buffer.addr.hi)
@ -109,17 +109,17 @@ noinline void update_sprite() {
segment(prgrom3)
noinline void react_to_input() {
strobe_joypad()
if read_joypad1() & 1 != 0 { // A button
reverse_dx()
read_joy1()
if input_a != 0 { // A button
if a == 0 {
reverse_dx()
}
}
read_joypad1() // B button
read_joypad1() // Select button
read_joypad1() // Start button
if read_joypad1() & 1 != 0 { // Up button
a = input_a
if input_dy < 0 { // Up button
if oam_buffer[0] > 7 { oam_buffer[0] -= 1}
}
if read_joypad1() & 1 != 0 { // Down button
if input_dy > 0 { // Down button
if oam_buffer[0] < 223 { oam_buffer[0] += 1}
}
}

View File

@ -6,13 +6,14 @@ arch=nmos
encoding=petscii
screen_encoding=petscr
modules=c64_hardware,loader_c64crt,c64_kernal,c64_panic,stdlib
ro_arrays=true
[allocation]
zp_pointers=2-$ff
segments=default,prgrom
default_code_segment=prgrom
ram_init_segment=prgrom
segment_default_start=$800
segment_default_end=$7fff
segment_prgrom_start=$8000

View File

@ -6,12 +6,13 @@ arch=nmos
encoding=petscii
screen_encoding=petscr
modules=c64_hardware,loader_c64crt,c64_kernal,c64_panic,stdlib
ro_arrays=true
[allocation]
zp_bytes=2-$ff
segments=default,prgrom
ram_init_segment=prgrom
default_code_segment=prgrom
segment_default_start=$800
segment_default_end=$7fff

View File

@ -9,6 +9,7 @@ modules=default_panic,stdlib,gb_hardware,gb_header_small
segments=default,rom,hiram
default_code_segment=rom
ram_init_segment=rom
segment_default_start=$c000
segment_default_end=$dfff

118
include/init_rw_memory.mfk Normal file
View File

@ -0,0 +1,118 @@
#if not(INIT_RW_MEMORY)
#error The init_rw_memory module cannot be used by the current target
#endif
#if ARCH_6502
#if TINY_RW_MEMORY
noinline asm void init_rw_memory() {
ldx #__rwdata_size.lo // can't be more than $00FC
beq __init_rw_memory__skip3
+ memory_barrier()
__init_rw_memory__loop3:
lda lo(__rwdata_init_start - 1),x
sta lo(__rwdata_start - 1),x
dex
bne __init_rw_memory__loop3
+ memory_barrier()
__init_rw_memory__skip3:
rts
}
#else
#if ZPREG_SIZE < 4
#error The init_rw_module requires at least 4 bytes of zeropage pseudoregister
#endif
noinline asm void init_rw_memory() {
lda #__rwdata_size.hi
ora #__rwdata_size.lo
beq __init_rw_memory__skip3
+ memory_barrier()
ldx #__rwdata_size.hi
beq __init_rw_memory__skip1
lda #__rwdata_init_start.lo
sta lo(__reg)
lda #__rwdata_init_start.hi
sta lo(__reg + 1)
lda #__rwdata_start.lo
sta lo(__reg + 2)
lda #__rwdata_start.hi
sta lo(__reg + 3)
__init_rw_memory__loop1:
ldy #0
__init_rw_memory__loop2:
lda (lo(__reg)),y
sta (lo(__reg + 2)),y
dey
bne __init_rw_memory__loop2
inc lo(__reg + 1)
inc lo(__reg + 3)
dex
bne __init_rw_memory__loop1
__init_rw_memory__skip1:
ldx #__rwdata_size.lo
beq __init_rw_memory__skip3
__init_rw_memory__loop3:
lda __rwdata_init_start + (__rwdata_size & $ff00) - 1,x
sta __rwdata_start + (__rwdata_size & $ff00) - 1,x
dex
bne __init_rw_memory__loop3
__init_rw_memory__skip3:
+ memory_barrier()
rts
}
#endif
#elseif CPUFEATURE_Z80
#pragma zilog_syntax
noinline asm void init_rw_memory() {
ld a,b
or c
ret z
+ memory_barrier()
ld hl,__rwdata_init_start
ld de,__rwdata_start
ld bc,__rwdata_size
ldir
+ memory_barrier()
ret
}
#elseif ARCH_I80
#pragma zilog_syntax
noinline asm void init_rw_memory() {
ld a,b
or c
ret z
+ memory_barrier()
ld bc,__rwdata_size
ld hl,__rwdata_init_start
ld de,__rwdata_start
__init_rw_memory__loop1:
? ld a,(hl)
? inc hl
ld (de),a
inc de
dec bc
? jp nz,__init_rw_memory__loop1
+ memory_barrier()
ret
}
#else
#error Unsupported architecture for init_rw_memory
#endif

View File

@ -1,8 +1,8 @@
; a NES MMC4 cartridge, uses iNES mapper 10
; 8×16 kB PRGROM and 32×4kB CHRROM
; $c000$ffff is assumed to be fixed to prgrom7, $8000-$bfff can be switched
; uses horizontal mirroring:
; - to use vertical mirroring, change byte #6 of the header from $A0 to $A1
; the initial values for variables are stored in prgrom0
; has switchable mirroring; call set_vertical_mirroring() or set_horizontal_mirroring() to switch
; uses extra 8K of RAM at $6000$7fff that is not battery-backed:
; - to disable it, change byte #10 of the header from $07 to 0 and remove the ram segment
; - to make it battery-backed, change byte #10 of the header from $07 to $77
@ -11,13 +11,13 @@
[compilation]
arch=ricoh
modules=nes_hardware,nes_routines,default_panic,nes_mmc4,stdlib
ro_arrays=true
[allocation]
zp_bytes=all
segments=default,ram,prgrom0,prgrom1,prgrom2,prgrom3,prgrom4,prgrom5,prgrom6,prgrom7,chrrom0,chrrom1
default_code_segment=prgrom7
ram_init_segment=prgrom0
segment_default_start=$200
segment_default_end=$7ff

View File

@ -7,13 +7,13 @@
[compilation]
arch=ricoh
modules=nes_hardware,nes_routines,default_panic,stdlib
ro_arrays=true
[allocation]
zp_bytes=all
segments=default,prgrom,chrrom
default_code_segment=prgrom
ram_init_segment=prgrom
segment_default_start=$200
segment_default_end=$7ff

View File

@ -4,7 +4,6 @@
[compilation]
arch=nmos
modules=vcs_hardware,default_panic,stdlib
ro_arrays=true
; use -fzp-register to override this:
zeropage_register=false
@ -13,6 +12,8 @@ zeropage_register=false
zp_bytes=$80-$a5
segments=default,prgrom
default_code_segment=prgrom
ram_init_segment=prgrom
; last 16 bytes are left for stack
segment_default_start=$80

View File

@ -6,13 +6,14 @@ arch=nmos
encoding=petscii
screen_encoding=petscr
modules=vic20_kernal,default_panic,stdlib,vic20_hardware,loader_a000
ro_arrays=true
[allocation]
zp_pointers=$C1,$C3,$FB,$FD,$39,$3B,$3D,$43,$4B
segments=default,prgrom
default_code_segment=prgrom
ram_init_segment=prgrom
segment_default_start=$1000
segment_default_end=$1fff
segment_prgrom_start=$a000

View File

@ -213,8 +213,10 @@ case class CompilationOptions(platform: Platform,
"CPUFEATURE_6502_ILLEGALS" -> toLong(platform.cpuFamily == CpuFamily.M6502 && flag(CompilationFlag.EmitIllegals)),
"CPUFEATURE_Z80_ILLEGALS" -> toLong(flag(CompilationFlag.EmitZ80Opcodes) && flag(CompilationFlag.EmitIllegals)),
"CPUFEATURE_8085_ILLEGALS" -> toLong(flag(CompilationFlag.EmitIntel8080Opcodes) && flag(CompilationFlag.EmitIllegals)),
"INIT_RW_MEMORY" -> toLong(platform.ramInitialValuesBank.isDefined),
"SYNTAX_INTEL" -> toLong(platform.cpuFamily == CpuFamily.I80 && flag(CompilationFlag.UseIntelSyntaxForInput)),
"SYNTAX_ZILOG" -> toLong(platform.cpuFamily == CpuFamily.I80 && !flag(CompilationFlag.UseIntelSyntaxForInput)),
"TINY_RW_MEMORY" -> toLong(platform.variableAllocators("default").totalHimemSize <= 256),
"USES_ZPREG" -> toLong(platform.cpuFamily == CpuFamily.M6502 && zpRegisterSize > 0),
"USES_IX_STACK" -> toLong(flag(CompilationFlag.UseIxForStack)),
"USES_IY_STACK" -> toLong(flag(CompilationFlag.UseIyForStack)),
@ -465,7 +467,7 @@ object Cpu extends Enumeration {
object CompilationFlag extends Enumeration {
val
// common compilation options:
EmitIllegals, DecimalMode, ReadOnlyArrays, LenientTextEncoding, LineNumbersInAssembly, SourceInAssembly,
EmitIllegals, DecimalMode, LenientTextEncoding, LineNumbersInAssembly, SourceInAssembly,
// compilation options for MOS:
EmitCmosOpcodes, EmitCmosNopOpcodes, EmitHudsonOpcodes, Emit65CE02Opcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes,
PreventJmpIndirectBug, LargeCode, ReturnWordsViaAccumulator, SoftwareStack,
@ -526,7 +528,6 @@ object CompilationFlag extends Enumeration {
"inline" -> InlineFunctions,
"dangerous_optimizations" -> DangerousOptimizations,
"decimal_mode" -> DecimalMode,
"ro_arrays" -> ReadOnlyArrays,
"ror_warn" -> RorWarning,
"prevent_jmp_indirect_bug" -> PreventJmpIndirectBug,
"compact_dispatch_params" -> CompactReturnDispatchParams,

View File

@ -35,6 +35,7 @@ class Platform(
val generateGameBoyChecksums: Boolean,
val bankNumbers: Map[String, Int],
val defaultCodeBank: String,
val ramInitialValuesBank: Option[String],
val outputLabelsFormat: DebugOutputFormat,
val outputStyle: OutputStyle.Value
) {
@ -151,6 +152,20 @@ object Platform {
case "" | null => "default"
case x => x
}
val ramInitialValuesBank = as.get(classOf[String], "ram_init_segment") match {
case "" | null => None
case "default" =>
log.error("Cannot use default as ram_init_segment")
None
case x if banks.contains(x) =>
if (CpuFamily.forType(cpu) == CpuFamily.M6502 && zpRegisterSize < 4) {
log.error("Using ram_init_segment requires zeropage register of size at least 4")
}
Some(x)
case x =>
log.error("Invalid ram_init_segment: " + x)
None
}
// used by 65816:
val bankNumbers = banks.map(b => b -> (as.get(classOf[String], s"segment_${b}_bank", "00") match {
case "" => 0
@ -236,10 +251,15 @@ object Platform {
k -> value
}.toMap
var actualStartingModules = startingModules
if (ramInitialValuesBank.isDefined) {
actualStartingModules = "init_rw_memory" :: actualStartingModules
}
new Platform(
cpu,
flagOverrides,
startingModules,
actualStartingModules,
codec,
srcCodec,
builtInFeatures ++ definedFeatures,
@ -253,6 +273,7 @@ object Platform {
generateGameBoyChecksums,
bankNumbers,
defaultCodeBank,
ramInitialValuesBank,
debugOutputFormat,
outputStyle)
}

View File

@ -457,10 +457,19 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
addThing(ConstantThing("nullptr.raw.lo", nullptrConstant.loByte.quickSimplify, b), None)
val __zeropage_usage = UnexpandedConstant("__zeropage_usage", 1)
addThing(ConstantThing("__zeropage_usage", __zeropage_usage, b), None)
val __heap_start = UnexpandedConstant("__heap_start", 2)
addThing(ConstantThing("__heap_start", __heap_start, p), None)
addThing(ConstantThing("__heap_start.hi", __heap_start.hiByte, b), None)
addThing(ConstantThing("__heap_start.lo", __heap_start.loByte, b), None)
def addUnexpandedWordConstant(name: String): Unit = {
val c = UnexpandedConstant(name, 2)
addThing(ConstantThing(name, c, p), None)
addThing(ConstantThing(name + ".hi", c.hiByte, b), None)
addThing(ConstantThing(name + ".lo", c.loByte, b), None)
}
addUnexpandedWordConstant("__rwdata_start")
addUnexpandedWordConstant("__rwdata_end")
if (options.platform.ramInitialValuesBank.isDefined) {
addUnexpandedWordConstant("__rwdata_init_start")
addUnexpandedWordConstant("__rwdata_init_end")
addUnexpandedWordConstant("__rwdata_size")
}
addThing(ConstantThing("$0000", NumericConstant(0, 2), p), None)
addThing(FlagBooleanType("set_carry",
BranchingOpcodeMapping(Opcode.BCS, IfFlagSet(ZFlag.C)),
@ -1388,9 +1397,6 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
}
case Some(contents1) =>
if (!stmt.const && options.flag(CompilationFlag.ReadOnlyArrays)) {
log.warn(s"Initialized array `${stmt.name}` is not defined as const, but the target platform doesn't support writable initialized arrays.", stmt.position)
}
val contents = extractArrayContents(contents1)
val indexType = stmt.length match {
case None => // array arr = [...]
@ -1430,6 +1436,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
AbstractExpressionCompiler.checkAssignmentType(this, element, e)
}
val array = InitializedArray(arrayName + ".array", address, contents, declaredBank = stmt.bank, indexType, e, readOnly = stmt.const, alignment)
if (!stmt.const && options.platform.ramInitialValuesBank.isDefined && array.bank(options) != "default") {
log.error(s"Preinitialized writable array `${stmt.name}` has to be in the default segment.", stmt.position)
}
addThing(array, stmt.position)
registerAddressConstant(UninitializedMemoryVariable(arrayName, p, VariableAllocationMethod.None,
declaredBank = stmt.bank, alignment, isVolatile = false), stmt.position, options, Some(e))
@ -1489,7 +1498,13 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
if (stmt.volatile && stmt.stack) log.error(s"`$name` cannot be simultaneously on stack and volatile", position)
if (stmt.volatile && stmt.register) log.error(s"`$name` cannot be simultaneously volatile and in a register", position)
if (stmt.initialValue.isDefined && parent.isDefined) log.error(s"`$name` is local and not a constant and therefore cannot have a value", position)
if (stmt.initialValue.isDefined && stmt.address.isDefined) log.warn(s"`$name` has both address and initial value - this may not work as expected!", position)
if (stmt.initialValue.isDefined && stmt.address.isDefined) {
if (options.platform.ramInitialValuesBank.isDefined) {
log.error(s"`$name` has both address and initial value, which is unsupported on this target", position)
} else {
log.warn(s"`$name` has both address and initial value - this may not work as expected!", position)
}
}
if (stmt.register && stmt.address.isDefined) log.error(s"`$name` cannot by simultaneously at an address and in a register", position)
if (stmt.stack) {
val v = StackVariable(prefix + name, typ, this.baseStackOffset)
@ -1514,9 +1529,6 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
}
val v = stmt.initialValue.fold[MemoryVariable](UninitializedMemoryVariable(prefix + name, typ, alloc,
declaredBank = stmt.bank, alignment, isVolatile = stmt.volatile)){ive =>
if (options.flags(CompilationFlag.ReadOnlyArrays)) {
log.warn("Initialized variable in read-only segment", position)
}
InitializedMemoryVariable(name, None, typ, ive, declaredBank = stmt.bank, alignment, isVolatile = stmt.volatile)
}
registerAddressConstant(v, stmt.position, options, Some(typ))

View File

@ -298,7 +298,8 @@ case class InitializedArray(name: String, address: Option[Constant], contents: S
override def isFar(compilationOptions: CompilationOptions): Boolean = farFlag.getOrElse(false)
override def bank(compilationOptions: CompilationOptions): String = declaredBank.getOrElse(compilationOptions.platform.defaultCodeBank)
override def bank(compilationOptions: CompilationOptions): String =
declaredBank.getOrElse(if (readOnly) compilationOptions.platform.defaultCodeBank else "default")
override def zeropage: Boolean = false

View File

@ -267,12 +267,15 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
})
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
case thing@InitializedArray(name, Some(NumericConstant(address, _)), items, _, _, _, _, _) =>
case thing@InitializedArray(name, Some(NumericConstant(address, _)), items, _, _, _, readOnly, _) =>
val bank = thing.bank(options)
if (!readOnly && options.platform.ramInitialValuesBank.isDefined) {
log.error(s"Preinitialized writable array $name cannot be put at a fixed address")
}
val bank0 = mem.banks(bank)
var index = address.toInt
assembly.append("* = $" + index.toHexString)
assembly.append(name)
assembly.append(name + ":")
for (item <- items) {
env.eval(item) match {
case Some(c) => writeByte(bank, index, c)
@ -352,7 +355,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
env.things += altName -> ConstantThing(altName, NumericConstant(index, 2), env.get[Type]("pointer"))
assembly.append("* = $" + index.toHexString)
assembly.append(" " + bytePseudoopcode + " $2c")
assembly.append(name)
assembly.append(name + ":")
val c = thing.toAddress
writeByte(bank, index, 0x2c.toByte) // BIT abs
index += 1
@ -372,57 +375,128 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
assembly.append(" " + bytePseudoopcode + " 2 ;; end of LUnix relocatable segment")
justAfterCode += "default" -> (index + 1)
}
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
case thing@InitializedArray(name, None, items, _, _, _, _, alignment) =>
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) = bank0.index -> index
assembly.append("* = $" + index.toHexString)
assembly.append(name)
for (item <- items) {
env.eval(item) match {
case Some(c) => writeByte(bank, index, c)
case None => log.error(s"Non-constant contents of array `$name`", item.position)
}
index += 1
}
items.grouped(16).foreach { group =>
assembly.append(" " + bytePseudoopcode + " " + group.map(expr => env.eval(expr) match {
case Some(c) => c.quickSimplify.toString
case None => "<? unknown constant ?>"
}).mkString(", "))
}
initializedVariablesSize += items.length
justAfterCode += bank -> index
case m@InitializedMemoryVariable(name, None, typ, value, _, alignment, _) =>
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) = 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)
assembly.append(name)
env.eval(value) match {
case Some(c) =>
for (i <- 0 until typ.size) {
writeByte(bank, index, c.subbyte(i))
assembly.append(" " + bytePseudoopcode + " " + c.subbyte(i).quickSimplify)
index += 1
}
case None =>
log.error(s"Non-constant initial value for variable `$name`")
index += typ.size
}
initializedVariablesSize += typ.size
justAfterCode += bank -> index
case _ =>
}
env.getAllFixedAddressObjects.foreach {
if (options.platform.ramInitialValuesBank.isDefined) env.getAllFixedAddressObjects.foreach {
case (bank, addr, size) =>
val bank0 = mem.banks(bank)
for(i <- 0 until size) bank0.occupied(addr + i) = true
variableAllocators(bank).notifyAboutHole(bank0, addr, size)
}
var rwDataStart = Int.MaxValue
var rwDataEnd = 0
for(readOnlyPass <- Seq(true, false)) {
if (!readOnlyPass) {
if (options.platform.ramInitialValuesBank.isDefined) {
codeAllocators("default").notifyAboutEndOfCode(codeAllocators("default").heapStart)
}
}
env.allPreallocatables.filterNot(o => unusedRuntimeObjects(o.name)).foreach {
case thing@InitializedArray(name, None, items, _, _, _, readOnly, alignment) if readOnly == readOnlyPass =>
val bank = thing.bank(options)
if (options.platform.ramInitialValuesBank.isDefined && !readOnly && bank != "default") {
log.error(s"Preinitialized writable array `$name` should be defined in the `default` bank")
}
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) = bank0.index -> index
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
rwDataEnd = rwDataEnd.min(index + items.size)
}
assembly.append("* = $" + index.toHexString)
assembly.append(name + ":")
for (item <- items) {
env.eval(item) match {
case Some(c) => writeByte(bank, index, c)
case None => log.error(s"Non-constant contents of array `$name`", item.position)
}
index += 1
}
items.grouped(16).foreach { group =>
assembly.append(" " + bytePseudoopcode + " " + group.map(expr => env.eval(expr) match {
case Some(c) => c.quickSimplify.toString
case None => "<? unknown constant ?>"
}).mkString(", "))
}
initializedVariablesSize += items.length
justAfterCode += bank -> index
case m@InitializedMemoryVariable(name, None, typ, value, _, alignment, _) if !readOnlyPass=>
val bank = m.bank(options)
if (options.platform.ramInitialValuesBank.isDefined && bank != "default") {
log.error(s"Preinitialized variable `$name` should be defined in the `default` bank")
}
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) = bank0.index -> index
if (!readOnlyPass) {
rwDataStart = rwDataStart.min(index)
rwDataEnd = rwDataEnd.max(index + typ.size)
}
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 + ":")
env.eval(value) match {
case Some(c) =>
for (i <- 0 until typ.size) {
writeByte(bank, index, c.subbyte(i))
assembly.append(" " + bytePseudoopcode + " " + c.subbyte(i).quickSimplify)
index += 1
}
case None =>
log.error(s"Non-constant initial value for variable `$name`")
index += typ.size
}
initializedVariablesSize += typ.size
justAfterCode += bank -> index
case _ =>
}
}
if (rwDataEnd == 0 && rwDataStart == Int.MaxValue) {
rwDataStart = 0
}
platform.ramInitialValuesBank match {
case None =>
case Some(ivBank) =>
val db = mem.banks("default")
val ib = mem.banks(ivBank)
val size = rwDataEnd - rwDataStart
if (size < 0) log.fatal("What")
val ivAddr = codeAllocators(ivBank).allocateBytes(ib, options, size, initialized = true, writeable = false, AllocationLocation.High, NoAlignment)
labelMap += "__rwdata_init_start" -> (ib.index -> ivAddr)
labelMap += "__rwdata_init_end" -> (ib.index -> (ivAddr + size))
labelMap += "__rwdata_size" -> (ib.index -> size)
for (i <- 0 until size) {
ib.output(ivAddr + i) = db.output(rwDataStart + i)
}
val debugArray = Array.fill[Option[Constant]](size)(None)
bytesToWriteLater ++= bytesToWriteLater.flatMap{
case ("default", addr, value) if addr >= rwDataStart && addr < rwDataEnd =>
debugArray(addr - rwDataStart) = Some(value)
Some(ivBank, addr + ivAddr - rwDataStart, value)
case _ => None
}
wordsToWriteLater ++= wordsToWriteLater.flatMap {
case ("default", addr, value) if addr >= rwDataStart && addr < rwDataEnd =>
debugArray(addr - rwDataStart) = Some(value.loByte)
debugArray(addr - rwDataStart + 1) = Some(value.hiByte)
Some(ivBank, addr + ivAddr - rwDataStart, value)
case _ => None
}
assembly.append("* = $" + ivAddr.toHexString)
assembly.append("__rwdata_init_start:")
for (addrs <- 0 until size grouped 16) {
assembly.append(" " + bytePseudoopcode + " " + addrs.map(i =>
debugArray(i).getOrElse(NumericConstant(ib.output(i + ivAddr) & 0xff, 1))
).mkString(", "))
}
}
if (options.platform.ramInitialValuesBank.isDefined) {
variableAllocators("default").notifyAboutEndOfData(rwDataEnd)
} else env.getAllFixedAddressObjects.foreach {
case (bank, addr, size) =>
val bank0 = mem.banks(bank)
for (i <- 0 until size) bank0.occupied(addr + i) = true
variableAllocators(bank).notifyAboutHole(bank0, addr, size)
}
variableAllocators.foreach { case (b, a) => a.notifyAboutEndOfCode(justAfterCode(b)) }
env.allocateVariables(None, mem, callGraph, variableAllocators, options, labelMap.put, 2, forZpOnly = false)
@ -442,6 +516,8 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program
labelMap += "__zeropage_last" -> (defaultBank -> 2)
labelMap += "__zeropage_end" -> (defaultBank -> 3)
}
labelMap += "__rwdata_start" -> (defaultBank -> rwDataStart)
labelMap += "__rwdata_end" -> (defaultBank -> rwDataEnd)
labelMap += "__heap_start" -> (defaultBank -> variableAllocators("default").heapStart)
env = rootEnv.allThings

View File

@ -23,6 +23,7 @@ object AllocationLocation extends Enumeration {
sealed trait ByteAllocator {
def startAt: Int
def endBefore: Int
def heapStart: Int
def preferredOrder: Option[List[Int]]
@ -64,7 +65,10 @@ sealed trait ByteAllocator {
}
class UpwardByteAllocator(val startAt: Int, val endBefore: Int) extends ByteAllocator {
def notifyAboutEndOfCode(org: Int): Unit = ()
var heapStart: Int = startAt
def notifyAboutEndOfCode(org: Int): Unit = {
heapStart = org
}
override def preferredOrder: Option[List[Int]] = None
}
@ -76,6 +80,8 @@ class ZeropageAllocator(val freeZpBytes: List[Int]) extends ByteAllocator {
override def startAt: Int = if (freeZpBytes.isEmpty) 2 else freeZpBytes.min
override def endBefore: Int = if (freeZpBytes.isEmpty) 2 else freeZpBytes.max + 1
override def heapStart: Int = 0
}
class AfterCodeByteAllocator(val endBefore: Int) extends ByteAllocator {
@ -83,15 +89,19 @@ class AfterCodeByteAllocator(val endBefore: Int) extends ByteAllocator {
def notifyAboutEndOfCode(org: Int): Unit = startAt = org
override def preferredOrder: Option[List[Int]] = None
override def heapStart: Int = startAt
}
class VariableAllocator(zpBytes: List[Int], private val bytes: ByteAllocator) {
def totalHimemSize: Int = bytes.endBefore - bytes.startAt
val zeropage: ByteAllocator = new ZeropageAllocator(zpBytes)
private val variableMap = mutable.Map[Int, mutable.Map[Int, Set[VariableVertex]]]()
var heapStart: Int = bytes.startAt
var heapStart: Int = bytes.heapStart
def allocateBytes(mem: MemoryBank, callGraph: CallGraph, p: VariableVertex, options: CompilationOptions, count: Int, initialized: Boolean, writeable: Boolean, location: AllocationLocation.Value, alignment: MemoryAlignment): Int = {
if (!variableMap.contains(count)) {
@ -173,5 +183,16 @@ class VariableAllocator(zpBytes: List[Int], private val bytes: ByteAllocator) {
}
}
//TODO: Everything about the three methods below is ugly and wrong. Fix later.
def notifyAboutEndOfCode(org: Int): Unit = bytes.notifyAboutEndOfCode(org)
def notifyAboutEndOfData(org: Int): Unit = heapStart = heapStart max org
def notifyAboutHole(mem: MemoryBank, addr: Int, size: Int): Unit = {
if (Math.abs(addr - heapStart) <= 1) {
heapStart += size
while (mem.occupied(heapStart)) heapStart += 1
bytes.notifyAboutEndOfCode(heapStart)
}
}
}

View File

@ -29,6 +29,7 @@ object EmuPlatform {
false,
Map("default" -> 0),
"default",
None,
ViceDebugOutputFormat,
OutputStyle.Single
)