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