emulator.c 87.6 KiB raw
1
#include <errno.h>
2
#include <fcntl.h>
3
#include <limits.h>
4
#include <stdint.h>
5
#include <stdio.h>
6
#include <stdlib.h>
7
#include <string.h>
8
#include <sys/ioctl.h>
9
#include <termios.h>
10
#include <unistd.h>
11
12
#include "color.h"
13
#include "io.h"
14
#include "jit.h"
15
#include "riscv.h"
16
#include "riscv/debug.h"
17
#include "types.h"
18
19
#ifndef PATH_MAX
20
#define PATH_MAX 4096
21
#endif
22
23
/* Define BINARY for `bail` and `assert` functions. */
24
#undef BINARY
25
#define BINARY "emulator"
26
27
#undef assert
28
#define assert(condition)                                                      \
29
    ((condition) ? (void)0 : _assert_failed(#condition, __FILE__, __LINE__))
30
31
static inline __attribute__((noreturn)) void _assert_failed(
32
    const char *condition, const char *file, int line
33
) {
34
    fprintf(stderr, "%s:%d: assertion `%s` failed\n", file, line, condition);
35
    abort();
36
}
37
38
/* Maximum physical memory size (384MB). The runtime can choose any size up to
39
 * this value with `-memory-size=...`. */
40
#define MEMORY_SIZE              (384 * 1024 * 1024)
41
/* Default physical memory size (128MB). */
42
#define DEFAULT_MEMORY_SIZE      (128 * 1024 * 1024)
43
/* Program memory size (4MB), reserved at start of memory for program code. */
44
#define PROGRAM_SIZE             (4 * 1024 * 1024)
45
/* Writable data region base. */
46
#define DATA_RW_OFFSET           0xFFFFF0
47
/* Data memory starts at writable data region. */
48
#define DATA_MEMORY_START        DATA_RW_OFFSET
49
/* Default data memory size. */
50
#define DEFAULT_DATA_MEMORY_SIZE (DEFAULT_MEMORY_SIZE - DATA_MEMORY_START)
51
/* Default stack size (256KB), allocated at the end of memory. */
52
#define DEFAULT_STACK_SIZE       (256 * 1024)
53
/* Maximum instructions to show in the TUI. */
54
#define MAX_INSTR_DISPLAY        40
55
/* Stack words to display in the TUI. */
56
#define STACK_DISPLAY_WORDS      32
57
/* Maximum number of CPU state snapshots to store for undo. */
58
#define MAX_SNAPSHOTS            64
59
/* Maximum open files in guest runtime. */
60
#define MAX_OPEN_FILES           32
61
/* Number of history entries to print when reporting faults. */
62
#define FAULT_TRACE_DEPTH        8
63
/* Instruction trace depth for headless tracing. */
64
#define TRACE_HISTORY            64
65
/* Maximum number of steps executed in headless mode before timing out. */
66
#define HEADLESS_MAX_STEPS       ((u64)1000000000000)
67
/* Height of header and footer in rows. */
68
#define HEADER_HEIGHT            2
69
#define FOOTER_HEIGHT            3
70
/* Read-only data offset. */
71
#define DATA_RO_OFFSET           0x10000
72
/* TTY escape codes. */
73
#define TTY_CLEAR                "\033[2J\033[H"
74
#define TTY_GOTO_RC              "\033[%d;%dH"
75
/* Exit code returned on EBREAK. */
76
#define EBREAK_EXIT_CODE         133
77
/* Self-contained Radiance image header. */
78
#define IMAGE_MAGIC              0x30444152U
79
#define IMAGE_VERSION            1U
80
#define IMAGE_HEADER_SIZE        20U
81
82
/* Registers displayed in the TUI, in order. */
83
static const reg_t registers_displayed[] = {
84
    SP, FP, RA, A0, A1, A2, A3, A4, A5, A6, A7, T0, T1, T2, T3, T4, T5, T6
85
};
86
87
/* Display mode for immediates and values. */
88
enum display { DISPLAY_HEX, DISPLAY_DEC };
89
90
/* Debug info entry mapping PC to source location. */
91
struct debug_entry {
92
    u32  pc;
93
    u32  offset;
94
    char file[PATH_MAX];
95
};
96
97
/* Debug info table. */
98
struct debug_info {
99
    struct debug_entry *entries;
100
    size_t              count;
101
    size_t              capacity;
102
};
103
104
/* Global debug info. */
105
static struct debug_info g_debug = { 0 };
106
107
/* CPU state. */
108
struct cpu {
109
    u64      regs[REGISTERS];
110
    u32      pc;          /* Program counter. */
111
    u32      programsize; /* Size of loaded program. */
112
    instr_t *program;     /* Program instructions. */
113
    bool     running;     /* Execution status. */
114
    bool     faulted;     /* There was a fault in execution. */
115
    bool     ebreak;      /* Program terminated via EBREAK. */
116
    reg_t    modified;    /* Index of the last modified register. */
117
    u64      mstatus;
118
    u64      mie;
119
    u64      mtvec;
120
    u64      mscratch;
121
    u64      mepc;
122
    u64      mcause;
123
    u64      mtval;
124
    u64      mip;
125
    u64      mhartid;
126
    u8       privilege;   /* RISC-V privilege level: U=0, M=3. */
127
};
128
129
/* Snapshot of CPU and memory state for reversing execution. */
130
struct snapshot {
131
    struct cpu cpu;                 /* Copy of CPU state. */
132
    u8         memory[MEMORY_SIZE]; /* Copy of memory. */
133
};
134
135
/* Circular buffer for snapshots. */
136
struct snapshot_buffer {
137
    struct snapshot snapshots[MAX_SNAPSHOTS];
138
    int             head; /* Index of most recent snapshot. */
139
    int             count;
140
};
141
142
/* CPU memory. */
143
static u8 memory[MEMORY_SIZE];
144
145
/* Loaded section sizes, used for bounds checking and diagnostics. */
146
static u32 program_base  = 0;
147
static u32 program_bytes = 0;
148
static u32 rodata_bytes  = 0;
149
static u32 data_bytes    = 0;
150
151
/* Snapshot buffer. */
152
static struct snapshot_buffer snapshots;
153
154
/* File descriptor table for guest file operations. */
155
static int guest_fds[MAX_OPEN_FILES];
156
157
/* Initialize the guest file descriptor table. */
158
static void guest_fd_table_init(void) {
159
    for (int i = 0; i < MAX_OPEN_FILES; i++) {
160
        guest_fds[i] = -1;
161
    }
162
}
163
164
/* Add a host file descriptor to the guest table. */
165
static int guest_fd_table_add(int host_fd) {
166
    /* Start at 3 to skip stdin/stdout/stderr. */
167
    for (int i = 3; i < MAX_OPEN_FILES; i++) {
168
        if (guest_fds[i] == -1) {
169
            guest_fds[i] = host_fd;
170
            return i;
171
        }
172
    }
173
    return -1;
174
}
175
176
/* Get the host fd for a guest file descriptor. */
177
static int guest_fd_table_get(int guest_fd) {
178
    if (guest_fd < 0 || guest_fd >= MAX_OPEN_FILES) {
179
        return -1;
180
    }
181
    /* Standard streams map directly. */
182
    if (guest_fd < 3) {
183
        return guest_fd;
184
    }
185
    return guest_fds[guest_fd];
186
}
187
188
/* Remove a file descriptor from the guest table. */
189
static void guest_fd_table_remove(int guest_fd) {
190
    if (guest_fd >= 3 && guest_fd < MAX_OPEN_FILES) {
191
        guest_fds[guest_fd] = -1;
192
    }
193
}
194
195
/* Single entry in the instruction trace ring buffer. */
196
struct trace_entry {
197
    u32     pc;
198
    instr_t instr;
199
    u64     regs[REGISTERS];
200
};
201
202
/* Circular buffer of recent instruction traces. */
203
struct trace_ring {
204
    struct trace_entry entries[TRACE_HISTORY];
205
    int                head;
206
    int                count;
207
};
208
209
/* Headless-mode instruction trace buffer. */
210
static struct trace_ring headless_trace = { .head = -1, .count = 0 };
211
212
/* Terminal dimensions in rows and columns. */
213
struct termsize {
214
    int rows;
215
    int cols;
216
};
217
218
/* Forward declarations. */
219
static void ui_render_instructions(
220
    struct cpu *, int col, int width, int height
221
);
222
static void ui_render_registers(
223
    struct cpu *, enum display, int col, int height
224
);
225
static void ui_render_stack(struct cpu *, enum display, int col, int height);
226
static void ui_render(struct cpu *, enum display);
227
static void cpu_execute(struct cpu *, enum display, bool headless);
228
static void emit_fault_diagnostics(struct cpu *, u32 pc);
229
230
/* Emulator runtime options, populated from CLI flags. */
231
struct emulator_options {
232
    bool stack_guard;
233
    u32  stack_size;
234
    bool debug_enabled;
235
    bool trace_headless;
236
    bool trace_enabled;
237
    bool trace_print_instructions;
238
    u32  trace_depth;
239
    u64  headless_max_steps;
240
    u32  memory_size;
241
    u32  data_memory_size;
242
    bool watch_enabled;
243
    u32  watch_addr;
244
    u32  watch_size;
245
    u32  watch_arm_pc;
246
    bool watch_zero_only;
247
    u32  watch_skip;
248
    bool watch_backtrace;
249
    u32  watch_backtrace_depth;
250
    bool validate_memory;
251
    bool count_instructions;
252
    bool jit_disabled;
253
    bool machine_mode;
254
};
255
256
/* Global emulator options. */
257
static struct emulator_options g_opts = {
258
    .stack_guard              = true,
259
    .stack_size               = DEFAULT_STACK_SIZE,
260
    .debug_enabled            = false,
261
    .trace_headless           = false,
262
    .trace_enabled            = false,
263
    .trace_print_instructions = false,
264
    .trace_depth              = 32,
265
    .headless_max_steps       = HEADLESS_MAX_STEPS,
266
    .memory_size              = DEFAULT_MEMORY_SIZE,
267
    .data_memory_size         = DEFAULT_DATA_MEMORY_SIZE,
268
    .watch_enabled            = false,
269
    .watch_addr               = 0,
270
    .watch_size               = 0,
271
    .watch_arm_pc             = 0,
272
    .watch_zero_only          = false,
273
    .watch_skip               = 0,
274
    .watch_backtrace          = false,
275
    .watch_backtrace_depth    = 8,
276
    .validate_memory          = true,
277
    .count_instructions       = false,
278
    .jit_disabled             = false,
279
    .machine_mode             = false,
280
};
281
282
static void dump_watch_context(struct cpu *, u32 addr, u32 size, u32 value);
283
284
/* Return true if the given address range overlaps the watched region. */
285
static inline bool watch_hit(u32 addr, u32 size) {
286
    if (!g_opts.watch_enabled)
287
        return false;
288
    u32 start = g_opts.watch_addr;
289
    u32 end   = start + (g_opts.watch_size ? g_opts.watch_size : 1);
290
    return addr < end && (addr + size) > start;
291
}
292
293
/* Check a store against the memory watchpoint and halt on a hit. */
294
static inline void watch_store(struct cpu *cpu, u32 addr, u32 size, u32 value) {
295
    if (!watch_hit(addr, size))
296
        return;
297
    if (g_opts.watch_arm_pc && cpu && cpu->pc < g_opts.watch_arm_pc)
298
        return;
299
    if (g_opts.watch_zero_only && value != 0)
300
        return;
301
    if (g_opts.watch_skip > 0) {
302
        g_opts.watch_skip--;
303
        return;
304
    }
305
    fprintf(
306
        stderr,
307
        "[WATCH] pc=%08x addr=%08x size=%u value=%08x\n",
308
        cpu ? cpu->pc : 0,
309
        addr,
310
        size,
311
        value
312
    );
313
    dump_watch_context(cpu, addr, size, value);
314
    if (cpu) {
315
        cpu->running = false;
316
        cpu->faulted = true;
317
        cpu->ebreak  = true;
318
    }
319
}
320
321
/* Fixed stack guard zone size. */
322
#define STACK_GUARD_BYTES 16
323
324
/* Clamp and align stack size to a valid range. */
325
static inline u32 sanitize_stack_bytes(u32 bytes) {
326
    if (bytes < WORD_SIZE)
327
        bytes = WORD_SIZE;
328
    bytes = (u32)align((i32)bytes, WORD_SIZE);
329
330
    /* Keep at least one word for guard computations. */
331
    if (bytes >= g_opts.memory_size)
332
        bytes = g_opts.memory_size - WORD_SIZE;
333
334
    return bytes;
335
}
336
337
/* Return the active stack guard size, or 0 if guards are disabled. */
338
static inline u32 stack_guard_bytes(void) {
339
    if (!g_opts.stack_guard)
340
        return 0;
341
    return STACK_GUARD_BYTES;
342
}
343
344
/* Return the configured stack size. */
345
static inline u32 stack_size(void) {
346
    return g_opts.stack_size;
347
}
348
349
/* Return the highest addressable word-aligned memory address. */
350
static inline u32 memory_top(void) {
351
    return g_opts.memory_size - WORD_SIZE;
352
}
353
354
/* Return the lowest address in the stack region. */
355
static inline u32 stack_bottom(void) {
356
    return memory_top() - stack_size() + WORD_SIZE;
357
}
358
359
/* Return the highest usable stack address (inside the guard zone). */
360
static inline u32 stack_usable_top(void) {
361
    u32 guard = stack_guard_bytes();
362
    u32 size  = stack_size();
363
    if (guard >= size)
364
        guard = size - WORD_SIZE;
365
    return memory_top() - guard;
366
}
367
368
/* Return the lowest usable stack address (inside the guard zone). */
369
static inline u32 stack_usable_bottom(void) {
370
    u32 guard = stack_guard_bytes();
371
    u32 size  = stack_size();
372
    if (guard >= size)
373
        guard = size - WORD_SIZE;
374
    return stack_bottom() + guard;
375
}
376
377
/* Return true if addr falls within the stack region. */
378
static inline bool stack_contains(u32 addr) {
379
    return addr >= stack_bottom() && addr <= memory_top();
380
}
381
382
/* Return true if the range [start, end] overlaps a stack guard zone. */
383
static inline bool stack_guard_overlaps(u32 guard, u32 start, u32 end) {
384
    if (guard == 0)
385
        return false;
386
387
    u32 low_guard_end    = stack_bottom() + guard - 1;
388
    u32 high_guard_start = memory_top() - guard + 1;
389
390
    return (start <= low_guard_end && end >= stack_bottom()) ||
391
           (end >= high_guard_start && start <= memory_top());
392
}
393
394
/* Return true if addr falls inside a stack guard zone. */
395
static inline bool stack_guard_contains(u32 guard, u32 addr) {
396
    return stack_guard_overlaps(guard, addr, addr);
397
}
398
399
/* Load a 16-bit value from memory in little-endian byte order. */
400
static inline u16 memory_load_u16(u32 addr) {
401
    return (u16)(memory[addr] | (memory[addr + 1] << 8));
402
}
403
404
/* Load a 32-bit value from memory in little-endian byte order. */
405
static inline u32 memory_load_u32(u32 addr) {
406
    return memory[addr] | (memory[addr + 1] << 8) | (memory[addr + 2] << 16) |
407
           (memory[addr + 3] << 24);
408
}
409
410
/* Load a 64-bit value from memory in little-endian byte order. */
411
static inline u64 memory_load_u64(u32 addr) {
412
    return (u64)memory[addr] | ((u64)memory[addr + 1] << 8) |
413
           ((u64)memory[addr + 2] << 16) | ((u64)memory[addr + 3] << 24) |
414
           ((u64)memory[addr + 4] << 32) | ((u64)memory[addr + 5] << 40) |
415
           ((u64)memory[addr + 6] << 48) | ((u64)memory[addr + 7] << 56);
416
}
417
418
/* Store a byte to memory. */
419
static inline void memory_store_u8(u32 addr, u8 value) {
420
    memory[addr] = value;
421
}
422
423
/* Store a 16-bit value to memory in little-endian byte order. */
424
static inline void memory_store_u16(u32 addr, u16 value) {
425
    memory[addr]     = (u8)(value & 0xFF);
426
    memory[addr + 1] = (u8)((value >> 8) & 0xFF);
427
}
428
429
/* Store a 32-bit value to memory in little-endian byte order. */
430
static inline void memory_store_u32(u32 addr, u32 value) {
431
    assert(addr + 3 < g_opts.memory_size);
432
    memory[addr]     = (u8)(value & 0xFF);
433
    memory[addr + 1] = (u8)((value >> 8) & 0xFF);
434
    memory[addr + 2] = (u8)((value >> 16) & 0xFF);
435
    memory[addr + 3] = (u8)((value >> 24) & 0xFF);
436
}
437
438
/* Store a 64-bit value to memory in little-endian byte order. */
439
static inline void memory_store_u64(u32 addr, u64 value) {
440
    assert(addr + 7 < g_opts.memory_size);
441
    memory_store_u32(addr, (u32)(value & 0xFFFFFFFF));
442
    memory_store_u32(addr + 4, (u32)(value >> 32));
443
}
444
445
/* Load a 32-bit word from memory, returning false if out of bounds. */
446
static inline bool load_word_safe(u32 addr, u32 *out) {
447
    if (addr > g_opts.memory_size - WORD_SIZE)
448
        return false;
449
    *out = memory_load_u32(addr);
450
    return true;
451
}
452
453
/* Dump register state and backtrace when a watchpoint fires. */
454
static void dump_watch_context(struct cpu *cpu, u32 addr, u32 size, u32 value) {
455
    if (!g_opts.watch_backtrace || !cpu)
456
        return;
457
458
    (void)addr;
459
    (void)size;
460
    (void)value;
461
462
    fprintf(
463
        stderr,
464
        "         regs: SP=%08x FP=%08x RA=%08x A0=%08x A1=%08x A2=%08x "
465
        "A3=%08x\n",
466
        (u32)cpu->regs[SP],
467
        (u32)cpu->regs[FP],
468
        (u32)cpu->regs[RA],
469
        (u32)cpu->regs[A0],
470
        (u32)cpu->regs[A1],
471
        (u32)cpu->regs[A2],
472
        (u32)cpu->regs[A3]
473
    );
474
475
    u64 fp64 = cpu->regs[FP];
476
    u32 fp   = (fp64 <= (u64)UINT32_MAX) ? (u32)fp64 : 0;
477
    u32 pc   = cpu->pc;
478
479
    fprintf(
480
        stderr, "         backtrace (depth %u):\n", g_opts.watch_backtrace_depth
481
    );
482
483
    for (u32 depth = 0; depth < g_opts.watch_backtrace_depth; depth++) {
484
        bool has_frame = stack_contains(fp) && fp >= (2 * WORD_SIZE);
485
        u32  saved_ra  = 0;
486
        u32  prev_fp   = 0;
487
488
        if (has_frame) {
489
            has_frame = load_word_safe(fp - WORD_SIZE, &saved_ra) &&
490
                        load_word_safe(fp - 2 * WORD_SIZE, &prev_fp) &&
491
                        stack_contains(prev_fp);
492
        }
493
494
        fprintf(
495
            stderr,
496
            "           #%u pc=%08x fp=%08x ra=%08x%s\n",
497
            depth,
498
            pc,
499
            fp,
500
            has_frame ? saved_ra : 0,
501
            has_frame ? "" : " (?)"
502
        );
503
504
        if (!has_frame || prev_fp == fp || prev_fp == 0)
505
            break;
506
507
        pc = saved_ra;
508
        fp = prev_fp;
509
    }
510
}
511
512
/* Print usage information and return 1. */
513
static int usage(const char *prog) {
514
    fprintf(
515
        stderr,
516
        "usage: %s [-run] [-machine] [-no-guard-stack]"
517
        " [-stack-size=KB] [-no-validate] [-debug]"
518
        " [-trace|-trace-headless] [-trace-depth=n] [-trace-instructions]"
519
        " [-max-steps=n] [-memory-size=KB] [-data-size=KB]"
520
        " [-watch=addr] [-watch-size=bytes] [-watch-arm-pc=addr]"
521
        " [-watch-zero-only] [-watch-skip=n]"
522
        " [-watch-backtrace] [-watch-bt-depth=n]"
523
        " [-count-instructions] [-no-jit]"
524
        " <file.bin> [program args...]\n",
525
        prog
526
    );
527
    return 1;
528
}
529
530
/* Configuration parsed from CLI flags prior to launching the emulator. */
531
struct cli_config {
532
    bool        headless;
533
    const char *program_path;
534
    int         arg_index;
535
};
536
537
/* Parse a string as an unsigned 32-bit integer.  Returns false on error. */
538
static bool parse_u32(const char *str, const char *label, int base, u32 *out) {
539
    char *end         = NULL;
540
    errno             = 0;
541
    unsigned long val = strtoul(str, &end, base);
542
    if (errno != 0 || end == str || *end != '\0') {
543
        fprintf(stderr, "invalid %s '%s'; expected integer\n", label, str);
544
        return false;
545
    }
546
    if (val > UINT32_MAX)
547
        val = UINT32_MAX;
548
    *out = (u32)val;
549
    return true;
550
}
551
552
/* Parse a string as an unsigned 64-bit integer.  Returns false on error. */
553
static bool parse_u64(const char *str, const char *label, u64 *out) {
554
    char *end              = NULL;
555
    errno                  = 0;
556
    unsigned long long val = strtoull(str, &end, 10);
557
    if (errno != 0 || end == str || *end != '\0') {
558
        fprintf(stderr, "invalid %s '%s'; expected integer\n", label, str);
559
        return false;
560
    }
561
    *out = (u64)val;
562
    return true;
563
}
564
565
/* Parse and validate the physical memory size passed to -memory-size=. */
566
static bool parse_memory_size_value(const char *value) {
567
    u64 parsed;
568
    if (!parse_u64(value, "memory size", &parsed))
569
        return false;
570
    u64 bytes = parsed * 1024;
571
    if (bytes <= (u64)(DATA_MEMORY_START + WORD_SIZE)) {
572
        fprintf(
573
            stderr,
574
            "memory size too small; minimum is %u KB\n",
575
            (DATA_MEMORY_START + WORD_SIZE + 1024) / 1024
576
        );
577
        return false;
578
    }
579
    if (bytes > (u64)MEMORY_SIZE) {
580
        fprintf(
581
            stderr,
582
            "memory size too large; maximum is %u KB (recompile emulator "
583
            "to increase)\n",
584
            MEMORY_SIZE / 1024
585
        );
586
        return false;
587
    }
588
    g_opts.memory_size = (u32)bytes;
589
    return true;
590
}
591
592
/* Parse and validate the depth passed to -trace-depth=. */
593
static bool parse_trace_depth_value(const char *value) {
594
    u32 parsed;
595
    if (!parse_u32(value, "trace depth", 10, &parsed))
596
        return false;
597
    if (parsed == 0) {
598
        fprintf(stderr, "trace depth must be greater than zero\n");
599
        return false;
600
    }
601
    if (parsed > TRACE_HISTORY)
602
        parsed = TRACE_HISTORY;
603
    g_opts.trace_depth = parsed;
604
    return true;
605
}
606
607
/* Parse and validate the step limit passed to -max-steps=. */
608
static bool parse_max_steps_value(const char *value) {
609
    u64 parsed;
610
    if (!parse_u64(value, "max steps", &parsed))
611
        return false;
612
    if (parsed == 0) {
613
        fprintf(stderr, "max steps must be greater than zero\n");
614
        return false;
615
    }
616
    g_opts.headless_max_steps = parsed;
617
    return true;
618
}
619
620
/* Parse and validate the stack size passed to -stack-size=. */
621
static bool parse_stack_size_value(const char *value) {
622
    u64 parsed;
623
    if (!parse_u64(value, "stack size", &parsed))
624
        return false;
625
    if (parsed == 0) {
626
        fprintf(stderr, "stack size must be greater than zero\n");
627
        return false;
628
    }
629
    u64 bytes = parsed * 1024;
630
    if (bytes >= MEMORY_SIZE) {
631
        fprintf(
632
            stderr,
633
            "stack size too large; maximum is %u KB\n",
634
            MEMORY_SIZE / 1024
635
        );
636
        return false;
637
    }
638
    g_opts.stack_size = sanitize_stack_bytes((u32)bytes);
639
    return true;
640
}
641
642
/* Parse and validate the data size passed to -data-size=. */
643
static bool parse_data_size_value(const char *value) {
644
    u64 parsed;
645
    if (!parse_u64(value, "data size", &parsed))
646
        return false;
647
    if (parsed == 0) {
648
        fprintf(stderr, "data size must be greater than zero\n");
649
        return false;
650
    }
651
    u64 bytes = parsed * 1024;
652
    if (bytes > (u64)MEMORY_SIZE) {
653
        fprintf(
654
            stderr,
655
            "data size too large; maximum is %u KB\n",
656
            MEMORY_SIZE / 1024
657
        );
658
        return false;
659
    }
660
    g_opts.data_memory_size = (u32)bytes;
661
    return true;
662
}
663
664
/* Validate that the stack fits within available memory. */
665
static bool validate_memory_layout(void) {
666
    if (g_opts.stack_size >= g_opts.memory_size) {
667
        fprintf(
668
            stderr,
669
            "stack size (%u) must be smaller than memory size (%u)\n",
670
            g_opts.stack_size,
671
            g_opts.memory_size
672
        );
673
        return false;
674
    }
675
    return true;
676
}
677
678
/* Parse emulator CLI arguments, returning the selected mode and file path. */
679
static bool parse_cli_args(int argc, char *argv[], struct cli_config *cfg) {
680
    bool headless = false;
681
    int  argi     = 1;
682
683
    while (argi < argc) {
684
        const char *arg = argv[argi];
685
686
        if (strcmp(arg, "--") == 0) {
687
            argi++;
688
            break;
689
        }
690
        if (arg[0] != '-')
691
            break;
692
693
        if (strcmp(arg, "-run") == 0) {
694
            headless = true;
695
            argi++;
696
            continue;
697
        }
698
        if (strcmp(arg, "-machine") == 0) {
699
            g_opts.machine_mode = true;
700
            g_opts.jit_disabled = true;
701
            argi++;
702
            continue;
703
        }
704
705
        if (strncmp(arg, "-stack-size=", 12) == 0) {
706
            if (!parse_stack_size_value(arg + 12))
707
                return false;
708
            argi++;
709
            continue;
710
        }
711
        if (strcmp(arg, "-no-guard-stack") == 0) {
712
            g_opts.stack_guard = false;
713
            argi++;
714
            continue;
715
        }
716
        if (strcmp(arg, "-no-validate") == 0) {
717
            g_opts.validate_memory = false;
718
            argi++;
719
            continue;
720
        }
721
        if (strcmp(arg, "-debug") == 0) {
722
            g_opts.debug_enabled = true;
723
            argi++;
724
            continue;
725
        }
726
        if (strcmp(arg, "-trace") == 0 || strcmp(arg, "-trace-headless") == 0) {
727
            g_opts.trace_enabled = true;
728
            argi++;
729
            continue;
730
        }
731
        if (strcmp(arg, "-trace-instructions") == 0) {
732
            g_opts.trace_print_instructions = true;
733
            argi++;
734
            continue;
735
        }
736
        if (strncmp(arg, "-trace-depth=", 13) == 0) {
737
            if (!parse_trace_depth_value(arg + 13))
738
                return false;
739
            argi++;
740
            continue;
741
        }
742
        if (strncmp(arg, "-max-steps=", 11) == 0) {
743
            if (!parse_max_steps_value(arg + 11))
744
                return false;
745
            argi++;
746
            continue;
747
        }
748
        if (strncmp(arg, "-memory-size=", 13) == 0) {
749
            if (!parse_memory_size_value(arg + 13))
750
                return false;
751
            argi++;
752
            continue;
753
        }
754
        if (strncmp(arg, "-data-size=", 11) == 0) {
755
            if (!parse_data_size_value(arg + 11))
756
                return false;
757
            argi++;
758
            continue;
759
        }
760
        if (strncmp(arg, "-watch=", 7) == 0) {
761
            if (!parse_u32(arg + 7, "watch address", 0, &g_opts.watch_addr))
762
                return false;
763
            g_opts.watch_enabled = true;
764
            argi++;
765
            continue;
766
        }
767
        if (strncmp(arg, "-watch-size=", 12) == 0) {
768
            if (!parse_u32(arg + 12, "watch size", 0, &g_opts.watch_size))
769
                return false;
770
            if (g_opts.watch_size == 0) {
771
                fprintf(stderr, "watch size must be greater than zero\n");
772
                return false;
773
            }
774
            argi++;
775
            continue;
776
        }
777
        if (strcmp(arg, "-watch-zero-only") == 0) {
778
            g_opts.watch_zero_only = true;
779
            argi++;
780
            continue;
781
        }
782
        if (strncmp(arg, "-watch-skip=", 12) == 0) {
783
            if (!parse_u32(arg + 12, "watch skip", 0, &g_opts.watch_skip))
784
                return false;
785
            argi++;
786
            continue;
787
        }
788
        if (strcmp(arg, "-watch-disable") == 0) {
789
            g_opts.watch_enabled = false;
790
            argi++;
791
            continue;
792
        }
793
        if (strncmp(arg, "-watch-arm-pc=", 14) == 0) {
794
            if (!parse_u32(arg + 14, "watch arm pc", 0, &g_opts.watch_arm_pc))
795
                return false;
796
            argi++;
797
            continue;
798
        }
799
        if (strcmp(arg, "-watch-backtrace") == 0) {
800
            g_opts.watch_backtrace = true;
801
            argi++;
802
            continue;
803
        }
804
        if (strncmp(arg, "-watch-bt-depth=", 16) == 0) {
805
            u32 depth;
806
            if (!parse_u32(arg + 16, "watch backtrace depth", 0, &depth))
807
                return false;
808
            if (depth == 0) {
809
                fprintf(
810
                    stderr, "watch backtrace depth must be greater than zero\n"
811
                );
812
                return false;
813
            }
814
            g_opts.watch_backtrace       = true;
815
            g_opts.watch_backtrace_depth = depth;
816
            argi++;
817
            continue;
818
        }
819
        if (strcmp(arg, "-count-instructions") == 0) {
820
            g_opts.count_instructions = true;
821
            argi++;
822
            continue;
823
        }
824
        if (strcmp(arg, "-no-jit") == 0) {
825
            g_opts.jit_disabled = true;
826
            argi++;
827
            continue;
828
        }
829
        usage(argv[0]);
830
831
        return false;
832
    }
833
    if (argi >= argc) {
834
        usage(argv[0]);
835
        return false;
836
    }
837
    cfg->program_path = argv[argi++];
838
    cfg->arg_index    = argi;
839
    cfg->headless     = headless;
840
    if (g_opts.watch_enabled && g_opts.watch_size == 0)
841
        g_opts.watch_size = 4;
842
    if (!validate_memory_layout())
843
        return false;
844
845
    g_opts.stack_size = sanitize_stack_bytes(g_opts.stack_size);
846
847
    return true;
848
}
849
850
/* Validate a load or store against memory bounds and stack guards. */
851
static bool validate_memory_access(
852
    struct cpu *cpu,
853
    u64         addr,
854
    u32         size,
855
    reg_t       base_reg,
856
    const char *op,
857
    bool        is_store
858
) {
859
    /* Skip validation for performance if disabled. */
860
    if (!g_opts.validate_memory)
861
        return true;
862
863
    if (size == 0)
864
        size = 1;
865
866
    const char *kind = is_store ? "store" : "load";
867
868
    u64 span_end = addr + (u64)size;
869
    if (addr > (u64)g_opts.memory_size || span_end > (u64)g_opts.memory_size) {
870
        printf(
871
            "Memory %s out of bounds at PC=%08x: addr=%016llx size=%u (%s)\n",
872
            kind,
873
            cpu->pc,
874
            (unsigned long long)addr,
875
            size,
876
            op
877
        );
878
        cpu->running = false;
879
        emit_fault_diagnostics(cpu, cpu->pc);
880
        return false;
881
    }
882
883
    u32 addr32 = (u32)addr;
884
    u32 end    = (u32)(span_end - 1);
885
886
    if (addr32 < DATA_MEMORY_START) {
887
        if (is_store) {
888
            printf(
889
                "Read-only memory store at PC=%08x: addr=%08x size=%u (%s)\n",
890
                cpu->pc,
891
                addr32,
892
                size,
893
                op
894
            );
895
            cpu->running = false;
896
            emit_fault_diagnostics(cpu, cpu->pc);
897
            return false;
898
        }
899
        return true;
900
    }
901
902
    u32  guard    = stack_guard_bytes();
903
    u64  base_val = cpu->regs[base_reg];
904
    bool base_in_stack =
905
        base_val <= (u64)UINT32_MAX && stack_contains((u32)base_val);
906
    bool start_in_stack = stack_contains(addr32);
907
    bool end_in_stack   = stack_contains(end);
908
909
    if (base_in_stack || start_in_stack || end_in_stack) {
910
        u32 bottom = stack_bottom();
911
        if (addr32 < bottom || end > memory_top()) {
912
            printf(
913
                "Stack %s out of bounds at PC=%08x: base=%s (0x%08x) addr=%08x "
914
                "size=%u (%s)\n",
915
                kind,
916
                cpu->pc,
917
                reg_names[base_reg],
918
                (u32)cpu->regs[base_reg],
919
                addr32,
920
                size,
921
                op
922
            );
923
            cpu->running = false;
924
            emit_fault_diagnostics(cpu, cpu->pc);
925
            return false;
926
        }
927
        if (stack_guard_overlaps(guard, addr32, end)) {
928
            printf(
929
                "Stack guard %s violation at PC=%08x: base=%s (0x%08x) "
930
                "addr=%08x "
931
                "size=%u guard=%u (%s)\n",
932
                kind,
933
                cpu->pc,
934
                reg_names[base_reg],
935
                (u32)cpu->regs[base_reg],
936
                addr32,
937
                size,
938
                guard,
939
                op
940
            );
941
            cpu->running = false;
942
            emit_fault_diagnostics(cpu, cpu->pc);
943
            return false;
944
        }
945
    }
946
    return true;
947
}
948
949
/* Validate that a register holds a valid stack address. */
950
static bool validate_stack_register(
951
    struct cpu *cpu, reg_t reg, const char *label, u32 pc, bool optional
952
) {
953
    /* Skip validation for performance if disabled. */
954
    if (!g_opts.validate_memory)
955
        return true;
956
957
    u64 value = cpu->regs[reg];
958
959
    if (optional && value == 0)
960
        return true;
961
962
    /* Detect addresses with upper bits set -- these can never be valid
963
     * stack addresses in the emulator's physical memory. */
964
    if (value > (u64)UINT32_MAX || (u32)value < stack_bottom() ||
965
        (u32)value > memory_top()) {
966
        printf(
967
            "%s (%s) out of stack bounds at PC=%08x: value=%016llx\n",
968
            label,
969
            reg_names[reg],
970
            pc,
971
            (unsigned long long)value
972
        );
973
        cpu->running = false;
974
        emit_fault_diagnostics(cpu, pc);
975
        return false;
976
    }
977
978
    u32 guard = stack_guard_bytes();
979
980
    if (stack_guard_contains(guard, (u32)value)) {
981
        printf(
982
            "Stack guard triggered by %s (%s) at PC=%08x: value=%08x "
983
            "guard=%u\n",
984
            label,
985
            reg_names[reg],
986
            pc,
987
            (u32)value,
988
            guard
989
        );
990
        cpu->running = false;
991
        emit_fault_diagnostics(cpu, pc);
992
        return false;
993
    }
994
    return true;
995
}
996
997
/* Toggle stack guarding in the TUI and re-validate live stack registers. */
998
static void toggle_stack_guard(struct cpu *cpu) {
999
    g_opts.stack_guard = !g_opts.stack_guard;
1000
1001
    printf(
1002
        "\nStack guard %s (%u bytes)\n",
1003
        g_opts.stack_guard ? "enabled" : "disabled",
1004
        STACK_GUARD_BYTES
1005
    );
1006
1007
    if (g_opts.stack_guard && cpu->running) {
1008
        validate_stack_register(cpu, SP, "SP", cpu->pc, false);
1009
        if (cpu->running) {
1010
            validate_stack_register(cpu, FP, "FP", cpu->pc, true);
1011
        }
1012
    }
1013
}
1014
1015
/* Get terminal dimensions. */
1016
static struct termsize termsize(void) {
1017
    struct winsize  w;
1018
    struct termsize size = { 24, 80 }; /* Default fallback. */
1019
1020
    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) {
1021
        size.rows = w.ws_row;
1022
        size.cols = w.ws_col;
1023
    }
1024
    return size;
1025
}
1026
1027
/* Take a snapshot of the current CPU and memory state. */
1028
static void snapshot_save(struct cpu *cpu) {
1029
    int nexti = (snapshots.head + 1 + MAX_SNAPSHOTS) % MAX_SNAPSHOTS;
1030
1031
    memcpy(&snapshots.snapshots[nexti].cpu, cpu, sizeof(struct cpu));
1032
    memcpy(snapshots.snapshots[nexti].memory, memory, g_opts.memory_size);
1033
1034
    /* Fix the program pointer to reference the snapshot's own memory. */
1035
    snapshots.snapshots[nexti].cpu.program =
1036
        (instr_t *)snapshots.snapshots[nexti].memory;
1037
1038
    snapshots.head = nexti;
1039
    if (snapshots.count < MAX_SNAPSHOTS)
1040
        snapshots.count++;
1041
}
1042
1043
/* Restore the most recent snapshot, returning false if none remain. */
1044
static bool snapshot_restore(struct cpu *cpu) {
1045
    if (snapshots.count <= 1)
1046
        return false;
1047
1048
    snapshots.head = (snapshots.head + MAX_SNAPSHOTS - 1) % MAX_SNAPSHOTS;
1049
    snapshots.count--;
1050
1051
    int previ = snapshots.head;
1052
    memcpy(cpu, &snapshots.snapshots[previ].cpu, sizeof(struct cpu));
1053
    memcpy(memory, snapshots.snapshots[previ].memory, g_opts.memory_size);
1054
1055
    /* Fix the program pointer to reference the live memory buffer. */
1056
    cpu->program = (instr_t *)memory;
1057
1058
    return true;
1059
}
1060
1061
/* Initialize the snapshot buffer with an initial snapshot. */
1062
static void snapshot_init(struct cpu *cpu) {
1063
    snapshots.head  = -1;
1064
    snapshots.count = 0;
1065
    snapshot_save(cpu);
1066
}
1067
1068
/* Reset the headless instruction trace buffer. */
1069
static void trace_reset(void) {
1070
    headless_trace.head  = -1;
1071
    headless_trace.count = 0;
1072
}
1073
1074
/* Record the current instruction into the trace ring buffer. */
1075
static void trace_record(struct cpu *cpu, instr_t ins) {
1076
    if (!g_opts.trace_enabled || !g_opts.trace_headless)
1077
        return;
1078
1079
    int next = (headless_trace.head + 1 + TRACE_HISTORY) % TRACE_HISTORY;
1080
1081
    headless_trace.head                = next;
1082
    headless_trace.entries[next].pc    = cpu->pc;
1083
    headless_trace.entries[next].instr = ins;
1084
    memcpy(headless_trace.entries[next].regs, cpu->regs, sizeof(cpu->regs));
1085
    if (headless_trace.count < TRACE_HISTORY)
1086
        headless_trace.count++;
1087
}
1088
1089
/* Dump the headless instruction trace to stdout. */
1090
static bool trace_dump(u32 fault_pc) {
1091
    if (!g_opts.trace_enabled || !g_opts.trace_headless ||
1092
        headless_trace.count == 0)
1093
        return false;
1094
1095
    int limit = (int)g_opts.trace_depth;
1096
    if (limit <= 0)
1097
        limit = FAULT_TRACE_DEPTH;
1098
    if (limit > TRACE_HISTORY)
1099
        limit = TRACE_HISTORY;
1100
    if (limit > headless_trace.count)
1101
        limit = headless_trace.count;
1102
1103
    printf("Headless trace (newest first):\n");
1104
    for (int i = 0; i < limit; i++) {
1105
        int idx = (headless_trace.head - i + TRACE_HISTORY) % TRACE_HISTORY;
1106
        struct trace_entry *entry = &headless_trace.entries[idx];
1107
        char                istr[MAX_INSTR_STR_LEN] = { 0 };
1108
1109
        sprint_instr(entry->instr, istr, true);
1110
1111
        printf(
1112
            "  [%d] PC=%08x %s%s\n",
1113
            i,
1114
            entry->pc,
1115
            istr,
1116
            (entry->pc == fault_pc) ? "  <-- fault" : ""
1117
        );
1118
        printf(
1119
            "       SP=%08x FP=%08x RA=%08x A0=%08x A1=%08x A2=%08x\n",
1120
            (u32)entry->regs[SP],
1121
            (u32)entry->regs[FP],
1122
            (u32)entry->regs[RA],
1123
            (u32)entry->regs[A0],
1124
            (u32)entry->regs[A1],
1125
            (u32)entry->regs[A2]
1126
        );
1127
    }
1128
    return true;
1129
}
1130
1131
/* Dump recent snapshot history to stdout for fault diagnostics. */
1132
static bool snapshot_dump_history(struct cpu *cpu, u32 fault_pc) {
1133
    (void)cpu;
1134
    if (snapshots.count == 0)
1135
        return false;
1136
1137
    int limit = FAULT_TRACE_DEPTH;
1138
    if (limit > snapshots.count)
1139
        limit = snapshots.count;
1140
1141
    printf("Snapshot history (newest first):\n");
1142
    for (int i = 0; i < limit; i++) {
1143
        int idx = (snapshots.head - i + MAX_SNAPSHOTS) % MAX_SNAPSHOTS;
1144
        struct snapshot *snap    = &snapshots.snapshots[idx];
1145
        u32              next_pc = snap->cpu.pc;
1146
        u32  exec_pc = next_pc >= INSTR_SIZE ? next_pc - INSTR_SIZE : next_pc;
1147
        char istr[MAX_INSTR_STR_LEN] = { 0 };
1148
        u32  instr_index             = exec_pc / INSTR_SIZE;
1149
1150
        if (instr_index < snap->cpu.programsize) {
1151
            sprint_instr(snap->cpu.program[instr_index], istr, true);
1152
        } else {
1153
            snprintf(istr, sizeof(istr), "<pc %08x>", exec_pc);
1154
        }
1155
1156
        printf(
1157
            "  [%d] PC next=%08x prev=%08x %s%s\n",
1158
            i,
1159
            next_pc,
1160
            exec_pc,
1161
            istr,
1162
            (exec_pc == fault_pc || next_pc == fault_pc) ? "  <-- fault" : ""
1163
        );
1164
        printf(
1165
            "       SP=%08x FP=%08x RA=%08x A0=%08x\n",
1166
            (u32)snap->cpu.regs[SP],
1167
            (u32)snap->cpu.regs[FP],
1168
            (u32)snap->cpu.regs[RA],
1169
            (u32)snap->cpu.regs[A0]
1170
        );
1171
    }
1172
    return true;
1173
}
1174
1175
/* Emit runtime fault diagnostics including trace and snapshot history. */
1176
static void emit_fault_diagnostics(struct cpu *cpu, u32 pc) {
1177
    if (cpu->faulted)
1178
        return;
1179
1180
    cpu->faulted = true;
1181
1182
    printf("\n--- runtime fault diagnostics ---\n");
1183
    bool printed = false;
1184
1185
    printed |= trace_dump(pc);
1186
    printed |= snapshot_dump_history(cpu, pc);
1187
1188
    if (!printed) {
1189
        printf("No trace data available.\n");
1190
    }
1191
    printf("--- end diagnostics ---\n");
1192
    fflush(stdout);
1193
}
1194
1195
/* Return true if the CPU's PC is outside the loaded program bounds. */
1196
static inline bool cpu_out_of_bounds(struct cpu *cpu) {
1197
    if (!g_opts.validate_memory)
1198
        return false;
1199
    if (program_bytes == 0)
1200
        return true;
1201
    if (cpu->pc < program_base)
1202
        return true;
1203
    return (cpu->pc - program_base) >= program_bytes;
1204
}
1205
1206
/* Last executed PC, used for detecting branches/jumps in trace mode. */
1207
static u32 last_executed_pc = 0;
1208
1209
/* Reset CPU state (keeping program loaded). */
1210
static void cpu_reset(struct cpu *cpu) {
1211
    trace_reset();
1212
    memset(cpu->regs, 0, sizeof(cpu->regs));
1213
    cpu->mstatus   = 0;
1214
    cpu->mie       = 0;
1215
    cpu->mtvec     = 0;
1216
    cpu->mscratch  = 0;
1217
    cpu->mepc      = 0;
1218
    cpu->mcause    = 0;
1219
    cpu->mtval     = 0;
1220
    cpu->mip       = 0;
1221
    cpu->mhartid   = 0;
1222
    cpu->privilege = 3;
1223
1224
    /* Set SP to the top of the usable stack, aligned to 16 bytes
1225
     * as required by the RISC-V ABI. */
1226
    cpu->regs[SP]    = stack_usable_top() & ~0xF;
1227
    cpu->pc          = program_base;
1228
    cpu->running     = true;
1229
    cpu->faulted     = false;
1230
    cpu->ebreak      = false;
1231
    cpu->modified    = ZERO;
1232
    last_executed_pc = 0;
1233
}
1234
1235
/* Initialize CPU and memory to a clean state. */
1236
static void cpu_init(struct cpu *cpu) {
1237
    memset(cpu, 0, sizeof(*cpu));
1238
    memset(memory, 0, g_opts.memory_size);
1239
    cpu->program     = (instr_t *)memory;
1240
    cpu->programsize = 0;
1241
    trace_reset();
1242
    cpu->privilege = 3;
1243
    cpu->mhartid   = 0;
1244
    guest_fd_table_init();
1245
    cpu_reset(cpu);
1246
}
1247
1248
/* Open a file via the openat syscall (56). */
1249
static i32 ecall_openat(u32 pathname_addr, i32 flags) {
1250
    if (pathname_addr >= g_opts.memory_size)
1251
        return -1;
1252
1253
    /* Find the null terminator to validate the string is in bounds. */
1254
    u32 path_end = pathname_addr;
1255
    while (path_end < g_opts.memory_size && memory[path_end] != 0)
1256
        path_end++;
1257
    if (path_end >= g_opts.memory_size)
1258
        return -1;
1259
1260
    i32 host_fd = open((const char *)&memory[pathname_addr], flags, 0644);
1261
    if (host_fd < 0)
1262
        return -1;
1263
1264
    i32 guest_fd = guest_fd_table_add(host_fd);
1265
    if (guest_fd < 0) {
1266
        close(host_fd);
1267
        return -1;
1268
    }
1269
    return guest_fd;
1270
}
1271
1272
/* Close a file descriptor via the close syscall (57). */
1273
static i32 ecall_close(i32 guest_fd) {
1274
    /* Don't close standard streams. */
1275
    if (guest_fd < 3)
1276
        return 0;
1277
1278
    i32 host_fd = guest_fd_table_get(guest_fd);
1279
    if (host_fd >= 0) {
1280
        i32 result = close(host_fd);
1281
        guest_fd_table_remove(guest_fd);
1282
        return result;
1283
    }
1284
    return -1;
1285
}
1286
1287
/* Load a binary section from disk into emulator memory at the given offset. */
1288
static u32 load_section(
1289
    const char *filepath,
1290
    const char *suffix,
1291
    u32         offset,
1292
    u32         limit,
1293
    const char *label
1294
) {
1295
    char path[PATH_MAX];
1296
    snprintf(path, sizeof(path), "%s.%s", filepath, suffix);
1297
1298
    FILE *file = fopen(path, "rb");
1299
    if (!file)
1300
        return 0;
1301
1302
    if (fseek(file, 0, SEEK_END) != 0) {
1303
        fclose(file);
1304
        bail("failed to seek %s section", label);
1305
    }
1306
    long size = ftell(file);
1307
    if (size < 0) {
1308
        fclose(file);
1309
        bail("failed to determine size of %s section", label);
1310
    }
1311
    if (fseek(file, 0, SEEK_SET) != 0) {
1312
        fclose(file);
1313
        bail("failed to rewind %s section", label);
1314
    }
1315
    if (size == 0) {
1316
        fclose(file);
1317
        return 0;
1318
    }
1319
1320
    u32 u_size = (u32)size;
1321
    u64 end    = (u64)offset + (u64)u_size;
1322
    if (end > (u64)limit) {
1323
        fclose(file);
1324
        u32 max_size = limit - offset;
1325
        bail(
1326
            "%s section too large for emulator memory: required %u bytes, max "
1327
            "%u bytes",
1328
            label,
1329
            u_size,
1330
            max_size
1331
        );
1332
    }
1333
    if (end > (u64)g_opts.memory_size) {
1334
        fclose(file);
1335
        u32 max_size = g_opts.memory_size - offset;
1336
        bail(
1337
            "%s section exceeds physical memory: required %u bytes at offset "
1338
            "%u, "
1339
            "but only %u bytes available (total memory=%u, use "
1340
            "-memory-size=... or recompile emulator with a larger "
1341
            "MEMORY_SIZE)",
1342
            label,
1343
            u_size,
1344
            offset,
1345
            max_size,
1346
            g_opts.memory_size
1347
        );
1348
    }
1349
    size_t read = fread(&memory[offset], 1, u_size, file);
1350
    fclose(file);
1351
1352
    if (read != u_size) {
1353
        bail(
1354
            "could not read entire %s section: read %zu bytes, expected %u "
1355
            "bytes (offset=%u, limit=%u)",
1356
            label,
1357
            read,
1358
            u_size,
1359
            offset,
1360
            limit
1361
        );
1362
    }
1363
    return u_size;
1364
}
1365
1366
/* Decode a little-endian word without relying on host alignment. */
1367
static u32 decode_u32_le(const u8 *bytes) {
1368
    return (u32)bytes[0] | ((u32)bytes[1] << 8) | ((u32)bytes[2] << 16) |
1369
           ((u32)bytes[3] << 24);
1370
}
1371
1372
/* Read exactly size bytes from file into guest memory at offset. */
1373
static void load_image_section(
1374
    FILE *file, u32 offset, u32 size, const char *label
1375
) {
1376
    if (size == 0)
1377
        return;
1378
1379
    size_t read = fread(&memory[offset], 1, size, file);
1380
    if (read != size)
1381
        bail(
1382
            "could not read entire %s section: read %zu bytes, expected %u "
1383
            "bytes",
1384
            label,
1385
            read,
1386
            size
1387
        );
1388
}
1389
1390
/* Prepare the environment block (argv) on the guest stack. */
1391
static void prepare_env(struct cpu *cpu, int argc, char **argv) {
1392
    if (argc < 0 || argv == NULL)
1393
        argc = 0;
1394
1395
    usize bytes = 0;
1396
    for (int i = 0; i < argc; i++)
1397
        bytes += strlen(argv[i]) + 1; /* Include terminating NUL. */
1398
1399
    /* In RV64, slices are 16 bytes (8-byte ptr + 4-byte len + 4 padding). */
1400
    u32 slice_size       = 16;
1401
    u32 slice_array_size = (u32)argc * slice_size;
1402
    u32 base_size        = slice_size + slice_array_size;
1403
    u32 total_size       = align(base_size + (u32)bytes, 16);
1404
1405
    /* Place the env block below the current stack pointer so it doesn't
1406
     * overlap with uninitialized static data.  The .rw.data file only
1407
     * contains initialized statics; undefined statics occupy memory after
1408
     * the loaded data but are not in the file.  Placing the env block in
1409
     * the data region would clobber those zero-initialized areas. */
1410
    u32 sp       = (u32)cpu->regs[SP];
1411
    u32 env_addr = (sp - total_size) & ~0xFu;
1412
1413
    if (env_addr <= DATA_MEMORY_START + data_bytes)
1414
        bail("not enough memory to prepare environment block");
1415
1416
    /* Move SP below the env block so the program's stack doesn't overwrite it.
1417
     */
1418
    cpu->regs[SP] = env_addr;
1419
1420
    u32 slices_addr  = env_addr + slice_size;
1421
    u32 strings_addr = slices_addr + (argc > 0 ? slice_array_size : 0);
1422
1423
    /* Write the Env slice header. */
1424
    memory_store_u64(env_addr, argc > 0 ? slices_addr : 0);
1425
    memory_store_u32(env_addr + 8, (u32)argc);
1426
1427
    /* Copy argument strings and populate slices. */
1428
    u32 curr = strings_addr;
1429
    for (int i = 0; i < argc; i++) {
1430
        size_t len = strlen(argv[i]);
1431
        if (curr + len >= g_opts.memory_size)
1432
            bail("environment string does not fit in emulator memory");
1433
1434
        memcpy(&memory[curr], argv[i], len);
1435
        memory[curr + len] = 0; /* Null-terminate for syscall compatibility. */
1436
1437
        u32 slice_entry = slices_addr + (u32)i * slice_size;
1438
        memory_store_u64(slice_entry, curr);
1439
        memory_store_u32(slice_entry + 8, (u32)len);
1440
1441
        curr += (u32)len + 1;
1442
    }
1443
    cpu->regs[A0] = env_addr;
1444
    cpu->regs[A1] = env_addr;
1445
}
1446
1447
/* Load debug information from the .debug file. */
1448
static void debug_load(const char *program_path) {
1449
    char debugpath[PATH_MAX];
1450
    snprintf(debugpath, sizeof(debugpath), "%s.debug", program_path);
1451
1452
    FILE *file = fopen(debugpath, "rb");
1453
    if (!file)
1454
        return; /* Debug file is optional. */
1455
1456
    g_debug.capacity = 64;
1457
    g_debug.entries  = malloc(sizeof(struct debug_entry) * g_debug.capacity);
1458
    g_debug.count    = 0;
1459
1460
    if (!g_debug.entries) {
1461
        fclose(file);
1462
        return;
1463
    }
1464
    while (!feof(file)) {
1465
        struct debug_entry entry;
1466
1467
        if (fread(&entry.pc, sizeof(u32), 1, file) != 1)
1468
            break;
1469
        if (fread(&entry.offset, sizeof(u32), 1, file) != 1)
1470
            break;
1471
1472
        /* Read null-terminated file path. */
1473
        size_t i = 0;
1474
        int    c;
1475
        while (i < PATH_MAX - 1 && (c = fgetc(file)) != EOF && c != '\0') {
1476
            entry.file[i++] = (char)c;
1477
        }
1478
        entry.file[i] = '\0';
1479
1480
        if (c == EOF && i == 0)
1481
            break;
1482
1483
        /* Grow array if needed. */
1484
        if (g_debug.count >= g_debug.capacity) {
1485
            g_debug.capacity *= 2;
1486
            g_debug.entries   = realloc(
1487
                g_debug.entries, sizeof(struct debug_entry) * g_debug.capacity
1488
            );
1489
            if (!g_debug.entries) {
1490
                fclose(file);
1491
                return;
1492
            }
1493
        }
1494
        g_debug.entries[g_debug.count++] = entry;
1495
    }
1496
    fclose(file);
1497
}
1498
1499
/* Look up source location for a given PC. */
1500
static struct debug_entry *debug_lookup(u32 pc) {
1501
    struct debug_entry *best = NULL;
1502
1503
    for (size_t i = 0; i < g_debug.count; i++) {
1504
        if (g_debug.entries[i].pc == pc) {
1505
            return &g_debug.entries[i];
1506
        }
1507
        /* Track the closest entry at or before this PC. */
1508
        if (g_debug.entries[i].pc <= pc) {
1509
            best = &g_debug.entries[i];
1510
        }
1511
    }
1512
    return best;
1513
}
1514
1515
/* Compute the line number from a file path and byte offset. */
1516
static int line_from_offset(const char *filepath, u32 offset) {
1517
    FILE *file = fopen(filepath, "r");
1518
    if (!file)
1519
        return 0;
1520
1521
    u32 line = 1;
1522
    for (u32 i = 0; i < offset; i++) {
1523
        int c = fgetc(file);
1524
        if (c == EOF)
1525
            break;
1526
        if (c == '\n')
1527
            line++;
1528
    }
1529
    fclose(file);
1530
1531
    return line;
1532
}
1533
1534
/* Load the program binary and data sections into memory. */
1535
static void program_init(struct cpu *cpu, const char *filepath) {
1536
    program_bytes = 0;
1537
    data_bytes    = 0;
1538
    if (g_opts.debug_enabled)
1539
        debug_load(filepath);
1540
1541
    FILE *file = fopen(filepath, "rb");
1542
    if (!file)
1543
        bail("failed to open file '%s'", filepath);
1544
    if (fseek(file, 0, SEEK_END) != 0) {
1545
        fclose(file);
1546
        bail("failed to seek program '%s'", filepath);
1547
    }
1548
    long size = ftell(file);
1549
    if (size <= 0) {
1550
        fclose(file);
1551
        bail("invalid file size: %ld", size);
1552
    }
1553
    if (fseek(file, 0, SEEK_SET) != 0) {
1554
        fclose(file);
1555
        bail("failed to rewind program '%s'", filepath);
1556
    }
1557
1558
    u32 data_limit = DATA_MEMORY_START + g_opts.data_memory_size;
1559
    if (data_limit > g_opts.memory_size)
1560
        data_limit = g_opts.memory_size;
1561
1562
    u8     header[IMAGE_HEADER_SIZE];
1563
    size_t header_read = fread(header, 1, sizeof(header), file);
1564
    bool   is_image =
1565
        header_read >= sizeof(u32) && decode_u32_le(header) == IMAGE_MAGIC;
1566
1567
    if (is_image) {
1568
        if (header_read != sizeof(header)) {
1569
            fclose(file);
1570
            bail("truncated Radiance image header");
1571
        }
1572
1573
        u32 version   = decode_u32_le(&header[4]);
1574
        program_bytes = decode_u32_le(&header[8]);
1575
        rodata_bytes  = decode_u32_le(&header[12]);
1576
        data_bytes    = decode_u32_le(&header[16]);
1577
1578
        if (version != IMAGE_VERSION) {
1579
            fclose(file);
1580
            bail("unsupported Radiance image version: %u", version);
1581
        }
1582
        if (program_bytes == 0 || program_bytes % sizeof(instr_t) != 0) {
1583
            fclose(file);
1584
            bail(
1585
                "invalid text section size in Radiance image: %u", program_bytes
1586
            );
1587
        }
1588
        u64 image_size =
1589
            (u64)IMAGE_HEADER_SIZE + program_bytes + rodata_bytes + data_bytes;
1590
        if (image_size != (u64)size) {
1591
            fclose(file);
1592
            bail(
1593
                "Radiance image size does not match header: file has %ld "
1594
                "bytes, header requires %llu",
1595
                size,
1596
                (unsigned long long)image_size
1597
            );
1598
        }
1599
        if (program_bytes > PROGRAM_SIZE) {
1600
            fclose(file);
1601
            bail(
1602
                "text section too large: %u bytes; maximum is %u bytes",
1603
                program_bytes,
1604
                PROGRAM_SIZE
1605
            );
1606
        }
1607
        if ((u64)DATA_RO_OFFSET + rodata_bytes > DATA_MEMORY_START) {
1608
            fclose(file);
1609
            bail("read-only data section exceeds available program memory");
1610
        }
1611
        program_base = align(DATA_RO_OFFSET + rodata_bytes, WORD_SIZE);
1612
        if ((u64)program_base + program_bytes > DATA_MEMORY_START) {
1613
            fclose(file);
1614
            bail("text section exceeds available program memory");
1615
        }
1616
        if ((u64)DATA_MEMORY_START + data_bytes > data_limit) {
1617
            fclose(file);
1618
            bail("read-write data section exceeds available data memory");
1619
        }
1620
1621
        load_image_section(file, program_base, program_bytes, "text");
1622
        load_image_section(
1623
            file, DATA_RO_OFFSET, rodata_bytes, "read-only data"
1624
        );
1625
        load_image_section(
1626
            file, DATA_MEMORY_START, data_bytes, "read-write data"
1627
        );
1628
    } else {
1629
        /* Legacy flat binaries use optional sidecar data files. */
1630
        rewind(file);
1631
        rodata_bytes = load_section(
1632
            filepath, "ro.data", DATA_RO_OFFSET, DATA_MEMORY_START, "ro.data"
1633
        );
1634
        program_base = align(DATA_RO_OFFSET + rodata_bytes, WORD_SIZE);
1635
        if (size > PROGRAM_SIZE) {
1636
            fclose(file);
1637
            bail(
1638
                "invalid file size: %ld; maximum program size is %d bytes",
1639
                size,
1640
                PROGRAM_SIZE
1641
            );
1642
        }
1643
        if ((u64)program_base + (u32)size > DATA_MEMORY_START) {
1644
            fclose(file);
1645
            bail("text section exceeds available program memory");
1646
        }
1647
        program_bytes = (u32)size;
1648
        load_image_section(file, program_base, program_bytes, "program");
1649
        data_bytes = load_section(
1650
            filepath, "rw.data", DATA_MEMORY_START, data_limit, "rw.data"
1651
        );
1652
    }
1653
    fclose(file);
1654
1655
    cpu->programsize = program_bytes / sizeof(instr_t);
1656
    cpu->pc          = program_base;
1657
}
1658
1659
static u64 *cpu_csr(struct cpu *cpu, u32 csr) {
1660
    switch (csr) {
1661
    case 0x300: return &cpu->mstatus;
1662
    case 0x304: return &cpu->mie;
1663
    case 0x305: return &cpu->mtvec;
1664
    case 0x340: return &cpu->mscratch;
1665
    case 0x341: return &cpu->mepc;
1666
    case 0x342: return &cpu->mcause;
1667
    case 0x343: return &cpu->mtval;
1668
    case 0x344: return &cpu->mip;
1669
    case 0xF14: return &cpu->mhartid;
1670
    default: return NULL;
1671
    }
1672
}
1673
1674
static void cpu_machine_trap(
1675
    struct cpu *cpu, u32 executed_pc, u64 cause, u64 value, u32 *pc_next
1676
) {
1677
    u64 mie = (cpu->mstatus >> 3) & 1;
1678
    cpu->mstatus &= ~((u64)3 << 11);
1679
    cpu->mstatus |= (u64)cpu->privilege << 11;
1680
    cpu->mstatus = (cpu->mstatus & ~((u64)1 << 7)) | (mie << 7);
1681
    cpu->mstatus &= ~((u64)1 << 3);
1682
    cpu->mepc      = executed_pc;
1683
    cpu->mcause    = cause;
1684
    cpu->mtval     = value;
1685
    cpu->privilege = 3;
1686
    *pc_next       = (u32)(cpu->mtvec & ~(u64)3);
1687
}
1688
1689
/* Execute a single instruction. */
1690
static void cpu_execute(struct cpu *cpu, enum display display, bool headless) {
1691
    if (cpu_out_of_bounds(cpu)) {
1692
        cpu->running = false;
1693
        emit_fault_diagnostics(cpu, cpu->pc);
1694
        if (headless) {
1695
            fprintf(stderr, "program is out of bounds\n");
1696
            return;
1697
        }
1698
        bail("program is out of bounds");
1699
    }
1700
1701
    u32     executed_pc = cpu->pc;
1702
    instr_t ins         = cpu->program[cpu->pc / sizeof(instr_t)];
1703
    u32     pc_next     = cpu->pc + INSTR_SIZE;
1704
    u32     opcode      = ins.r.opcode;
1705
1706
    cpu->modified = ZERO;
1707
    trace_record(cpu, ins);
1708
1709
    /* Print instruction if tracing is enabled in headless mode.
1710
     * Skip NOPs (addi x0, x0, 0 = 0x00000013). */
1711
    if (headless && g_opts.trace_print_instructions && ins.raw != 0x00000013) {
1712
        /* Print ellipsis if we jumped to a non-sequential instruction. */
1713
        if (last_executed_pc != 0 &&
1714
            executed_pc != last_executed_pc + INSTR_SIZE) {
1715
            printf("%s  :%s\n", COLOR_GREY, COLOR_RESET);
1716
        }
1717
1718
        char istr[MAX_INSTR_STR_LEN] = { 0 };
1719
        int  len                     = sprint_instr(ins, istr, true);
1720
        int  padding                 = INSTR_STR_LEN - len;
1721
        if (padding < 0)
1722
            padding = 0;
1723
        printf(
1724
            "%s%08x%s %s%-*s%s",
1725
            COLOR_GREY,
1726
            executed_pc,
1727
            COLOR_RESET,
1728
            istr,
1729
            padding,
1730
            "",
1731
            COLOR_GREY
1732
        );
1733
1734
        /* Print all non-zero registers. */
1735
        bool first = true;
1736
        for (int i = 0; i < REGISTERS; i++) {
1737
            if (cpu->regs[i] != 0) {
1738
                if (!first)
1739
                    printf(" ");
1740
                printf("%s=%08x", reg_names[i], (u32)cpu->regs[i]);
1741
                first = false;
1742
            }
1743
        }
1744
        printf("%s\n", COLOR_RESET);
1745
    }
1746
1747
    switch (opcode) {
1748
    case OP_LUI:
1749
        if (ins.u.rd != 0) {
1750
            u32 lui_val = ins.u.imm_31_12 << 12;
1751
            cpu->regs[ins.u.rd] =
1752
                (u64)(i64)(i32)lui_val; /* RV64: sign-extend to 64 bits. */
1753
            cpu->modified = ins.u.rd;
1754
        }
1755
        break;
1756
1757
    case OP_AUIPC:
1758
        if (ins.u.rd != 0) {
1759
            u32 auipc_val = ins.u.imm_31_12 << 12;
1760
            cpu->regs[ins.u.rd] =
1761
                cpu->pc +
1762
                (u64)(i64)(i32)auipc_val; /* RV64: sign-extend offset. */
1763
            cpu->modified = (reg_t)ins.u.rd;
1764
        }
1765
        break;
1766
1767
    case OP_JAL: {
1768
        i32 imm = get_j_imm(ins);
1769
        if (ins.j.rd != 0) {
1770
            cpu->regs[ins.j.rd] = pc_next;
1771
            cpu->modified       = (reg_t)ins.j.rd;
1772
        }
1773
        pc_next = cpu->pc + imm;
1774
        break;
1775
    }
1776
1777
    case OP_JALR: {
1778
        i32 imm = get_i_imm(ins);
1779
        if (ins.i.rd != 0) {
1780
            cpu->regs[ins.i.rd] = pc_next;
1781
            cpu->modified       = (reg_t)ins.i.rd;
1782
        }
1783
        /* Calculate target address in full 64-bit precision. */
1784
        u64 jalr_target = (cpu->regs[ins.i.rs1] + (i64)imm) & ~(u64)1;
1785
        /* Check if this is a RET instruction (jalr x0, ra, 0). */
1786
        if (ins.i.rd == 0 && ins.i.rs1 == 1 && imm == 0 && jalr_target == 0) {
1787
            cpu->running = false;
1788
            if (!headless) {
1789
                ui_render(cpu, display);
1790
1791
                printf(
1792
                    "\n%sProgram terminated with return value %d (0x%08x)%s ",
1793
                    COLOR_BOLD_GREEN,
1794
                    (i32)cpu->regs[A0],
1795
                    (u32)cpu->regs[A0],
1796
                    COLOR_RESET
1797
                );
1798
            }
1799
        } else {
1800
            pc_next = (u32)jalr_target;
1801
        }
1802
        break;
1803
    }
1804
1805
    case OP_BRANCH: {
1806
        bool jump = false;
1807
        i32  imm  = get_b_imm(ins);
1808
1809
        switch (ins.b.funct3) {
1810
        case FUNCT3_BYTE: /* beq.  */
1811
            jump = (cpu->regs[ins.b.rs1] == cpu->regs[ins.b.rs2]);
1812
            break;
1813
        case FUNCT3_HALF: /* bne.  */
1814
            jump = (cpu->regs[ins.b.rs1] != cpu->regs[ins.b.rs2]);
1815
            break;
1816
        case FUNCT3_BYTE_U: /* blt.  */
1817
            jump = ((i64)cpu->regs[ins.b.rs1] < (i64)cpu->regs[ins.b.rs2]);
1818
            break;
1819
        case FUNCT3_HALF_U: /* bge.  */
1820
            jump = ((i64)cpu->regs[ins.b.rs1] >= (i64)cpu->regs[ins.b.rs2]);
1821
            break;
1822
        case FUNCT3_OR: /* bltu. */
1823
            jump = (cpu->regs[ins.b.rs1] < cpu->regs[ins.b.rs2]);
1824
            break;
1825
        case FUNCT3_AND: /* bgeu. */
1826
            jump = (cpu->regs[ins.b.rs1] >= cpu->regs[ins.b.rs2]);
1827
            break;
1828
        }
1829
        if (jump) {
1830
            pc_next = cpu->pc + imm;
1831
        }
1832
        break;
1833
    }
1834
1835
    case OP_LOAD: {
1836
        i32 imm  = get_i_imm(ins);
1837
        u64 addr = cpu->regs[ins.i.rs1] + (i64)imm;
1838
1839
        if (ins.i.rd == ZERO)
1840
            break;
1841
1842
        cpu->modified = (reg_t)ins.i.rd;
1843
        bool fault    = false;
1844
1845
        switch (ins.i.funct3) {
1846
        case FUNCT3_BYTE: /* lb. */
1847
            if (!validate_memory_access(cpu, addr, 1, ins.i.rs1, "lb", false)) {
1848
                fault = true;
1849
                break;
1850
            }
1851
            /* sign_extend returns i32; on RV64 we sign-extend to 64 bits. */
1852
            cpu->regs[ins.i.rd] = (u64)(i64)sign_extend(memory[addr], 8);
1853
            break;
1854
        case FUNCT3_HALF: /* lh. */
1855
            if (!validate_memory_access(cpu, addr, 2, ins.i.rs1, "lh", false)) {
1856
                fault = true;
1857
                break;
1858
            }
1859
            cpu->regs[ins.i.rd] =
1860
                (u64)(i64)sign_extend(memory_load_u16(addr), 16);
1861
            break;
1862
        case FUNCT3_WORD: /* lw. */
1863
            if (!validate_memory_access(cpu, addr, 4, ins.i.rs1, "lw", false)) {
1864
                fault = true;
1865
                break;
1866
            }
1867
            /* RV64: lw sign-extends the 32-bit value to 64 bits. */
1868
            cpu->regs[ins.i.rd] = (u64)(i64)(i32)memory_load_u32(addr);
1869
            break;
1870
        case 0x6: /* lwu (RV64). */
1871
            if (!validate_memory_access(
1872
                    cpu, addr, 4, ins.i.rs1, "lwu", false
1873
                )) {
1874
                fault = true;
1875
                break;
1876
            }
1877
            cpu->regs[ins.i.rd] = (u64)memory_load_u32(addr);
1878
            break;
1879
        case FUNCT3_BYTE_U: /* lbu. */
1880
            if (!validate_memory_access(
1881
                    cpu, addr, 1, ins.i.rs1, "lbu", false
1882
                )) {
1883
                fault = true;
1884
                break;
1885
            }
1886
            cpu->regs[ins.i.rd] = memory[addr];
1887
            break;
1888
        case FUNCT3_HALF_U: /* lhu. */
1889
            if (!validate_memory_access(
1890
                    cpu, addr, 2, ins.i.rs1, "lhu", false
1891
                )) {
1892
                fault = true;
1893
                break;
1894
            }
1895
            cpu->regs[ins.i.rd] = memory_load_u16(addr);
1896
            break;
1897
        case 0x3: /* ld (RV64). */
1898
            if (!validate_memory_access(cpu, addr, 8, ins.i.rs1, "ld", false)) {
1899
                fault = true;
1900
                break;
1901
            }
1902
            cpu->regs[ins.i.rd] = memory_load_u64(addr);
1903
            break;
1904
        }
1905
        if (fault || !cpu->running)
1906
            break;
1907
        break;
1908
    }
1909
1910
    case OP_STORE: {
1911
        i32 imm  = get_s_imm(ins);
1912
        u64 addr = cpu->regs[ins.s.rs1] + (i64)imm;
1913
1914
        switch (ins.s.funct3) {
1915
        case FUNCT3_BYTE: /* sb. */
1916
            if (!validate_memory_access(cpu, addr, 1, ins.s.rs1, "sb", true))
1917
                break;
1918
            watch_store(cpu, (u32)addr, 1, (u32)cpu->regs[ins.s.rs2]);
1919
            memory_store_u8(addr, (u8)cpu->regs[ins.s.rs2]);
1920
            break;
1921
        case FUNCT3_HALF: /* sh. */
1922
            if (!validate_memory_access(cpu, addr, 2, ins.s.rs1, "sh", true))
1923
                break;
1924
            watch_store(cpu, (u32)addr, 2, (u32)cpu->regs[ins.s.rs2]);
1925
            memory_store_u16(addr, (u16)cpu->regs[ins.s.rs2]);
1926
            break;
1927
        case FUNCT3_WORD: /* sw. */
1928
            if (!validate_memory_access(cpu, addr, 4, ins.s.rs1, "sw", true))
1929
                break;
1930
            watch_store(cpu, (u32)addr, 4, (u32)cpu->regs[ins.s.rs2]);
1931
            memory_store_u32(addr, (u32)cpu->regs[ins.s.rs2]);
1932
            break;
1933
        case 0x3: /* sd (RV64). */
1934
            if (!validate_memory_access(cpu, addr, 8, ins.s.rs1, "sd", true))
1935
                break;
1936
            watch_store(cpu, (u32)addr, 8, (u32)cpu->regs[ins.s.rs2]);
1937
            memory_store_u64(addr, cpu->regs[ins.s.rs2]);
1938
            break;
1939
        }
1940
        break;
1941
    }
1942
1943
    case OP_IMM: {
1944
        i32 imm        = get_i_imm(ins);
1945
        u32 shamt_mask = 0x3F; /* RV64: 6-bit shift amounts. */
1946
1947
        if (ins.i.rd == ZERO)
1948
            break;
1949
1950
        cpu->modified = (reg_t)ins.i.rd;
1951
1952
        switch (ins.i.funct3) {
1953
        case FUNCT3_ADD: /* addi.  */
1954
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] + imm;
1955
            break;
1956
        case FUNCT3_SLL: /* slli.  */
1957
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] << (imm & shamt_mask);
1958
            break;
1959
        case FUNCT3_SLT: /* slti.  */
1960
            cpu->regs[ins.i.rd] =
1961
                ((i64)cpu->regs[ins.i.rs1] < (i64)imm) ? 1 : 0;
1962
            break;
1963
        case FUNCT3_SLTU: /* sltiu. */
1964
            cpu->regs[ins.i.rd] =
1965
                (cpu->regs[ins.i.rs1] < (u64)(i64)imm) ? 1 : 0;
1966
            break;
1967
        case FUNCT3_XOR: /* xori.  */
1968
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] ^ imm;
1969
            break;
1970
        case FUNCT3_SRL: /* srli/srai. */
1971
            if ((imm & 0x400) == 0) {
1972
                /* srli -- logical right shift. */
1973
                cpu->regs[ins.i.rd] =
1974
                    cpu->regs[ins.i.rs1] >> (imm & shamt_mask);
1975
            } else {
1976
                /* srai -- arithmetic right shift. */
1977
                cpu->regs[ins.i.rd] =
1978
                    (u64)((i64)cpu->regs[ins.i.rs1] >> (imm & shamt_mask));
1979
            }
1980
            break;
1981
        case FUNCT3_OR: /* ori.   */
1982
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] | imm;
1983
            break;
1984
        case FUNCT3_AND: /* andi.  */
1985
            cpu->regs[ins.i.rd] = cpu->regs[ins.i.rs1] & imm;
1986
            break;
1987
        }
1988
        break;
1989
    }
1990
1991
    case OP_IMM_32: {
1992
        /* RV64I: 32-bit immediate operations (ADDIW, SLLIW, SRLIW, SRAIW).
1993
         * These operate on the lower 32 bits and sign-extend the result. */
1994
        i32 imm = get_i_imm(ins);
1995
1996
        if (ins.i.rd == ZERO)
1997
            break;
1998
1999
        cpu->modified = (reg_t)ins.i.rd;
2000
2001
        switch (ins.i.funct3) {
2002
        case FUNCT3_ADD: { /* addiw. */
2003
            i32 result          = (i32)cpu->regs[ins.i.rs1] + imm;
2004
            cpu->regs[ins.i.rd] = (u64)(i64)result;
2005
            break;
2006
        }
2007
        case FUNCT3_SLL: { /* slliw. */
2008
            i32 result = (i32)((u32)cpu->regs[ins.i.rs1] << (imm & 0x1F));
2009
            cpu->regs[ins.i.rd] = (u64)(i64)result;
2010
            break;
2011
        }
2012
        case FUNCT3_SRL: { /* srliw/sraiw. */
2013
            if ((imm & 0x400) == 0) {
2014
                /* srliw -- logical right shift, then sign-extend. */
2015
                i32 result = (i32)((u32)cpu->regs[ins.i.rs1] >> (imm & 0x1F));
2016
                cpu->regs[ins.i.rd] = (u64)(i64)result;
2017
            } else {
2018
                /* sraiw -- arithmetic right shift, then sign-extend. */
2019
                i32 result          = (i32)cpu->regs[ins.i.rs1] >> (imm & 0x1F);
2020
                cpu->regs[ins.i.rd] = (u64)(i64)result;
2021
            }
2022
            break;
2023
        }
2024
        }
2025
        break;
2026
    }
2027
2028
    case OP_OP: {
2029
        if (ins.r.rd == ZERO)
2030
            break;
2031
2032
        cpu->modified = (reg_t)ins.r.rd;
2033
2034
        switch (ins.r.funct7) {
2035
        case FUNCT7_NORMAL: {
2036
            u32 shamt_mask = 0x3F;
2037
            switch (ins.r.funct3) {
2038
            case FUNCT3_ADD: /* add.  */
2039
                cpu->regs[ins.r.rd] =
2040
                    cpu->regs[ins.r.rs1] + cpu->regs[ins.r.rs2];
2041
                break;
2042
            case FUNCT3_SLL: /* sll.  */
2043
                cpu->regs[ins.r.rd] = cpu->regs[ins.r.rs1]
2044
                                      << (cpu->regs[ins.r.rs2] & shamt_mask);
2045
                break;
2046
            case FUNCT3_SLT: /* slt.  */
2047
                cpu->regs[ins.r.rd] =
2048
                    ((i64)cpu->regs[ins.r.rs1] < (i64)cpu->regs[ins.r.rs2]) ? 1
2049
                                                                            : 0;
2050
                break;
2051
            case FUNCT3_SLTU: /* sltu. */
2052
                cpu->regs[ins.r.rd] =
2053
                    (cpu->regs[ins.r.rs1] < cpu->regs[ins.r.rs2]) ? 1 : 0;
2054
                break;
2055
            case FUNCT3_XOR: /* xor.  */
2056
                cpu->regs[ins.r.rd] =
2057
                    cpu->regs[ins.r.rs1] ^ cpu->regs[ins.r.rs2];
2058
                break;
2059
            case FUNCT3_SRL: /* srl.  */
2060
                cpu->regs[ins.r.rd] =
2061
                    cpu->regs[ins.r.rs1] >> (cpu->regs[ins.r.rs2] & shamt_mask);
2062
                break;
2063
            case FUNCT3_OR: /* or.   */
2064
                cpu->regs[ins.r.rd] =
2065
                    cpu->regs[ins.r.rs1] | cpu->regs[ins.r.rs2];
2066
                break;
2067
            case FUNCT3_AND: /* and.  */
2068
                cpu->regs[ins.r.rd] =
2069
                    cpu->regs[ins.r.rs1] & cpu->regs[ins.r.rs2];
2070
                break;
2071
            }
2072
            break;
2073
        }
2074
2075
        case FUNCT7_SUB:
2076
            switch (ins.r.funct3) {
2077
            case FUNCT3_ADD: /* sub. */
2078
                cpu->regs[ins.r.rd] =
2079
                    cpu->regs[ins.r.rs1] - cpu->regs[ins.r.rs2];
2080
                break;
2081
            case FUNCT3_SRL: /* sra. */
2082
                cpu->regs[ins.r.rd] = (u64)((i64)cpu->regs[ins.r.rs1] >>
2083
                                            (cpu->regs[ins.r.rs2] & 0x3F));
2084
                break;
2085
            }
2086
            break;
2087
2088
        case FUNCT7_MUL:
2089
            switch (ins.r.funct3) {
2090
            case FUNCT3_ADD: /* mul.  */
2091
                cpu->regs[ins.r.rd] =
2092
                    cpu->regs[ins.r.rs1] * cpu->regs[ins.r.rs2];
2093
                break;
2094
            case FUNCT3_XOR: /* div.  */
2095
                if (cpu->regs[ins.r.rs2] != 0) {
2096
                    cpu->regs[ins.r.rd] = (u64)((i64)cpu->regs[ins.r.rs1] /
2097
                                                (i64)cpu->regs[ins.r.rs2]);
2098
                } else {
2099
                    cpu->regs[ins.r.rd] = (u64)-1; /* Division by zero. */
2100
                }
2101
                break;
2102
            case FUNCT3_SRL: /* divu. */
2103
                if (cpu->regs[ins.r.rs2] != 0) {
2104
                    cpu->regs[ins.r.rd] =
2105
                        cpu->regs[ins.r.rs1] / cpu->regs[ins.r.rs2];
2106
                } else {
2107
                    cpu->regs[ins.r.rd] = (u64)-1; /* Division by zero. */
2108
                }
2109
                break;
2110
            case FUNCT3_OR: /* rem.  */
2111
                if (cpu->regs[ins.r.rs2] != 0) {
2112
                    cpu->regs[ins.r.rd] = (u64)((i64)cpu->regs[ins.r.rs1] %
2113
                                                (i64)cpu->regs[ins.r.rs2]);
2114
                } else {
2115
                    cpu->regs[ins.r.rd] = cpu->regs[ins.r.rs1];
2116
                }
2117
                break;
2118
            case FUNCT3_AND: /* remu. */
2119
                if (cpu->regs[ins.r.rs2] != 0) {
2120
                    cpu->regs[ins.r.rd] =
2121
                        cpu->regs[ins.r.rs1] % cpu->regs[ins.r.rs2];
2122
                } else {
2123
                    cpu->regs[ins.r.rd] = cpu->regs[ins.r.rs1];
2124
                }
2125
                break;
2126
            }
2127
            break;
2128
        }
2129
        break;
2130
    }
2131
2132
    case OP_OP_32: {
2133
        /* RV64I: 32-bit register-register operations (ADDW, SUBW, SLLW, SRLW,
2134
         * SRAW, MULW, DIVW, DIVUW, REMW, REMUW). These operate on the lower 32
2135
         * bits and sign-extend the result to 64 bits. */
2136
        if (ins.r.rd == ZERO)
2137
            break;
2138
2139
        cpu->modified = (reg_t)ins.r.rd;
2140
        u32 rs1_32    = (u32)cpu->regs[ins.r.rs1];
2141
        u32 rs2_32    = (u32)cpu->regs[ins.r.rs2];
2142
2143
        switch (ins.r.funct7) {
2144
        case FUNCT7_NORMAL:
2145
            switch (ins.r.funct3) {
2146
            case FUNCT3_ADD: { /* addw. */
2147
                i32 result          = (i32)(rs1_32 + rs2_32);
2148
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2149
                break;
2150
            }
2151
            case FUNCT3_SLL: { /* sllw. */
2152
                i32 result          = (i32)(rs1_32 << (rs2_32 & 0x1F));
2153
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2154
                break;
2155
            }
2156
            case FUNCT3_SRL: { /* srlw. */
2157
                i32 result          = (i32)(rs1_32 >> (rs2_32 & 0x1F));
2158
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2159
                break;
2160
            }
2161
            }
2162
            break;
2163
2164
        case FUNCT7_SUB:
2165
            switch (ins.r.funct3) {
2166
            case FUNCT3_ADD: { /* subw. */
2167
                i32 result          = (i32)(rs1_32 - rs2_32);
2168
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2169
                break;
2170
            }
2171
            case FUNCT3_SRL: { /* sraw. */
2172
                i32 result          = (i32)rs1_32 >> (rs2_32 & 0x1F);
2173
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2174
                break;
2175
            }
2176
            }
2177
            break;
2178
2179
        case FUNCT7_MUL:
2180
            switch (ins.r.funct3) {
2181
            case FUNCT3_ADD: { /* mulw.  */
2182
                i32 result          = (i32)(rs1_32 * rs2_32);
2183
                cpu->regs[ins.r.rd] = (u64)(i64)result;
2184
                break;
2185
            }
2186
            case FUNCT3_XOR: { /* divw.  */
2187
                if (rs2_32 != 0) {
2188
                    i32 result          = (i32)rs1_32 / (i32)rs2_32;
2189
                    cpu->regs[ins.r.rd] = (u64)(i64)result;
2190
                } else {
2191
                    cpu->regs[ins.r.rd] = (u64)(i64)(i32)-1;
2192
                }
2193
                break;
2194
            }
2195
            case FUNCT3_SRL: { /* divuw. */
2196
                if (rs2_32 != 0) {
2197
                    i32 result          = (i32)(rs1_32 / rs2_32);
2198
                    cpu->regs[ins.r.rd] = (u64)(i64)result;
2199
                } else {
2200
                    cpu->regs[ins.r.rd] = (u64)(i64)(i32)-1;
2201
                }
2202
                break;
2203
            }
2204
            case FUNCT3_OR: { /* remw.  */
2205
                if (rs2_32 != 0) {
2206
                    i32 result          = (i32)rs1_32 % (i32)rs2_32;
2207
                    cpu->regs[ins.r.rd] = (u64)(i64)result;
2208
                } else {
2209
                    cpu->regs[ins.r.rd] = (u64)(i64)(i32)rs1_32;
2210
                }
2211
                break;
2212
            }
2213
            case FUNCT3_AND: { /* remuw. */
2214
                if (rs2_32 != 0) {
2215
                    i32 result          = (i32)(rs1_32 % rs2_32);
2216
                    cpu->regs[ins.r.rd] = (u64)(i64)result;
2217
                } else {
2218
                    cpu->regs[ins.r.rd] = (u64)(i64)(i32)rs1_32;
2219
                }
2220
                break;
2221
            }
2222
            }
2223
            break;
2224
        }
2225
        break;
2226
    }
2227
2228
    case OP_SYSTEM: {
2229
        u32 funct12 = ins.i.imm_11_0;
2230
2231
        if (g_opts.machine_mode) {
2232
            u32 funct3 = (ins.raw >> 12) & 7;
2233
            if (funct3 != 0) {
2234
                u32 csr_num = (ins.raw >> 20) & 0xFFF;
2235
                u32 rd      = (ins.raw >> 7) & 0x1F;
2236
                u32 rs1     = (ins.raw >> 15) & 0x1F;
2237
                u64 src     = funct3 >= 5 ? rs1 : cpu->regs[rs1];
2238
                bool writes = funct3 == 1 || funct3 == 5
2239
                    || ((funct3 == 2 || funct3 == 3
2240
                         || funct3 == 6 || funct3 == 7) && src != 0);
2241
                u64 *csr = cpu_csr(cpu, csr_num);
2242
                if (funct3 == 4 || !csr || cpu->privilege < ((csr_num >> 8) & 3)
2243
                    || (writes && ((csr_num >> 10) & 3) == 3)) {
2244
                    cpu_machine_trap(cpu, executed_pc, 2, ins.raw, &pc_next);
2245
                    break;
2246
                }
2247
                u64 old = *csr;
2248
                switch (funct3) {
2249
                case 1: case 5: *csr = src; break;
2250
                case 2: case 6: if (src != 0) *csr = old | src; break;
2251
                case 3: case 7: if (src != 0) *csr = old & ~src; break;
2252
                default: break;
2253
                }
2254
                if (writes && csr_num == 0x300) {
2255
                    u64 mpp = (*csr >> 11) & 3;
2256
                    if (mpp != 0 && mpp != 3)
2257
                        *csr &= ~((u64)3 << 11);
2258
                } else if (writes && (csr_num == 0x305 || csr_num == 0x341)) {
2259
                    *csr &= ~(u64)3;
2260
                }
2261
                if (rd != 0)
2262
                    cpu->regs[rd] = old;
2263
                break;
2264
            }
2265
            if (funct12 == 0) {
2266
                cpu_machine_trap(
2267
                    cpu, executed_pc, cpu->privilege == 0 ? 8 : 11, 0, &pc_next
2268
                );
2269
            } else if (funct12 == 1) {
2270
                cpu_machine_trap(cpu, executed_pc, 3, executed_pc, &pc_next);
2271
            } else if (funct12 == 0x105) {
2272
                cpu->running = false;
2273
            } else if (funct12 == 0x302) {
2274
                if (cpu->privilege != 3) {
2275
                    cpu_machine_trap(cpu, executed_pc, 2, ins.raw, &pc_next);
2276
                    break;
2277
                }
2278
                u8 next_priv = (cpu->mstatus >> 11) & 3;
2279
                u64 mpie     = (cpu->mstatus >> 7) & 1;
2280
                if (next_priv != 0 && next_priv != 3)
2281
                    next_priv = 0;
2282
                cpu->mstatus = (cpu->mstatus & ~((u64)1 << 3)) | (mpie << 3);
2283
                cpu->mstatus |= (u64)1 << 7;
2284
                cpu->mstatus &= ~((u64)3 << 11);
2285
                cpu->privilege = next_priv;
2286
                pc_next        = (u32)(cpu->mepc & ~(u64)3);
2287
            } else {
2288
                cpu_machine_trap(cpu, executed_pc, 2, ins.raw, &pc_next);
2289
            }
2290
            break;
2291
        }
2292
2293
        if (funct12 == 0) {
2294
            u32 syscall_num = (u32)cpu->regs[A7];
2295
2296
            switch (syscall_num) {
2297
            case 64: { /* write. */
2298
                int guest_fd = (int)cpu->regs[A0];
2299
                u64 addr     = cpu->regs[A1];
2300
                u64 count    = cpu->regs[A2];
2301
2302
                if (addr + count > g_opts.memory_size ||
2303
                    addr > (u64)g_opts.memory_size) {
2304
                    printf(
2305
                        "sys_write out of bounds: addr=%016llx len=%llu\n",
2306
                        (unsigned long long)addr,
2307
                        (unsigned long long)count
2308
                    );
2309
                    cpu->running = false;
2310
                    emit_fault_diagnostics(cpu, executed_pc);
2311
                    break;
2312
                }
2313
                ssize_t written = 0;
2314
                int     host_fd = guest_fd_table_get(guest_fd);
2315
2316
                if (host_fd >= 0 && count > 0) {
2317
                    written = write(host_fd, &memory[(u32)addr], (u32)count);
2318
                    if (written < 0) {
2319
                        written = 0;
2320
                    }
2321
                }
2322
                cpu->regs[A0] = (u64)written;
2323
                break;
2324
            }
2325
            case 63: { /* read. */
2326
                int guest_fd = (int)cpu->regs[A0];
2327
                u64 addr     = cpu->regs[A1];
2328
                u64 count    = cpu->regs[A2];
2329
2330
                if (addr + count > g_opts.memory_size ||
2331
                    addr > (u64)g_opts.memory_size) {
2332
                    printf(
2333
                        "sys_read out of bounds: addr=%016llx len=%llu\n",
2334
                        (unsigned long long)addr,
2335
                        (unsigned long long)count
2336
                    );
2337
                    cpu->running = false;
2338
                    emit_fault_diagnostics(cpu, executed_pc);
2339
                    break;
2340
                }
2341
                ssize_t read_bytes = 0;
2342
                int     host_fd    = guest_fd_table_get(guest_fd);
2343
2344
                if (host_fd >= 0 && count > 0) {
2345
                    read_bytes = read(host_fd, &memory[(u32)addr], (u32)count);
2346
                    if (read_bytes < 0) {
2347
                        read_bytes = 0;
2348
                    }
2349
                }
2350
                cpu->regs[A0] = (u64)read_bytes;
2351
                break;
2352
            }
2353
            case 93: { /* exit. */
2354
                cpu->running = false;
2355
                break;
2356
            }
2357
            case 56: { /* openat. */
2358
                u64 pathname_addr = cpu->regs[A1];
2359
                i32 flags         = (i32)cpu->regs[A2];
2360
                if (pathname_addr > (u64)g_opts.memory_size) {
2361
                    cpu->regs[A0] = (u64)(i64)(i32)-1;
2362
                    break;
2363
                }
2364
                cpu->regs[A0] =
2365
                    (u64)(i64)(i32)ecall_openat((u32)pathname_addr, flags);
2366
                break;
2367
            }
2368
            case 57: { /* close. */
2369
                i32 guest_fd  = (i32)cpu->regs[A0];
2370
                cpu->regs[A0] = (u64)(i64)ecall_close(guest_fd);
2371
                break;
2372
            }
2373
            default:
2374
                cpu->regs[A0] = (u32)syscall_num;
2375
                break;
2376
            }
2377
        } else if (funct12 == 1) {
2378
            /* Look up source location for this EBREAK.  PC in the debug
2379
             * file is relative to program start, so subtract base. */
2380
            u32                 relative_pc = executed_pc - program_base;
2381
            struct debug_entry *entry       = debug_lookup(relative_pc);
2382
2383
            printf("\n%sRuntime error (EBREAK)%s", COLOR_BOLD_RED, COLOR_RESET);
2384
            if (entry) {
2385
                u32 line = line_from_offset(entry->file, entry->offset);
2386
                printf(
2387
                    " at %s%s:%d%s", COLOR_CYAN, entry->file, line, COLOR_RESET
2388
                );
2389
            }
2390
            printf("\n");
2391
2392
            cpu->running  = false;
2393
            cpu->regs[A0] = EBREAK_EXIT_CODE;
2394
            cpu->ebreak   = true;
2395
            emit_fault_diagnostics(cpu, executed_pc);
2396
            cpu->faulted = false;
2397
        } else {
2398
            printf(
2399
                "\n%sUnknown system instruction (imm=%08x)%s\n",
2400
                COLOR_BOLD_RED,
2401
                funct12,
2402
                COLOR_RESET
2403
            );
2404
            cpu->running = false;
2405
            emit_fault_diagnostics(cpu, executed_pc);
2406
        }
2407
        break;
2408
    }
2409
2410
    case OP_FENCE:
2411
        /* Memory barriers are not implemented. */
2412
        break;
2413
2414
    default:
2415
        printf("Unknown opcode %02x at PC=%08x\n", opcode, cpu->pc);
2416
        cpu->running = false;
2417
        emit_fault_diagnostics(cpu, executed_pc);
2418
        break;
2419
    }
2420
    /* Register x0 is hardwired to zero. */
2421
    cpu->regs[ZERO] = 0;
2422
    cpu->pc         = pc_next;
2423
2424
    if (cpu->running) {
2425
        validate_stack_register(cpu, SP, "SP", executed_pc, false);
2426
        if (cpu->running)
2427
            validate_stack_register(cpu, FP, "FP", executed_pc, true);
2428
    }
2429
2430
    /* Track last executed PC for trace mode. */
2431
    if (headless && g_opts.trace_print_instructions)
2432
        last_executed_pc = executed_pc;
2433
}
2434
2435
/* Render the instructions column. */
2436
static void ui_render_instructions(
2437
    struct cpu *cpu, int col, int width, int height
2438
) {
2439
    int row = 1;
2440
2441
    u32 program_start_idx = program_base / INSTR_SIZE;
2442
    u32 program_end_idx   = program_start_idx + cpu->programsize;
2443
2444
    /* Calculate PC index in program. */
2445
    u32 pc_idx = cpu->pc / sizeof(instr_t);
2446
    if (pc_idx < program_start_idx || pc_idx >= program_end_idx)
2447
        pc_idx = program_start_idx;
2448
2449
    /* Calculate first instruction to display, centering PC if possible. */
2450
    i32 progstart = (i32)pc_idx - height / 2;
2451
    i32 min_start = (i32)program_start_idx;
2452
    i32 max_start = (i32)program_end_idx - height;
2453
2454
    if (max_start < min_start)
2455
        max_start = min_start;
2456
    if (progstart < min_start)
2457
        progstart = min_start;
2458
    if (progstart > max_start)
2459
        progstart = max_start;
2460
2461
    printf(TTY_GOTO_RC, row++, col);
2462
    printf("  INSTRUCTIONS");
2463
2464
    for (int i = 0; i < height; i++) {
2465
        u32 idx = (u32)progstart + i;
2466
        if (idx >= program_end_idx)
2467
            break;
2468
2469
        printf(TTY_GOTO_RC, row + i + 1, col);
2470
2471
        char istr[MAX_INSTR_STR_LEN] = { 0 };
2472
        int  len = sprint_instr(cpu->program[idx], istr, true);
2473
2474
        if (idx == pc_idx) { /* Highlight current instruction. */
2475
            printf("%s>%s %04x: ", COLOR_GREEN, COLOR_RESET, idx * INSTR_SIZE);
2476
        } else {
2477
            printf("  %s%04x:%s ", COLOR_GREY, idx * INSTR_SIZE, COLOR_RESET);
2478
        }
2479
        printf("%s", istr);
2480
        printf("%-*s", width - len - 8, "");
2481
    }
2482
}
2483
2484
/* Render the registers column. */
2485
static void ui_render_registers(
2486
    struct cpu *cpu, enum display display, int col, int height
2487
) {
2488
    int row = 1;
2489
2490
    printf(TTY_GOTO_RC, row++, col);
2491
    printf("REGISTERS");
2492
2493
    int reg_count =
2494
        sizeof(registers_displayed) / sizeof(registers_displayed[0]);
2495
    if (reg_count > height)
2496
        reg_count = height;
2497
2498
    for (int i = 0; i < reg_count; i++) {
2499
        printf(TTY_GOTO_RC, row + i + 1, col);
2500
2501
        reg_t       r = registers_displayed[i];
2502
        const char *reg_color =
2503
            (r == cpu->modified) ? COLOR_BOLD_BLUE : COLOR_BLUE;
2504
        u64  reg_value = cpu->regs[r];
2505
        bool is_stack_addr =
2506
            reg_value <= (u64)UINT32_MAX && stack_contains((u32)reg_value);
2507
2508
        /* Always show registers that contain stack addresses in hex. */
2509
        printf("%s%-2s%s = ", COLOR_GREEN, reg_names[r], COLOR_RESET);
2510
        if (display == DISPLAY_HEX || is_stack_addr) {
2511
            printf("%s0x%08x%s", reg_color, (u32)reg_value, COLOR_RESET);
2512
        } else {
2513
            printf("%s%-10d%s", reg_color, (i32)reg_value, COLOR_RESET);
2514
        }
2515
    }
2516
}
2517
2518
/* Render the stack column. */
2519
static void ui_render_stack(
2520
    struct cpu *cpu, enum display display, int col, int height
2521
) {
2522
    int row = 1;
2523
2524
    printf(TTY_GOTO_RC, row++, col);
2525
    printf("     STACK FRAME");
2526
2527
    assert(cpu->regs[SP] <= memory_top() && cpu->regs[FP] <= memory_top());
2528
2529
    u32 fp   = (u32)cpu->regs[FP];
2530
    u32 sp   = (u32)cpu->regs[SP];
2531
    u32 rows = (u32)height;
2532
    if (rows > STACK_DISPLAY_WORDS)
2533
        rows = STACK_DISPLAY_WORDS;
2534
    if (rows == 0)
2535
        return;
2536
2537
    u32 top    = stack_usable_top();
2538
    u32 bottom = stack_usable_bottom();
2539
    if (sp > top)
2540
        sp = top;
2541
    if (fp > top)
2542
        fp = top;
2543
2544
    u32 used_bytes  = (top >= sp) ? (top - sp) : 0;
2545
    u32 total_words = (used_bytes / WORD_SIZE) + 1;
2546
    u32 frame_words = total_words;
2547
    if (frame_words > rows)
2548
        frame_words = rows;
2549
    if (frame_words == 0)
2550
        return;
2551
2552
    u32 start;
2553
    if (frame_words == total_words) {
2554
        start = top;
2555
    } else {
2556
        start = sp + (frame_words - 1) * WORD_SIZE;
2557
        if (start > top)
2558
            start = top;
2559
    }
2560
2561
    if (start < bottom)
2562
        start = bottom;
2563
2564
    u32 addr   = start;
2565
    i32 offset = (i32)(start - sp);
2566
2567
    for (u32 i = 0; i < frame_words; i++) {
2568
        if (addr < bottom)
2569
            break;
2570
2571
        assert(addr <= memory_top());
2572
        printf(TTY_GOTO_RC, row + i + 1, col);
2573
2574
        /* Mark SP and FP positions. */
2575
        const char *marker = "  ";
2576
2577
        if (addr == sp) {
2578
            marker = "sp";
2579
        } else if (addr == fp) {
2580
            marker = "fp";
2581
        }
2582
        u32 word = memory_load_u32(addr);
2583
2584
        char offset_buf[6];
2585
        if (addr == sp) {
2586
            memcpy(offset_buf, "    ", 5);
2587
        } else {
2588
            snprintf(offset_buf, sizeof(offset_buf), "%+4d", offset);
2589
        }
2590
2591
        printf(
2592
            "%s%s %s%s%s %08x: ",
2593
            COLOR_GREEN,
2594
            marker,
2595
            COLOR_GREY,
2596
            offset_buf,
2597
            COLOR_RESET,
2598
            addr
2599
        );
2600
        bool is_stack_addr = stack_contains(word);
2601
2602
        if (display == DISPLAY_HEX || is_stack_addr) {
2603
            printf("%s0x%08x%s", COLOR_BLUE, word, COLOR_RESET);
2604
        } else {
2605
            printf("%s%-10d%s", COLOR_BLUE, (i32)word, COLOR_RESET);
2606
        }
2607
        if (addr < WORD_SIZE)
2608
            break;
2609
2610
        addr   -= WORD_SIZE;
2611
        offset -= WORD_SIZE;
2612
    }
2613
}
2614
2615
/* Render the full debugger TUI. */
2616
static void ui_render(struct cpu *cpu, enum display display) {
2617
    printf(TTY_CLEAR);
2618
2619
    struct termsize tsize = termsize();
2620
2621
    /* Enforce a minimum display size. */
2622
    if (tsize.cols < 60)
2623
        tsize.cols = 60;
2624
    if (tsize.rows < 15)
2625
        tsize.rows = 15;
2626
2627
    /* Column layout: 40% instructions, 20% registers, rest for stack. */
2628
    int instr_width = (tsize.cols * 2) / 5;
2629
    int reg_width   = tsize.cols / 5;
2630
2631
    int instr_col = 1;
2632
    int reg_col   = instr_col + instr_width + 2;
2633
    int stack_col = reg_col + reg_width + 2;
2634
2635
    int display_height = tsize.rows - FOOTER_HEIGHT - HEADER_HEIGHT;
2636
    if (display_height > MAX_INSTR_DISPLAY)
2637
        display_height = MAX_INSTR_DISPLAY;
2638
    if (display_height <= 0)
2639
        display_height = 1;
2640
2641
    ui_render_instructions(cpu, instr_col, instr_width, display_height);
2642
    ui_render_registers(cpu, display, reg_col, display_height);
2643
    ui_render_stack(cpu, display, stack_col, display_height);
2644
2645
    printf(TTY_GOTO_RC, display_height + FOOTER_HEIGHT, 1);
2646
    printf(
2647
        "%sPress `j` to step forward, `k` to step backward, `q` to quit,\n"
2648
        "`d` to toggle decimal display, `r` to reset program.%s ",
2649
        COLOR_GREY,
2650
        COLOR_RESET
2651
    );
2652
}
2653
2654
/* Set up the terminal for interactive mode, saving the original settings. */
2655
static void term_init(struct termios *oldterm) {
2656
    struct termios term;
2657
2658
    tcgetattr(STDIN_FILENO, oldterm);
2659
    term          = *oldterm;
2660
    term.c_lflag &= ~(ICANON | ECHO);
2661
    tcsetattr(STDIN_FILENO, TCSANOW, &term);
2662
}
2663
2664
/* Restore terminal settings. */
2665
static void term_restore(struct termios *old) {
2666
    tcsetattr(STDIN_FILENO, TCSANOW, old);
2667
}
2668
2669
int main(int argc, char *argv[]) {
2670
    struct cpu        cpu;
2671
    enum display      display = DISPLAY_DEC;
2672
    struct cli_config cli     = { 0 };
2673
2674
    if (!parse_cli_args(argc, argv, &cli))
2675
        return 1;
2676
2677
    bool headless = cli.headless;
2678
2679
    g_opts.trace_headless = headless;
2680
2681
    cpu_init(&cpu);
2682
    program_init(&cpu, cli.program_path);
2683
    int    prog_argc = argc - cli.arg_index;
2684
    char **prog_argv = &argv[cli.arg_index];
2685
    prepare_env(&cpu, prog_argc, prog_argv);
2686
2687
    if (headless) {
2688
        u64 max_steps = g_opts.headless_max_steps;
2689
        u64 steps     = 0;
2690
2691
        /* Try to initialise the JIT for headless mode.  Falls back to the
2692
         * interpreter automatically when JIT is disabled, unavailable,
2693
         * or when the code cache fills up. */
2694
        static struct jit_state jit;
2695
        bool                    use_jit = false;
2696
2697
        if (!g_opts.jit_disabled && !g_opts.trace_enabled &&
2698
            !g_opts.trace_print_instructions && !g_opts.watch_enabled) {
2699
            use_jit = jit_init(&jit);
2700
        }
2701
2702
        if (use_jit) {
2703
            /* ---- JIT execution loop ---- */
2704
            while (cpu.running && steps < max_steps) {
2705
                struct jit_block *block = jit_get_block(
2706
                    &jit, cpu.pc, memory, program_base, program_bytes
2707
                );
2708
                if (!block) {
2709
                    /* Cache full or compilation error -- fall back to
2710
                     * interpreter for remainder. */
2711
                    while (cpu.running && steps++ < max_steps) {
2712
                        cpu_execute(&cpu, display, true);
2713
                    }
2714
                    break;
2715
                }
2716
                u32 next_pc = 0;
2717
                int exit_reason =
2718
                    jit_exec_block(block, cpu.regs, memory, &next_pc);
2719
                steps += block->insn_count;
2720
                jit.blocks_executed++;
2721
                jit.insns_executed += block->insn_count;
2722
2723
                switch (exit_reason) {
2724
                case JIT_EXIT_BRANCH:
2725
                case JIT_EXIT_CHAIN:
2726
                    cpu.pc = next_pc;
2727
                    break;
2728
2729
                case JIT_EXIT_RET:
2730
                    cpu.running = false;
2731
                    break;
2732
2733
                default:
2734
                    /* ECALL, EBREAK, FAULT -- interpreter handles it.
2735
                     * The instruction is already counted above,
2736
                     * so don't increment steps again. */
2737
                    cpu.pc = next_pc;
2738
                    cpu_execute(&cpu, display, true);
2739
                    break;
2740
                }
2741
            }
2742
            jit_destroy(&jit);
2743
        } else {
2744
            /* ---- Interpreter-only loop ---- */
2745
            while (cpu.running && steps++ < max_steps) {
2746
                cpu_execute(&cpu, display, true);
2747
            }
2748
        }
2749
2750
        if (cpu.running) {
2751
            fprintf(
2752
                stderr,
2753
                "program did not terminate within %zu steps\n",
2754
                (size_t)max_steps
2755
            );
2756
            return -1;
2757
        }
2758
        if (cpu.faulted) {
2759
            fprintf(stderr, "program terminated due to runtime fault\n");
2760
            return -1;
2761
        }
2762
        if (g_opts.count_instructions) {
2763
            fprintf(
2764
                stderr,
2765
                "Processed %llu instructions\n",
2766
                (unsigned long long)steps
2767
            );
2768
        }
2769
        return (int)cpu.regs[A0];
2770
    }
2771
    struct termios oldterm;
2772
    term_init(&oldterm);
2773
    snapshot_init(&cpu);
2774
2775
    for (;;) {
2776
        if (cpu.running)
2777
            ui_render(&cpu, display);
2778
2779
        int ch = getchar();
2780
2781
        if (ch == 'q' || ch == 'Q') {
2782
            printf("\n");
2783
            break;
2784
        } else if (ch == 'd' || ch == 'D') { /* Toggle display mode. */
2785
            display = (display == DISPLAY_HEX) ? DISPLAY_DEC : DISPLAY_HEX;
2786
        } else if (ch == 'r' || ch == 'R') { /* Reset program and state. */
2787
            cpu_reset(&cpu);
2788
            snapshot_init(&cpu);
2789
        } else if (ch == 'g' || ch == 'G') { /* Toggle stack guard. */
2790
            toggle_stack_guard(&cpu);
2791
        } else if (ch == 'j' && cpu.running) { /* Step forward. */
2792
            cpu_execute(&cpu, display, false);
2793
            snapshot_save(&cpu);
2794
        } else if (ch == 'k' && cpu.pc > 0) { /* Step backward. */
2795
            bool restored = snapshot_restore(&cpu);
2796
2797
            if (restored) {
2798
                cpu.running = true;
2799
            } else {
2800
                printf(
2801
                    "\n%sNo more history to go back to.%s\n",
2802
                    COLOR_BOLD_RED,
2803
                    COLOR_RESET
2804
                );
2805
            }
2806
        }
2807
    }
2808
    term_restore(&oldterm);
2809
2810
    return 0;
2811
}