native_argmove.c (3746B)
1 #include "cg/native_argmove.h" 2 3 /* Upper bound on moves in one shuffle. Callers (per-call register args, entry 4 * param binds) are bounded by the ABI register pools (<= 16 per class); 32 5 * covers both classes with headroom. */ 6 #define NATIVE_ARG_SHUFFLE_MAX 32u 7 8 /* The register a move reads, or REG_NONE if it reads none (immediate, frame 9 * slot, or an address derived from fp/sp/a global). Only a register source can 10 * be invalidated by another move writing its destination register, so only 11 * these constrain the emission order. */ 12 static Reg nam_src_reg(const NativeArgMove* m, NativeAllocClass* cls_out) { 13 if (!m->is_addr && m->src.kind == NATIVE_LOC_REG) { 14 *cls_out = (NativeAllocClass)m->src.cls; 15 return m->src.v.reg; 16 } 17 return REG_NONE; 18 } 19 20 void native_arg_shuffle(const NativeArgShuffle* s, NativeArgMove* moves, 21 u32 n) { 22 u8 done[NATIVE_ARG_SHUFFLE_MAX]; 23 u32 emitted = 0; 24 if (n > NATIVE_ARG_SHUFFLE_MAX) 25 compiler_panic(s->t->c, (SrcLoc){0, 0, 0}, 26 "native arg shuffle: too many moves"); 27 memset(done, 0, sizeof done); 28 /* Mark-and-sweep over the move set. Each pass emits every move whose 29 * destination is no longer needed as a source (topological order); when only 30 * a true cycle remains, break one member and continue. */ 31 while (emitted < n) { 32 int progress = 0; 33 u32 i, j; 34 for (i = 0; i < n; ++i) { 35 int blocked = 0; 36 if (done[i]) continue; 37 for (j = 0; j < n && !blocked; ++j) { 38 NativeAllocClass sc; 39 Reg sr; 40 if (j == i || done[j]) continue; 41 sr = nam_src_reg(&moves[j], &sc); 42 if (sr != REG_NONE && sr == moves[i].dst.v.reg && 43 sc == (NativeAllocClass)moves[i].dst.cls) 44 blocked = 1; 45 } 46 if (!blocked) { 47 s->emit_one(s->t, &moves[i]); 48 done[i] = 1; 49 emitted++; 50 progress = 1; 51 } 52 } 53 if (!progress) { 54 /* Every remaining move is part of a cycle; a cycle's ring is reg->reg, so 55 * at least one remaining move is register-sourced. Break on such a move: 56 * its destination register still holds a pending source value, so copy it 57 * to the class scratch and point that value's readers at the scratch. */ 58 u32 k = 0; 59 NativeAllocClass bc, sc; 60 NativeLoc scratchloc; 61 Reg broken_reg; 62 KitCgTypeId val_type; 63 while (k < n && (done[k] || nam_src_reg(&moves[k], &sc) == REG_NONE)) ++k; 64 bc = (NativeAllocClass)moves[k].dst.cls; 65 broken_reg = moves[k].dst.v.reg; 66 /* The value parked in broken_reg is whatever the cycle's readers consume, 67 * and its width is their *source* type — not moves[k].dst's own incoming 68 * type, which can be narrower (e.g. a 4-byte int rotating into the slot a 69 * reader treats as an 8-byte pointer). Saving at the destination's width 70 * would truncate the parked value before the reader loads it back, so use 71 * a reader's source type to preserve every bit. */ 72 val_type = moves[k].dst.type; 73 for (j = 0; j < n; ++j) { 74 Reg sr = nam_src_reg(&moves[j], &sc); 75 if (!done[j] && sr == broken_reg && sc == bc) { 76 val_type = moves[j].src.type; 77 break; 78 } 79 } 80 memset(&scratchloc, 0, sizeof scratchloc); 81 scratchloc.kind = NATIVE_LOC_REG; 82 scratchloc.cls = (u8)bc; 83 scratchloc.type = val_type; 84 scratchloc.v.reg = s->scratch[bc]; 85 s->reg_move(s->t, scratchloc, moves[k].dst); 86 for (j = 0; j < n; ++j) { 87 Reg sr = nam_src_reg(&moves[j], &sc); 88 if (!done[j] && sr != REG_NONE && sr == broken_reg && sc == bc) { 89 moves[j].src = scratchloc; 90 moves[j].src_offset = 0; 91 } 92 } 93 } 94 } 95 }