rv32_jit_test.c (10407B)
1 /* RV32 JIT smoke test. 2 * 3 * Builds a tiny ELF relocatable object in memory for rv32 containing 4 * one function: 5 * 6 * .text 7 * .globl rv32_jit_answer 8 * rv32_jit_answer: 9 * addi a0, zero, 42 # 0x02a00513 10 * jalr zero, ra, 0 # 0x00008067 (ret) 11 * 12 * Feeds it through kit_link_session in KIT_LINK_OUTPUT_JIT mode, 13 * which exercises the rv32 path of: 14 * - executable-memory reservation + W^X protect cycle 15 * - relocation application (none needed here, but the path runs) 16 * - symbol resolution / lookup by C-mangled name 17 * - icache flush (fence.i / __builtin___clear_cache on riscv hosts) 18 * 19 * If we are running on a rv32 host, the test then *calls* the JITed 20 * function and asserts the return is 42 — that's the native-host 21 * execution leg the parity checklist asked for. On non-rv32 hosts 22 * we still build the image (verifying the in-memory machinery is wired 23 * end-to-end) but SKIP the actual call: the bytes are valid rv32 but 24 * the host CPU can't decode them. The test prints "SKIP <reason>" and 25 * exits 77 (the GNU autotools "skipped" convention) when this happens. 26 * 27 * Wired into mk/test.mk via test-rv32-jit. Always builds; calls only on 28 * rv32 hosts. This mirrors the rv64 JIT smoke test 29 * (test/link/rv64_jit_test.c): have the code path in place for the day someone 30 * runs kit on a rv32 dev box. 31 * 32 * The two base-ISA encodings are identical on rv32 and rv64 (they are RV32I 33 * instructions reused unchanged by RV64I), so the encoding constants are the 34 * same as the rv64 sibling test. */ 35 36 #include <kit/core.h> 37 #include <kit/jit.h> 38 #include <kit/link.h> 39 #include <kit/object.h> 40 #include <stdarg.h> 41 #include <stdint.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <sys/mman.h> 46 #include <unistd.h> 47 48 #include "lib/kit_unit.h" 49 50 /* Native execution requires the host CPU to be rv32 (any OS that gives 51 * us POSIX mmap + mprotect, which on rv32 means Linux today). Anywhere 52 * else we still build the JIT image but skip the call. */ 53 #if defined(__riscv) && (__riscv_xlen == 32) 54 #define RV32_HOST_NATIVE 1 55 #else 56 #define RV32_HOST_NATIVE 0 57 #endif 58 59 /* ---- host glue: heap + diag come from the shared KitUnit ---- */ 60 static KitUnit g_u; 61 62 /* ---- execmem with W^X dual-mapping (mirrors test/link/harness) ---- */ 63 static int xm_to_posix(int p) { 64 int q = 0; 65 if (p & KIT_PROT_READ) q |= PROT_READ; 66 if (p & KIT_PROT_WRITE) q |= PROT_WRITE; 67 if (p & KIT_PROT_EXEC) q |= PROT_EXEC; 68 return q; 69 } 70 71 #if defined(__linux__) 72 #include <sys/syscall.h> 73 #define XM_DUAL_LINUX 1 74 #else 75 #define XM_DUAL_LINUX 0 76 #endif 77 78 typedef struct XmTok { 79 void* w; 80 void* r; 81 size_t n; 82 } XmTok; 83 84 static KitStatus xm_reserve_single(size_t n, KitExecMemRegion* out) { 85 void* p = 86 mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); 87 if (p == MAP_FAILED) return KIT_NOMEM; 88 out->write = out->runtime = p; 89 out->size = n; 90 out->token = NULL; 91 return KIT_OK; 92 } 93 94 static KitStatus xm_reserve(void* u, size_t n, int p, KitExecMemRegion* out) { 95 (void)u; 96 if (!out || !n) return KIT_INVALID; 97 if (!(p & KIT_PROT_EXEC)) return xm_reserve_single(n, out); 98 #if XM_DUAL_LINUX 99 { 100 int fd = (int)syscall(SYS_memfd_create, "kit-rv32-jit-test", 0u); 101 void *w, *r; 102 XmTok* tok; 103 if (fd < 0) return KIT_NOMEM; 104 if (ftruncate(fd, (off_t)n) != 0) { 105 close(fd); 106 return KIT_NOMEM; 107 } 108 w = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 109 if (w == MAP_FAILED) { 110 close(fd); 111 return KIT_NOMEM; 112 } 113 r = mmap(NULL, n, PROT_READ, MAP_SHARED, fd, 0); 114 close(fd); 115 if (r == MAP_FAILED) { 116 munmap(w, n); 117 return KIT_NOMEM; 118 } 119 tok = (XmTok*)malloc(sizeof(*tok)); 120 if (!tok) { 121 munmap(r, n); 122 munmap(w, n); 123 return KIT_NOMEM; 124 } 125 tok->w = w; 126 tok->r = r; 127 tok->n = n; 128 out->write = w; 129 out->runtime = r; 130 out->size = n; 131 out->token = tok; 132 return KIT_OK; 133 } 134 #else 135 return xm_reserve_single(n, out); 136 #endif 137 } 138 139 static KitStatus xm_protect(void* u, void* a, size_t n, int p) { 140 (void)u; 141 return mprotect(a, n, xm_to_posix(p)) == 0 ? KIT_OK : KIT_IO; 142 } 143 144 static void xm_release(void* u, KitExecMemRegion* region) { 145 (void)u; 146 if (!region || !region->size) return; 147 if (region->token) { 148 XmTok* tok = (XmTok*)region->token; 149 if (tok->r && tok->r != tok->w) munmap(tok->r, tok->n); 150 if (tok->w) munmap(tok->w, tok->n); 151 free(tok); 152 } else if (region->write) { 153 munmap(region->write, region->size); 154 } 155 region->write = region->runtime = NULL; 156 region->size = 0; 157 region->token = NULL; 158 } 159 160 static void xm_flush(void* u, void* a, size_t n) { 161 (void)u; 162 #if defined(__aarch64__) || defined(__arm__) || defined(__riscv) 163 #if defined(__riscv) 164 /* Local-hart self-modify ordering; __builtin___clear_cache below also 165 * issues the cross-hart syscall on Linux. */ 166 __asm__ __volatile__("fence.i" ::: "memory"); 167 #endif 168 __builtin___clear_cache((char*)a, (char*)a + n); 169 #else 170 (void)a; 171 (void)n; 172 #endif 173 } 174 175 static KitExecMem g_execmem = { 176 16 * 1024, xm_reserve, xm_protect, xm_release, xm_flush, NULL, 177 }; 178 179 /* ---- rv32 instruction encodings used by the test ---- */ 180 /* These are RV32I base-ISA instructions, byte-identical to the rv64 forms. */ 181 /* `addi a0, zero, 42` — I-type: imm[11:0]=42, rs1=0, funct3=000 (ADDI), 182 * rd=10 (a0), opcode=0010011. */ 183 #define ENC_ADDI_A0_ZERO_42 0x02a00513u 184 /* `jalr zero, 0(ra)` (= ret) — I-type: imm=0, rs1=1 (ra), funct3=000, 185 * rd=0 (zero), opcode=1100111. */ 186 #define ENC_RET 0x00008067u 187 188 /* ---- the test ---- */ 189 typedef int (*answer_fn)(void); 190 191 int main(void) { 192 /* Page size for the execmem. Same dance as the other runners. */ 193 { 194 long ps = sysconf(_SC_PAGESIZE); 195 if (ps > 0) g_execmem.page_size = (size_t)ps; 196 } 197 198 KitTargetSpec target = 199 kit_unit_target(KIT_ARCH_RV32, KIT_OS_LINUX, KIT_OBJ_ELF); 200 /* kit_unit_target hardcodes ptr_size/ptr_align=8 for the rv64-era suite; 201 * rv32 is ILP32 and the ELF class (ELFCLASS32 vs 64) is selected from 202 * ptr_size, so narrow them here. */ 203 target.ptr_size = 4; 204 target.ptr_align = 4; 205 206 kit_unit_init(&g_u); 207 g_u.ctx.now = -1; 208 209 KitCompiler* c = NULL; 210 if (kit_unit_compiler_new(&g_u, target, &c) != KIT_OK || !c) { 211 fprintf(stderr, "rv32_jit_test: compiler_new failed\n"); 212 return 2; 213 } 214 215 /* Build the object. */ 216 KitObjBuilder* ob = NULL; 217 if (kit_obj_builder_new(c, &ob) != KIT_OK || !ob) { 218 fprintf(stderr, "rv32_jit_test: obj_builder_new failed\n"); 219 kit_compiler_free(c); 220 return 2; 221 } 222 223 KitObjSectionDesc sec_desc; 224 memset(&sec_desc, 0, sizeof(sec_desc)); 225 sec_desc.name = kit_sym_intern(c, KIT_SLICE_LIT(".text")); 226 sec_desc.kind = KIT_SEC_TEXT; 227 sec_desc.flags = KIT_SF_EXEC | KIT_SF_ALLOC; 228 sec_desc.align = 4; 229 KitObjSection text = KIT_SECTION_NONE; 230 if (kit_obj_builder_section(ob, &sec_desc, &text) != KIT_OK) { 231 fprintf(stderr, "rv32_jit_test: section failed\n"); 232 return 2; 233 } 234 235 uint32_t code[2] = {ENC_ADDI_A0_ZERO_42, ENC_RET}; 236 if (kit_obj_builder_write(ob, text, code, sizeof(code)) != KIT_OK) { 237 fprintf(stderr, "rv32_jit_test: write failed\n"); 238 return 2; 239 } 240 241 KitObjSymbolDesc sym_desc; 242 memset(&sym_desc, 0, sizeof(sym_desc)); 243 sym_desc.name = kit_sym_intern(c, KIT_SLICE_LIT("rv32_jit_answer")); 244 sym_desc.bind = KIT_SB_GLOBAL; 245 sym_desc.kind = KIT_SK_FUNC; 246 sym_desc.section = text; 247 sym_desc.value = 0; 248 sym_desc.size = sizeof(code); 249 KitObjSymbol sym = KIT_OBJ_SYMBOL_NONE; 250 if (kit_obj_builder_symbol(ob, &sym_desc, &sym) != KIT_OK) { 251 fprintf(stderr, "rv32_jit_test: symbol failed\n"); 252 return 2; 253 } 254 255 if (kit_obj_builder_finalize(ob) != KIT_OK) { 256 fprintf(stderr, "rv32_jit_test: finalize failed\n"); 257 return 2; 258 } 259 260 /* JIT the object. The host's execmem is the W^X dual-map above. */ 261 KitJitHost jhost; 262 memset(&jhost, 0, sizeof(jhost)); 263 jhost.execmem = &g_execmem; 264 265 KitLinkSessionOptions opts; 266 memset(&opts, 0, sizeof(opts)); 267 opts.output_kind = KIT_LINK_OUTPUT_JIT; 268 opts.entry = KIT_SLICE_LIT("rv32_jit_answer"); 269 opts.jit_host = &jhost; 270 271 KitLinkSession* sess = NULL; 272 if (kit_link_session_new(c, &opts, &sess) != KIT_OK || !sess) { 273 fprintf(stderr, "rv32_jit_test: link_session_new failed\n"); 274 return 1; 275 } 276 if (kit_link_session_add_obj(sess, ob) != KIT_OK) { 277 fprintf(stderr, "rv32_jit_test: add_obj failed\n"); 278 kit_link_session_free(sess); 279 return 1; 280 } 281 282 KitJit* jit = NULL; 283 if (kit_link_session_jit(sess, &jit) != KIT_OK || !jit) { 284 fprintf(stderr, "rv32_jit_test: link_session_jit failed\n"); 285 kit_link_session_free(sess); 286 return 1; 287 } 288 kit_link_session_free(sess); 289 290 void* fn = kit_jit_lookup(jit, KIT_SLICE_LIT("rv32_jit_answer")); 291 if (!fn) { 292 fprintf(stderr, "rv32_jit_test: lookup failed\n"); 293 kit_jit_free(jit); 294 kit_compiler_free(c); 295 return 1; 296 } 297 298 /* Reading back the first instruction bytes through the runtime alias 299 * is always safe and verifies the bytes survived the W^X dance plus 300 * the icache-flush hook fired without crashing. This is the portable 301 * check on non-rv32 hosts. */ 302 uint32_t got = 0; 303 memcpy(&got, fn, sizeof(got)); 304 if (got != ENC_ADDI_A0_ZERO_42) { 305 fprintf(stderr, 306 "rv32_jit_test: bytes corrupted at runtime alias: got 0x%08x " 307 "expected 0x%08x\n", 308 (unsigned)got, (unsigned)ENC_ADDI_A0_ZERO_42); 309 kit_jit_free(jit); 310 kit_compiler_free(c); 311 return 1; 312 } 313 314 #if RV32_HOST_NATIVE 315 /* Real execution on a rv32 host. */ 316 { 317 answer_fn f = (answer_fn)(uintptr_t)fn; 318 int r = f(); 319 if (r != 42) { 320 fprintf(stderr, "rv32_jit_test: jit fn returned %d, expected 42\n", r); 321 kit_jit_free(jit); 322 kit_compiler_free(c); 323 return 1; 324 } 325 printf("rv32_jit_test: PASS (native rv32 execution returned 42)\n"); 326 } 327 #else 328 /* Non-rv32 host: JIT plumbing worked end-to-end (image built, 329 * permissions flipped, lookup resolved, bytes intact at the runtime 330 * alias). Skip the actual call — calling rv32 bytes on a non-rv32 331 * CPU would SIGILL. Exit-code 77 is the GNU autotools convention 332 * for "skipped" so test wrappers can distinguish from pass/fail. */ 333 printf( 334 "rv32_jit_test: SKIP — non-rv32 host (image built, " 335 "lookup OK, bytes intact)\n"); 336 kit_jit_free(jit); 337 kit_compiler_free(c); 338 return 77; 339 #endif 340 341 kit_jit_free(jit); 342 kit_compiler_free(c); 343 return 0; 344 }