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