Unaligned Memory Access

Table of Contents

1. Unaligned Memory Access

cpu 对 unaligned memory access (UMA) 的支持分为几种情况:

  1. 不支持, 会触发 trap
  2. 支持, 但比较慢
  3. 支持且不慢

如果编译器能判断出当前指令是 UMA, 则会根据 slow_unaligned_access 和 `-mstrict-align` 参数决定对于 UMA 是直接生成 load/store 指令还是通过其它多条指令. 例如, 为了支持非对齐的 sw, 编译器可能会使用 4 条 sb 指令

1.1. 通过 trap handler 模拟 UMA

有些时候编译器能判断出是否是 UMA, 例如

struct __attribute__((packed)) X {
    short a;
    int b;
};

编译器知道访问 b 时不是 4 字节对齐的.

但有时编译器无法判断出是否对齐, 例如:

short *y = ((char *)&x) + k;
*y = 1;

可见如果硬件不支持 UMA, 试图通过编译器来完全避免 UMA 是不可行的, 这时需要用户自己提供 trap handler 通过其它方式 (例如使用支持 UMA 的指令或转换为以字节为单位的指令例如 riscv 的 sb 指令) 模拟当前指令.

以 riscv 为例:

int main(int argc, char *argv[]) {
    int x = 10;
    short *y = ((char *)&x) + 1;
    printf("test:%p\n", y);
    *y = 1;
    return 0;
}

spike 对应 store 指令的代码:

void mmu_t::store_slow_path(
    reg_t addr, reg_t len, const uint8_t* bytes, uint32_t xlate_flags,
    bool actually_store, bool UNUSED require_alignment) {
    if (actually_store)
        check_triggers(
            triggers::OPERATION_STORE, addr, reg_from_bytes(len, bytes));
    printf("ssp:1:%x %d\n", addr, len);
    if (addr & (len - 1)) {
        bool gva = ((proc) ? proc->state.v : false) ||
                   (RISCV_XLATE_VIRT & xlate_flags);
        if (!is_misaligned_enabled()) {
            printf("ssp:2:%x\n", addr);
            throw trap_store_address_misaligned(gva, addr, 0, 0);
        }

        if (require_alignment) throw trap_store_access_fault(gva, addr, 0, 0);

        reg_t len_page0 = std::min(len, PGSIZE - addr % PGSIZE);
        store_slow_path_intrapage(
            addr, len_page0, bytes, xlate_flags, actually_store);
        if (len_page0 != len)
            store_slow_path_intrapage(
                addr + len_page0, len - len_page0, bytes + len_page0,
                xlate_flags, actually_store);
    } else {
        store_slow_path_intrapage(
            addr, len, bytes, xlate_flags, actually_store);
    }
    printf("ssp:3:%x\n", addr);
}

执行的 log 为:

#> spike pk ./a.out
test:0x3ffffff9f5
ssp:1:fffff9f5 2
ssp:2:fffff9f5
...
...
ssp:1:fffff9f5 1
ssp:2:fffff9f5
ssp:3:fffff9f5
ssp:1:fffff9f6 1
ssp:2:fffff9f6
ssp:3:fffff9f6

// 做为对比, spike 的 `--misaligned` 参数表示硬件支持 UMA
#> spike --misaligned pk ./a.out
test:0x3ffffff9f5 2
ssp:1:fffff9f5 2
ssp:3:fffff9f5

上面例子中发生 trap 后由 pk 的 misaligned_store_trap 负责把指令转换为两次一字节的 store

void misaligned_store_trap(uintptr_t* regs, uintptr_t mcause, uintptr_t mepc) {
    union byte_array val;
    uintptr_t mstatus;
    insn_t insn = get_insn(mepc, &mstatus);
    uintptr_t npc = mepc + insn_len(insn);
    int len;

    val.intx = GET_RS2(insn, regs);
    if ((insn & MASK_SW) == MATCH_SW)
        len = 4;
    else if ((insn & MASK_SH) == MATCH_SH)
        len = 2;
    else {
        mcause = CAUSE_STORE_ACCESS;
        write_csr(mcause, mcause);
        return truly_illegal_insn(regs, mcause, mepc, mstatus, insn);
    }

    /* NOTE: misaligned_store 异常时 mtval 为要 store 的地址 */
    /* NOTE: val 是 RS2, 即要 store 的寄存器 */
    uintptr_t addr = read_csr(mtval);
    intptr_t offs = 0;
    /* NOTE: 循环多次, 每次写一个 byte */
    for (int i = 0; i < len; i++)
        store_uint8_t((void*)(addr + i), val.bytes[offs + i], mepc);

    write_csr(mepc, npc);
}

另外, 上述只针对 unaligned store/load, 对于其它要求对齐的情况例如 branch 地址对齐, 原子指令内存对齐等并不适用.

Author: [email protected]
Date: 2023-08-17 Thu 13:43
Last updated: 2023-08-17 Thu 15:44

知识共享许可协议