OXYGENHUB
◆ main enhanced-x16-emulator / src / code_map.c
31.4 KiB · C · last changed chore(): initial commit 25 days ago source blame history raw
1
// Commander X16 Emulator — disassembly-accuracy core ("code map").
2
// See code_map.h for the rationale. This file is deliberately dependency-light
3
// (no SDL / glue.h) so it stays warning-clean under /W3 /WX and is easy to unit
4
// test in isolation: it leans on just disasm(), dbg_info, and the two core
5
// symbols declared directly below.
6
#include "code_map.h"
7
#include "disasm.h"
8
#include "dbg_info.h"
9
#include "cpu/registers.h"   // FLAG_* bits + struct regs layout
10
11
#include <string.h>
12
#include <stdlib.h>
13
#include <stdio.h>   // snprintf, for rendering a cut-short line as data bytes
14
15
// --- Core symbols, declared directly to avoid pulling in memory.h/glue.h ---
16
// The global CPU register file (defined in fake6502.c) and the non-intrusive
17
// memory read used by disasm(). Signatures match memory.h / glue.h exactly.
18
extern struct regs regs;
19
extern bool        is_gen2;
20
uint8_t real_read6502(uint16_t address, uint8_t bank, bool debugOn, int16_t x16Bank);
21
22
#define CM_X16_CURRENT_BANK ((int16_t)-1)
23
#define CM_READ(a, b, xb) real_read6502((uint16_t)(a), (uint8_t)(b), true, (int16_t)(xb))
24
25
// Longest instruction we ever have to back up over when self-syncing (the
26
// 65C816 tops out at 4 bytes).
27
#define CM_MAX_INSN_LEN 4
28
29
// ---------------------------------------------------------------------------
30
//  Coverage + flag storage, keyed by the bank context backing the address.
31
// ---------------------------------------------------------------------------
32
//  Each context is a 64K bit coverage map (1 bit per address = 8 KB) plus a
33
//  per-address effective-status byte. Contexts are allocated lazily and capped;
34
//  the least-recently-used one is recycled once the cap is reached.
35
//
36
//  The key is deliberately NOT the full live bank state. Only part of the
37
//  address space is banked, so keying every address by (rambank, rombank) would
38
//  file the same unbanked low-memory code under a fresh context for every bank
39
//  combination the program happens to be in. With 32 ROM banks and 256 RAM
40
//  banks that exhausts the context cache during ROM boot alone (measured: the
41
//  cap is hit before BASIC is ready), and worse, coverage recorded at $0801
42
//  under one ROM bank could no longer be found once the program switched banks
43
//  -- so the disassembler lost the very ground truth it exists to collect.
44
//
45
//  Instead each address is keyed by what actually backs it, mirroring the
46
//  decode in real_read6502():
47
//    - gen2 with a non-zero program bank: the whole 64K is flat bank RAM and
48
//      the window registers select nothing, so only the program bank keys it;
49
//    - otherwise the program bank selects nothing (read6502() forces it to 0 on
50
//      gen1, even with -c816), and the key is the RAM bank for $A000-$BFFF, the
51
//      ROM bank for $C000-$FFFF, and nothing at all below $A000.
52
53
//  Anchors are never deleted when the memory under them changes -- there is no
54
//  write hook, and an overlay LOAD or a decompressor replaces code wholesale --
55
//  so each anchor also stores the opcode byte that was executing. An anchor is
56
//  only believed while that byte is still there. Without this, running a second
57
//  program over the first leaves stale "instruction starts" mid-instruction in
58
//  the new code, and they outrank the .dbg info that would have been right.
59
60
#define CM_ADDR_SPACE   0x10000
61
#define CM_COVER_BYTES  (CM_ADDR_SPACE / 8)   // 8 KB: one bit per address
62
// Contexts cost ~136 KB each. Keying by backing memory (above) means a real ROM
63
// boot settles at four, so a cap of 16 leaves ample headroom at ~2.2 MB total.
64
#define CM_MAX_CONTEXTS 16
65
66
typedef struct {
67
	bool     used;
68
	uint8_t  pbank;
69
	uint8_t  rambank;
70
	uint8_t  rombank;
71
	uint64_t lru;
72
	uint8_t  cover[CM_COVER_BYTES];
73
	uint8_t  status[CM_ADDR_SPACE];       // valid only where the cover bit is set
74
	uint8_t  opcode[CM_ADDR_SPACE];       // ditto: the byte that was executing
75
} cm_context_t;
76
77
static cm_context_t *g_ctx[CM_MAX_CONTEXTS];
78
static uint64_t       g_lru_clock;
79
static cm_context_t  *g_last_ctx;   // fast-path cache for the hot record loop
80
81
// Which banked window a given address reads from, for the disasm/real_read6502
82
// x16Bank argument. Low memory ($0000-$9FFF) is not banked (the CPU program
83
// bank handles gen2), so it follows the current bank.
84
static int16_t
85
cm_x16bank_for(uint16_t addr, uint8_t rambank, uint8_t rombank)
86
{
87
	if (addr >= 0xC000)
88
		return (int16_t)rombank;
89
	if (addr >= 0xA000)
90
		return (int16_t)rambank;
91
	return CM_X16_CURRENT_BANK;
92
}
93
94
// Reduce the live bank state to the part that actually selects the memory
95
// behind `addr`. See the note above.
96
static void
97
cm_key_for(uint16_t addr, uint8_t pbank, uint8_t rambank, uint8_t rombank,
98
           uint8_t *key_pb, uint8_t *key_ram, uint8_t *key_rom)
99
{
100
	if (is_gen2 && pbank != 0) {
101
		*key_pb  = pbank;
102
		*key_ram = 0;
103
		*key_rom = 0;
104
		return;
105
	}
106
	*key_pb  = 0;
107
	*key_ram = (addr >= 0xA000 && addr < 0xC000) ? rambank : 0;
108
	*key_rom = (addr >= 0xC000) ? rombank : 0;
109
}
110
111
static cm_context_t *
112
cm_ctx_find(uint8_t pbank, uint8_t rambank, uint8_t rombank)
113
{
114
	for (int i = 0; i < CM_MAX_CONTEXTS; i++) {
115
		cm_context_t *c = g_ctx[i];
116
		if (c && c->used && c->pbank == pbank && c->rambank == rambank && c->rombank == rombank)
117
			return c;
118
	}
119
	return NULL;
120
}
121
122
static cm_context_t *
123
cm_ctx_get_or_create(uint8_t pbank, uint8_t rambank, uint8_t rombank)
124
{
125
	cm_context_t *c = cm_ctx_find(pbank, rambank, rombank);
126
	if (c)
127
		return c;
128
129
	// Prefer a free slot; otherwise evict the least-recently-used context.
130
	int slot = -1;
131
	for (int i = 0; i < CM_MAX_CONTEXTS; i++) {
132
		if (!g_ctx[i] || !g_ctx[i]->used) { slot = i; break; }
133
		if (slot < 0 || g_ctx[i]->lru < g_ctx[slot]->lru) slot = i;
134
	}
135
	if (slot < 0)
136
		slot = 0;
137
138
	if (!g_ctx[slot]) {
139
		g_ctx[slot] = (cm_context_t *)calloc(1, sizeof(cm_context_t));
140
		if (!g_ctx[slot])
141
			return NULL;
142
	} else {
143
		// Recycling: only the coverage bits must be cleared — status bytes are
144
		// never read unless their cover bit is set. The evicted context may be
145
		// the one g_last_ctx points at, so drop that cache too rather than let
146
		// it name a context that now holds different banks.
147
		memset(g_ctx[slot]->cover, 0, sizeof(g_ctx[slot]->cover));
148
		if (g_last_ctx == g_ctx[slot])
149
			g_last_ctx = NULL;
150
	}
151
	g_ctx[slot]->used    = true;
152
	g_ctx[slot]->pbank   = pbank;
153
	g_ctx[slot]->rambank = rambank;
154
	g_ctx[slot]->rombank = rombank;
155
	g_ctx[slot]->lru     = ++g_lru_clock;
156
	return g_ctx[slot];
157
}
158
159
static bool
160
cm_bit(const cm_context_t *c, uint16_t addr)
161
{
162
	return c && (c->cover[addr >> 3] & (uint8_t)(1u << (addr & 7))) != 0;
163
}
164
165
// True if `addr` is a recorded instruction start AND the opcode byte recorded
166
// there still matches memory.
167
//
168
// Anchors are only ever added; nothing removes them when memory changes, and on
169
// this machine memory under code changes all the time -- an overlay LOAD, a
170
// second program loaded over the first, a decompressor unpacking over its own
171
// loader, self-modifying code. A stale anchor is worse than no anchor, because
172
// live-execution evidence outranks the .dbg spans that would have been right,
173
// so it can confidently place an instruction boundary in the middle of a real
174
// instruction.
175
//
176
// Comparing the recorded opcode byte against memory catches most of those for
177
// the price of one byte per address, and it keeps the DETECTION off the hot
178
// path -- staleness is noticed when an anchor is *used*, not when memory is
179
// written, so no write hook is needed. (Recording still pays one read and one
180
// store per executed instruction to capture the byte.) It is a heuristic, not a
181
// proof: replacement code that repeats the same opcode byte at the same address
182
// slips through. See the note in code_map.h and the KNOWN LIMITATION checks in
183
// tests/test_code_map.c.
184
static bool
185
cm_anchor_ok(const cm_context_t *c, uint16_t addr, uint8_t bank, uint8_t rambank, uint8_t rombank)
186
{
187
	if (!cm_bit(c, addr))
188
		return false;
189
	return c->opcode[addr] == (uint8_t)CM_READ(addr, bank, cm_x16bank_for(addr, rambank, rombank));
190
}
191
192
// ---------------------------------------------------------------------------
193
//  Recording
194
// ---------------------------------------------------------------------------
195
196
void
197
code_map_record(uint16_t pc, uint8_t pbank, uint8_t rambank, uint8_t rombank, uint8_t status)
198
{
199
	uint8_t k_pb, k_ram, k_rom;
200
	cm_key_for(pc, pbank, rambank, rombank, &k_pb, &k_ram, &k_rom);
201
202
	// Fast path: this is called for every executed instruction, so avoid the
203
	// context scan -- and the LRU bookkeeping -- when the bank context has not
204
	// changed since last time, which is overwhelmingly the common case.
205
	cm_context_t *c = g_last_ctx;
206
	if (!c || !c->used || c->pbank != k_pb || c->rambank != k_ram || c->rombank != k_rom) {
207
		c = cm_ctx_get_or_create(k_pb, k_ram, k_rom);
208
		if (!c)
209
			return;
210
		c->lru     = ++g_lru_clock;
211
		g_last_ctx = c;
212
	}
213
	c->cover[pc >> 3] |= (uint8_t)(1u << (pc & 7));
214
	c->status[pc]      = status;
215
	c->opcode[pc]      = (uint8_t)CM_READ(pc, pbank, cm_x16bank_for(pc, rambank, rombank));
216
}
217
218
// Locate the context holding coverage for `addr` under the given live banks.
219
static cm_context_t *
220
cm_ctx_for_addr(uint16_t addr, uint8_t pbank, uint8_t rambank, uint8_t rombank)
221
{
222
	uint8_t k_pb, k_ram, k_rom;
223
	cm_key_for(addr, pbank, rambank, rombank, &k_pb, &k_ram, &k_rom);
224
	return cm_ctx_find(k_pb, k_ram, k_rom);
225
}
226
227
bool
228
code_map_is_recorded_start(uint16_t pc, uint8_t pbank, uint8_t rambank, uint8_t rombank)
229
{
230
	return cm_anchor_ok(cm_ctx_for_addr(pc, pbank, rambank, rombank), pc, pbank, rambank, rombank);
231
}
232
233
uint8_t
234
code_map_recorded_status(uint16_t pc, uint8_t pbank, uint8_t rambank, uint8_t rombank, uint8_t fallback)
235
{
236
	cm_context_t *c = cm_ctx_for_addr(pc, pbank, rambank, rombank);
237
	if (cm_anchor_ok(c, pc, pbank, rambank, rombank))
238
		return c->status[pc];
239
	return fallback;
240
}
241
242
void
243
code_map_reset(void)
244
{
245
	for (int i = 0; i < CM_MAX_CONTEXTS; i++) {
246
		if (g_ctx[i]) {
247
			g_ctx[i]->used = false;
248
			memset(g_ctx[i]->cover, 0, sizeof(g_ctx[i]->cover));
249
		}
250
	}
251
	g_lru_clock = 0;
252
	g_last_ctx  = NULL;
253
}
254
255
// ---------------------------------------------------------------------------
256
//  Decoding helpers
257
// ---------------------------------------------------------------------------
258
259
static int
260
cm_decode(uint16_t addr, uint8_t bank, int16_t x16bank, uint8_t status,
261
          char *line, unsigned line_sz, int32_t *eff)
262
{
263
	int32_t e  = -1;
264
	int     sz = disasm(addr, bank, NULL, line, line_sz, x16bank, status, &e);
265
	if (sz < 1)
266
		sz = 1;
267
	if (eff)
268
		*eff = e;
269
	return sz;
270
}
271
272
// Predict the implied status of the NEXT instruction from the current opcode.
273
// Only used to fill gaps where live coverage is missing; recorded status always
274
// takes precedence over this estimate.
275
//
276
// Just five instructions change the register widths -- REP, SEP, XCE, PLP and
277
// RTI. REP and SEP apply their own status bits exactly, since the bits are in
278
// the operand; the WIDTHS they imply are still only as good as the E estimate
279
// below, because emulation mode overrides them.
280
//
281
// XCE is exact only when BOTH of its inputs are right, and neither is
282
// guaranteed. It swaps the carry and emulation flags, so it needs the incoming
283
// carry -- which is carried through the instructions handled in this switch
284
// (CLC, SEC, and whatever REP/SEP/XCE themselves do to it), but NOT through any
285
// data-dependent change: ADC, SBC, the compares, the shifts and rotates all
286
// move the carry without this estimate noticing. It also needs the incoming E,
287
// which anchors do not record at all.
288
//
289
// PLP and RTI restore a status byte from the stack, so this deliberately leaves
290
// the estimate alone for them rather than inventing a value.
291
//
292
// HOW WELL EACH GAP IS BOUNDED differs, and none is bounded unconditionally:
293
//   - a wrong STATUS estimate can be corrected when an accurate, believed
294
//     anchor is reached. Not "the next recorded anchor": a same-opcode stale
295
//     anchor is still believed and hands back its old status, there may be no
296
//     accurate anchor ahead at all, and even an accurate one only fixes its OWN
297
//     line -- an anchor on a PLP or RTI gives the status going INTO it, and the
298
//     byte it restores is still unmodelled, so the next line can be wrong again;
299
//   - a wrong E is weaker again, because anchors do not store E. An anchor
300
//     fixes the width of its own line, and propagation folds the stale E back
301
//     in (`if (e) status |= INDEX|MEMORY`), which can mis-size the next
302
//     unanchored line wherever the stale E and the real one imply different
303
//     widths. It is not unrecoverable: an XCE decoded with an accurate carry
304
//     writes a correct E (that is exactly what the real instruction does), as
305
//     does an explicit CLC/SEC before one -- but nothing guarantees such a
306
//     sequence appears.
307
// That fold-in also means "emulation mode is always right" holds for the real
308
// CPU but NOT for this estimate: a diverged E sizes operands as though native
309
// while the machine is really in emulation mode.
310
// Either way every line drawn from the estimate reports recorded = false, so a
311
// consumer is never told a guess is ground truth. All of this is pinned by
312
// tests.
313
static uint8_t
314
cm_propagate(uint16_t addr, uint8_t bank, uint8_t rambank, uint8_t rombank,
315
             uint8_t status, uint8_t *e_inout)
316
{
317
	if (!regs.is65c816)
318
		return status;
319
320
	// The operand is read through the window backing the OPERAND's address,
321
	// not the opcode's. They differ for an instruction whose opcode is the last
322
	// byte of a window: a REP at $BFFF takes its operand from $C000, which is
323
	// banked ROM, and reading that through the RAM window yields some other
324
	// bank's byte. Getting it wrong here mis-sizes the following instructions
325
	// until an accurate anchor is reached -- the exact drift this file exists
326
	// to prevent.
327
	uint16_t argp   = (uint16_t)((addr + 1) & 0xFFFF);
328
	int16_t  xb_op  = cm_x16bank_for(addr, rambank, rombank);
329
	int16_t  xb_arg = cm_x16bank_for(argp, rambank, rombank);
330
331
	uint8_t opcode = (uint8_t)CM_READ(addr, bank, xb_op);
332
	uint8_t e      = *e_inout;
333
334
	switch (opcode) {
335
		case 0x18: // CLC
336
			status &= (uint8_t)~FLAG_CARRY;
337
			break;
338
		case 0x38: // SEC
339
			status |= FLAG_CARRY;
340
			break;
341
		case 0xC2: // REP #imm — clears the given status bits
342
			status &= (uint8_t)~CM_READ(argp, bank, xb_arg);
343
			break;
344
		case 0xE2: // SEP #imm — sets the given status bits
345
			status |= (uint8_t)CM_READ(argp, bank, xb_arg);
346
			break;
347
		case 0xFB: { // XCE — exchange carry and emulation flags
348
			uint8_t carry = (uint8_t)(status & FLAG_CARRY);
349
			status = (uint8_t)((status & (uint8_t)~FLAG_CARRY) | (e ? FLAG_CARRY : 0));
350
			e = carry ? 1 : 0;
351
			break;
352
		}
353
354
		// TODO: PLP (0x28) and RTI (0x40) also change the widths, by restoring a
355
		// status byte from the stack. Nothing is done for them because the byte
356
		// is not knowable from the instruction stream -- see the header note on
357
		// where the widths are still a guess. Two avenues if this is ever worth
358
		// closing, both of which recover a real value rather than guessing:
359
		//   - PLP is almost always the tail of a PHP/PLP save-restore pair. Walk
360
		//     back to the matching PHP within the same routine; if the status
361
		//     there is known, so is the status the PLP restores.
362
		//   - RTI restores what the interrupt pushed, which is the status at the
363
		//     moment the interrupt was taken. The emulator sees every interrupt,
364
		//     so that value could simply be recorded when it happens.
365
		// Both need a notion of "known vs unknown" per status bit so a failed
366
		// attempt reports uncertainty instead of substituting another guess.
367
368
		default:
369
			break;
370
	}
371
372
	// In emulation mode the 65C816 forces 8-bit memory/index widths.
373
	if (e)
374
		status |= (uint8_t)(FLAG_INDEX_WIDTH | FLAG_MEMORY_WIDTH);
375
376
	*e_inout = e;
377
	return status;
378
}
379
380
// Fill one line at `addr`, decoded with an explicit status.
381
//
382
// The size the decode produces is clamped twice, and both clamps exist because
383
// a decode is the weakest evidence in this file:
384
//
385
//   * `max_size`, when positive, is a boundary the caller has already committed
386
//     to -- the address the next line starts at. The backward walk resolves
387
//     that boundary from evidence the fill does not have (and its last-resort
388
//     fallback does not decode at all, it just backs off a byte), so a decode
389
//     that disagrees has to give way rather than emit a line overlapping the
390
//     next one.
391
//   * a recorded anchor inside the instruction is live-execution evidence that
392
//     a real instruction starts there, which outranks a width ESTIMATE;
393
//     truncate onto it rather than stepping over the very ground truth this
394
//     file exists to collect. This applies only when the line being decoded is
395
//     itself a guess. When the line is recorded too, its width came from the
396
//     status captured as it executed and is evidence of the same rank, so
397
//     the two anchors are simply two real, overlapping instruction starts --
398
//     the `.byte $2C` skip idiom -- and the one the caller asked for wins.
399
//     Clipping it there would discard a whole known instruction to display a
400
//     different execution path than the one requested.
401
//
402
// Either clamp can leave the line shorter than the decode wanted. Such a line
403
// no longer describes a whole instruction, so it is not reported as one: it
404
// comes back with `recorded == false`, no effective address, and `text`
405
// rewritten as the raw data bytes it actually owns, so `size`, `bytes` and
406
// `text` always agree. Covering the leftover bytes is the caller's job; see the
407
// tiling loop in code_map_disasm_window().
408
static int
409
cm_fill(uint16_t addr, uint8_t bank, uint8_t rambank, uint8_t rombank,
410
        uint8_t st, bool recorded, int max_size, code_map_line_t *ln)
411
{
412
	int16_t xb = cm_x16bank_for(addr, rambank, rombank);
413
414
	int32_t eff;
415
	int     sz      = cm_decode(addr, bank, xb, st, ln->text, (unsigned)sizeof(ln->text), &eff);
416
	int     decoded = sz;
417
418
	if (max_size > 0 && sz > max_size)
419
		sz = max_size;
420
421
	// Only a guessed width gives way to an interior anchor. A recorded line's
422
	// width came from the status captured as it executed, so it is evidence of
423
	// the same rank -- two overlapping real instruction starts, not a bad guess
424
	// straddling a good one.
425
	//
426
	// The exemption is re-derived here rather than taken from the `recorded`
427
	// argument, because it is only sound while `st` really is the status the
428
	// anchor recorded: a caller that passed `recorded` with some other status
429
	// would be decoding at a guessed width and would then be allowed to step
430
	// over the very ground truth this clamp protects. The one caller does
431
	// satisfy that today; checking it locally means a second one cannot quietly
432
	// break it. (Defensive: no current path violates it, so this is not
433
	// exercised by a test.)
434
	cm_context_t *self  = cm_ctx_for_addr(addr, bank, rambank, rombank);
435
	bool          truth = cm_anchor_ok(self, addr, bank, rambank, rombank) &&
436
	                      self->status[addr] == st;
437
438
	if (!truth) {
439
		for (int i = 1; i < sz; i++) {
440
			uint16_t p = (uint16_t)((addr + i) & 0xFFFF);
441
			if (cm_anchor_ok(cm_ctx_for_addr(p, bank, rambank, rombank), p, bank, rambank, rombank)) {
442
				sz = i;
443
				break;
444
			}
445
		}
446
	}
447
448
	// A line the decode did not get to finish is not an instruction, and must
449
	// not be dressed as one: `text` would describe more bytes than `size`
450
	// covers, and `eff` was computed from bytes the line does not own -- for a
451
	// clamped line those bytes are the NEXT instruction's opcode. Render the
452
	// bytes it does own as data instead, so size, bytes and text agree and a
453
	// consumer (the panel, or DAP's instruction/instructionBytes pair) cannot
454
	// be handed a self-contradictory row.
455
	//
456
	// This is the honest rendering of a genuine ambiguity rather than a
457
	// failure: the `.byte $2C` skip idiom really does put two overlapping
458
	// instruction starts in memory, both real and both executed, and no linear
459
	// non-overlapping tiling can show both whole. The policy is that the start
460
	// the caller selected wins -- forward from the outer one shows the outer
461
	// instruction, forward from the inner one shows the inner -- and only a
462
	// guessed decode, or one crossing a boundary the window already committed
463
	// to, is reduced to the bytes it owns on that path.
464
	if (sz < decoded) {
465
		recorded = false;
466
		eff      = -1;
467
468
		int used = 0;
469
		for (int b = 0; b < sz && b < 4; b++) {
470
			uint16_t p = (uint16_t)((addr + b) & 0xFFFF);
471
			int      w = snprintf(ln->text + used, sizeof(ln->text) - (size_t)used,
472
			                      "%s$%02x", b == 0 ? ".byte " : ",",
473
			                      (unsigned)CM_READ(p, bank, cm_x16bank_for(p, rambank, rombank)));
474
			if (w < 0 || (size_t)(used + w) >= sizeof(ln->text))
475
				break;
476
			used += w;
477
		}
478
	}
479
480
	ln->addr     = addr;
481
	ln->size     = (uint8_t)sz;
482
	ln->status   = st;
483
	ln->eff_addr = eff;
484
	ln->recorded = recorded;
485
	ln->kind     = (uint8_t)(sz < decoded ? CM_LINE_DATA : CM_LINE_INSTRUCTION);
486
	// Whether the ADDRESS was executed is separate from whether this row could
487
	// be shown as a whole instruction: a boundary can force a recorded start to
488
	// be emitted as data, and a consumer still wants to know it is real code.
489
	ln->start_recorded = cm_anchor_ok(self, addr, bank, rambank, rombank);
490
	memset(ln->bytes, 0, sizeof(ln->bytes));
491
	// Each byte is read through the window that backs its own address, so an
492
	// instruction straddling $BFFF/$C000 shows the ROM bank's bytes for the part
493
	// that lives there rather than the RAM bank's.
494
	for (int b = 0; b < sz && b < 4; b++) {
495
		uint16_t p = (uint16_t)((addr + b) & 0xFFFF);
496
		ln->bytes[b] = (uint8_t)CM_READ(p, bank, cm_x16bank_for(p, rambank, rombank));
497
	}
498
	return sz;
499
}
500
501
// Emit one line at `addr` and advance the running status estimate. Recorded
502
// status wins; otherwise *run_status is used. `max_size` is passed through to
503
// cm_fill. Returns the number of bytes the emitted row covers (always >= 1),
504
// which is the instruction's size unless a clamp cut the row short.
505
static int
506
cm_emit(uint16_t addr, uint8_t bank, uint8_t rambank, uint8_t rombank,
507
        uint8_t *run_status, uint8_t *run_e, int max_size, code_map_line_t *ln)
508
{
509
	cm_context_t *c  = cm_ctx_for_addr(addr, bank, rambank, rombank);
510
	bool     recorded = cm_anchor_ok(c, addr, bank, rambank, rombank);
511
	uint8_t  st       = recorded ? c->status[addr] : *run_status;
512
513
	int sz = cm_fill(addr, bank, rambank, rombank, st, recorded, max_size, ln);
514
515
	*run_status = cm_propagate(addr, bank, rambank, rombank, st, run_e);
516
	return sz;
517
}
518
519
// ---------------------------------------------------------------------------
520
//  Anchored alignment
521
// ---------------------------------------------------------------------------
522
523
// The decision the backward walk made about one instruction: where it starts,
524
// how long it is, and the status it was sized with. Phase B seeds itself from
525
// this rather than re-deriving the boundary from a propagated estimate, which
526
// is what used to make the walk step over `center`. It does NOT render the line
527
// verbatim -- cm_emit re-resolves the evidence at each address, and on the
528
// fallback path can reach a different answer; see the note in phase B.
529
typedef struct {
530
	uint16_t addr;
531
	uint8_t  status;
532
	uint8_t  size;
533
	bool     recorded;
534
} cm_anchor_t;
535
536
static bool
537
cm_prev_anchor(uint16_t addr, uint8_t bank, uint8_t rambank, uint8_t rombank, cm_anchor_t *out)
538
{
539
	cm_context_t *ac            = cm_ctx_for_addr(addr, bank, rambank, rombank);
540
	uint8_t       anchor_status = cm_anchor_ok(ac, addr, bank, rambank, rombank)
541
	                                  ? ac->status[addr]
542
	                                  : regs.status;
543
544
	cm_anchor_t best_recorded = { 0, 0, 0, false };
545
	cm_anchor_t best_span     = { 0, 0, 0, false };
546
	cm_anchor_t best_sync     = { 0, 0, 0, false };
547
	bool        has_recorded = false, has_span = false, has_sync = false;
548
549
	for (int backoff = 1; backoff <= CM_MAX_INSN_LEN; backoff++) {
550
		uint16_t p  = (uint16_t)((addr - backoff) & 0xFFFF);
551
		int16_t  xb = cm_x16bank_for(p, rambank, rombank);
552
		// A candidate may sit in a different window than the anchor (a window
553
		// boundary can fall mid-instruction), so resolve its context itself.
554
		cm_context_t *c        = cm_ctx_for_addr(p, bank, rambank, rombank);
555
		bool          recorded = cm_anchor_ok(c, p, bank, rambank, rombank);
556
		uint8_t       st       = recorded ? c->status[p] : anchor_status;
557
558
		char    scratch[48];
559
		int32_t eff;
560
		int     sz = cm_decode(p, bank, xb, st, scratch, (unsigned)sizeof(scratch), &eff);
561
562
		// A candidate is only valid if it decodes to land exactly on `addr`.
563
		if (sz != backoff)
564
			continue;
565
566
		cm_anchor_t cand = { p, st, (uint8_t)sz, recorded };
567
		if (!has_recorded && recorded) {
568
			best_recorded = cand;
569
			has_recorded  = true;
570
		}
571
		// The .dbg address space is a flat 24-bit one, so the program bank has
572
		// to be folded in -- otherwise bank-K code never matches its own spans,
573
		// and a bank-0 span at the same offset matches when it should not.
574
		if (!has_span && dbg_info_is_span_start(((dbg_addr_t)bank << 16) | p)) {
575
			best_span = cand;
576
			has_span  = true;
577
		}
578
		if (!has_sync) {
579
			best_sync = cand;
580
			has_sync  = true;
581
		}
582
	}
583
584
	if (has_recorded) { *out = best_recorded; return true; } // (1) live coverage
585
	if (has_span)     { *out = best_span;     return true; } // (2) cc65 .dbg spans
586
	if (has_sync)     { *out = best_sync;     return true; } // (3) self-sync
587
588
	// Nothing aligned cleanly. Back up a single byte so callers still make
589
	// progress, and say the size that implies.
590
	out->addr     = (uint16_t)((addr - 1) & 0xFFFF);
591
	out->status   = anchor_status;
592
	out->size     = 1;
593
	out->recorded = false;
594
	return true;
595
}
596
597
uint16_t
598
code_map_prev_instruction(uint16_t addr, uint8_t bank, uint8_t rambank, uint8_t rombank)
599
{
600
	cm_anchor_t an;
601
	if (!cm_prev_anchor(addr, bank, rambank, rombank, &an))
602
		return (uint16_t)((addr - 1) & 0xFFFF);
603
	return an.addr;
604
}
605
606
int
607
code_map_disasm_window(uint16_t center, uint8_t bank, uint8_t rambank, uint8_t rombank,
608
                       int lines_before, int lines_after,
609
                       code_map_line_t *out, int max_out, int *out_center_index)
610
{
611
	if (out_center_index)
612
		*out_center_index = -1;
613
	if (!out || max_out <= 0)
614
		return 0;
615
	if (lines_before < 0)
616
		lines_before = 0;
617
	if (lines_after < 1)
618
		lines_after = 1; // always include the center line itself
619
620
	// Phase A — walk backward, keeping the decision made about each instruction
621
	// rather than just its address. Re-deriving these on a forward pass would
622
	// use a different (propagated) status estimate, and where the two disagree
623
	// the walk drifts: it steps over `center`, loses the current-line marker and
624
	// renders every following line at the wrong offset.
625
	cm_anchor_t before[256];
626
	int         nbefore   = lines_before > 255 ? 255 : lines_before;
627
	int         collected = 0;
628
	uint16_t    a         = center;
629
	for (int i = 0; i < nbefore; i++) {
630
		cm_anchor_t an;
631
		if (!cm_prev_anchor(a, bank, rambank, rombank, &an))
632
			break;
633
		if (an.addr == a)
634
			break;
635
		before[collected++] = an;
636
		a                   = an.addr;
637
	}
638
639
	// Phase B — render the preceding instructions, tiling the range the backward
640
	// walk resolved: each line must start exactly where the previous one ended,
641
	// or the caller cannot draw the result.
642
	//
643
	// The walk fixes where each instruction ENDS (the next line's start), but
644
	// the fill can still land short of it or, left unchecked, past it:
645
	//   * the walk's last-resort fallback backs off one byte without decoding,
646
	//     so the byte there can decode wider than the one byte allowed for;
647
	//   * the fill truncates onto a recorded start found inside the
648
	//     instruction, which is right but leaves the remaining bytes uncovered.
649
	// Capping each fill at the distance to the boundary kills the overlap, and
650
	// looping until the boundary is reached exactly kills the gap.
651
	//
652
	// run_status is seeded from the walk's decision, but cm_emit re-resolves
653
	// the evidence at each address rather than taking before[i] verbatim, so
654
	// the first line is not guaranteed to match what the walk decided. It can
655
	// differ on the fallback path: the walk rejects a candidate whose decode
656
	// does not land on the anchor and stores the anchor's status, while cm_emit
657
	// looks up that address and may find a recorded anchor of its own. Where
658
	// they differ, cm_emit's answer is the better one -- it is live-execution
659
	// evidence recorded at that exact address -- and it cannot drift, because
660
	// the fallback has one byte of room and phase C reseeds independently.
661
	//
662
	// Because one resolved instruction can now need more than one line, this
663
	// resolves them NEAREST-THE-CENTER FIRST and places them backwards from the
664
	// end of the reserved region. Filling forwards would spend the buffer on
665
	// the furthest context and push `center` -- the address the caller asked to
666
	// be centered on -- off the end of a full buffer, which is the one row every
667
	// consumer most needs. One slot is always held back for it.
668
	code_map_line_t tile[CM_MAX_INSN_LEN];
669
	const int       b_cap = max_out > 1 ? max_out - 1 : 0;
670
	int             write = b_cap;
671
672
	for (int i = 0; i < collected && write > 0; i++) {
673
		uint16_t limit      = (i > 0) ? before[i - 1].addr : center;
674
		uint16_t a          = before[i].addr;
675
		uint8_t  run_status = before[i].status;
676
		uint8_t  run_e      = regs.e;
677
		int      k          = 0;
678
		bool     closed     = false;
679
680
		// Terminates: every emit covers at least one byte and never more than
681
		// the bytes left, so `a` closes on `limit` monotonically. `room` starts
682
		// at before[i].size, which the walk bounds by CM_MAX_INSN_LEN.
683
		do {
684
			int room = (int)(uint16_t)((limit - a) & 0xFFFF);
685
			int sz   = cm_emit(a, bank, rambank, rombank, &run_status, &run_e, room, &tile[k]);
686
			k++;
687
			a = (uint16_t)((a + sz) & 0xFFFF);
688
			if (sz >= room) {
689
				closed = true;
690
				break;
691
			}
692
		} while (k < CM_MAX_INSN_LEN);
693
694
		// Commit only a group that reached its boundary exactly; one that did
695
		// not is not a tiling of anything.
696
		//
697
		// A group too large for the space left is trimmed to its SUFFIX -- the
698
		// lines nearest the center -- rather than dropped whole. Every line in
699
		// the group is contiguous and the last one ends on the boundary, so any
700
		// suffix still tiles; it just starts the window later. (A prefix would
701
		// not: it would end short of the boundary and reintroduce the gap this
702
		// loop exists to remove.) Dropping the group instead would throw away
703
		// context the caller has room for.
704
		if (!closed)
705
			break;
706
		int from = 0;
707
		if (k > write) {
708
			from = k - write;
709
			k    = write;
710
		}
711
		write -= k;
712
		memcpy(&out[write], &tile[from], (size_t)k * sizeof(*tile));
713
	}
714
715
	int n = b_cap - write;
716
	if (write > 0 && n > 0)
717
		memmove(&out[0], &out[write], (size_t)n * sizeof(*out));
718
719
	// Phase C — forward from `center` itself, so the center line is always
720
	// present and always starts where the caller asked. The slot reserved above
721
	// guarantees n < max_out here whenever the caller gave any capacity at all.
722
	if (n < max_out && out_center_index)
723
		*out_center_index = n;
724
725
	uint16_t addr       = center;
726
	uint8_t  run_status = code_map_recorded_status(center, bank, rambank, rombank, regs.status);
727
	uint8_t  run_e      = regs.e;
728
	for (int i = 0; i < lines_after && n < max_out; i++) {
729
		int sz = cm_emit(addr, bank, rambank, rombank, &run_status, &run_e, 0, &out[n]);
730
		n++;
731
		addr = (uint16_t)((addr + sz) & 0xFFFF);
732
	}
733
	return n;
734
}
735
736
int
737
code_map_disasm_forward(uint16_t start, uint8_t bank, uint8_t rambank, uint8_t rombank,
738
                        int count, code_map_line_t *out, int max_out, uint16_t *next_addr)
739
{
740
	if (!out || max_out <= 0) {
741
		if (next_addr)
742
			*next_addr = start;
743
		return 0;
744
	}
745
	if (count > max_out)
746
		count = max_out;
747
	if (count < 0)
748
		count = 0;
749
750
	uint16_t addr       = start;
751
	uint8_t  run_status = code_map_recorded_status(start, bank, rambank, rombank, regs.status);
752
	uint8_t  run_e      = regs.e;
753
	int      n          = 0;
754
755
	while (n < count) {
756
		int sz = cm_emit(addr, bank, rambank, rombank, &run_status, &run_e, 0, &out[n]);
757
		n++;
758
		addr = (uint16_t)((addr + sz) & 0xFFFF);
759
	}
760
	if (next_addr)
761
		*next_addr = addr;
762
	return n;
763
}