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