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