Unaligned Memory Access
Table of Contents
1. Unaligned Memory Access
cpu 对 unaligned memory access (UMA) 的支持分为几种情况:
- 不支持, 会触发 trap
- 支持, 但比较慢
- 支持且不慢
如果编译器能判断出当前指令是 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 地址对齐, 原子指令内存对齐等并不适用.