Support new radiance image format

92cdb0c5293447964be053214fac403b49193ac3ec07c902576346ebaa205535
Alexis Sellier committed ago 1 parent aef68e69
README +8 -5
28 28
29 29
  Headless:
30 30
31 31
    $ bin/emulator -run <program.bin> [<option>..]
32 32
33 -
  The emulator loads a flat binary along with optional data sections:
33 +
  The emulator loads a self-contained Radiance RV64 image:
34 34
35 -
    <program.bin>           Program text (machine instructions)
36 -
    <program.bin>.ro.data   Read-only data section
37 -
    <program.bin>.rw.data   Read-write data section
38 -
    <program.bin>.debug     Debug info (optional, for source locations)
35 +
    Header                  "RAD0", version, and section sizes
36 +
    Text                    Machine instructions
37 +
    Read-only data          Loaded at 0x00010000
38 +
    Read-write data         Loaded at 0x00FFFFF0
39 +
40 +
  Legacy flat binaries with optional `.ro.data` and `.rw.data` sidecars remain
41 +
  supported. A `<program>.debug` sidecar may be used for source locations.
39 42
40 43
41 44
OPTIONS
42 45
43 46
    -run                    Run headless (no TUI).
emulator.c +125 -27
72 72
/* TTY escape codes. */
73 73
#define TTY_CLEAR                "\033[2J\033[H"
74 74
#define TTY_GOTO_RC              "\033[%d;%dH"
75 75
/* Exit code returned on EBREAK. */
76 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
77 81
78 82
/* Registers displayed in the TUI, in order. */
79 83
static const reg_t registers_displayed[] = {
80 84
    SP, FP, RA, A0, A1, A2, A3, A4, A5, A6, A7, T0, T1, T2, T3, T4, T5, T6
81 85
};
1326 1330
        );
1327 1331
    }
1328 1332
    return u_size;
1329 1333
}
1330 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 +
1331 1359
/* Prepare the environment block (argv) on the guest stack. */
1332 1360
static void prepare_env(struct cpu *cpu, int argc, char **argv) {
1333 1361
    if (argc < 0 || argv == NULL)
1334 1362
        argc = 0;
1335 1363
1474 1502
1475 1503
/* Load the program binary and data sections into memory. */
1476 1504
static void program_init(struct cpu *cpu, const char *filepath) {
1477 1505
    program_bytes = 0;
1478 1506
    data_bytes    = 0;
1479 -
    rodata_bytes  = load_section(
1480 -
        filepath, "ro.data", DATA_RO_OFFSET, DATA_MEMORY_START, "ro.data"
1481 -
    );
1482 -
    program_base = align(DATA_RO_OFFSET + rodata_bytes, WORD_SIZE);
1483 -
1484 1507
    if (g_opts.debug_enabled)
1485 1508
        debug_load(filepath);
1486 1509
1487 1510
    FILE *file = fopen(filepath, "rb");
1488 1511
    if (!file)
1490 1513
    if (fseek(file, 0, SEEK_END) != 0) {
1491 1514
        fclose(file);
1492 1515
        bail("failed to seek program '%s'", filepath);
1493 1516
    }
1494 1517
    long size = ftell(file);
1495 -
    if (size <= 0 || size > PROGRAM_SIZE) {
1518 +
    if (size <= 0) {
1496 1519
        fclose(file);
1497 -
        bail(
1498 -
            "invalid file size: %ld; maximum program size is %d bytes",
1499 -
            size,
1500 -
            PROGRAM_SIZE
1501 -
        );
1502 -
    }
1503 -
    if (program_base + (u32)size > DATA_MEMORY_START) {
1504 -
        fclose(file);
1505 -
        bail("text section exceeds available program memory");
1520 +
        bail("invalid file size: %ld", size);
1506 1521
    }
1507 1522
    if (fseek(file, 0, SEEK_SET) != 0) {
1508 1523
        fclose(file);
1509 1524
        bail("failed to rewind program '%s'", filepath);
1510 1525
    }
1511 1526
1512 -
    usize read = fread(&memory[program_base], 1, size, file);
1513 -
    fclose(file);
1514 -
    if (read != (size_t)size)
1515 -
        bail("could not read entire file");
1516 -
1517 -
    program_bytes    = (u32)size;
1518 -
    cpu->programsize = (u32)size / sizeof(instr_t);
1519 -
1520 1527
    u32 data_limit = DATA_MEMORY_START + g_opts.data_memory_size;
1521 1528
    if (data_limit > g_opts.memory_size)
1522 1529
        data_limit = g_opts.memory_size;
1523 1530
1524 -
    data_bytes = load_section(
1525 -
        filepath, "rw.data", DATA_MEMORY_START, data_limit, "rw.data"
1526 -
    );
1527 -
    cpu->pc = program_base;
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;
1528 1626
}
1529 1627
1530 1628
/* Execute a single instruction. */
1531 1629
static void cpu_execute(struct cpu *cpu, enum display display, bool headless) {
1532 1630
    if (cpu_out_of_bounds(cpu)) {