lib/std/arch/rv64/isel.rad 50.3 KiB raw
1
//! RV64 instruction selection.
2
//!
3
//! Walks IL and selects RV64 instructions for each operation.
4
//!
5
//! *Register resolution hierarchy*
6
//!
7
//!   getReg(ssa) -> Reg
8
//!     Primitive physical register lookup. Panics if the register is spilled.
9
//!     Used as a building block by the functions below.
10
//!
11
//!   getSrcReg(ssa, scratch) -> Reg
12
//!     Source register for an [`il::Reg`] operand. Returns the physical register,
13
//!     or loads a spilled value into `scratch`. Used for instruction fields
14
//!     typed as [`il::Reg`] (e.g. base addresses in Load/Store/Blit).
15
//!
16
//!   getDstReg(ssa, scratch) -> Reg
17
//!     Destination register for an instruction result. Returns the physical
18
//!     register, or records a pending spill and returns `scratch`. The pending
19
//!     spill is flushed by [`selectBlock`] after each instruction.
20
//!
21
//!   resolveVal(scratch, val) -> Reg
22
//!     Resolve an [`il::Val`] to whatever register holds it. Delegates to [`getSrcReg`]
23
//!     for register values; materializes immediates and symbols into `scratch`.
24
//!     Used for operands that can be consumed from any register.
25
//!
26
//!   loadVal(rd, val) -> Reg
27
//!     Force an [`il::Val`] into a specific register `rd`. Built on [`resolveVal`] + [`emitMv`].
28
//!     Used when the instruction requires the value in `rd` (e.g. `sub rd, rd, rs2`).
29
30
use std::mem;
31
use std::lang::il;
32
use std::lang::gen;
33
use std::lang::gen::regalloc;
34
use std::lang::gen::labels;
35
36
use super::encode;
37
use super::emit;
38
39
///////////////
40
// Constants //
41
///////////////
42
43
/// Shift amount for byte sign/zero extension.
44
constant SHIFT_W8: i32 = 64 - 8;
45
/// Shift amount for halfword sign/zero extension.
46
constant SHIFT_W16: i32 = 64 - 16;
47
/// Shift amount for word sign/zero extension.
48
constant SHIFT_W32: i32 = 64 - 32;
49
/// Mask for extracting byte value.
50
constant MASK_W8: i32 = 0xFF;
51
/// Maximum number of block arguments supported.
52
constant MAX_BLOCK_ARGS: u32 = 16;
53
54
/// Signed integer range limits.
55
constant I8_MIN: i64 = -128;
56
constant I8_MAX: i64 = 127;
57
constant I16_MIN: i64 = -32768;
58
constant I16_MAX: i64 = 32767;
59
constant I32_MIN: i64 = -2147483648;
60
constant I32_MAX: i64 = 2147483647;
61
62
/// Unsigned integer range limits.
63
constant U8_MAX: i64 = 255;
64
constant U16_MAX: i64 = 65535;
65
constant U32_MAX: i64 = 4294967295;
66
67
/// Binary operation.
68
union BinOp { Add, And, Or, Xor }
69
/// Shift operation.
70
union ShiftOp { Sll, Srl, Sra }
71
/// Compare operation.
72
union CmpOp { Slt, Ult }
73
74
/// A pending spill store to be flushed after instruction selection.
75
record PendingSpill {
76
    /// The SSA register that was spilled.
77
    ssa: il::Reg,
78
    /// The physical register holding the value to store.
79
    rd: gen::Reg,
80
}
81
82
////////////////////
83
// Selector State //
84
////////////////////
85
86
/// Instruction selector state.
87
export record Selector {
88
    /// Emitter for outputting instructions.
89
    e: *mut emit::Emitter,
90
    /// Register allocation result.
91
    ralloc: *regalloc::AllocResult,
92
    /// Total stack frame size.
93
    frameSize: i32,
94
    /// Running offset into the reserve region of the frame.
95
    /// Tracks current position within the pre-allocated reserve slots.
96
    reserveOffset: i32,
97
    /// Pending spill store, auto-committed after each instruction.
98
    pendingSpill: ?PendingSpill,
99
    /// Next synthetic block index for skip-branch targets.
100
    nextSynthBlock: u32,
101
    /// Whether dynamic allocations exist.
102
    isDynamic: bool,
103
}
104
105
/////////////////////////
106
// Register Allocation //
107
/////////////////////////
108
109
/// Get the physical register for an already-allocated SSA register.
110
fn getReg(s: *Selector, ssa: il::Reg) -> gen::Reg {
111
    let phys = s.ralloc.assignments[ssa.n] else {
112
        panic "getReg: spilled register has no physical assignment";
113
    };
114
    return phys;
115
}
116
117
/// Compute the offset for a spill slot.
118
/// When using FP (dynamic): offset from FP = `slot - totalSize`.
119
/// When using SP: offset from SP = `slot`.
120
fn spillOffset(s: *Selector, slot: i32) -> i32 {
121
    if s.isDynamic {
122
        return slot - s.frameSize;
123
    }
124
    return slot;
125
}
126
127
/// Get the base register for spill slot addressing (FP or SP).
128
fn spillBase(s: *Selector) -> gen::Reg {
129
    if s.isDynamic {
130
        return super::FP;
131
    }
132
    return super::SP;
133
}
134
135
/// Get the destination register for an SSA register.
136
/// If the register is spilled, records a pending spill and returns the scratch
137
/// register. The pending spill is auto-committed by [`selectBlock`] after each
138
/// instruction. If not spilled, returns the physical register.
139
fn getDstReg(s: *mut Selector, ssa: il::Reg, scratch: gen::Reg) -> gen::Reg {
140
    if let _ = regalloc::spill::spillSlot(&s.ralloc.spill, ssa) {
141
        set s.pendingSpill = PendingSpill { ssa, rd: scratch };
142
        return scratch;
143
    }
144
    return getReg(s, ssa);
145
}
146
147
/// Get the source register for an SSA register.
148
/// If the register is spilled, loads the value from the spill slot into the
149
/// scratch register and returns it. Otherwise returns the physical register.
150
fn getSrcReg(s: *mut Selector, ssa: il::Reg, scratch: gen::Reg) -> gen::Reg {
151
    if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, ssa) {
152
        emit::emitLd(s.e, scratch, spillBase(s), spillOffset(s, slot));
153
        return scratch;
154
    }
155
    return getReg(s, ssa);
156
}
157
158
/// Resolve an IL value to the physical register holding it.
159
/// For non-spilled register values, returns the physical register directly.
160
/// For immediates, symbols, and spilled registers, materializes into `scratch`.
161
fn resolveVal(s: *mut Selector, scratch: gen::Reg, val: il::Val) -> gen::Reg {
162
    match val {
163
        case il::Val::Reg(r) => {
164
            return getSrcReg(s, r, scratch);
165
        },
166
        case il::Val::Imm(imm) => {
167
            if imm == 0 {
168
                return super::ZERO;
169
            }
170
            emit::loadImm(s.e, scratch, imm);
171
            return scratch;
172
        },
173
        case il::Val::DataSym(name) => {
174
            emit::recordDataAddrLoad(s.e, name, scratch);
175
            return scratch;
176
        },
177
        case il::Val::FnAddr(name) => {
178
            emit::recordAddrLoad(s.e, name, scratch);
179
            return scratch;
180
        },
181
        case il::Val::Undef => {
182
            return scratch;
183
        }
184
    }
185
}
186
187
/// Load an IL value into a specific physical register.
188
/// Like [`resolveVal`], but ensures the value ends up in `rd`.
189
fn loadVal(s: *mut Selector, rd: gen::Reg, val: il::Val) -> gen::Reg {
190
    let rs = resolveVal(s, rd, val);
191
    emitMv(s, rd, rs);
192
    return rd;
193
}
194
195
/// Emit a move instruction if source and destination differ.
196
fn emitMv(s: *mut Selector, rd: gen::Reg, rs: gen::Reg) {
197
    if *rd <> *rs {
198
        emit::emit(s.e, encode::mv(rd, rs));
199
    }
200
}
201
202
/// Emit zero-extension from a sub-word type to the full register width.
203
fn emitZext(e: *mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) {
204
    match typ {
205
        case il::Type::W8 => emit::emit(e, encode::andi(rd, rs, MASK_W8)),
206
        case il::Type::W16 => {
207
            emit::emit(e, encode::slli(rd, rs, SHIFT_W16));
208
            emit::emit(e, encode::srli(rd, rd, SHIFT_W16));
209
        },
210
        case il::Type::W32 => {
211
            emit::emit(e, encode::slli(rd, rs, SHIFT_W32));
212
            emit::emit(e, encode::srli(rd, rd, SHIFT_W32));
213
        },
214
        case il::Type::W64 => {}
215
    }
216
}
217
218
/// Emit sign-extension from a sub-word type to the full register width.
219
fn emitSext(e: *mut emit::Emitter, rd: gen::Reg, rs: gen::Reg, typ: il::Type) {
220
    match typ {
221
        case il::Type::W8 => {
222
            emit::emit(e, encode::slli(rd, rs, SHIFT_W8));
223
            emit::emit(e, encode::srai(rd, rd, SHIFT_W8));
224
        },
225
        case il::Type::W16 => {
226
            emit::emit(e, encode::slli(rd, rs, SHIFT_W16));
227
            emit::emit(e, encode::srai(rd, rd, SHIFT_W16));
228
        },
229
        case il::Type::W32 => {
230
            emit::emit(e, encode::addiw(rd, rs, 0));
231
        },
232
        case il::Type::W64 => {}
233
    }
234
}
235
236
/// Resolve a divisor in its declared width, trap if it becomes zero, and
237
/// return the canonicalized register.
238
fn resolveAndTrapIfZero(
239
    s: *mut Selector,
240
    b: il::Val,
241
    typ: il::Type,
242
    signed: bool
243
) -> gen::Reg {
244
    let mut divisor = b;
245
    if let case il::Val::Imm(imm) = b {
246
        set divisor = il::Val::Imm(canonicalCmpImm(imm, typ, signed));
247
    }
248
    let rs2 = resolveVal(s, super::SCRATCH2, divisor);
249
    if not isExtendedImm(divisor, typ, signed) {
250
        emitCmpExt(s.e, rs2, rs2, typ, signed);
251
    }
252
    let mut knownNonZero = false;
253
    if let case il::Val::Imm(imm) = divisor {
254
        set knownNonZero = imm <> 0;
255
    }
256
    if not knownNonZero {
257
        emit::emit(s.e, encode::bne(rs2, super::ZERO, super::INSTR_SIZE * 2));
258
        emit::emit(s.e, encode::ebreak());
259
    }
260
    return rs2;
261
}
262
263
////////////////////////
264
// Instruction Select //
265
////////////////////////
266
267
/// Pre-scan result for reserve analysis.
268
record ReserveInfo {
269
    /// Total size needed for constant-sized reserves.
270
    size: i32,
271
    /// Whether any dynamic-sized reserves exist.
272
    isDynamic: bool,
273
}
274
275
/// Pre-scan all blocks for constant-sized reserve instructions.
276
/// Returns the total size needed for all static reserves, respecting alignment.
277
fn computeReserveInfo(func: *il::Fn) -> ReserveInfo {
278
    let mut offset: i32 = 0;
279
    let mut isDynamic = false;
280
281
    for b in 0..func.blocks.len {
282
        let block = &func.blocks[b];
283
        for instr in block.instrs {
284
            match instr {
285
                case il::Instr::Reserve { size, alignment, .. } => {
286
                    if let case il::Val::Imm(sz) = size {
287
                        set offset = mem::alignUpI32(offset, alignment as i32);
288
                        set offset += sz as i32;
289
                    } else {
290
                        set isDynamic = true;
291
                    }
292
                },
293
                else => {},
294
            }
295
        }
296
    }
297
    return ReserveInfo { size: offset, isDynamic };
298
}
299
300
/// Select instructions for a function.
301
export fn selectFn(
302
    e: *mut emit::Emitter,
303
    ralloc: *regalloc::AllocResult,
304
    func: *il::Fn
305
) {
306
    // Reset block offsets for this function.
307
    labels::resetBlocks(&mut e.labels);
308
    // Pre-scan for constant-sized reserves to promote to fixed frame slots.
309
    let reserveInfo = computeReserveInfo(func);
310
    let isLeaf = func.isLeaf;
311
    // Compute frame layout from spill slots, reserve slots, and used callee-saved registers.
312
    let frame = emit::computeFrame(
313
        ralloc.spill.frameSize + reserveInfo.size,
314
        ralloc.usedCalleeSaved,
315
        func.blocks.len,
316
        isLeaf,
317
        reserveInfo.isDynamic
318
    );
319
    // Synthetic block indices start after real blocks and the epilogue block.
320
    let mut s = Selector {
321
        e, ralloc, frameSize: frame.totalSize,
322
        reserveOffset: 0, pendingSpill: nil,
323
        nextSynthBlock: func.blocks.len + 1,
324
        isDynamic: frame.isDynamic,
325
    };
326
    // Record function name for printing.
327
    emit::recordFunc(s.e, func.name);
328
    // Record function code offset for call patching.
329
    emit::recordFuncOffset(s.e, func.name);
330
    // Emit prologue.
331
    emit::emitPrologue(s.e, &frame);
332
333
    // Move function params from arg registers to assigned registers.
334
    // Cross-call params may have been assigned to callee-saved registers
335
    // instead of their natural arg registers. Spilled params are stored
336
    // directly to their spill slots.
337
    for funcParam, i in func.params {
338
        if i < super::ARG_REGS.len {
339
            let param = funcParam.value;
340
            let argReg = super::ARG_REGS[i];
341
342
            if let slot = regalloc::spill::spillSlot(&ralloc.spill, param) {
343
                // Spilled parameter: store arg register to spill slot.
344
                emit::emitSd(s.e, argReg, spillBase(&s), spillOffset(&s, slot));
345
            } else if let assigned = ralloc.assignments[param.n] {
346
                emitMv(&mut s, assigned, argReg);
347
            }
348
        }
349
    }
350
351
    // Emit each block.
352
    for i in 0..func.blocks.len {
353
        selectBlock(&mut s, i, &func.blocks[i], &frame, func);
354
    }
355
    // Emit epilogue.
356
    emit::emitEpilogue(s.e, &frame);
357
    // Patch local branches now that all blocks are emitted.
358
    emit::patchLocalBranches(s.e);
359
}
360
361
/// Select instructions for a block.
362
fn selectBlock(s: *mut Selector, blockIdx: u32, block: *il::Block, frame: *emit::Frame, func: *il::Fn) {
363
    // Record block address for branch patching.
364
    emit::recordBlock(s.e, blockIdx);
365
366
    // Block parameters are handled at jump sites (in `Jmp`/`Br`).
367
    // By the time we enter the block, the arguments have already been
368
    // moved to the parameter registers by the predecessor's terminator.
369
370
    // Process each instruction, auto-committing any pending spill after each.
371
    let hasLocs = block.locs.len > 0;
372
    for instr, i in block.instrs {
373
        // Record debug location before emitting machine instructions.
374
        if hasLocs {
375
            emit::recordSrcLoc(s.e, block.locs[i]);
376
        }
377
        set s.pendingSpill = nil;
378
        selectInstr(s, blockIdx, instr, frame, func);
379
380
        // Flush the pending spill store, if any.
381
        if let p = s.pendingSpill {
382
            if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, p.ssa) {
383
                emit::emitSd(s.e, p.rd, spillBase(s), spillOffset(s, slot));
384
            }
385
            set s.pendingSpill = nil;
386
        }
387
    }
388
}
389
390
/// Select instructions for a single IL instruction.
391
fn selectInstr(s: *mut Selector, blockIdx: u32, instr: il::Instr, frame: *emit::Frame, func: *il::Fn) {
392
    match instr {
393
        case il::Instr::BinOp { op, typ, dst, a, b } => {
394
            let rd = getDstReg(s, dst, super::SCRATCH1);
395
            let rs1 = resolveVal(s, super::SCRATCH1, a);
396
            selectAluBinOp(s, op, typ, rd, rs1, b);
397
        },
398
        case il::Instr::UnOp { op, typ, dst, a } => {
399
            let rd = getDstReg(s, dst, super::SCRATCH1);
400
            let rs = resolveVal(s, super::SCRATCH1, a);
401
            selectAluUnOp(s, op, typ, rd, rs);
402
        },
403
        case il::Instr::Load { typ, dst, src, offset } => {
404
            let rd = getDstReg(s, dst, super::SCRATCH1);
405
            let base = getSrcReg(s, src, super::SCRATCH2);
406
            emit::emitLoad(s.e, rd, base, offset, typ);
407
        },
408
        case il::Instr::Sload { typ, dst, src, offset } => {
409
            let rd = getDstReg(s, dst, super::SCRATCH1);
410
            let base = getSrcReg(s, src, super::SCRATCH2);
411
            emit::emitSload(s.e, rd, base, offset, typ);
412
        },
413
        case il::Instr::Store { typ, src, dst, offset } => {
414
            let base = getSrcReg(s, dst, super::SCRATCH2);
415
            let rs = resolveVal(s, super::SCRATCH1, src);
416
            emit::emitStore(s.e, rs, base, offset, typ);
417
        },
418
        case il::Instr::Copy { dst, val } => {
419
            let rd = getDstReg(s, dst, super::SCRATCH1);
420
            let rs = resolveVal(s, super::SCRATCH1, val);
421
            emitMv(s, rd, rs);
422
        },
423
        case il::Instr::Reserve { dst, size, alignment } => {
424
            match size {
425
                case il::Val::Imm(sz) => {
426
                    // Constant-sized reserve: use pre-allocated frame slot.
427
                    let rd = getDstReg(s, dst, super::SCRATCH1);
428
                    let aligned: i32 = mem::alignUpI32(s.reserveOffset, alignment as i32);
429
                    let base = spillBase(s);
430
                    let offset = s.ralloc.spill.frameSize + aligned
431
                        - (s.frameSize if s.isDynamic else 0);
432
433
                    emit::emitAddImm(s.e, rd, base, offset);
434
                    set s.reserveOffset = aligned + (sz as i32);
435
                },
436
                case il::Val::Reg(r) => {
437
                    // Dynamic-sized reserve: runtime SP adjustment.
438
                    let rd = getDstReg(s, dst, super::SCRATCH1);
439
                    let rs = getSrcReg(s, r, super::SCRATCH2);
440
441
                    emit::emit(s.e, encode::sub(super::SP, super::SP, rs));
442
443
                    if alignment > 1 {
444
                        let mask = 0 - alignment as i32;
445
                        assert encode::isSmallImm(mask);
446
447
                        emit::emit(s.e, encode::andi(super::SP, super::SP, mask));
448
                    }
449
                    emit::emit(s.e, encode::mv(rd, super::SP));
450
                },
451
                else =>
452
                    panic "selectInstr: invalid reserve operand",
453
            }
454
        },
455
        case il::Instr::Blit { dst, src, size } => {
456
            let case il::Val::Imm(staticSize) = size
457
                else panic "selectInstr: blit requires immediate size";
458
459
            let bothSpilled = regalloc::spill::isSpilled(&s.ralloc.spill, dst)
460
                and regalloc::spill::isSpilled(&s.ralloc.spill, src);
461
462
            // When both are spilled, offsets must fit 12-bit immediates
463
            // since we can't advance base registers (they live in spill
464
            // slots, not real registers we can mutate).
465
            assert not (bothSpilled and staticSize as i32 > super::MAX_IMM), "selectInstr: blit both-spilled with large size";
466
467
            // Resolve dst/src base registers.
468
            let mut rdst = super::SCRATCH2;
469
            let mut rsrc = super::SCRATCH1;
470
            let mut srcReload: ?i32 = nil;
471
472
            if bothSpilled {
473
                let dstSlot = regalloc::spill::spillSlot(&s.ralloc.spill, dst) else {
474
                    panic "selectInstr: blit dst not spilled";
475
                };
476
                let srcSlot = regalloc::spill::spillSlot(&s.ralloc.spill, src) else {
477
                    panic "selectInstr: blit src not spilled";
478
                };
479
                emit::emitLd(s.e, super::SCRATCH2, spillBase(s), spillOffset(s, dstSlot));
480
                set srcReload = spillOffset(s, srcSlot);
481
            } else {
482
                set rdst = getSrcReg(s, dst, super::SCRATCH2);
483
                set rsrc = getSrcReg(s, src, super::SCRATCH2);
484
            }
485
            let mut offset: i32 = 0;
486
            let mut remaining = staticSize as i32;
487
488
            // For large blits where both pointers are in real registers,
489
            // use an inline loop instead of unrolled LD/SD pairs.
490
            let dwordBytes = remaining & ~(super::DWORD_SIZE - 1);
491
            let canLoop = not bothSpilled
492
                and *rsrc <> *super::SCRATCH1 and *rsrc <> *super::SCRATCH2
493
                and *rdst <> *super::SCRATCH1 and *rdst <> *super::SCRATCH2;
494
495
            if canLoop and dwordBytes >= super::BLIT_LOOP_THRESHOLD {
496
                emit::emitAddImm(s.e, super::SCRATCH1, rsrc, dwordBytes);
497
498
                let loopStart = s.e.codeLen;
499
500
                emit::emitLd(s.e, super::SCRATCH2, rsrc, 0);
501
                emit::emitSd(s.e, super::SCRATCH2, rdst, 0);
502
                emit::emit(s.e, encode::addi(rsrc, rsrc, super::DWORD_SIZE));
503
504
                if *rdst <> *rsrc {
505
                    emit::emit(s.e, encode::addi(rdst, rdst, super::DWORD_SIZE));
506
                }
507
                let brOff = (loopStart as i32 - s.e.codeLen as i32) * super::INSTR_SIZE;
508
509
                emit::emit(s.e, encode::bne(rsrc, super::SCRATCH1, brOff));
510
                set remaining -= dwordBytes;
511
            }
512
513
            // Copy remaining: 8 bytes, then 4 bytes, then 1 byte at a time.
514
            // Before each load/store pair, check whether the offset is
515
            // about to exceed the 12-bit signed immediate range. When
516
            // it does, advance the base registers by the accumulated
517
            // offset and reset to zero.
518
            while remaining >= super::DWORD_SIZE {
519
                if offset > super::MAX_IMM - super::DWORD_SIZE {
520
                    emit::emitAddImm(s.e, rsrc, rsrc, offset);
521
                    if *rdst <> *rsrc {
522
                        emit::emitAddImm(s.e, rdst, rdst, offset);
523
                    }
524
                    set offset = 0;
525
                }
526
                if let off = srcReload {
527
                    emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off);
528
                    emit::emitLd(s.e, super::SCRATCH1, super::SCRATCH1, offset);
529
                } else {
530
                    emit::emitLd(s.e, super::SCRATCH1, rsrc, offset);
531
                }
532
                emit::emitSd(s.e, super::SCRATCH1, rdst, offset);
533
                set offset += super::DWORD_SIZE;
534
                set remaining -= super::DWORD_SIZE;
535
            }
536
            if remaining >= super::WORD_SIZE {
537
                if offset > super::MAX_IMM - super::WORD_SIZE {
538
                    emit::emitAddImm(s.e, rsrc, rsrc, offset);
539
                    if *rdst <> *rsrc {
540
                        emit::emitAddImm(s.e, rdst, rdst, offset);
541
                    }
542
                    set offset = 0;
543
                }
544
                if let off = srcReload {
545
                    emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off);
546
                    emit::emitLw(s.e, super::SCRATCH1, super::SCRATCH1, offset);
547
                } else {
548
                    emit::emitLw(s.e, super::SCRATCH1, rsrc, offset);
549
                }
550
                emit::emitSw(s.e, super::SCRATCH1, rdst, offset);
551
                set offset += super::WORD_SIZE;
552
                set remaining -= super::WORD_SIZE;
553
            }
554
            while remaining > 0 {
555
                if offset > super::MAX_IMM - 1 {
556
                    emit::emitAddImm(s.e, rsrc, rsrc, offset);
557
                    if *rdst <> *rsrc {
558
                        emit::emitAddImm(s.e, rdst, rdst, offset);
559
                    }
560
                    set offset = 0;
561
                }
562
                if let off = srcReload {
563
                    emit::emitLd(s.e, super::SCRATCH1, spillBase(s), off);
564
                    emit::emitLb(s.e, super::SCRATCH1, super::SCRATCH1, offset);
565
                } else {
566
                    emit::emitLb(s.e, super::SCRATCH1, rsrc, offset);
567
                }
568
                emit::emitSb(s.e, super::SCRATCH1, rdst, offset);
569
                set offset += 1;
570
                set remaining -= 1;
571
            }
572
            // Restore base registers if they were advanced (never happens
573
            // in the both-spilled case since size <= MAX_IMM).
574
            if not bothSpilled {
575
                let advanced = staticSize as i32 - offset;
576
                if advanced <> 0 {
577
                    emit::emitAddImm(s.e, rsrc, rsrc, 0 - advanced);
578
                    if *rdst <> *rsrc {
579
                        emit::emitAddImm(s.e, rdst, rdst, 0 - advanced);
580
                    }
581
                }
582
            }
583
        },
584
        case il::Instr::Zext { typ, dst, val } => {
585
            let rd = getDstReg(s, dst, super::SCRATCH1);
586
            let rs = resolveVal(s, super::SCRATCH1, val);
587
            emitZext(s.e, rd, rs, typ);
588
        },
589
        case il::Instr::Sext { typ, dst, val } => {
590
            let rd = getDstReg(s, dst, super::SCRATCH1);
591
            let rs = resolveVal(s, super::SCRATCH1, val);
592
            emitSext(s.e, rd, rs, typ);
593
        },
594
        case il::Instr::Ret { val } => {
595
            if let v = val {
596
                let rs = resolveVal(s, super::SCRATCH1, v);
597
                emitMv(s, super::A0, rs);
598
            }
599
            // Skip the jump to epilogue if this RET is in the last block,
600
            // since the epilogue immediately follows.
601
            if frame.totalSize <> 0 and blockIdx + 1 == frame.epilogueBlock {
602
                // Epilogue is the next block; fallthrough is sufficient.
603
            } else {
604
                emit::emitReturn(s.e, frame);
605
            }
606
        },
607
        case il::Instr::Jmp { target, args } => {
608
            // Move arguments to target block's parameter registers.
609
            emitBlockArgs(s, func, target, args);
610
            // Skip branch if target is the next block (fallthrough).
611
            if target <> blockIdx + 1 {
612
                emit::recordBranch(s.e, target, emit::BranchKind::Jump);
613
            }
614
        },
615
        case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => {
616
            // Use zero register directly for immediate `0` operands.
617
            let aIsZero = isZeroImm(a);
618
            let bIsZero = isZeroImm(b);
619
620
            let rs1 = super::ZERO if aIsZero else resolveVal(s, super::SCRATCH1, a);
621
            let rs2 = super::ZERO if bIsZero else resolveVal(s, super::SCRATCH2, b);
622
623
            // Normalize sub-word operands so that both registers have the same
624
            // canonical representation. Without this, eg. `-1 : i8 ` loaded as
625
            // `0xFFFFFFFFFFFFFFFF` and `255 : i8` loaded as `0xFF` would compare
626
            // unequal even though they are the same 8-bit pattern.
627
            //
628
            // For SLT: sign-extension needed (signed comparison).
629
            // For ULT: zero-extension needed (unsigned magnitude comparison).
630
            // For EQ/NE with W32: sign-extension is cheaper.
631
            // For EQ/NE with W8/W16: keep zero-extension.
632
            // Skip extension for zero register.
633
            let mut signed = false;
634
            if let case il::CmpOp::Slt = op {
635
                set signed = true;
636
            }
637
            let useSext = cmpUsesSext(typ, signed);
638
            if not aIsZero and not isExtendedImm(a, typ, useSext) {
639
                emitCmpExt(s.e, rs1, rs1, typ, useSext);
640
            }
641
            if not bIsZero and not isExtendedImm(b, typ, useSext) {
642
                emitCmpExt(s.e, rs2, rs2, typ, useSext);
643
            }
644
            // Block-argument moves must only execute on the taken path.
645
            // When `thenArgs` is non-empty, invert the branch so that the
646
            // then-moves land on the fall-through (taken) side.
647
            //
648
            // When one target is the next block in layout order, we can
649
            // eliminate the trailing unconditional jump by arranging the
650
            // conditional branch to skip to the *other* target and letting
651
            // execution fall through.
652
            if thenArgs.len > 0 and elseArgs.len > 0 {
653
                panic "selectInstr: both `then` and `else` have block arguments";
654
            } else if thenArgs.len > 0 {
655
                emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 });
656
                emitBlockArgs(s, func, thenTarget, thenArgs);
657
                // Skip trailing jump if then is the next block (fallthrough).
658
                if thenTarget <> blockIdx + 1 {
659
                    emit::recordBranch(s.e, thenTarget, emit::BranchKind::Jump);
660
                }
661
            } else if thenTarget == blockIdx + 1 and elseArgs.len == 0 {
662
                // Then is the next block and no else args: invert the
663
                // condition to branch to else and fall through to then.
664
                emit::recordBranch(s.e, elseTarget, emit::BranchKind::InvertedCond { op, rs1, rs2 });
665
            } else {
666
                emit::recordBranch(s.e, thenTarget, emit::BranchKind::Cond { op, rs1, rs2 });
667
                emitBlockArgs(s, func, elseTarget, elseArgs);
668
                // Skip trailing jump if else is the next block (fallthrough).
669
                if elseTarget <> blockIdx + 1 {
670
                    emit::recordBranch(s.e, elseTarget, emit::BranchKind::Jump);
671
                }
672
            }
673
        },
674
        case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => {
675
            let rs1 = resolveVal(s, super::SCRATCH1, val);
676
            // When a case has block args, invert the branch to skip past
677
            // the arg moves.
678
            for c in cases {
679
                emit::loadImm(s.e, super::SCRATCH2, c.value);
680
681
                if c.args.len > 0 {
682
                    let skip = s.nextSynthBlock;
683
                    set s.nextSynthBlock = skip + 1;
684
685
                    emit::recordBranch(s.e, skip, emit::BranchKind::InvertedCond {
686
                        op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2,
687
                    });
688
                    emitBlockArgs(s, func, c.target, c.args);
689
                    emit::recordBranch(s.e, c.target, emit::BranchKind::Jump);
690
                    emit::recordBlock(s.e, skip);
691
                } else {
692
                    emit::recordBranch(s.e, c.target, emit::BranchKind::Cond {
693
                        op: il::CmpOp::Eq, rs1, rs2: super::SCRATCH2,
694
                    });
695
                }
696
            }
697
            // Fall through to default.
698
            emitBlockArgs(s, func, defaultTarget, defaultArgs);
699
            emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump);
700
        },
701
        case il::Instr::Unreachable => {
702
            emit::emit(s.e, encode::ebreak());
703
        },
704
        case il::Instr::Call { retTy, dst, func, args } => {
705
            // For indirect calls, save target to scratch register before arg
706
            // setup can clobber it.
707
            if let case il::Val::Reg(r) = func {
708
                let target = getSrcReg(s, r, super::SCRATCH2);
709
                emitMv(s, super::SCRATCH2, target);
710
            }
711
            // Move arguments to A0-A7 using parallel move resolution.
712
            assert args.len <= super::ARG_REGS.len, "selectInstr: too many call arguments";
713
            emitParallelMoves(s, &super::ARG_REGS[..], args);
714
715
            // Emit call.
716
            match func {
717
                case il::Val::FnAddr(name) => {
718
                    emit::recordCall(s.e, name);
719
                },
720
                case il::Val::Reg(_) => {
721
                    emit::emit(s.e, encode::jalr(super::RA, super::SCRATCH2, 0));
722
                },
723
                else => {
724
                    panic "selectInstr: invalid call target";
725
                }
726
            }
727
            // Move result from A0.
728
            if let d = dst {
729
                let rd = getDstReg(s, d, super::SCRATCH1);
730
                emitMv(s, rd, super::A0);
731
            }
732
        },
733
        case il::Instr::Ecall { dst, num, a0, a1, a2, a3 } => {
734
            // Move arguments using parallel move.
735
            // TODO: Can't use slice literals here because the lowerer doesn't
736
            // support constant-evaluating struct/union values in them.
737
            let ecallDsts: [gen::Reg; 5] = [super::A7, super::A0, super::A1, super::A2, super::A3];
738
            let ecallArgs: [il::Val; 5] = [num, a0, a1, a2, a3];
739
740
            emitParallelMoves(s, &ecallDsts[..], &ecallArgs[..]);
741
            emit::emit(s.e, encode::ecall());
742
743
            // Result in A0.
744
            let ecallRd = getDstReg(s, dst, super::SCRATCH1);
745
            emitMv(s, ecallRd, super::A0);
746
        },
747
        case il::Instr::Ebreak => {
748
            emit::emit(s.e, encode::ebreak());
749
        },
750
        case il::Instr::MemoryFence => {
751
            emit::emit(s.e, encode::fence());
752
        },
753
    }
754
}
755
756
/// Choose the cheapest canonical representation that preserves the comparison.
757
/// RV64 word operations naturally sign-extend, and sign-extension preserves
758
/// unsigned ordering when both operands have the same declared width.
759
fn cmpUsesSext(typ: il::Type, signed: bool) -> bool {
760
    return signed or typ == il::Type::W32;
761
}
762
763
/// Extend a comparison operand to its selected canonical representation.
764
fn emitCmpExt(
765
    e: *mut emit::Emitter,
766
    rd: gen::Reg,
767
    rs: gen::Reg,
768
    typ: il::Type,
769
    useSext: bool
770
) {
771
    if useSext {
772
        emitSext(e, rd, rs, typ);
773
    } else {
774
        emitZext(e, rd, rs, typ);
775
    }
776
}
777
778
/// Truncate and extend an immediate exactly as its register operand would be.
779
fn canonicalCmpImm(imm: i64, typ: il::Type, useSext: bool) -> i64 {
780
    if useSext {
781
        match typ {
782
            case il::Type::W8 => return (imm as i8) as i64,
783
            case il::Type::W16 => return (imm as i16) as i64,
784
            case il::Type::W32 => return (imm as i32) as i64,
785
            case il::Type::W64 => return imm,
786
        }
787
    } else {
788
        match typ {
789
            case il::Type::W8 => return (imm as u8) as i64,
790
            case il::Type::W16 => return (imm as u16) as i64,
791
            case il::Type::W32 => return (imm as u32) as i64,
792
            case il::Type::W64 => return imm,
793
        }
794
    }
795
}
796
797
/// Check if a value is an immediate that's already correctly extended.
798
/// `loadImm` produces the exact 64-bit value; this checks whether that value
799
/// already matches what sign/zero-extension to the given type would produce.
800
fn isExtendedImm(val: il::Val, typ: il::Type, signed: bool) -> bool {
801
    if let case il::Val::Imm(imm) = val {
802
        if signed {
803
            // Sign-extension truncates to the type width and sign-extends.
804
            // The 64-bit value is already correctly sign-extended if it
805
            // fits in the signed range of the target type.
806
            match typ {
807
                case il::Type::W8 => return imm >= I8_MIN and imm <= I8_MAX,
808
                case il::Type::W16 => return imm >= I16_MIN and imm <= I16_MAX,
809
                case il::Type::W32 => return imm >= I32_MIN and imm <= I32_MAX,
810
                case il::Type::W64 => return true,
811
            }
812
        } else {
813
            // Zero-extension: value must be non-negative and within unsigned range.
814
            match typ {
815
                case il::Type::W8 => return imm >= 0 and imm <= U8_MAX,
816
                case il::Type::W16 => return imm >= 0 and imm <= U16_MAX,
817
                case il::Type::W32 => return imm >= 0 and imm <= U32_MAX,
818
                case il::Type::W64 => return true,
819
            }
820
        }
821
    }
822
    return false;
823
}
824
825
/// Check if a value is an immediate zero.
826
fn isZeroImm(val: il::Val) -> bool {
827
    if let case il::Val::Imm(imm) = val {
828
        return imm == 0;
829
    }
830
    return false;
831
}
832
833
/// Select a binary ALU operation, dispatching to the appropriate
834
/// instruction pattern based on the operation kind and type.
835
fn selectAluBinOp(s: *mut Selector, op: il::BinOp, typ: il::Type, rd: gen::Reg, rs1: gen::Reg, b: il::Val) {
836
    match op {
837
        case il::BinOp::Add => {
838
            if typ == il::Type::W32 {
839
                // Inline W32 ADD with immediate optimization.
840
                if let case il::Val::Imm(imm) = b {
841
                    if encode::isSmallImm64(imm) {
842
                        emit::emit(s.e, encode::addiw(rd, rs1, imm as i32));
843
                        return;
844
                    }
845
                }
846
                let rs2 = resolveVal(s, super::SCRATCH2, b);
847
                emit::emit(s.e, encode::addw(rd, rs1, rs2));
848
            } else {
849
                selectBinOp(s, rd, rs1, b, BinOp::Add, super::SCRATCH2);
850
            }
851
        }
852
        case il::BinOp::Sub => {
853
            // Optimize subtraction by small immediate: use ADDI with negated value.
854
            if let case il::Val::Imm(imm) = b {
855
                let neg = -imm;
856
                if neg >= super::MIN_IMM as i64 and neg <= super::MAX_IMM as i64 {
857
                    emit::emit(s.e,
858
                        encode::addiw(rd, rs1, neg as i32)
859
                            if typ == il::Type::W32 else
860
                        encode::addi(rd, rs1, neg as i32));
861
                    return;
862
                }
863
            }
864
            let rs2 = resolveVal(s, super::SCRATCH2, b);
865
866
            emit::emit(s.e,
867
                encode::subw(rd, rs1, rs2)
868
                    if typ == il::Type::W32 else
869
                encode::sub(rd, rs1, rs2));
870
        }
871
        case il::BinOp::Mul => {
872
            // Strength-reduce multiplication by known constants.
873
            if let case il::Val::Imm(imm) = b {
874
                if imm == 0 {
875
                    emit::emit(s.e, encode::mv(rd, super::ZERO));
876
                    return;
877
                } else if imm == 1 {
878
                    emitMv(s, rd, rs1);
879
                    return;
880
                } else if imm == 2 {
881
                    emit::emit(s.e, encode::slli(rd, rs1, 1));
882
                    return;
883
                } else if imm == 4 {
884
                    emit::emit(s.e, encode::slli(rd, rs1, 2));
885
                    return;
886
                } else if imm == 8 {
887
                    emit::emit(s.e, encode::slli(rd, rs1, 3));
888
                    return;
889
                }
890
            }
891
            let rs2 = resolveVal(s, super::SCRATCH2, b);
892
            emit::emit(s.e,
893
                encode::mulw(rd, rs1, rs2)
894
                    if typ == il::Type::W32 else
895
                encode::mul(rd, rs1, rs2));
896
        }
897
        case il::BinOp::Sdiv => {
898
            let rs2 = resolveAndTrapIfZero(s, b, typ, true);
899
            emit::emit(s.e,
900
                encode::divw(rd, rs1, rs2)
901
                    if typ == il::Type::W32 else
902
                encode::div(rd, rs1, rs2));
903
        }
904
        case il::BinOp::Udiv => {
905
            let rs2 = resolveAndTrapIfZero(s, b, typ, false);
906
            emit::emit(s.e,
907
                encode::divuw(rd, rs1, rs2)
908
                    if typ == il::Type::W32 else
909
                encode::divu(rd, rs1, rs2));
910
        }
911
        case il::BinOp::Srem => {
912
            let rs2 = resolveAndTrapIfZero(s, b, typ, true);
913
            emit::emit(s.e,
914
                encode::remw(rd, rs1, rs2)
915
                    if typ == il::Type::W32 else
916
                encode::rem(rd, rs1, rs2));
917
        }
918
        case il::BinOp::Urem => {
919
            let rs2 = resolveAndTrapIfZero(s, b, typ, false);
920
            emit::emit(s.e,
921
                encode::remuw(rd, rs1, rs2)
922
                    if typ == il::Type::W32 else
923
                encode::remu(rd, rs1, rs2));
924
        }
925
        case il::BinOp::And =>
926
            selectBinOp(s, rd, rs1, b, BinOp::And, super::SCRATCH2),
927
        case il::BinOp::Or =>
928
            selectBinOp(s, rd, rs1, b, BinOp::Or, super::SCRATCH2),
929
        case il::BinOp::Xor =>
930
            selectBinOp(s, rd, rs1, b, BinOp::Xor, super::SCRATCH2),
931
        case il::BinOp::Shl =>
932
            selectShift(s, rd, rs1, b, ShiftOp::Sll, typ, super::SCRATCH2),
933
        case il::BinOp::Sshr =>
934
            selectShift(s, rd, rs1, b, ShiftOp::Sra, typ, super::SCRATCH2),
935
        case il::BinOp::Ushr =>
936
            selectShift(s, rd, rs1, b, ShiftOp::Srl, typ, super::SCRATCH2),
937
        case il::BinOp::Eq, il::BinOp::Ne => {
938
            let rs2 = resolveVal(s, super::SCRATCH2, b);
939
            let useSext = cmpUsesSext(typ, false);
940
            emitCmpExt(s.e, rs1, rs1, typ, useSext);
941
            if not isExtendedImm(b, typ, useSext) {
942
                emitCmpExt(s.e, rs2, rs2, typ, useSext);
943
            }
944
            emit::emit(s.e, encode::xor(rd, rs1, rs2));
945
            if let case il::BinOp::Eq = op {
946
                emit::emit(s.e, encode::sltiu(rd, rd, 1));
947
            } else {
948
                emit::emit(s.e, encode::sltu(rd, super::ZERO, rd));
949
            }
950
        }
951
        case il::BinOp::Slt =>
952
            selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, false, super::SCRATCH2),
953
        case il::BinOp::Ult =>
954
            selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, false, super::SCRATCH2),
955
        case il::BinOp::Sge =>
956
            selectCmp(s, typ, rd, rs1, b, CmpOp::Slt, true, super::SCRATCH2),
957
        case il::BinOp::Uge =>
958
            selectCmp(s, typ, rd, rs1, b, CmpOp::Ult, true, super::SCRATCH2),
959
    }
960
}
961
962
/// Select a unary ALU operation.
963
fn selectAluUnOp(s: *mut Selector, op: il::UnOp, typ: il::Type, rd: gen::Reg, rs: gen::Reg) {
964
    match op {
965
        case il::UnOp::Neg => {
966
            if typ == il::Type::W32 {
967
                emit::emit(s.e, encode::subw(rd, super::ZERO, rs));
968
            } else {
969
                emit::emit(s.e, encode::neg(rd, rs));
970
            }
971
        }
972
        case il::UnOp::Not =>
973
            emit::emit(s.e, encode::not_(rd, rs)),
974
    }
975
}
976
977
/// Select binary operation with immediate optimization.
978
fn selectBinOp(s: *mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: BinOp, scratch: gen::Reg) {
979
    // Try immediate optimization first.
980
    if let case il::Val::Imm(imm) = b {
981
        if encode::isSmallImm64(imm) {
982
            let simm = imm as i32;
983
            match op {
984
                case BinOp::Add => emit::emit(s.e, encode::addi(rd, rs1, simm)),
985
                case BinOp::And => emit::emit(s.e, encode::andi(rd, rs1, simm)),
986
                case BinOp::Or  => emit::emit(s.e, encode::ori(rd, rs1, simm)),
987
                case BinOp::Xor => emit::emit(s.e, encode::xori(rd, rs1, simm)),
988
            }
989
            return;
990
        }
991
    }
992
    // Fallback: load into register.
993
    let rs2 = resolveVal(s, scratch, b);
994
    match op {
995
        case BinOp::Add => emit::emit(s.e, encode::add(rd, rs1, rs2)),
996
        case BinOp::And => emit::emit(s.e, encode::and_(rd, rs1, rs2)),
997
        case BinOp::Or  => emit::emit(s.e, encode::or_(rd, rs1, rs2)),
998
        case BinOp::Xor => emit::emit(s.e, encode::xor(rd, rs1, rs2)),
999
    }
1000
}
1001
1002
/// Select shift operation with immediate optimization.
1003
/// For 32-bit operations, uses the `*w` variants that operate on the lower 32 bits
1004
/// and sign-extend the result.
1005
fn selectShift(s: *mut Selector, rd: gen::Reg, rs1: gen::Reg, b: il::Val, op: ShiftOp, typ: il::Type, scratch: gen::Reg) {
1006
    let isW32: bool = typ == il::Type::W32;
1007
1008
    // Try immediate optimization first.
1009
    if let case il::Val::Imm(shamt) = b {
1010
        // Keep immediate forms only for encodable shift amounts.
1011
        // Otherwise fall back to register shifts, which naturally mask the count.
1012
        if shamt >= 0 and ((isW32 and shamt < 32) or (not isW32 and shamt < 64)) {
1013
            let sa = shamt as i32;
1014
            if isW32 {
1015
                match op {
1016
                    case ShiftOp::Sll => emit::emit(s.e, encode::slliw(rd, rs1, sa)),
1017
                    case ShiftOp::Srl => emit::emit(s.e, encode::srliw(rd, rs1, sa)),
1018
                    case ShiftOp::Sra => emit::emit(s.e, encode::sraiw(rd, rs1, sa)),
1019
                }
1020
            } else {
1021
                match op {
1022
                    case ShiftOp::Sll => emit::emit(s.e, encode::slli(rd, rs1, sa)),
1023
                    case ShiftOp::Srl => emit::emit(s.e, encode::srli(rd, rs1, sa)),
1024
                    case ShiftOp::Sra => emit::emit(s.e, encode::srai(rd, rs1, sa)),
1025
                }
1026
            }
1027
            return;
1028
        }
1029
    }
1030
    // Fallback: load into register.
1031
    let rs2 = resolveVal(s, scratch, b);
1032
    if isW32 {
1033
        match op {
1034
            case ShiftOp::Sll => emit::emit(s.e, encode::sllw(rd, rs1, rs2)),
1035
            case ShiftOp::Srl => emit::emit(s.e, encode::srlw(rd, rs1, rs2)),
1036
            case ShiftOp::Sra => emit::emit(s.e, encode::sraw(rd, rs1, rs2)),
1037
        }
1038
    } else {
1039
        match op {
1040
            case ShiftOp::Sll => emit::emit(s.e, encode::sll(rd, rs1, rs2)),
1041
            case ShiftOp::Srl => emit::emit(s.e, encode::srl(rd, rs1, rs2)),
1042
            case ShiftOp::Sra => emit::emit(s.e, encode::sra(rd, rs1, rs2)),
1043
        }
1044
    }
1045
}
1046
1047
/// Resolve parallel moves from IL values to physical destination registers.
1048
///
1049
/// The parallel move problem arises when moving values between registers where
1050
/// there may be dependencies (e.g. moving A0 to A1 and A1 to A0 simultaneously).
1051
///
1052
/// This algorithm:
1053
/// 1. Identifies "ready" moves.
1054
/// 2. Executes ready moves.
1055
/// 3. Breaks cycles using scratch register.
1056
///
1057
/// Entries with `ZERO` destination are skipped, as they are handled by caller.
1058
fn emitParallelMoves(s: *mut Selector, dsts: *[gen::Reg], args: *[il::Val]) {
1059
    let n: u32 = args.len;
1060
    if n == 0 {
1061
        return;
1062
    }
1063
    assert n <= MAX_BLOCK_ARGS, "emitParallelMoves: too many arguments";
1064
    // Source registers for each arg.
1065
    let mut srcRegs: [gen::Reg; MAX_BLOCK_ARGS] = [super::ZERO; MAX_BLOCK_ARGS];
1066
    // If this is a register-to-register move.
1067
    let mut isRegMove: [bool; MAX_BLOCK_ARGS] = [false; MAX_BLOCK_ARGS];
1068
    // If this move still needs to be executed.
1069
    let mut pending: [bool; MAX_BLOCK_ARGS] = [false; MAX_BLOCK_ARGS];
1070
    // Number of pending moves.
1071
    let mut numPending: u32 = 0;
1072
1073
    for i in 0..n {
1074
        let dst = dsts[i];
1075
        if dst <> super::ZERO { // Skip entries with no destination.
1076
            match args[i] {
1077
                case il::Val::Reg(r) => {
1078
                    if let _ = regalloc::spill::spillSlot(&s.ralloc.spill, r) {
1079
                        // Spilled value needs load, not a register move.
1080
                        set pending[i] = true;
1081
                        set numPending += 1;
1082
                    } else {
1083
                        let src = getReg(s, r);
1084
                        if src <> dst {
1085
                            // Register-to-register move needed.
1086
                            set srcRegs[i] = src;
1087
                            set isRegMove[i] = true;
1088
                            set pending[i] = true;
1089
                            set numPending += 1;
1090
                        } else {
1091
                            // No move needed.
1092
                        }
1093
                    }
1094
                },
1095
                case il::Val::Imm(_), il::Val::DataSym(_), il::Val::FnAddr(_) => {
1096
                    set pending[i] = true;
1097
                    set numPending += 1;
1098
                },
1099
                case il::Val::Undef => {
1100
                    // Undefined values don't need any move.
1101
                }
1102
            }
1103
        } else {
1104
            // Nothing to do.
1105
        }
1106
    }
1107
1108
    // Execute parallel move algorithm.
1109
    while numPending > 0 {
1110
        let mut found = false;
1111
1112
        // Find a ready move: one whose destination is not a source of any
1113
        // pending register move.
1114
        for i in 0..n {
1115
            if pending[i] {
1116
                let dst = dsts[i];
1117
                let mut isReady = true;
1118
1119
                // Check if `dst` is used as source by any other pending register move.
1120
                for j in 0..n {
1121
                    if j <> i and pending[j] and isRegMove[j] and srcRegs[j] == dst {
1122
                        set isReady = false;
1123
                        break;
1124
                    }
1125
                }
1126
                if isReady {
1127
                    // Execute this move.
1128
                    if isRegMove[i] {
1129
                        emitMv(s, dst, srcRegs[i]);
1130
                    } else {
1131
                        // Load immediate, symbol, or spilled value.
1132
                        loadVal(s, dst, args[i]);
1133
                    }
1134
                    set found = true;
1135
                    set pending[i] = false;
1136
                    set numPending -= 1;
1137
1138
                    break;
1139
                }
1140
            }
1141
        }
1142
1143
        if not found {
1144
            // No ready move, we have a cycle among register moves.
1145
            // Break it by saving one source to scratch.
1146
            for i in 0..n {
1147
                if pending[i] and isRegMove[i] {
1148
                    let src = srcRegs[i];
1149
                    // Save this source to scratch.
1150
                    emitMv(s, super::SCRATCH1, src);
1151
                    // Update all pending moves that use this source.
1152
                    for j in 0..n {
1153
                        if pending[j] and isRegMove[j] and srcRegs[j] == src {
1154
                            set srcRegs[j] = super::SCRATCH1;
1155
                        }
1156
                    }
1157
                    break;
1158
                }
1159
            }
1160
        }
1161
    }
1162
}
1163
1164
/// Emit moves from block arguments to target block's parameter registers.
1165
///
1166
/// Handles spilled destinations directly, then delegates to [`emitParallelMoves`]
1167
/// for the remaining register-to-register parallel move resolution. Edges that
1168
/// would overwrite an unconsumed spill source are unsupported.
1169
fn emitBlockArgs(s: *mut Selector, func: *il::Fn, target: u32, args: *mut [il::Val]) {
1170
    if args.len == 0 {
1171
        return;
1172
    }
1173
    let block = &func.blocks[target];
1174
    assert args.len == block.params.len, "emitBlockArgs: argument/parameter count mismatch";
1175
    assert args.len <= MAX_BLOCK_ARGS, "emitBlockArgs: too many block arguments";
1176
1177
    // The parallel-move resolver only handles register destinations. Keep eager
1178
    // stores for independent spill slots, but reject dependencies that would
1179
    // require stack staging rather than silently miscompiling them.
1180
    for arg, i in args {
1181
        if let dstSlot = regalloc::spill::spillSlot(&s.ralloc.spill, block.params[i].value) {
1182
            let mut changesSlot = true;
1183
            if let case il::Val::Reg(src) = arg {
1184
                if let srcSlot = regalloc::spill::spillSlot(&s.ralloc.spill, src) {
1185
                    if srcSlot == dstSlot {
1186
                        set changesSlot = false;
1187
                    }
1188
                }
1189
            }
1190
            if changesSlot {
1191
                for source, j in args {
1192
                    if let case il::Val::Reg(src) = source {
1193
                        if let sourceSlot = regalloc::spill::spillSlot(&s.ralloc.spill, src) {
1194
                            if sourceSlot == dstSlot {
1195
                                if let sourceDstSlot = regalloc::spill::spillSlot(
1196
                                    &s.ralloc.spill, block.params[j].value
1197
                                ) {
1198
                                    assert sourceDstSlot == dstSlot or j < i,
1199
                                        "emitBlockArgs: overlapping spilled block arguments are unsupported";
1200
                                } else {
1201
                                    panic "emitBlockArgs: overlapping spilled block arguments are unsupported";
1202
                                }
1203
                            }
1204
                        }
1205
                    }
1206
                }
1207
            }
1208
        }
1209
    }
1210
1211
    // Destination registers for each arg.
1212
    // Zero means the destination is spilled or skipped.
1213
    let mut dsts: [gen::Reg; MAX_BLOCK_ARGS] = [super::ZERO; MAX_BLOCK_ARGS];
1214
1215
    for arg, i in args {
1216
        let param = block.params[i].value;
1217
1218
        // Spilled destinations: store directly to spill slot.
1219
        // These don't participate in the parallel move algorithm.
1220
        if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, param) {
1221
            if let case il::Val::Undef = arg {
1222
                // Undefined values don't need any move.
1223
            } else {
1224
                let rs = resolveVal(s, super::SCRATCH1, arg);
1225
                emit::emitSd(s.e, rs, spillBase(s), spillOffset(s, slot));
1226
            }
1227
        } else {
1228
            set dsts[i] = getReg(s, param);
1229
        }
1230
    }
1231
    emitParallelMoves(s, &dsts[..], args);
1232
}
1233
1234
/// Select a comparison with immediate optimization.
1235
fn selectCmp(
1236
    s: *mut Selector,
1237
    typ: il::Type,
1238
    rd: gen::Reg,
1239
    rs1: gen::Reg,
1240
    b: il::Val,
1241
    op: CmpOp,
1242
    invert: bool,
1243
    scratch: gen::Reg
1244
) {
1245
    let mut signed = false;
1246
    if let case CmpOp::Slt = op {
1247
        set signed = true;
1248
    }
1249
    let useSext = cmpUsesSext(typ, signed);
1250
    emitCmpExt(s.e, rs1, rs1, typ, useSext);
1251
1252
    // Canonicalizing the immediate can expose an immediate instruction even
1253
    // when the IL value used a different representation for the same width.
1254
    let mut rhs = b;
1255
    if let case il::Val::Imm(imm) = b {
1256
        let canonical = canonicalCmpImm(imm, typ, useSext);
1257
        set rhs = il::Val::Imm(canonical);
1258
        if encode::isSmallImm64(canonical) {
1259
            let simm = canonical as i32;
1260
            match op {
1261
                case CmpOp::Slt => emit::emit(s.e, encode::slti(rd, rs1, simm)),
1262
                case CmpOp::Ult => emit::emit(s.e, encode::sltiu(rd, rs1, simm)),
1263
            }
1264
            if invert {
1265
                emit::emit(s.e, encode::xori(rd, rd, 1));
1266
            }
1267
            return;
1268
        }
1269
    }
1270
1271
    let rs2 = resolveVal(s, scratch, rhs);
1272
    if not isExtendedImm(rhs, typ, useSext) {
1273
        emitCmpExt(s.e, rs2, rs2, typ, useSext);
1274
    }
1275
    match op {
1276
        case CmpOp::Slt => emit::emit(s.e, encode::slt(rd, rs1, rs2)),
1277
        case CmpOp::Ult => emit::emit(s.e, encode::sltu(rd, rs1, rs2)),
1278
    }
1279
    if invert {
1280
        emit::emit(s.e, encode::xori(rd, rd, 1));
1281
    }
1282
}