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