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