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