Skip to content

Investigation: The Apple IIgs Memory Manager

Thomas Harte edited this page Feb 21, 2021 · 34 revisions

As of the first draft of this document, all parts are present for Apple IIgs emulation but bugs remain. At least one of those manifests as the in-ROM memory manager not responding as expected; in particular:

  • titles including Airball, Bouncing Bluster and Crystal Quest meet an unexpected BRK during startup, somewhere in the 00:BB or 00:BC page, dropping the user into the monitor; and
  • titles such as Defender of the Crown, Block Out and Bubble Ghost stop completely on an error of 'Unable to reserve memory segment $0201'.

The second set would appear overtly to be memory manager related; the former also seems to be.

Unexpected BRK

Digging down on Airball, the immediate progenitor of the error is this routine in the ROM's MoveUp (from mm.asm):

lda Count+2
adc Dest+2	;A = 00bank
xba	;A = bank00
ora #$44	;MVP opcode
pha
bra muFirst

i.e. the ROM is intending to assemble the sequence MVP, RTS on the stack, to which it later jumps. An ORA is used because a single-byte load isn't available. In my emulator the value at Dest+2 originates from whatever the contents of RAM were at power up rather than necessarily being 00 and as a result the generated opcode isn't MVP and the on-stack sequence that is jumped to runs off the rails.

Specifically the value at Dest+2 at that point in the code is 7f15.

Origin of the 7f15

By the time MoveUp is reached, the value is in Dest+2 (i.e. 00bcb1 and 00bcb2).

(Aside on cycle times below: they are relative to the 14Mhz bus, and depend on things like the exact disk rotation speeds I currently have, so should not be expected necessarily to be accurate in the future)

It was placed there by a STY at fc0d5f, 502081910 cycles since the machine launched. That immediately follows a load from 00bcc5/6.

The value was put into 00bcc5/6 by a STY at fc0d5f, 502078270 cycles since the machine launched. The value had been picked up by a LDY [$21] at fc0cb2, which read the value from 00bcc9/a.

It had been placed into 00bcc9/a by a STA at fc0fcb, 502077180 cycles since the machine launched. It picked the value up from from a LDA [$13],y with y=2 at fc0fc9, which read the value from e11806/7.

In context, that is likely to be the upper word of the block of memory pointed to by a memory handle; see précis on memory manager handles below.

The value was written to e11806/7 by a STA [$17],y at fc0dac with y=2, 497930810 cycles since machine launch. Its origin then was one instruction earlier, a LDA $03,x with x=2 and d=$bca4 at fc0daa, which accessed bca9/a.

7f15 had been stored to bca9/a at fc0da4 by a STA [$05], 497929740 cycles since machine launch, having been read from 00bcc1/2 one instruction earlier by a LDA [$1d].

It had been stored there by a STY [$1d] at fc0d5f, 497928970 cycles since machine launch, having been fetched by a LDY [$21] at fc0cb2, 497928450 cycles since machine launched, which fetched from 00bcc5/6.

That value was left by a STA [$21] at fc0fcb, 497927880 cycles since machine launch. It was fetched one instruction earlier by a LDA [$13], y with y=2, and d=$bca4 yielding a base for the long address of bcb7 which gave e119a8; that plus y leads to a two-byte fetch from e119aa/b.

Digging down on e119ab, logging reads and writes to it since launch until its relevant access at 497927875 cycles since launch shows:

e119ab <- 00 [15775430]
e119ab -> 00 [141578780]
e119ab -> 00 [141579715]
e119ab <- 00 [469272795]
e119ab -> 00 [469274435]
e119ab -> 00 [469274490]
e119ab -> 00 [473880845]
e119ab -> 00 [473881875]
e119ab -> 00 [473920325]
e119ab <- 00 [480022440]
e119ab <- 7f [495809345]
e119ab -> 7f [495811040]
e119ab -> 7f [495811095]
e119ab -> 7f [495820535]
e119ab -> 7f [495820590]
e119ab -> 7f [497927875]

Therefore: it looks like the handle map is properly cleared at startup, which at least takes guesses around detecting a cold start and with loop lengths off the table.

Digging down on cycle 495809345 after machine launch returns us to fc0dac, the same location that wrote to e11806/7 above; that instruction begins 495809310 cycles after machine launch.

This time the LDA $03,x at fc0daa with x=2 has d=$bc4a and therefore loads from 00bc4f and 00bc50.

The store at fc0da4 this time stores a value previously loaded from bc67.

bc67 was stored to by fc0d5f at 495807470, with the offending 7f15 having been picked up from bc6b/c by the LDY [$21] at fc0cb2 which read from bc6b. That instruction began at 495806950.

The fc0fc9/fc0fcb load and store which stored 7f15 to bc6b/bc6c occured from time 495806380, and specifically the LDA [$13], y at fc0fc9 occurred with d=$bc4a and y=2 so read from bc5d, bc5e and bc5f, which contained $755b4a. Which is not in the memory manager handle set.

I also don't currently believe that 755b4c has been initialised. Therefore it's likely to be the pointer to there that has been calculated or written incorrectly somehow.

755b4a was written in two steps, at times 495802640 and 495805825 for 00bc5d/00bc5e and at 495805880 for 00bc5f. These timings possibly aren't yet fixed, which might be a clue.

The former occurs at fc0fa2 from a STA [$13] with d=$bc4a, following a LDA [#0f] at fc0fa0; that is followed by a LDA [$11] at fc0fa4 which obtains $5575 and then writes that at fc0fa6 via STA [$15]. Therefore the 4-byte address at bc5d is copied from 00bc59.

That address ended up at bc59 from the instruction at fc0fb2 at time 495804590, which is a STA [$11] that follows the LDA [$13], y at fc0fb0, with y = $e following a direct LDY #$0e at fc0fae and d=$bc4a.

Memory Manager Notes

The memory manager uses the region between e11700 and e11aff for a list of handles. Each handle is 20 bytes long and has the format:

  • 0–3: address of held memory;
  • 4–5: attributes;
  • 6–7: segment owner ID;
  • 8–11: size of held memory;
  • 12–15: pointer to next;
  • 16–19: pointer to previous.

e119aa/b is 682 bytes into the full list of handles; it is therefore the high two bytes of the first field — the address of the memory held — of the 34th handle.