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