frame_value_backend_test.c (37165B)
1 /* Real-backend regression for typed FRAME_VALUE address components. 2 * 3 * Spill-slot coloring is allowed to reuse a physical frame slot across values 4 * with different type IDs. NativeAddr therefore carries base_type/index_type 5 * as the value-width authority: a backend must not infer a reload width from 6 * the (possibly wider) slot descriptor. Build two deliberately-wide i64 7 * slots, consume them as a pointer base plus an i32 scaled index, and inspect 8 * only the body bytes emitted by each native backend. 9 * 10 * This also pins the RV64 load_addr path: it used to load the frame base but 11 * silently omit an INDEX_FRAME_VALUE. The final sh2add proves the index 12 * participates in the address. 13 */ 14 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <string.h> 18 19 #include "arch/aa64/aa64.h" 20 #include "arch/arch.h" 21 #include "arch/arm32/arm32.h" 22 #include "arch/riscv/rv64.h" 23 #include "arch/x64/isa.h" 24 #include "arch/x64/x64.h" 25 #include "cg/type.h" 26 #include "core/pool.h" 27 #include "lib/kit_unit.h" 28 #include "obj/obj.h" 29 #include "opt/opt.h" 30 31 /* Keep NativeTarget callbacks on the semantic descriptor while naming the 32 * optimizer-private descriptor explicitly in the forced-emitter test. */ 33 #undef CGFuncDesc 34 35 typedef NativeTarget *(*NativeCtor)(Compiler *, ObjBuilder *, MCEmitter *); 36 37 typedef struct DecodedInsn { 38 char mnemonic[20]; 39 char operands[96]; 40 } DecodedInsn; 41 42 typedef struct BackendCase { 43 KitArchKind arch; 44 const char *name; 45 NativeCtor ctor; 46 Reg dst_reg; 47 u8 ptr_size; 48 } BackendCase; 49 50 static void slice_copy(KitSlice src, char *dst, size_t cap) { 51 size_t n = src.len < cap - 1u ? src.len : cap - 1u; 52 if (n && src.s) 53 memcpy(dst, src.s, n); 54 dst[n] = '\0'; 55 } 56 57 static u32 decode_body(KitUnit *unit, Compiler *c, ObjBuilder *obj, 58 ObjSecId text, u32 begin, u32 end, DecodedInsn *out, 59 u32 cap, const char *tag) { 60 const Section *sec = obj_section_get(obj, text); 61 ArchDisasm *dis = NULL; 62 u8 *flat = NULL; 63 u32 size; 64 u32 pos = begin; 65 u32 nout = 0; 66 67 CU_CHECK(unit, sec != NULL, "%s: missing text section", tag); 68 if (!sec) 69 return 0; 70 size = buf_pos(&sec->bytes); 71 CU_CHECK(unit, begin <= end && end <= size, 72 "%s: invalid body range [%u,%u) in %u-byte text", tag, begin, end, 73 size); 74 if (begin > end || end > size) 75 return 0; 76 flat = size ? (u8 *)malloc(size) : NULL; 77 CU_CHECK(unit, size == 0u || flat != NULL, 78 "%s: could not allocate flattened text", tag); 79 if (size && !flat) 80 return 0; 81 if (size) 82 buf_flatten(&sec->bytes, flat); 83 dis = arch_disasm_new(c); 84 CU_CHECK(unit, dis != NULL, "%s: could not create disassembler", tag); 85 if (!dis) { 86 free(flat); 87 return 0; 88 } 89 90 while (pos < end) { 91 KitInsn insn; 92 u32 used; 93 memset(&insn, 0, sizeof insn); 94 used = arch_disasm_decode(dis, flat + pos, end - pos, pos, &insn); 95 CU_CHECK(unit, used != 0u, "%s: undecodable body byte at offset %u", tag, 96 pos); 97 if (!used) 98 break; 99 CU_CHECK(unit, nout < cap, "%s: body exceeds %u decoded instructions", tag, 100 cap); 101 if (nout < cap) { 102 slice_copy(insn.mnemonic, out[nout].mnemonic, sizeof out[nout].mnemonic); 103 slice_copy(insn.operands, out[nout].operands, sizeof out[nout].operands); 104 ++nout; 105 } 106 pos += used; 107 } 108 CU_EXPECT(unit, pos == end, "%s: decoder stopped at %u, body ends at %u", tag, 109 pos, end); 110 arch_disasm_free(dis); 111 free(flat); 112 return nout; 113 } 114 115 static int has_insn(const DecodedInsn *insns, u32 ninsns, const char *mnemonic, 116 const char *operand) { 117 for (u32 i = 0; i < ninsns; ++i) { 118 if (mnemonic && !strstr(insns[i].mnemonic, mnemonic)) 119 continue; 120 if (operand && !strstr(insns[i].operands, operand)) 121 continue; 122 return 1; 123 } 124 return 0; 125 } 126 127 static i32 find_insn(const DecodedInsn *insns, u32 ninsns, 128 const char *mnemonic, const char *operand) { 129 for (u32 i = 0; i < ninsns; ++i) { 130 if (mnemonic && !strstr(insns[i].mnemonic, mnemonic)) 131 continue; 132 if (operand && !strstr(insns[i].operands, operand)) 133 continue; 134 return (i32)i; 135 } 136 return -1; 137 } 138 139 static u32 count_insns(const DecodedInsn *insns, u32 ninsns, 140 const char *mnemonic) { 141 u32 count = 0; 142 for (u32 i = 0; i < ninsns; ++i) 143 if (strstr(insns[i].mnemonic, mnemonic)) 144 ++count; 145 return count; 146 } 147 148 static u32 collect_relocs_to(const ObjBuilder *obj, ObjSecId section, 149 u32 begin, u32 end, ObjSymId sym, Reloc *out, 150 u32 cap) { 151 u32 n = 0; 152 for (u32 i = 0; i < obj_reloc_total(obj); ++i) { 153 const Reloc *r = obj_reloc_at(obj, i); 154 if (!r || r->removed || r->section_id != section || r->offset < begin || 155 r->offset >= end || r->sym != sym) 156 continue; 157 if (n < cap) 158 out[n] = *r; 159 ++n; 160 } 161 return n; 162 } 163 164 static void check_location_address_roles(KitUnit *unit) { 165 NativeLoc storage = {0}; 166 NativeLoc frame_addr = {0}; 167 NativeLoc global = {0}; 168 NativeLoc imm = {0}; 169 NativeAddr addr; 170 i64 part = 0; 171 172 storage.kind = NATIVE_LOC_STACK; 173 storage.type = 11u; 174 storage.v.stack.slot = 7u; 175 storage.v.stack.offset = 5; 176 CU_EXPECT(unit, 177 native_loc_addr_role(storage) == NATIVE_LOC_ADDR_ROLE_STORAGE && 178 native_loc_storage_addr(storage, 3, &addr) && 179 addr.base_kind == NATIVE_ADDR_BASE_FRAME && 180 addr.base.frame == 7u && addr.offset == 8, 181 "STACK must resolve only as offset storage"); 182 CU_EXPECT(unit, !native_loc_address_value(storage, &addr), 183 "storage location was accepted as an address value"); 184 185 frame_addr.kind = NATIVE_LOC_FRAME_ADDR; 186 frame_addr.type = 12u; 187 frame_addr.v.frame = 9u; 188 CU_EXPECT(unit, 189 native_loc_addr_role(frame_addr) == NATIVE_LOC_ADDR_ROLE_VALUE && 190 native_loc_address_value(frame_addr, &addr) && 191 addr.base_kind == NATIVE_ADDR_BASE_FRAME && 192 addr.base.frame == 9u && addr.offset == 0, 193 "FRAME_ADDR must resolve only as a scalar address recipe"); 194 CU_EXPECT(unit, !native_loc_storage_addr(frame_addr, 0, &addr), 195 "frame address value was accepted as storage"); 196 197 global.kind = NATIVE_LOC_GLOBAL; 198 global.type = 13u; 199 global.v.global.sym = 17u; 200 global.v.global.addend = 37; 201 CU_EXPECT(unit, 202 native_loc_address_value(global, &addr) && 203 addr.base_kind == NATIVE_ADDR_BASE_GLOBAL && 204 addr.base.global.sym == 17u && 205 addr.base.global.addend == 37, 206 "GLOBAL address recipe lost its symbol/addend"); 207 CU_EXPECT(unit, !native_loc_storage_addr(global, 0, &addr), 208 "global address value was accepted as storage"); 209 210 imm.kind = NATIVE_LOC_IMM; 211 imm.v.imm = (i64)0x1122334455667788ull; 212 CU_EXPECT(unit, 213 native_loc_imm_part(imm, 4u, 4u, &part) && 214 (u64)part == 0x11223344u, 215 "immediate ABI part selection returned the wrong high word"); 216 CU_EXPECT(unit, !native_loc_imm_part(imm, 7u, 2u, &part), 217 "immediate ABI part selection accepted an out-of-bounds lane"); 218 } 219 220 static void check_typed_frame_address(KitUnit *unit, const BackendCase *tc) { 221 KitTargetSpec spec = kit_unit_target(tc->arch, KIT_OS_LINUX, KIT_OBJ_ELF); 222 KitCompiler *kit_c = NULL; 223 Compiler *c; 224 ObjBuilder *obj = NULL; 225 MCEmitter *mc = NULL; 226 NativeTarget *native; 227 ObjSecId text, data; 228 ObjSymId sym, global_sym; 229 KitCgTypeId void_type, i8_type, i32_type, i64_type, ptr_type, tiny3_type, 230 block8_type, fn_type, call_fn_type, wide_call_fn_type, 231 exact_call_fn_type, forced_vararg_fn_type; 232 KitCgFuncParam call_param, exact_call_param; 233 KitCgFuncResult result; 234 KitCgFuncSig sig, call_sig, forced_vararg_sig; 235 CGFuncDesc func; 236 NativeFrameSlotDesc slot_descs[2]; 237 NativeKnownFrameDesc frame; 238 NativeFrameSlot slots[2]; 239 NativeAddr addr; 240 NativeLoc dst; 241 NativeLoc call_arg; 242 NativeLoc exact_arg; 243 NativeCallDesc call_desc; 244 NativeCallPhase call_plan; 245 DecodedInsn body[12]; 246 DecodedInsn call_body[8]; 247 DecodedInsn global_body[12]; 248 DecodedInsn imm_body[12]; 249 DecodedInsn exact_call_body[24]; 250 DecodedInsn forced_vararg_body[32]; 251 DecodedInsn copy_frame_value_body[16]; 252 DecodedInsn set_frame_value_body[24]; 253 DecodedInsn indexed_frame_value_body[12]; 254 Reloc global_relocs[4]; 255 u32 body_begin, body_end, nbody; 256 u32 call_begin, call_end, ncall; 257 u32 global_begin, global_end, nglobal, nglobal_relocs; 258 u32 imm_begin = 0, imm_end = 0, nimm = 0; 259 u32 exact_call_begin = 0, exact_call_end = 0, nexact_call = 0; 260 u32 forced_vararg_begin = 0, forced_vararg_end = 0, nforced_vararg = 0; 261 u32 copy_frame_value_begin = 0, copy_frame_value_end = 0, 262 ncopy_frame_value = 0; 263 u32 set_frame_value_begin = 0, set_frame_value_end = 0, 264 nset_frame_value = 0; 265 u32 indexed_frame_value_begin = 0, indexed_frame_value_end = 0, 266 nindexed_frame_value = 0; 267 268 spec.ptr_size = tc->ptr_size; 269 spec.ptr_align = tc->ptr_size; 270 CU_CHECK(unit, kit_unit_compiler_new(unit, spec, &kit_c) == KIT_OK && kit_c, 271 "%s: compiler allocation failed", tc->name); 272 if (!kit_c) 273 return; 274 c = (Compiler *)kit_c; 275 obj = obj_new(c); 276 CU_CHECK(unit, obj != NULL, "%s: object allocation failed", tc->name); 277 if (!obj) 278 goto done; 279 mc = mc_new(c, obj); 280 CU_CHECK(unit, mc != NULL, "%s: MC allocation failed", tc->name); 281 if (!mc) 282 goto done; 283 native = tc->ctor(c, obj, mc); 284 CU_CHECK(unit, native != NULL, "%s: native target allocation failed", 285 tc->name); 286 if (!native) 287 goto done; 288 289 text = obj_section(obj, pool_intern_slice(c->global, SLICE_LIT(".text")), 290 SEC_TEXT, SF_EXEC | SF_ALLOC, 16u); 291 data = obj_section(obj, pool_intern_slice(c->global, SLICE_LIT(".data")), 292 SEC_DATA, SF_WRITE | SF_ALLOC, 16u); 293 sym = obj_symbol(obj, pool_intern_slice(c->global, SLICE_LIT("frame_value")), 294 SB_GLOBAL, SK_FUNC, text, 0u, 0u); 295 global_sym = obj_symbol( 296 obj, pool_intern_slice(c->global, SLICE_LIT("address_value_global")), 297 SB_LOCAL, SK_OBJ, data, 0u, 64u); 298 void_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_VOID); 299 i8_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_I8); 300 i32_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_I32); 301 i64_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_I64); 302 ptr_type = kit_cg_type_ptr(kit_c, void_type, 0u); 303 tiny3_type = kit_cg_type_array(kit_c, i8_type, 3u); 304 block8_type = kit_cg_type_array(kit_c, i8_type, 8u); 305 memset(&result, 0, sizeof result); 306 result.type = void_type; 307 memset(&sig, 0, sizeof sig); 308 sig.result = result; 309 sig.call_conv = KIT_CG_CC_TARGET_C; 310 fn_type = kit_cg_type_func(kit_c, sig); 311 memset(&call_param, 0, sizeof call_param); 312 call_param.type = ptr_type; 313 memset(&call_sig, 0, sizeof call_sig); 314 call_sig.result = result; 315 call_sig.params = &call_param; 316 call_sig.nparams = 1u; 317 call_sig.call_conv = KIT_CG_CC_TARGET_C; 318 call_fn_type = kit_cg_type_func(kit_c, call_sig); 319 call_param.type = i64_type; 320 wide_call_fn_type = kit_cg_type_func(kit_c, call_sig); 321 memset(&exact_call_param, 0, sizeof exact_call_param); 322 exact_call_param.type = tiny3_type; 323 call_sig.params = &exact_call_param; 324 exact_call_fn_type = kit_cg_type_func(kit_c, call_sig); 325 memset(&forced_vararg_sig, 0, sizeof forced_vararg_sig); 326 forced_vararg_sig.result = result; 327 forced_vararg_sig.call_conv = KIT_CG_CC_TARGET_C; 328 forced_vararg_sig.abi_variadic = true; 329 forced_vararg_fn_type = kit_cg_type_func(kit_c, forced_vararg_sig); 330 331 memset(&func, 0, sizeof func); 332 func.sym = sym; 333 func.text_section_id = text; 334 func.fn_type = fn_type; 335 func.result_type = void_type; 336 337 /* Both colored homes advertise i64/8. The address contract below is the 338 * authority: pointer-width base, four-byte index. */ 339 memset(slot_descs, 0, sizeof slot_descs); 340 for (u32 i = 0; i < 2u; ++i) { 341 slot_descs[i].type = i64_type; 342 slot_descs[i].size = 8u; 343 slot_descs[i].align = 8u; 344 slot_descs[i].kind = NATIVE_FRAME_SLOT_SPILL; 345 } 346 memset(&frame, 0, sizeof frame); 347 frame.slots = slot_descs; 348 frame.nslots = 2u; 349 /* The RV-only forced-vararg construction below owns one rounded 16-byte 350 * outgoing region. Reserve it before known-frame finalization just as the 351 * optimizer's frame pre-pass does for a real call. */ 352 if (tc->arch == KIT_ARCH_RV64) frame.max_outgoing = 16u; 353 /* Keep a real frame record on every target so frame-relative body reloads 354 * have a settled anchor independent of leaf-frame elision. */ 355 frame.is_leaf = 0u; 356 native->func_begin_known_frame(native, &func, &frame, slots); 357 358 memset(&addr, 0, sizeof addr); 359 addr.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE; 360 addr.base.frame = slots[0]; 361 addr.base_type = ptr_type; 362 addr.cls = NATIVE_REG_INT; 363 addr.index_kind = NATIVE_ADDR_INDEX_FRAME_VALUE; 364 addr.index.frame = slots[1]; 365 addr.index_type = i32_type; 366 addr.index_cls = NATIVE_REG_INT; 367 addr.log2_scale = 2u; 368 addr.offset = 12; 369 dst = native_loc_reg(ptr_type, NATIVE_REG_INT, tc->dst_reg); 370 body_begin = mc_pos(mc); 371 native->load_addr(native, dst, addr); 372 body_end = mc_pos(mc); 373 374 /* A post-allocation rematerialization may present `&local` directly to the 375 * ABI marshaller. It is an address value, not the scalar stored in the 376 * frame slot, and must not require an optimizer-owned staging register. */ 377 memset(&call_arg, 0, sizeof call_arg); 378 call_arg.kind = NATIVE_LOC_FRAME_ADDR; 379 call_arg.cls = NATIVE_REG_INT; 380 call_arg.type = ptr_type; 381 call_arg.v.frame = slots[0]; 382 memset(&call_desc, 0, sizeof call_desc); 383 call_desc.fn_type = call_fn_type; 384 call_desc.args = &call_arg; 385 call_desc.nargs = 1u; 386 memset(&call_plan, 0, sizeof call_plan); 387 call_begin = mc_pos(mc); 388 native->marshal_call(native, &call_desc, &call_plan); 389 call_end = mc_pos(mc); 390 391 /* A GLOBAL NativeLoc is the scalar address value `&symbol + addend`, never 392 * the bytes stored at that address. Exercise the ABI boundary directly: 393 * ordinary C currently materializes globals before constructing its call 394 * descriptor, so a source-level call cannot pin this lower-level contract. */ 395 memset(&call_arg, 0, sizeof call_arg); 396 call_arg.kind = NATIVE_LOC_GLOBAL; 397 call_arg.cls = NATIVE_REG_INT; 398 call_arg.type = ptr_type; 399 call_arg.v.global.sym = global_sym; 400 call_arg.v.global.addend = 37; 401 memset(&call_desc, 0, sizeof call_desc); 402 call_desc.fn_type = call_fn_type; 403 call_desc.args = &call_arg; 404 call_desc.nargs = 1u; 405 memset(&call_plan, 0, sizeof call_plan); 406 global_begin = mc_pos(mc); 407 native->marshal_call(native, &call_desc, &call_plan); 408 global_end = mc_pos(mc); 409 410 /* ARM32 splits an i64 direct argument into low/high 32-bit ABI lanes. A 411 * NativeLoc immediate denotes the complete scalar, so each lane must select 412 * its own source bits instead of rematerializing the low word twice. */ 413 if (tc->arch == KIT_ARCH_ARM_32) { 414 memset(&call_arg, 0, sizeof call_arg); 415 call_arg.kind = NATIVE_LOC_IMM; 416 call_arg.cls = NATIVE_REG_INT; 417 call_arg.type = i64_type; 418 call_arg.v.imm = (i64)0x1122334455667788ull; 419 memset(&call_desc, 0, sizeof call_desc); 420 call_desc.fn_type = wide_call_fn_type; 421 call_desc.args = &call_arg; 422 call_desc.nargs = 1u; 423 memset(&call_plan, 0, sizeof call_plan); 424 imm_begin = mc_pos(mc); 425 native->marshal_call(native, &call_desc, &call_plan); 426 imm_end = mc_pos(mc); 427 } 428 429 /* x64 lowerers may legally hand the ABI boundary an exact-width aggregate 430 * in NATIVE_LOC_ADDR storage whose explicit base is also its destination ABI 431 * register. Keep a register-indirect callee live across that whole marshal: 432 * the 3-byte argument forces address stabilization through r11, which was 433 * formerly also the parked call target. This backend-level construction pins 434 * the allowed shape even when current source lowering chooses a frame home. */ 435 if (tc->arch == KIT_ARCH_X86_64) { 436 memset(&exact_arg, 0, sizeof exact_arg); 437 exact_arg.kind = NATIVE_LOC_ADDR; 438 exact_arg.cls = NATIVE_REG_INT; 439 exact_arg.type = tiny3_type; 440 exact_arg.v.addr.base_kind = NATIVE_ADDR_BASE_REG; 441 exact_arg.v.addr.base.reg = X64_RDI; 442 exact_arg.v.addr.base_type = ptr_type; 443 exact_arg.v.addr.cls = NATIVE_REG_INT; 444 memset(&call_desc, 0, sizeof call_desc); 445 call_desc.fn_type = exact_call_fn_type; 446 call_desc.callee = native_loc_reg(ptr_type, NATIVE_REG_INT, X64_RCX); 447 call_desc.args = &exact_arg; 448 call_desc.nargs = 1u; 449 memset(&call_plan, 0, sizeof call_plan); 450 exact_call_begin = mc_pos(mc); 451 native->marshal_call(native, &call_desc, &call_plan); 452 native->emit_call(native, &call_plan); 453 exact_call_end = mc_pos(mc); 454 } 455 456 /* RISC-V does not currently select vararg_on_stack, but it still implements 457 * the shared ABI trait and must not encode a latent rounded-slot overread. 458 * Synthesize that trait for one three-byte unnamed argument: the physical 459 * carrier is eight bytes (and the whole outgoing area rounds to 16), while 460 * source and destination accesses must remain exactly 2+1 bytes. Keep the 461 * indirect target in a STACK home at the same time: current lowering usually 462 * materializes that shape before this boundary, but NativeCallDesc permits it 463 * and the backend must park it outside the exact/address temporary bank. */ 464 if (tc->arch == KIT_ARCH_RV64) { 465 const ABIFuncInfo* classified = 466 abi_cg_func_info(c->abi, forced_vararg_fn_type); 467 ABIFuncInfo* synthetic = (ABIFuncInfo*)classified; 468 u8 saved_vararg_on_stack; 469 CU_CHECK(unit, synthetic != NULL, 470 "rv64: variadic function classification failed"); 471 if (synthetic) { 472 saved_vararg_on_stack = synthetic->vararg_on_stack; 473 synthetic->vararg_on_stack = 1u; 474 exact_arg = native_loc_stack(tiny3_type, slots[0], 0); 475 memset(&call_desc, 0, sizeof call_desc); 476 call_desc.fn_type = forced_vararg_fn_type; 477 call_desc.callee = native_loc_stack(ptr_type, slots[1], 0); 478 call_desc.args = &exact_arg; 479 call_desc.nargs = 1u; 480 memset(&call_plan, 0, sizeof call_plan); 481 forced_vararg_begin = mc_pos(mc); 482 native->marshal_call(native, &call_desc, &call_plan); 483 native->emit_call(native, &call_plan); 484 forced_vararg_end = mc_pos(mc); 485 synthetic->vararg_on_stack = saved_vararg_on_stack; 486 } 487 } 488 489 /* The AArch64 aggregate primitives carry their payload in x16. A 490 * FRAME_VALUE destination must therefore materialize its pointer through a 491 * different address scratch. Otherwise both copy_bytes and widened 492 * set_bytes degenerate to `str x16, [x16]`, storing the pointer value rather 493 * than the aggregate payload. Keep this at the backend boundary so the 494 * contract stays independent of optimizer register-allocation choices. */ 495 if (tc->arch == KIT_ARCH_ARM_64) { 496 AggregateAccess access; 497 NativeAddr frame_value_dst; 498 NativeAddr frame_src; 499 NativeLoc fill; 500 MemAccess indexed_mem; 501 memset(&access, 0, sizeof access); 502 access.type = block8_type; 503 access.size = 8u; 504 access.align = 8u; 505 access.mem.type = block8_type; 506 access.mem.size = 8u; 507 access.mem.align = 8u; 508 memset(&frame_value_dst, 0, sizeof frame_value_dst); 509 frame_value_dst.base_kind = NATIVE_ADDR_BASE_FRAME_VALUE; 510 frame_value_dst.base.frame = slots[0]; 511 frame_value_dst.base_type = ptr_type; 512 frame_value_dst.cls = NATIVE_REG_INT; 513 memset(&frame_src, 0, sizeof frame_src); 514 frame_src.base_kind = NATIVE_ADDR_BASE_FRAME; 515 frame_src.base.frame = slots[1]; 516 frame_src.base_type = ptr_type; 517 frame_src.cls = NATIVE_REG_INT; 518 fill = native_loc_reg(i8_type, NATIVE_REG_INT, 9u); 519 520 copy_frame_value_begin = mc_pos(mc); 521 native->copy_bytes(native, frame_value_dst, frame_src, access); 522 copy_frame_value_end = mc_pos(mc); 523 set_frame_value_begin = mc_pos(mc); 524 native->set_bytes(native, frame_value_dst, fill, access); 525 set_frame_value_end = mc_pos(mc); 526 527 /* A register index remains live until the final addressing-mode 528 * instruction. Pin x16 as that index and require base materialization to 529 * select x17 even when the store payload itself uses neither scratch. */ 530 frame_value_dst.index_kind = NATIVE_ADDR_INDEX_REG; 531 frame_value_dst.index.reg = 16u; 532 frame_value_dst.index_type = i64_type; 533 frame_value_dst.index_cls = NATIVE_REG_INT; 534 memset(&indexed_mem, 0, sizeof indexed_mem); 535 indexed_mem.type = i64_type; 536 indexed_mem.size = 8u; 537 indexed_mem.align = 8u; 538 indexed_frame_value_begin = mc_pos(mc); 539 native->store(native, frame_value_dst, 540 native_loc_reg(i64_type, NATIVE_REG_INT, 9u), indexed_mem); 541 indexed_frame_value_end = mc_pos(mc); 542 } 543 native->func_end(native); 544 545 memset(body, 0, sizeof body); 546 nbody = decode_body(unit, c, obj, text, body_begin, body_end, body, 547 (u32)(sizeof body / sizeof body[0]), tc->name); 548 memset(call_body, 0, sizeof call_body); 549 ncall = decode_body(unit, c, obj, text, call_begin, call_end, call_body, 550 (u32)(sizeof call_body / sizeof call_body[0]), tc->name); 551 memset(global_body, 0, sizeof global_body); 552 nglobal = decode_body(unit, c, obj, text, global_begin, global_end, 553 global_body, 554 (u32)(sizeof global_body / sizeof global_body[0]), 555 tc->name); 556 memset(global_relocs, 0, sizeof global_relocs); 557 nglobal_relocs = collect_relocs_to( 558 obj, text, global_begin, global_end, global_sym, global_relocs, 559 (u32)(sizeof global_relocs / sizeof global_relocs[0])); 560 if (tc->arch == KIT_ARCH_ARM_32) { 561 memset(imm_body, 0, sizeof imm_body); 562 nimm = decode_body(unit, c, obj, text, imm_begin, imm_end, imm_body, 563 (u32)(sizeof imm_body / sizeof imm_body[0]), tc->name); 564 } 565 if (tc->arch == KIT_ARCH_X86_64) { 566 memset(exact_call_body, 0, sizeof exact_call_body); 567 nexact_call = decode_body( 568 unit, c, obj, text, exact_call_begin, exact_call_end, exact_call_body, 569 (u32)(sizeof exact_call_body / sizeof exact_call_body[0]), tc->name); 570 } 571 if (tc->arch == KIT_ARCH_RV64) { 572 memset(forced_vararg_body, 0, sizeof forced_vararg_body); 573 nforced_vararg = decode_body( 574 unit, c, obj, text, forced_vararg_begin, forced_vararg_end, 575 forced_vararg_body, 576 (u32)(sizeof forced_vararg_body / sizeof forced_vararg_body[0]), 577 tc->name); 578 } 579 if (tc->arch == KIT_ARCH_ARM_64) { 580 memset(copy_frame_value_body, 0, sizeof copy_frame_value_body); 581 ncopy_frame_value = decode_body( 582 unit, c, obj, text, copy_frame_value_begin, copy_frame_value_end, 583 copy_frame_value_body, 584 (u32)(sizeof copy_frame_value_body / sizeof copy_frame_value_body[0]), 585 "aa64-copy-frame-value"); 586 memset(set_frame_value_body, 0, sizeof set_frame_value_body); 587 nset_frame_value = decode_body( 588 unit, c, obj, text, set_frame_value_begin, set_frame_value_end, 589 set_frame_value_body, 590 (u32)(sizeof set_frame_value_body / sizeof set_frame_value_body[0]), 591 "aa64-set-frame-value"); 592 memset(indexed_frame_value_body, 0, sizeof indexed_frame_value_body); 593 nindexed_frame_value = decode_body( 594 unit, c, obj, text, indexed_frame_value_begin, 595 indexed_frame_value_end, indexed_frame_value_body, 596 (u32)(sizeof indexed_frame_value_body / 597 sizeof indexed_frame_value_body[0]), 598 "aa64-indexed-frame-value"); 599 } 600 CU_EXPECT(unit, nbody >= 3u, "%s: expected typed base/index/add sequence", 601 tc->name); 602 CU_EXPECT(unit, ncall != 0u, 603 "%s: frame-address call argument emitted no materialization", 604 tc->name); 605 CU_EXPECT(unit, nglobal != 0u, 606 "%s: global-address call argument emitted no materialization", 607 tc->name); 608 609 switch (tc->arch) { 610 case KIT_ARCH_X86_64: 611 CU_EXPECT(unit, has_insn(body, nbody, "mov", "r11d"), 612 "x64: i32 frame index was not loaded through r11d"); 613 CU_EXPECT(unit, has_insn(body, nbody, "lea", NULL), 614 "x64: scaled frame index was not folded into LEA"); 615 CU_EXPECT(unit, 616 nglobal_relocs == 1u && global_relocs[0].kind == R_PC32 && 617 global_relocs[0].addend == 33, 618 "x64: GLOBAL call arg lost symbol or +37 addend"); 619 CU_EXPECT(unit, has_insn(global_body, nglobal, "lea", NULL), 620 "x64: GLOBAL call arg was not materialized with LEA"); 621 CU_EXPECT(unit, nglobal == 1u, 622 "x64: GLOBAL call arg unexpectedly dereferenced storage"); 623 { 624 i32 push = find_insn(exact_call_body, nexact_call, "push", "rcx"); 625 i32 exact_load = 626 find_insn(exact_call_body, nexact_call, "mov", "(%r11)"); 627 i32 pop = find_insn(exact_call_body, nexact_call, "pop", "r11"); 628 i32 call = find_insn(exact_call_body, nexact_call, "call", "r11"); 629 CU_EXPECT(unit, push >= 0, 630 "x64: indirect callee was not parked before marshalling"); 631 CU_EXPECT(unit, exact_load > push, 632 "x64: exact address argument did not consume stabilized r11"); 633 CU_EXPECT(unit, pop > exact_load, 634 "x64: indirect callee was restored before argument transport"); 635 CU_EXPECT(unit, call > pop, 636 "x64: restored indirect callee was not the call target"); 637 } 638 break; 639 case KIT_ARCH_ARM_64: 640 CU_EXPECT(unit, has_insn(body, nbody, "ldr", "w17"), 641 "aa64: i32 frame index was not loaded through w17"); 642 CU_EXPECT(unit, has_insn(body, nbody, "add", "lsl #2"), 643 "aa64: scaled frame index was not folded into the address"); 644 CU_EXPECT(unit, 645 nglobal_relocs == 2u && 646 global_relocs[0].kind == R_AARCH64_ADR_PREL_PG_HI21 && 647 global_relocs[1].kind == R_AARCH64_ADD_ABS_LO12_NC && 648 global_relocs[0].addend == 37 && 649 global_relocs[1].addend == 37, 650 "aa64: GLOBAL call arg lost symbol or +37 addend"); 651 CU_EXPECT(unit, nglobal == 2u, 652 "aa64: GLOBAL call arg unexpectedly dereferenced storage"); 653 CU_EXPECT(unit, 654 has_insn(copy_frame_value_body, ncopy_frame_value, "str", 655 "x16, [x17") && 656 !has_insn(copy_frame_value_body, ncopy_frame_value, "str", 657 "x16, [x16"), 658 "aa64: aggregate copy clobbered x16 while materializing its " 659 "FRAME_VALUE destination"); 660 CU_EXPECT(unit, 661 has_insn(set_frame_value_body, nset_frame_value, "str", 662 "x16, [x17") && 663 !has_insn(set_frame_value_body, nset_frame_value, "str", 664 "x16, [x16"), 665 "aa64: widened aggregate set clobbered x16 while materializing " 666 "its FRAME_VALUE destination"); 667 CU_EXPECT(unit, 668 has_insn(indexed_frame_value_body, nindexed_frame_value, "ldr", 669 "x17") && 670 has_insn(indexed_frame_value_body, nindexed_frame_value, 671 "str", "x9, [x17, x16"), 672 "aa64: FRAME_VALUE base materialization clobbered its x16 " 673 "register index"); 674 break; 675 case KIT_ARCH_RV64: 676 CU_EXPECT(unit, has_insn(body, nbody, "lw", "t1"), 677 "rv64: i32 frame index was not loaded with lw"); 678 /* The RV decoder does not yet describe Zba, so it intentionally renders 679 * sh2add a0,t1,a0 as its exact raw word. Pinning that word is stronger 680 * than counting instructions: the old dropped-index path had no fourth 681 * instruction at all. */ 682 CU_EXPECT(unit, has_insn(body, nbody, ".word", "0x20a34533"), 683 "rv64: frame index was dropped from load_addr"); 684 CU_EXPECT(unit, 685 nglobal_relocs == 1u && 686 global_relocs[0].kind == R_RV_PCREL_HI20, 687 "rv64: GLOBAL call arg did not retain its symbol relocation"); 688 CU_EXPECT(unit, has_insn(global_body, nglobal, "addi", "37"), 689 "rv64: GLOBAL call arg lost its +37 addend"); 690 CU_EXPECT(unit, nglobal == 3u && 691 !has_insn(global_body, nglobal, "ld", NULL) && 692 !has_insn(global_body, nglobal, "lw", NULL), 693 "rv64: GLOBAL call arg unexpectedly dereferenced storage"); 694 { 695 i32 park = find_insn(forced_vararg_body, nforced_vararg, "ld", "ra"); 696 i32 exact_load = 697 find_insn(forced_vararg_body, nforced_vararg, "lhu", NULL); 698 i32 exact_store = 699 find_insn(forced_vararg_body, nforced_vararg, "sh", NULL); 700 i32 call = 701 find_insn(forced_vararg_body, nforced_vararg, "jalr", "ra, 0(ra)"); 702 CU_EXPECT(unit, park >= 0, 703 "rv64: indirect callee was not parked outside transport temps"); 704 CU_EXPECT(unit, exact_load > park && 705 has_insn(forced_vararg_body, nforced_vararg, "lbu", 706 NULL), 707 "rv64: 3-byte forced vararg did not use exact 2+1 loads"); 708 CU_EXPECT(unit, exact_store > exact_load && 709 has_insn(forced_vararg_body, nforced_vararg, "sb", 710 NULL), 711 "rv64: 3-byte forced vararg did not use exact 2+1 stores"); 712 CU_EXPECT(unit, 713 count_insns(forced_vararg_body, nforced_vararg, "ld") == 1u && 714 !has_insn(forced_vararg_body, nforced_vararg, "sd", NULL), 715 "rv64: rounded vararg carrier became a semantic access width"); 716 CU_EXPECT(unit, call > exact_store, 717 "rv64: parked indirect target did not survive argument transport"); 718 } 719 break; 720 case KIT_ARCH_ARM_32: 721 CU_EXPECT(unit, count_insns(body, nbody, "ldr") == 2u, 722 "arm32: exact-width base/index should require two word loads"); 723 CU_EXPECT(unit, has_insn(body, nbody, "add", "lsl #2"), 724 "arm32: scaled frame index was not folded into the address"); 725 CU_EXPECT(unit, 726 nglobal_relocs == 2u && 727 global_relocs[0].kind == R_ARM_THM_MOVW_ABS_NC && 728 global_relocs[1].kind == R_ARM_THM_MOVT_ABS, 729 "arm32: GLOBAL call arg did not retain MOVW/MOVT relocations"); 730 CU_EXPECT(unit, has_insn(global_body, nglobal, "add", "37"), 731 "arm32: GLOBAL call arg lost its +37 addend"); 732 CU_EXPECT(unit, nglobal == 3u && 733 !has_insn(global_body, nglobal, "ldr", NULL), 734 "arm32: GLOBAL call arg unexpectedly dereferenced storage"); 735 CU_EXPECT(unit, nimm >= 4u, 736 "arm32: split i64 immediate did not materialize both lanes"); 737 CU_EXPECT(unit, has_insn(imm_body, nimm, "movw", "r0, #30600") && 738 has_insn(imm_body, nimm, "movt", "r0, #21862"), 739 "arm32: i64 immediate low lane is not 0x55667788"); 740 CU_EXPECT(unit, has_insn(imm_body, nimm, "movw", "r1, #13124") && 741 has_insn(imm_body, nimm, "movt", "r1, #4386"), 742 "arm32: i64 immediate high lane is not 0x11223344"); 743 break; 744 default: 745 CU_EXPECT(unit, 0, "%s: unhandled architecture", tc->name); 746 break; 747 } 748 749 done: 750 if (mc) 751 mc_free(mc); 752 if (obj) 753 obj_free(obj); 754 kit_compiler_free(kit_c); 755 } 756 757 static Operand forced_stack_op(FrameSlot slot, KitCgTypeId type) { 758 Operand op; 759 memset(&op, 0, sizeof op); 760 op.kind = OPK_STACK; 761 op.cls = RC_INT; 762 op.type = type; 763 op.v.frame_slot = slot; 764 return op; 765 } 766 767 static FrameSlot forced_slot_new(Func *f, KitCgTypeId type) { 768 FrameSlotDesc desc; 769 memset(&desc, 0, sizeof desc); 770 desc.type = type; 771 desc.size = 4u; 772 desc.align = 4u; 773 desc.kind = FS_SPILL; 774 return ir_frame_slot_new(f, &desc); 775 } 776 777 static const NativeAllocClassInfo *int_class_info(const NativeTarget *native) { 778 if (!native || !native->regs) return NULL; 779 for (u32 i = 0; i < native->regs->nclasses; ++i) 780 if (native->regs->classes[i].cls == NATIVE_REG_INT) 781 return &native->regs->classes[i]; 782 return NULL; 783 } 784 785 static void check_forced_scavenger(KitUnit *unit, const BackendCase *tc) { 786 KitTargetSpec spec = kit_unit_target(tc->arch, KIT_OS_LINUX, KIT_OBJ_ELF); 787 KitCompiler *kit_c = NULL; 788 Compiler *c; 789 ObjBuilder *obj = NULL; 790 MCEmitter *mc = NULL; 791 NativeTarget *native; 792 const NativeAllocClassInfo *ci; 793 ObjSecId text; 794 ObjSymId sym; 795 KitCgTypeId void_type, i32_type, fn_type; 796 KitCgFuncResult result; 797 KitCgFuncSig sig; 798 OptCGFuncDesc desc; 799 Func *f; 800 FrameSlot dst_slot, lhs_slot, rhs_slot; 801 Inst *in; 802 u32 block; 803 Reg emit_temp, scavenge_temp; 804 805 CU_CHECK(unit, unit->ctx.profiler != NULL, 806 "%s: profiler allocation failed", tc->name); 807 if (!unit->ctx.profiler) return; 808 spec.ptr_size = tc->ptr_size; 809 spec.ptr_align = tc->ptr_size; 810 CU_CHECK(unit, kit_unit_compiler_new(unit, spec, &kit_c) == KIT_OK && kit_c, 811 "%s: forced-scavenger compiler allocation failed", tc->name); 812 if (!kit_c) return; 813 c = (Compiler *)kit_c; 814 obj = obj_new(c); 815 CU_CHECK(unit, obj != NULL, "%s: forced-scavenger object allocation failed", 816 tc->name); 817 if (!obj) goto done; 818 mc = mc_new(c, obj); 819 CU_CHECK(unit, mc != NULL, "%s: forced-scavenger MC allocation failed", 820 tc->name); 821 if (!mc) goto done; 822 native = tc->ctor(c, obj, mc); 823 CU_CHECK(unit, native != NULL, 824 "%s: forced-scavenger native target allocation failed", tc->name); 825 if (!native) goto done; 826 ci = int_class_info(native); 827 CU_CHECK(unit, ci != NULL && ci->nemit_temps >= 2u, 828 "%s: forced-scavenger test needs two target-legal integer temps", 829 tc->name); 830 if (!ci || ci->nemit_temps < 2u) goto done; 831 emit_temp = ci->emit_temps[0]; 832 scavenge_temp = ci->emit_temps[1]; 833 834 text = obj_section(obj, pool_intern_slice(c->global, SLICE_LIT(".text")), 835 SEC_TEXT, SF_EXEC | SF_ALLOC, 16u); 836 sym = obj_symbol(obj, 837 pool_intern_slice(c->global, 838 SLICE_LIT("forced_native_scavenger")), 839 SB_LOCAL, SK_FUNC, text, 0u, 0u); 840 void_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_VOID); 841 i32_type = kit_cg_type_builtin(kit_c, KIT_CG_BUILTIN_I32); 842 memset(&result, 0, sizeof result); 843 result.type = void_type; 844 memset(&sig, 0, sizeof sig); 845 sig.result = result; 846 sig.call_conv = KIT_CG_CC_TARGET_C; 847 fn_type = kit_cg_type_func(kit_c, sig); 848 849 memset(&desc, 0, sizeof desc); 850 desc.sym = sym; 851 desc.text_section_id = text; 852 desc.fn_type = fn_type; 853 desc.result_type = void_type; 854 f = ir_func_new(c, &desc); 855 block = ir_block_new(f); 856 f->entry = block; 857 ir_note_emit(f, block); 858 f->opt_rewritten = 1u; 859 860 /* Narrow the instruction bank to one register, then lend a second 861 * target-legal caller-saved register through the allocator list. An all-stack 862 * binary op must hold lhs and rhs simultaneously, deterministically entering 863 * the liveness-guarded scavenger before calling the real backend hook. */ 864 f->emit_temp_regs[RC_INT][0] = emit_temp; 865 f->emit_temp_reg_count[RC_INT] = 1u; 866 f->opt_reserved_regs[RC_INT] = 1u << emit_temp; 867 f->opt_hard_regs[RC_INT][0] = scavenge_temp; 868 f->opt_hard_reg_count[RC_INT] = 1u; 869 f->opt_caller_saved[RC_INT] = 1u << scavenge_temp; 870 871 dst_slot = forced_slot_new(f, i32_type); 872 lhs_slot = forced_slot_new(f, i32_type); 873 rhs_slot = forced_slot_new(f, i32_type); 874 in = ir_emit(f, block, IR_BINOP); 875 in->nopnds = 3u; 876 in->opnds = arena_zarray(f->arena, Operand, in->nopnds); 877 in->opnds[0] = forced_stack_op(dst_slot, i32_type); 878 in->opnds[1] = forced_stack_op(lhs_slot, i32_type); 879 in->opnds[2] = forced_stack_op(rhs_slot, i32_type); 880 in->extra.imm = BO_IADD; 881 882 kit_profiler_reset(unit->ctx.profiler); 883 opt_emit_native(c, f, native); 884 CU_EXPECT(unit, 885 unit->ctx.profiler 886 ->counters[KIT_PROFILE_COUNTER_OPT_NATIVE_EMIT_SCAVENGES] > 887 0u, 888 "%s: forced pressure did not enter native scavenger", tc->name); 889 CU_EXPECT(unit, mc_pos(mc) != 0u, 890 "%s: forced-scavenger backend emitted no code", tc->name); 891 892 done: 893 if (mc) mc_free(mc); 894 if (obj) obj_free(obj); 895 kit_compiler_free(kit_c); 896 } 897 898 int main(void) { 899 static const BackendCase cases[] = { 900 {KIT_ARCH_X86_64, "x64", x64_native_target_new, 0u, 8u}, 901 {KIT_ARCH_ARM_64, "aa64", aa64_native_target_new, 0u, 8u}, 902 {KIT_ARCH_RV64, "rv64", rv64_native_target_new, 10u, 8u}, 903 {KIT_ARCH_ARM_32, "arm32", arm32_native_target_new, 0u, 4u}, 904 }; 905 KitUnit unit; 906 KitProfiler *profiler; 907 kit_unit_init(&unit); 908 profiler = (KitProfiler *)malloc(sizeof *profiler); 909 CU_CHECK(&unit, profiler != NULL, "profiler allocation failed"); 910 if (profiler) { 911 kit_profiler_reset(profiler); 912 unit.ctx.profiler = profiler; 913 } 914 check_location_address_roles(&unit); 915 for (u32 i = 0; i < (u32)(sizeof cases / sizeof cases[0]); ++i) { 916 check_typed_frame_address(&unit, &cases[i]); 917 check_forced_scavenger(&unit, &cases[i]); 918 } 919 kit_unit_summary(&unit, "frame-value-backends"); 920 free(profiler); 921 return kit_unit_status(&unit); 922 }