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:
parent
7f28a6b10f
commit
674f8d1983
@ -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
49
docs/api/rom-vs-ram.md
Normal 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.
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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}
|
||||
}
|
||||
|
@ -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}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
118
include/init_rw_memory.mfk
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
34
src/main/scala/millfork/env/Environment.scala
vendored
34
src/main/scala/millfork/env/Environment.scala
vendored
@ -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))
|
||||
|
3
src/main/scala/millfork/env/Thing.scala
vendored
3
src/main/scala/millfork/env/Thing.scala
vendored
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ object EmuPlatform {
|
||||
false,
|
||||
Map("default" -> 0),
|
||||
"default",
|
||||
None,
|
||||
ViceDebugOutputFormat,
|
||||
OutputStyle.Single
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user