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