lib/std/arch/rv64/emit.rad 27.0 KiB raw
1
//! RV64 binary emission.
2
//!
3
//! Emits RV64 machine code as `u32` list.
4
5
use std::lang::il;
6
use std::lang::alloc;
7
use std::lang::gen;
8
use std::lang::gen::data;
9
use std::lang::gen::labels;
10
use std::lang::gen::types;
11
use std::collections::dict;
12
use std::mem;
13
14
use super::encode;
15
16
/// Maximum number of instructions in code buffer.
17
constant MAX_INSTRS: u32 = 2097152;
18
/// Maximum code length before byte offset overflows signed 32-bits.
19
constant MAX_CODE_LEN: u32 = 0x7FFFFFFF / super::INSTR_SIZE as u32;
20
/// Maximum positive value encodable by a signed 32-bit address calculation.
21
constant MAX_I32_ADDR: u32 = 0x7FFFFFFF;
22
/// Maximum number of pending branches awaiting patching.
23
constant MAX_PENDING: u32 = 65536;
24
/// Maximum number of function entries.
25
constant MAX_FUNCS: u32 = 4096;
26
/// Maximum number of debug entries.
27
constant MAX_DEBUG_ENTRIES: u32 = 524288;
28
29
//////////////////////
30
// Emission Context //
31
//////////////////////
32
33
/// Branch/jump that needs offset patching after all blocks are emitted.
34
export record PendingBranch: Copy {
35
    /// Index into code buffer where the branch instruction is.
36
    index: u32,
37
    /// Target block index.
38
    target: u32,
39
    /// Type of branch for re-encoding.
40
    kind: BranchKind,
41
}
42
43
/// Type of branch instruction.
44
export union BranchKind: Copy {
45
    /// Conditional branch (B-type encoding).
46
    Cond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg },
47
    /// Inverted conditional branch (B-type encoding with negated condition).
48
    InvertedCond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg },
49
    /// Unconditional jump (J-type encoding).
50
    Jump,
51
}
52
53
/// Function call that needs offset patching.
54
export record PendingCall: Copy {
55
    /// Index in code buffer where the call was emitted.
56
    index: u32,
57
    /// Target function name.
58
    target: *[u8],
59
}
60
61
/// Assembly jump that needs offset patching after all text is emitted.
62
export record PendingJump: Copy {
63
    /// Index in code buffer where the jump was emitted.
64
    index: u32,
65
    /// Target function name.
66
    target: *[u8],
67
    /// Destination register.
68
    rd: gen::Reg,
69
}
70
71
/// Address load that needs patching after layout is known.
72
export record PendingAddrLoad: Copy {
73
    /// Index in code buffer where the load was emitted.
74
    index: u32,
75
    /// Target function or data symbol name.
76
    target: *[u8],
77
    /// Destination register.
78
    rd: gen::Reg,
79
    /// Whether this is an absolute data symbol address.
80
    isData: bool,
81
}
82
83
/// Adjusted base register and offset for addressing.
84
export record AdjustedOffset: Copy {
85
    /// Base register.
86
    base: gen::Reg,
87
    /// Byte offset from register.
88
    offset: i32,
89
}
90
91
/// Callee-saved register with its stack offset.
92
export record SavedReg: Copy {
93
    /// Register to save/restore.
94
    reg: gen::Reg,
95
    /// Offset from SP.
96
    offset: i32,
97
}
98
99
/// Emission context. Tracks state during code generation.
100
export record Emitter: Copy {
101
    /// Allocator for growing append-backed emitter lists.
102
    allocator: alloc::Allocator,
103
    /// Emitted instructions storage.
104
    code: *mut [u32],
105
    /// Current number of emitted instructions.
106
    codeLen: u32,
107
    /// Local branches needing offset patching.
108
    pendingBranches: *mut [PendingBranch],
109
    /// Function calls needing offset patching.
110
    pendingCalls: *mut [PendingCall],
111
    /// Assembly jumps needing offset patching.
112
    pendingJumps: *mut [PendingJump],
113
    /// Function address loads needing offset patching.
114
    pendingAddrLoads: *mut [PendingAddrLoad],
115
    /// Current image's private data offsets relative to gp, or nil for fixed data.
116
    instanceData: ?*dict::Dict,
117
    /// Block label tracking.
118
    labels: labels::Labels,
119
    /// Function start positions for printing.
120
    funcs: *mut [types::FuncAddr],
121
    /// Debug entries mapping PCs to source locations.
122
    debugEntries: *mut [types::DebugEntry],
123
    /// Number of debug entries recorded.
124
    debugEntriesLen: u32,
125
}
126
127
/// Computed stack frame layout for a function.
128
export record Frame: Copy {
129
    /// Total frame size in bytes (aligned).
130
    totalSize: i32,
131
    /// Callee-saved registers and their offsets.
132
    // TODO: Use constant length when language supports it.
133
    savedRegs: [SavedReg; super::NUM_SAVED_REGISTERS],
134
    /// Number of saved registers.
135
    savedRegsLen: u32,
136
    /// Epilogue block index for return jumps.
137
    epilogueBlock: u32,
138
    /// Whether this is a leaf function. Leaf functions
139
    /// skip saving/restoring RA since it is never clobbered.
140
    isLeaf: bool,
141
    /// Whether the function has dynamic stack allocations.
142
    /// When false, SP never changes after the prologue.
143
    isDynamic: bool,
144
}
145
146
/// Compute frame layout from local size and used callee-saved registers.
147
export fn computeFrame(localSize: i32, usedCalleeSaved: u32, epilogueBlock: u32, isLeaf: bool, isDynamic: bool) -> Frame {
148
    let mut frame = Frame {
149
        totalSize: 0,
150
        savedRegs: undefined,
151
        savedRegsLen: 0,
152
        epilogueBlock,
153
        isLeaf,
154
        isDynamic,
155
    };
156
    // Skip frame allocation for leaf functions with no locals and no
157
    // callee-saved registers. Leaf functions don't call other functions,
158
    // so RA is never clobbered and doesn't need saving.
159
    if isLeaf and localSize == 0 and usedCalleeSaved == 0 {
160
        return frame;
161
    }
162
    // Compute total frame size. Includes RA and FP registers.
163
    let savedRegs = mem::popCount(usedCalleeSaved) + 2;
164
    let totalSize = mem::alignUpI32(
165
        localSize + savedRegs * super::DWORD_SIZE,
166
        super::STACK_ALIGNMENT
167
    );
168
    set frame.totalSize = totalSize;
169
170
    // Build list of callee-saved registers with offsets.
171
    let mut offset = totalSize - (super::DWORD_SIZE * 3);
172
    for reg, i in super::CALLEE_SAVED {
173
        // Check if this register is in use.
174
        if (usedCalleeSaved & (1 << i)) <> 0 {
175
            set frame.savedRegs[frame.savedRegsLen] = SavedReg {
176
                reg,
177
                offset,
178
            };
179
            set frame.savedRegsLen += 1;
180
            set offset -= super::DWORD_SIZE;
181
        }
182
    }
183
    return frame;
184
}
185
186
/// Create a new emitter.
187
export fn emitter(arena: *mut alloc::Arena, debug: bool) -> Emitter throws (alloc::AllocError) {
188
    let code = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), MAX_INSTRS);
189
    let pendingBranches = try alloc::allocSlice(arena, @sizeOf(PendingBranch), @alignOf(PendingBranch), MAX_PENDING);
190
    let pendingCalls = try alloc::allocSlice(arena, @sizeOf(PendingCall), @alignOf(PendingCall), MAX_PENDING);
191
    let pendingJumps = try alloc::allocSlice(arena, @sizeOf(PendingJump), @alignOf(PendingJump), MAX_PENDING);
192
    let pendingAddrLoads = try alloc::allocSlice(arena, @sizeOf(PendingAddrLoad), @alignOf(PendingAddrLoad), MAX_PENDING);
193
    let blockOffsets = try alloc::allocSlice(arena, @sizeOf(i32), @alignOf(i32), labels::MAX_BLOCKS_PER_FN);
194
    let funcEntries = try alloc::allocSlice(arena, @sizeOf(dict::Entry), @alignOf(dict::Entry), labels::FUNC_TABLE_SIZE);
195
    let funcs = try alloc::allocSlice(arena, @sizeOf(types::FuncAddr), @alignOf(types::FuncAddr), MAX_FUNCS);
196
197
    let mut debugEntries: *mut [types::DebugEntry] = &mut [];
198
    if debug {
199
        set debugEntries = try alloc::allocSlice(
200
            arena, @sizeOf(types::DebugEntry), @alignOf(types::DebugEntry), MAX_DEBUG_ENTRIES
201
        ) as *mut [types::DebugEntry];
202
    }
203
    return Emitter {
204
        allocator: alloc::arenaAllocator(arena),
205
        code: code as *mut [u32],
206
        codeLen: 0,
207
        pendingBranches: @sliceOf((pendingBranches as *mut [PendingBranch]).ptr, 0, MAX_PENDING),
208
        pendingCalls: @sliceOf((pendingCalls as *mut [PendingCall]).ptr, 0, MAX_PENDING),
209
        pendingJumps: @sliceOf((pendingJumps as *mut [PendingJump]).ptr, 0, MAX_PENDING),
210
        pendingAddrLoads: @sliceOf((pendingAddrLoads as *mut [PendingAddrLoad]).ptr, 0, MAX_PENDING),
211
        instanceData: nil,
212
        labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]),
213
        funcs: @sliceOf((funcs as *mut [types::FuncAddr]).ptr, 0, MAX_FUNCS),
214
        debugEntries,
215
        debugEntriesLen: 0,
216
    };
217
}
218
219
///////////////////////
220
// Emission Helpers  //
221
///////////////////////
222
223
/// Emit a single instruction.
224
export fn emit(e: *mut Emitter, instr: u32) {
225
    assert e.codeLen < e.code.len, "emit: code buffer full";
226
    set e.code[e.codeLen] = instr;
227
    set e.codeLen += 1;
228
}
229
230
/// Compute branch offset to a function by name.
231
export fn branchOffsetToFunc(e: *Emitter, srcIndex: u32, name: *[u8]) -> i32 {
232
    return labels::branchToFunc(&e.labels, srcIndex, name, super::INSTR_SIZE);
233
}
234
235
/// Patch an instruction at a given index.
236
export fn patch(e: *mut Emitter, index: u32, instr: u32) {
237
    set e.code[index] = instr;
238
}
239
240
/// Record a block's address for branch resolution.
241
export fn recordBlock(e: *mut Emitter, blockIdx: u32) {
242
    assert e.codeLen <= MAX_CODE_LEN;
243
    labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE);
244
}
245
246
/// Record a function's code offset for call resolution.
247
export fn recordFuncOffset(e: *mut Emitter, name: *[u8]) {
248
    recordFuncOffsetAt(e, name, e.codeLen);
249
}
250
251
/// Record a function's code offset at `index` for call resolution.
252
export fn recordFuncOffsetAt(e: *mut Emitter, name: *[u8], index: u32) {
253
    assert index <= MAX_CODE_LEN;
254
    dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE);
255
}
256
257
/// Record a function's start position for printing.
258
export fn recordFunc(e: *mut Emitter, name: *[u8]) {
259
    recordFuncAt(e, name, e.codeLen);
260
}
261
262
/// Record a function's start position at `index` for printing.
263
export fn recordFuncAt(e: *mut Emitter, name: *[u8], index: u32) {
264
    e.funcs.append(types::FuncAddr { name, index }, e.allocator);
265
}
266
267
/// Record a local branch needing later patching.
268
/// Unconditional jumps use a single slot (J-type, +-1MB range).
269
/// Conditional branches use two slots (B-type has only +-4KB range,
270
/// so large functions may need the inverted-branch + JAL fallback).
271
export fn recordBranch(e: *mut Emitter, targetBlock: u32, kind: BranchKind) {
272
    e.pendingBranches.append(PendingBranch {
273
        index: e.codeLen,
274
        target: targetBlock,
275
        kind: kind,
276
    }, e.allocator);
277
278
    emit(e, encode::nop()); // First slot, always needed.
279
280
    match kind {
281
        case BranchKind::Jump => {},
282
        else => emit(e, encode::nop()), // Second slot for conditional branches.
283
    }
284
}
285
286
/// Record a function call needing later patching.
287
/// Emits placeholder instructions that will be patched later.
288
/// Uses two slots to support long-distance calls.
289
export fn recordCall(e: *mut Emitter, target: *[u8]) {
290
    e.pendingCalls.append(PendingCall {
291
        index: e.codeLen,
292
        target,
293
    }, e.allocator);
294
295
    emit(e, encode::nop()); // Placeholder for AUIPC.
296
    emit(e, encode::nop()); // Placeholder for JALR.
297
}
298
299
/// Record a jump emitted by assembly that needs whole-program patching.
300
export fn recordJumpAt(e: *mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
301
    e.pendingJumps.append(PendingJump {
302
        index,
303
        target,
304
        rd,
305
    }, e.allocator);
306
}
307
308
/// Record a function address load needing later patching.
309
/// Emits placeholder instructions that will be patched to load the function's address.
310
/// Uses two slots to compute long-distance addresses.
311
export fn recordAddrLoad(e: *mut Emitter, target: *[u8], rd: gen::Reg) {
312
    recordAddrLoadAt(e, target, rd, e.codeLen);
313
314
    emit(e, encode::nop()); // Placeholder for AUIPC.
315
    emit(e, encode::nop()); // Placeholder for ADDI.
316
}
317
318
/// Record a function address load already reserved by assembly.
319
export fn recordAddrLoadAt(e: *mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
320
    e.pendingAddrLoads.append(PendingAddrLoad {
321
        index,
322
        target,
323
        rd: rd,
324
        isData: false,
325
    }, e.allocator);
326
}
327
328
/// Record a data address load needing later patching.
329
/// Uses an absolute 32-bit load sequence matching the current data memory map.
330
export fn recordDataAddrLoad(e: *mut Emitter, target: *[u8], rd: gen::Reg) {
331
    e.pendingAddrLoads.append(PendingAddrLoad {
332
        index: e.codeLen,
333
        target,
334
        rd: rd,
335
        isData: true,
336
    }, e.allocator);
337
338
    emit(e, encode::nop()); // Placeholder for LUI.
339
    emit(e, encode::nop()); // Placeholder for ADDIW.
340
}
341
342
/// Patch local branches and clear the pending list.
343
///
344
/// Called after each function.
345
///
346
/// Uses two-instruction sequences: short branches use `branch` and `nop`,
347
/// long branches use inverted branch  and `jal` or `auipc` and `jalr`.
348
export fn patchLocalBranches(e: *mut Emitter) {
349
    for i in 0..e.pendingBranches.len {
350
        let p = e.pendingBranches[i];
351
        let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE);
352
        match p.kind {
353
            case BranchKind::Cond { op, rs1, rs2 } => {
354
                if encode::isBranchImm(offset) {
355
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset));
356
                    patch(e, p.index + 1, encode::nop());
357
                } else {
358
                    let adj = offset - super::INSTR_SIZE;
359
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
360
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
361
                }
362
            },
363
            case BranchKind::InvertedCond { op, rs1, rs2 } => {
364
                if encode::isBranchImm(offset) {
365
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset));
366
                    patch(e, p.index + 1, encode::nop());
367
                } else {
368
                    let adj = offset - super::INSTR_SIZE;
369
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
370
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
371
                }
372
            },
373
            case BranchKind::Jump => {
374
                // Single-slot jump (J-type, +-1MB range).
375
                assert encode::isJumpImm(offset), "patchLocalBranches: jump offset too large";
376
                patch(e, p.index, encode::jal(super::ZERO, offset));
377
            },
378
        }
379
    }
380
    set e.pendingBranches = @sliceOf(e.pendingBranches.ptr, 0, e.pendingBranches.cap);
381
}
382
383
/// Encode a conditional branch instruction.
384
fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 {
385
    match op {
386
        case il::CmpOp::Eq => return encode::beq(rs1, rs2, offset),
387
        case il::CmpOp::Ne => return encode::bne(rs1, rs2, offset),
388
        case il::CmpOp::Slt => return encode::blt(rs1, rs2, offset),
389
        case il::CmpOp::Ult => return encode::bltu(rs1, rs2, offset),
390
    }
391
}
392
393
/// Encode an inverted conditional branch instruction.
394
fn encodeInvertedBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 {
395
    match op {
396
        case il::CmpOp::Eq => return encode::bne(rs1, rs2, offset),
397
        case il::CmpOp::Ne => return encode::beq(rs1, rs2, offset),
398
        case il::CmpOp::Slt => return encode::bge(rs1, rs2, offset),
399
        case il::CmpOp::Ult => return encode::bgeu(rs1, rs2, offset),
400
    }
401
}
402
403
/// Patch all pending function calls.
404
/// Called after all functions have been generated.
405
export fn patchCalls(e: *mut Emitter) {
406
    for i in 0..e.pendingCalls.len {
407
        let p = e.pendingCalls[i];
408
        let offset = branchOffsetToFunc(e, p.index, p.target);
409
        let s = splitImm(offset);
410
411
        // `AUIPC scratch, hi(offset)`.
412
        patch(e, p.index, encode::auipc(super::SCRATCH1, s.hi));
413
        // `JALR ra, scratch, lo(offset)`.
414
        patch(e, p.index + 1, encode::jalr(super::RA, super::SCRATCH1, s.lo));
415
    }
416
}
417
418
/// Patch all pending assembly jumps.
419
export fn patchJumps(e: *mut Emitter) {
420
    for i in 0..e.pendingJumps.len {
421
        let p = e.pendingJumps[i];
422
        let offset = branchOffsetToFunc(e, p.index, p.target);
423
424
        assert encode::isJumpImm(offset), "patchJumps: jump offset too large";
425
        patch(e, p.index, encode::jal(p.rd, offset));
426
    }
427
}
428
429
/// Patch all pending function and data address loads.
430
/// Called after all functions have been generated and data layout is known.
431
export fn patchAddrLoads(e: *mut Emitter, dataSymMap: *data::DataSymMap) {
432
    for i in 0..e.pendingAddrLoads.len {
433
        let p = e.pendingAddrLoads[i];
434
        if p.isData {
435
            let addr = data::lookupAddr(dataSymMap, p.target) else {
436
                panic "patchAddrLoads: data symbol not found";
437
            };
438
            assert addr <= MAX_I32_ADDR, "patchAddrLoads: data address too large";
439
            let s = splitImm(addr as i32);
440
441
            patch(e, p.index, encode::lui(p.rd, s.hi));
442
            patch(e, p.index + 1, encode::addiw(p.rd, p.rd, s.lo));
443
444
            continue;
445
        }
446
447
        let offset = branchOffsetToFunc(e, p.index, p.target);
448
        let s = splitImm(offset);
449
        // `AUIPC rd, hi(offset)`.
450
        patch(e, p.index, encode::auipc(p.rd, s.hi));
451
        // `ADDI rd, rd, lo(offset)`.
452
        patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo));
453
    }
454
}
455
456
/////////////////////////
457
// Immediate Handling  //
458
/////////////////////////
459
460
/// Split immediate into `hi` and `lo` bits.
461
export record SplitImm: Copy {
462
    /// Upper 20 bits.
463
    hi: i32,
464
    /// Lower 12 bits.
465
    lo: i32,
466
}
467
468
/// Split a 32-bit immediate for `AUIPC, ADDI` / `JALR` sequences.
469
/// Handles sign extension: if *lo* is negative, increment *hi*.
470
export fn splitImm(imm: i32) -> SplitImm {
471
    let lo = imm & 0xFFF;
472
    let mut hi = (imm >> 12) & 0xFFFFF;
473
    // If `lo`'s sign bit is set, it will be sign-extended to negative.
474
    // Compensate by incrementing `hi`.
475
    if (lo & 0x800) <> 0 {
476
        set hi += 1;
477
        return SplitImm { hi, lo: lo | 0xFFFFF000 as i32 };
478
    }
479
    return SplitImm { hi, lo };
480
}
481
482
/// Adjust a large offset by loading *hi* bits into [`super::ADDR_SCRATCH`].
483
/// Returns adjusted base register and remaining offset.
484
///
485
/// When the offset fits a 12-bit signed immediate, returns it unchanged.
486
/// Otherwise uses [`super::ADDR_SCRATCH`] for the LUI+ADD decomposition.
487
fn adjustOffset(e: *mut Emitter, base: gen::Reg, offset: i32) -> AdjustedOffset {
488
    if offset >= super::MIN_IMM and offset <= super::MAX_IMM {
489
        return AdjustedOffset { base, offset };
490
    }
491
    let s = splitImm(offset);
492
    emit(e, encode::lui(super::ADDR_SCRATCH, s.hi));
493
    emit(e, encode::add(super::ADDR_SCRATCH, super::ADDR_SCRATCH, base));
494
495
    return AdjustedOffset { base: super::ADDR_SCRATCH, offset: s.lo };
496
}
497
498
/// Load an immediate value into a register.
499
/// Handles the full range of 64-bit immediates.
500
/// For values fitting in 12 bits, uses a single `ADDI`.
501
/// For values fitting in 32 bits, uses `LUI` + `ADDIW`.
502
/// For wider values, loads upper and lower halves then combines with shift and add.
503
export fn loadImm(e: *mut Emitter, rd: gen::Reg, imm: i64) {
504
    let immMin = super::MIN_IMM as i64;
505
    let immMax = super::MAX_IMM as i64;
506
507
    if imm >= immMin and imm <= immMax {
508
        emit(e, encode::addi(rd, super::ZERO, imm as i32));
509
        return;
510
    }
511
    // Check if the value fits in 32 bits (sign-extended).
512
    let lo32 = imm as i32;
513
    if lo32 as i64 == imm {
514
        let s = splitImm(lo32);
515
        emit(e, encode::lui(rd, s.hi));
516
        if s.lo <> 0 {
517
            emit(e, encode::addiw(rd, rd, s.lo));
518
        }
519
        return;
520
    }
521
    // Full 64-bit immediate: use only rd, no scratch registers.
522
    // Load upper 32 bits first via the 32-bit path (LUI+ADDIW),
523
    // then shift and add lower bits in 11-bit groups to avoid
524
    // sign-extension issues with ADDI's 12-bit signed immediate.
525
    let hi32 = (imm >> 32) as i32;
526
    let lower = imm as i32;
527
528
    // Load upper 32 bits.
529
    loadImm(e, rd, hi32 as i64);
530
    // Shift left by 11, add bits [31:21].
531
    emit(e, encode::slli(rd, rd, 11));
532
    let chunkHi = (lower >> 21) & 0x7FF;
533
    if chunkHi <> 0 {
534
        emit(e, encode::addi(rd, rd, chunkHi));
535
    }
536
    // Shift left by 11, add bits [20:10].
537
    emit(e, encode::slli(rd, rd, 11));
538
    let chunkMid = (lower >> 10) & 0x7FF;
539
    if chunkMid <> 0 {
540
        emit(e, encode::addi(rd, rd, chunkMid));
541
    }
542
    // Shift left by 10, add bits [9:0].
543
    emit(e, encode::slli(rd, rd, 10));
544
    let chunkLo = lower & 0x3FF;
545
    if chunkLo <> 0 {
546
        emit(e, encode::addi(rd, rd, chunkLo));
547
    }
548
}
549
550
/// Emit add-immediate, handling large immediates.
551
export fn emitAddImm(e: *mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) {
552
    if imm >= super::MIN_IMM and imm <= super::MAX_IMM {
553
        emit(e, encode::addi(rd, rs, imm));
554
    } else {
555
        loadImm(e, super::SCRATCH1, imm as i64);
556
        emit(e, encode::add(rd, rs, super::SCRATCH1));
557
    }
558
}
559
560
////////////////////////
561
// Load/Store Helpers //
562
////////////////////////
563
564
/// Emit unsigned load with automatic offset adjustment.
565
export fn emitLoad(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
566
    let adj = adjustOffset(e, base, offset);
567
    match typ {
568
        case il::Type::W8 => emit(e, encode::lbu(rd, adj.base, adj.offset)),
569
        case il::Type::W16 => emit(e, encode::lhu(rd, adj.base, adj.offset)),
570
        case il::Type::W32 => emit(e, encode::lwu(rd, adj.base, adj.offset)),
571
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
572
    }
573
}
574
575
/// Emit signed load with automatic offset adjustment.
576
export fn emitSload(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
577
    let adj = adjustOffset(e, base, offset);
578
    match typ {
579
        case il::Type::W8 => emit(e, encode::lb(rd, adj.base, adj.offset)),
580
        case il::Type::W16 => emit(e, encode::lh(rd, adj.base, adj.offset)),
581
        case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)),
582
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
583
    }
584
}
585
586
/// Emit store with automatic offset adjustment.
587
export fn emitStore(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
588
    let adj = adjustOffset(e, base, offset);
589
    match typ {
590
        case il::Type::W8 => emit(e, encode::sb(rs, adj.base, adj.offset)),
591
        case il::Type::W16 => emit(e, encode::sh(rs, adj.base, adj.offset)),
592
        case il::Type::W32 => emit(e, encode::sw(rs, adj.base, adj.offset)),
593
        case il::Type::W64 => emit(e, encode::sd(rs, adj.base, adj.offset)),
594
    }
595
}
596
597
/// Emit 64-bit load with automatic offset adjustment.
598
export fn emitLd(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
599
    let adj = adjustOffset(e, base, offset);
600
    emit(e, encode::ld(rd, adj.base, adj.offset));
601
}
602
603
/// Emit 64-bit store with automatic offset adjustment.
604
export fn emitSd(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
605
    let adj = adjustOffset(e, base, offset);
606
    emit(e, encode::sd(rs, adj.base, adj.offset));
607
}
608
609
/// Emit 32-bit load with automatic offset adjustment.
610
export fn emitLw(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
611
    let adj = adjustOffset(e, base, offset);
612
    emit(e, encode::lw(rd, adj.base, adj.offset));
613
}
614
615
/// Emit 32-bit store with automatic offset adjustment.
616
export fn emitSw(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
617
    let adj = adjustOffset(e, base, offset);
618
    emit(e, encode::sw(rs, adj.base, adj.offset));
619
}
620
621
/// Emit 8-bit load with automatic offset adjustment.
622
export fn emitLb(e: *mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
623
    let adj = adjustOffset(e, base, offset);
624
    emit(e, encode::lb(rd, adj.base, adj.offset));
625
}
626
627
/// Emit 8-bit store with automatic offset adjustment.
628
export fn emitSb(e: *mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
629
    let adj = adjustOffset(e, base, offset);
630
    emit(e, encode::sb(rs, adj.base, adj.offset));
631
}
632
633
//////////////////////////
634
// Prologue / Epilogue  //
635
//////////////////////////
636
637
/// Emit function prologue.
638
/// Allocate the frame and save registers. Save FP only for dynamic frames.
639
export fn emitPrologue(e: *mut Emitter, frame: *Frame) {
640
    // Fast path: leaf function with no locals.
641
    if frame.totalSize == 0 {
642
        return;
643
    }
644
    let totalSize = frame.totalSize;
645
646
    // Allocate stack frame.
647
    let negFrame = 0 - totalSize;
648
    if negFrame >= super::MIN_IMM {
649
        emit(e, encode::addi(super::SP, super::SP, negFrame));
650
    } else {
651
        loadImm(e, super::SCRATCH1, totalSize as i64);
652
        emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1));
653
    }
654
    // Save return address.
655
    if not frame.isLeaf {
656
        emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
657
    }
658
    // Save and set FP only when dynamic allocations can move SP.
659
    if frame.isDynamic {
660
        emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
661
        emitAddImm(e, super::FP, super::SP, totalSize);
662
    }
663
    // Save callee-saved registers.
664
    for i in 0..frame.savedRegsLen {
665
        let sr = frame.savedRegs[i];
666
        emitSd(e, sr.reg, super::SP, sr.offset);
667
    }
668
}
669
670
/// Emit a return: jump to epilogue, or emit `ret` directly for leaf functions.
671
export fn emitReturn(e: *mut Emitter, frame: *Frame) {
672
    if frame.totalSize == 0 {
673
        // Leaf function: no frame to tear down, emit ret directly.
674
        emit(e, encode::ret());
675
        return;
676
    }
677
    recordBranch(e, frame.epilogueBlock, BranchKind::Jump);
678
}
679
680
/// Emit function epilogue.
681
/// Restore saved registers and release the frame. Restore FP only for dynamic frames.
682
export fn emitEpilogue(e: *mut Emitter, frame: *Frame) {
683
    // Record epilogue block address for return jumps.
684
    recordBlock(e, frame.epilogueBlock);
685
686
    // Fast path: leaf function with no locals.
687
    if frame.totalSize == 0 {
688
        emit(e, encode::ret());
689
        return;
690
    }
691
    let totalSize = frame.totalSize;
692
693
    // Restore SP to post-prologue value. Only needed when dynamic stack
694
    // allocation may have moved SP.
695
    if frame.isDynamic {
696
        emitAddImm(e, super::SP, super::FP, 0 - totalSize);
697
    }
698
    // Restore callee-saved registers.
699
    for i in 0..frame.savedRegsLen {
700
        let sr = frame.savedRegs[i];
701
        emitLd(e, sr.reg, super::SP, sr.offset);
702
    }
703
    // Restore FP only if the prologue saved and changed it.
704
    if frame.isDynamic {
705
        emitLd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
706
    }
707
    // Restore return address.
708
    if not frame.isLeaf {
709
        emitLd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
710
    }
711
    // Deallocate stack frame.
712
    emitAddImm(e, super::SP, super::SP, totalSize);
713
    emit(e, encode::ret());
714
}
715
716
//////////////////
717
// Code Access  //
718
//////////////////
719
720
/// Get emitted code as a slice.
721
export fn getCode(e: *Emitter) -> *[u32] {
722
    return &e.code[..e.codeLen];
723
}
724
725
/// Record a debug entry mapping the current PC to a source location.
726
/// Deduplicates consecutive entries with the same location.
727
export fn recordSrcLoc(e: *mut Emitter, loc: il::SrcLoc) {
728
    let pc = e.codeLen * super::INSTR_SIZE as u32;
729
730
    // Skip if this is the same location as the previous entry.
731
    if e.debugEntriesLen > 0 {
732
        let prev = &e.debugEntries[e.debugEntriesLen - 1];
733
        if prev.offset == loc.offset and prev.moduleId == loc.moduleId {
734
            return;
735
        }
736
    }
737
    assert e.debugEntriesLen < e.debugEntries.len, "recordSrcLoc: debug entry buffer full";
738
    set e.debugEntries[e.debugEntriesLen] = types::DebugEntry {
739
        pc,
740
        moduleId: loc.moduleId,
741
        offset: loc.offset,
742
    };
743
    set e.debugEntriesLen += 1;
744
}
745
746
/// Get debug entries as a slice.
747
export fn getDebugEntries(e: *Emitter) -> *[types::DebugEntry] {
748
    return &e.debugEntries[..e.debugEntriesLen];
749
}