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