The WonderSwan poses unique challenges for implementing a robust memory management system:
In a C environment, by default, near pointers are utilized - they're smaller and faster to use, but they can only point to the program's default data segment - for all wswan
targets, this is the console's internal RAM (IRAM).
To point to data in ROM, a far pointer is required. These are explicitly declared by using the __far
modifier:
// The variable below will be stored in IRAM, even if it's declared const.
static const uint16_t table_iram[] = {0, 1, 2, 3};
// The variable below will now be stored in ROM instead of IRAM.
static const uint16_t __far table[] = {0, 1, 2, 3};
void process_table_iram(uint16_t *tbl); // This function can accept pointers to IRAM only.
void process_table(uint16_t __far *tbl); // This function can accept pointers to both IRAM and ROM.
However, certain subtargets model things differently:
wswan/bootfriend
, all of the program's code and data is stored in RAM.wwitch
, the default data segment actually points to SRAM - on this platform, IRAM only contains the stack.For this reason, Wonderful provides helper modifiers which abstract away the differences; these are useful for developing libraries usable across all subtargets:
__wf_rom
- points to read-only program code/data on every subtarget;__wf_iram
- points to IRAM on every subtarget.By default, as discussed above, the following types of code and data will be placed into the following locations by the linker:
.fartext
section) and read-only data marked __wf_rom
or __far
(.farrodata
section) - the last 768 KiB “linear” bank of ROM,.rodata
, .data
sections) - console IRAM.However, on the WonderSwan, especially the Color model, there are many restrictions as to where or with what alignment certain types of data (screen tables, tiles, audio wavetables) may be placed. For this reason, Wonderful's linking scheme has been extended to allow a special section name format. For example:
__attribute__((section(".iramx_2bpp_2000.tiles")))
ws_tile_t tile_2bpp[512];
The above block of code will allocate 512 tiles worth of memory in:
* internal console RAM (.iram
),
x
),0x2000
- 0x5FFF
, aligned to a multiple of 16 bytes (2bpp
),2000
).The following prefixes exist:
Prefix | Optional arguments | Description |
---|---|---|
.iram[x] | offset | Mono internal RAM (0x0000 - 0x3FFF) [zeroed on startup?] |
.iramc[x] | offset | Mono/Color internal RAM (0x0000 - 0xFFFF) [zeroed on startup?] |
.iramC[x] | offset | Color internal RAM (0x4000 - 0xFFFF) [zeroed on startup?] |
.iram[…]_screen | offset | Screen data (aligned to 0x800 bytes) |
.iram[…]_sprite | offset | Sprite table (aligned to 0x200 bytes) |
.iram[…]_2bpp | offset | 2bpp tile data (aligned to one tile) |
.iram[…]_4bpp | offset | 4bpp tile data (aligned to one tile) |
.iram[…]_wave | offset | Audio wavetable data (aligned to 0x40 bytes) |
.iram[…]_palette | offset | Color palette data (aligned to 0x20 bytes) |
.rom0 | bank index, offset | ROM bank 0 |
.rom1 | bank index, offset | ROM bank 1 |
.romL | bank index, offset | ROM linear bank |
with the following optional arguments:
Note that the linker requires distinct section names to place things separately: in other banks, memory locations, or for garbage collection. One can use .
after the prefix to specify an unique name.
wf-process supports an easy syntax for moving data to a specific ROM bank, index, or offset:
process.emit_symbol("gfx_title_screen", tilemap_color, {
["bank"] = 0, -- optional; or 1, or "L"
["bank_index"] = "F", -- optional; using a string is preferred over a number
["offset"] = 0x1000 -- optional.
})
This approach also ensures that sections are granted unique names by default.
A given 20-bit linear address to memory contains the bank type information (highest 4 bits) and the offset (lowest 16 bits), but it does not contain the bank index itself. For this purpose, the Wonderful linker was extended with support for additional symbols;
__attribute__((section(".rom0.gfx_font")))
const uint8_t __wf_rom gfx_font[1024] = { ... }; // array "gfx_font"
extern const void *__bank_gfx_font; // address = bank for "gfx_font"
</code C>
To access data in a separate bank, the following method is recommended:
<code C>
void load_font(void) {
// Change bank, saving the previous bank value.
// Assumes "gfx_font" exists at ROM bank 0
ws_bank_t previous_bank = ws_bank_rom0_save(&__bank_gfx_font);
// Load font data from ROM to IRAM.
memcpy(MEM_TILE(0), gfx_font, sizeof(gfx_font));
// Restore the previous bank value.
ws_bank_rom0_restore(previous_bank);
}
Observe the save/restore pattern. As the parent function may already be using ROM bank 0 for something else, it's a good habit to ensure at function exit that the ROM bank remains unchanged from what it was at function entry:
bool load_graphics(void) {
ws_bank_rom0_set(__bank_gfx_title_screen);
// This function changes ROM bank 0!
load_font();
// If load_font() did not restore the previous bank index.
// the code below will copy unexpected data.
memcpy(screen_1, gfx_title_screen, sizeof(gfx_title_screen));
// load_graphics() did not restore the previous bank index either.
// If the function calling load_graphics() relies on said index,
// it too will be handling unexpected data.
return true;
}
Note that ws_bank_rom0_*
can accept both numeric values and const void*
pointers; in the latter case, the address of the pointer will be used.
In addition, when using wf_process
, a `gfx_font_bank` define is provided for convenience - it equals `&__bank_gfx_font`.