lib/std/arch/rv64/emit.rad 30.1 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 number of pending branches awaiting patching.
21
constant MAX_PENDING: u32 = 65536;
22
/// Maximum number of function entries.
23
constant MAX_FUNCS: u32 = 4096;
24
/// Maximum number of debug entries.
25
constant MAX_DEBUG_ENTRIES: u32 = 524288;
26
27
//////////////////////
28
// Emission Context //
29
//////////////////////
30
31
/// Branch/jump that needs offset patching after all blocks are emitted.
32
export record PendingBranch: Copy {
33
    /// Index into code buffer where the branch instruction is.
34
    index: u32,
35
    /// Target block index.
36
    target: u32,
37
    /// Type of branch for re-encoding.
38
    kind: BranchKind,
39
}
40
41
/// Type of branch instruction.
42
export union BranchKind: Copy {
43
    /// Conditional branch (B-type encoding).
44
    Cond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg },
45
    /// Inverted conditional branch (B-type encoding with negated condition).
46
    InvertedCond { op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg },
47
    /// Unconditional jump (J-type encoding).
48
    Jump,
49
}
50
51
/// Function call that needs offset patching.
52
export record PendingCall: Copy {
53
    /// Index in code buffer where the call was emitted.
54
    index: u32,
55
    /// Target function name.
56
    target: *[u8],
57
}
58
59
/// Assembly jump that needs offset patching after all text is emitted.
60
export record PendingJump: Copy {
61
    /// Index in code buffer where the jump was emitted.
62
    index: u32,
63
    /// Target function name.
64
    target: *[u8],
65
    /// Destination register.
66
    rd: gen::Reg,
67
}
68
69
/// Address load that needs patching after layout is known.
70
export record PendingAddrLoad: Copy {
71
    /// Index in code buffer where the load was emitted.
72
    index: u32,
73
    /// Target function or data symbol name.
74
    target: *[u8],
75
    /// Destination register.
76
    rd: gen::Reg,
77
    /// Whether this is an absolute data symbol address.
78
    isData: bool,
79
}
80
81
/// Adjusted base register and offset for addressing.
82
export record AdjustedOffset: Copy {
83
    /// Base register.
84
    base: gen::Reg,
85
    /// Byte offset from register.
86
    offset: i32,
87
}
88
89
/// Callee-saved register with its stack offset.
90
export record SavedReg: Copy {
91
    /// Register to save/restore.
92
    reg: gen::Reg,
93
    /// Offset from SP.
94
    offset: i32,
95
}
96
97
/// Emission context. Tracks state during code generation.
98
export record Emitter {
99
    /// First failure. Further emission stops until the workspace is rebuilt.
100
    error: ?super::Error,
101
    /// Data address loads use the current domain package-state table.
102
    sharedData: bool,
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: Copy {
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: [SavedReg { reg: super::ZERO, offset: 0 }; super::NUM_SAVED_REGISTERS],
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 unsafe 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
    let mut pendingBranchesBuf = pendingBranches as *mut [PendingBranch];
202
    let mut pendingCallsBuf = pendingCalls as *mut [PendingCall];
203
    let mut pendingJumpsBuf = pendingJumps as *mut [PendingJump];
204
    let mut pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad];
205
    let mut funcsBuf = funcs as *mut [types::FuncAddr];
206
    set pendingBranchesBuf.len = 0;
207
    set pendingCallsBuf.len = 0;
208
    set pendingJumpsBuf.len = 0;
209
    set pendingAddrLoadsBuf.len = 0;
210
    set funcsBuf.len = 0;
211
    return Emitter {
212
        error: nil,
213
        sharedData: false,
214
        code: code as *mut [u32],
215
        codeLen: 0,
216
        pendingBranches: pendingBranchesBuf,
217
        pendingCalls: pendingCallsBuf,
218
        pendingJumps: pendingJumpsBuf,
219
        pendingAddrLoads: pendingAddrLoadsBuf,
220
        labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]),
221
        funcs: funcsBuf,
222
        debugEntries,
223
        debugEntriesLen: 0,
224
    };
225
}
226
227
///////////////////////
228
// Emission Helpers  //
229
///////////////////////
230
231
/// Emit a single instruction.
232
export fn emit(e: &mut Emitter, instr: u32) {
233
    if e.error <> nil { return; }
234
    if e.codeLen == e.code.len { set e.error = super::Error::Capacity; return; }
235
    set e.code[e.codeLen] = instr;
236
    set e.codeLen += 1;
237
}
238
239
/// Compute branch offset to a function by name.
240
export fn branchOffsetToFunc(e: &mut Emitter, srcIndex: u32, name: *[u8]) -> i32 {
241
    if e.error <> nil { return 0; }
242
    let target = dict::get(&e.labels.funcs, name) else {
243
        set e.error = super::Error::Symbol; return 0;
244
    };
245
    return target - srcIndex as i32 * super::INSTR_SIZE;
246
}
247
248
/// Patch an instruction at a given index.
249
export fn patch(e: &mut Emitter, index: u32, instr: u32) {
250
    if e.error <> nil { return; }
251
    if index >= e.codeLen { set e.error = super::Error::Capacity; return; }
252
    set e.code[index] = instr;
253
}
254
255
/// Record a block's address for branch resolution.
256
export fn recordBlock(e: &mut Emitter, blockIdx: u32) {
257
    if e.error <> nil { return; }
258
    if e.codeLen > MAX_CODE_LEN or blockIdx >= e.labels.blockOffsets.len {
259
        set e.error = super::Error::Capacity; return;
260
    }
261
    labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE);
262
}
263
264
/// Record a function's code offset for call resolution.
265
export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) {
266
    let codeLen = e.codeLen;
267
    recordFuncOffsetAt(e, name, codeLen);
268
}
269
270
/// Record a function's code offset at `index` for call resolution.
271
export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) {
272
    if e.error <> nil { return; }
273
    if index > MAX_CODE_LEN { set e.error = super::Error::Capacity; return; }
274
    if name.len == 0 { set e.error = super::Error::Symbol; return; }
275
    if e.labels.funcs.count >= e.labels.funcs.entries.len / 2 and dict::get(&e.labels.funcs, name) == nil {
276
        set e.error = super::Error::Capacity; return;
277
    }
278
    dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE);
279
}
280
281
/// Record a function's start position for printing.
282
export fn recordFunc(e: &mut Emitter, name: *[u8]) {
283
    let codeLen = e.codeLen;
284
    recordFuncAt(e, name, codeLen);
285
}
286
287
/// Record a function's start position at `index` for printing.
288
export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) {
289
    if e.error <> nil { return; }
290
    if e.funcs.len == e.funcs.cap { set e.error = super::Error::Capacity; return; }
291
    let count = e.funcs.len;
292
    unsafe { set e.funcs.len = count + 1; }
293
    set e.funcs[count] = types::FuncAddr { name, index };
294
}
295
296
/// Record a local branch needing later patching.
297
/// Unconditional jumps use a single slot (J-type, +-1MB range).
298
/// Conditional branches use two slots (B-type has only +-4KB range,
299
/// so large functions may need the inverted-branch + JAL fallback).
300
export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) {
301
    if e.error <> nil { return; }
302
    if e.pendingBranches.len == e.pendingBranches.cap { set e.error = super::Error::Capacity; return; }
303
    let count = e.pendingBranches.len;
304
    unsafe { set e.pendingBranches.len = count + 1; }
305
    set e.pendingBranches[count] = PendingBranch {
306
        index: e.codeLen,
307
        target: targetBlock,
308
        kind: kind,
309
    };
310
311
    emit(e, encode::nop()); // First slot, always needed.
312
313
    match kind {
314
        case BranchKind::Jump => {},
315
        else => emit(e, encode::nop()), // Second slot for conditional branches.
316
    }
317
}
318
319
/// Record a function call needing later patching.
320
/// Emits placeholder instructions that will be patched later.
321
/// Uses two slots to support long-distance calls.
322
export fn recordCall(e: &mut Emitter, target: *[u8]) {
323
    if e.error <> nil { return; }
324
    if e.pendingCalls.len == e.pendingCalls.cap { set e.error = super::Error::Capacity; return; }
325
    let count = e.pendingCalls.len;
326
    unsafe { set e.pendingCalls.len = count + 1; }
327
    set e.pendingCalls[count] = PendingCall {
328
        index: e.codeLen,
329
        target,
330
    };
331
332
    emit(e, encode::nop()); // Placeholder for AUIPC.
333
    emit(e, encode::nop()); // Placeholder for JALR.
334
}
335
336
/// Record a jump emitted by assembly that needs whole-program patching.
337
export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
338
    if e.error <> nil { return; }
339
    if e.pendingJumps.len == e.pendingJumps.cap { set e.error = super::Error::Capacity; return; }
340
    let count = e.pendingJumps.len;
341
    unsafe { set e.pendingJumps.len = count + 1; }
342
    set e.pendingJumps[count] = PendingJump {
343
        index,
344
        target,
345
        rd,
346
    };
347
}
348
349
/// Record a function address load needing later patching.
350
/// Emits placeholder instructions that will be patched to load the function's address.
351
/// Uses two slots to compute long-distance addresses.
352
export fn recordAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) {
353
    let codeLen = e.codeLen;
354
    recordAddrLoadAt(e, target, rd, codeLen);
355
356
    emit(e, encode::nop()); // Placeholder for AUIPC.
357
    emit(e, encode::nop()); // Placeholder for ADDI.
358
}
359
360
/// Record a function address load already reserved by assembly.
361
export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
362
    if e.error <> nil { return; }
363
    if e.pendingAddrLoads.len == e.pendingAddrLoads.cap { set e.error = super::Error::Capacity; return; }
364
    let count = e.pendingAddrLoads.len;
365
    unsafe { set e.pendingAddrLoads.len = count + 1; }
366
    set e.pendingAddrLoads[count] = PendingAddrLoad {
367
        index,
368
        target,
369
        rd: rd,
370
        isData: false,
371
    };
372
}
373
374
/// Record a data address load needing later patching.
375
/// Reserves two instructions for a PC-relative address load.
376
export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) {
377
    if e.error <> nil { return; }
378
    if e.pendingAddrLoads.len == e.pendingAddrLoads.cap { set e.error = super::Error::Capacity; return; }
379
    let count = e.pendingAddrLoads.len;
380
    unsafe { set e.pendingAddrLoads.len = count + 1; }
381
    set e.pendingAddrLoads[count] = PendingAddrLoad {
382
        index: e.codeLen,
383
        target,
384
        rd: rd,
385
        isData: true,
386
    };
387
388
    emit(e, encode::nop()); // Address-load upper instruction.
389
    emit(e, encode::nop()); // Address-load lower instruction.
390
    if e.sharedData {
391
        emit(e, encode::nop()); // Package offset addition.
392
        emit(e, encode::nop()); // Package offset low bits.
393
    }
394
}
395
396
/// Patch local branches and clear the pending list.
397
///
398
/// Called after each function.
399
///
400
/// Uses two-instruction sequences: short branches use `branch` and `nop`,
401
/// long branches use inverted branch  and `jal` or `auipc` and `jalr`.
402
export unsafe fn patchLocalBranches(e: &mut Emitter) {
403
    if e.error <> nil { return; }
404
    for i in 0..e.pendingBranches.len {
405
        let p = e.pendingBranches[i];
406
        if p.target >= e.labels.blockCount { set e.error = super::Error::Symbol; return; }
407
        let offset = labels::branchToBlock(&e.labels, p.index, p.target, super::INSTR_SIZE);
408
        match p.kind {
409
            case BranchKind::Cond { op, rs1, rs2 } => {
410
                if encode::isBranchImm(offset) {
411
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset));
412
                    patch(e, p.index + 1, encode::nop());
413
                } else {
414
                    let adj = offset - super::INSTR_SIZE;
415
                    if not encode::isJumpImm(adj) { set e.error = super::Error::Relocation; return; }
416
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
417
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
418
                }
419
            },
420
            case BranchKind::InvertedCond { op, rs1, rs2 } => {
421
                if encode::isBranchImm(offset) {
422
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset));
423
                    patch(e, p.index + 1, encode::nop());
424
                } else {
425
                    let adj = offset - super::INSTR_SIZE;
426
                    if not encode::isJumpImm(adj) { set e.error = super::Error::Relocation; return; }
427
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
428
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
429
                }
430
            },
431
            case BranchKind::Jump => {
432
                // Single-slot jump (J-type, +-1MB range).
433
                if not encode::isJumpImm(offset) { set e.error = super::Error::Relocation; return; }
434
                patch(e, p.index, encode::jal(super::ZERO, offset));
435
            },
436
        }
437
    }
438
    set e.pendingBranches.len = 0;
439
}
440
441
/// Encode a conditional branch instruction.
442
fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 {
443
    match op {
444
        case il::CmpOp::Eq => return encode::beq(rs1, rs2, offset),
445
        case il::CmpOp::Ne => return encode::bne(rs1, rs2, offset),
446
        case il::CmpOp::Slt => return encode::blt(rs1, rs2, offset),
447
        case il::CmpOp::Ult => return encode::bltu(rs1, rs2, offset),
448
    }
449
}
450
451
/// Encode an inverted conditional branch instruction.
452
fn encodeInvertedBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 {
453
    match op {
454
        case il::CmpOp::Eq => return encode::bne(rs1, rs2, offset),
455
        case il::CmpOp::Ne => return encode::beq(rs1, rs2, offset),
456
        case il::CmpOp::Slt => return encode::bge(rs1, rs2, offset),
457
        case il::CmpOp::Ult => return encode::bgeu(rs1, rs2, offset),
458
    }
459
}
460
461
/// Patch all pending function calls.
462
/// Called after all functions have been generated.
463
export fn patchCalls(e: &mut Emitter) {
464
    for i in 0..e.pendingCalls.len {
465
        let p = e.pendingCalls[i];
466
        let offset = branchOffsetToFunc(e, p.index, p.target);
467
        if offset > 0x7ffff7ff { set e.error = super::Error::Relocation; return; }
468
        let s = splitImm(offset);
469
470
        // `AUIPC scratch, hi(offset)`.
471
        patch(e, p.index, encode::auipc(super::SCRATCH1, s.hi));
472
        // `JALR ra, scratch, lo(offset)`.
473
        patch(e, p.index + 1, encode::jalr(super::RA, super::SCRATCH1, s.lo));
474
    }
475
}
476
477
/// Patch all pending assembly jumps.
478
export fn patchJumps(e: &mut Emitter) {
479
    for i in 0..e.pendingJumps.len {
480
        let p = e.pendingJumps[i];
481
        let offset = branchOffsetToFunc(e, p.index, p.target);
482
483
        if not encode::isJumpImm(offset) { set e.error = super::Error::Relocation; return; }
484
        patch(e, p.index, encode::jal(p.rd, offset));
485
    }
486
}
487
488
/// Patch all pending function and data address loads.
489
/// Called after all functions have been generated and data layout is known.
490
export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap, codeBase: u64) throws (super::Error) {
491
    try check(e);
492
    for i in 0..e.pendingAddrLoads.len {
493
        let p = e.pendingAddrLoads[i];
494
        if p.isData {
495
            let addr = data::lookupAddr(dataSymMap, p.target) else {
496
                throw super::Error::Symbol;
497
            };
498
            let offset = super::image::displacement(codeBase + p.index as u64 * 4, addr) else {
499
                throw super::Error::Relocation;
500
            };
501
            let s = splitImm(offset);
502
503
            patch(e, p.index, encode::auipc(p.rd, s.hi));
504
            patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo));
505
506
            continue;
507
        }
508
509
        let offset = branchOffsetToFunc(e, p.index, p.target);
510
        if offset > 0x7ffff7ff { set e.error = super::Error::Relocation; return; }
511
        let s = splitImm(offset);
512
        // `AUIPC rd, hi(offset)`.
513
        patch(e, p.index, encode::auipc(p.rd, s.hi));
514
        // `ADDI rd, rd, lo(offset)`.
515
        patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo));
516
    }
517
    try check(e);
518
}
519
520
/////////////////////////
521
// Immediate Handling  //
522
/////////////////////////
523
524
/// Split immediate into `hi` and `lo` bits.
525
export record SplitImm: Copy {
526
    /// Upper 20 bits.
527
    hi: i32,
528
    /// Lower 12 bits.
529
    lo: i32,
530
}
531
532
/// Split a 32-bit immediate for `AUIPC, ADDI` / `JALR` sequences.
533
/// Handles sign extension: if *lo* is negative, increment *hi*.
534
export fn splitImm(imm: i32) -> SplitImm {
535
    let lo = imm & 0xFFF;
536
    let mut hi = (imm >> 12) & 0xFFFFF;
537
    // If `lo`'s sign bit is set, it will be sign-extended to negative.
538
    // Compensate by incrementing `hi`.
539
    if (lo & 0x800) <> 0 {
540
        set hi += 1;
541
        return SplitImm { hi, lo: lo | 0xFFFFF000 as i32 };
542
    }
543
    return SplitImm { hi, lo };
544
}
545
546
/// Adjust a large offset by loading *hi* bits into [`super::ADDR_SCRATCH`].
547
/// Returns adjusted base register and remaining offset.
548
///
549
/// When the offset fits a 12-bit signed immediate, returns it unchanged.
550
/// Otherwise uses [`super::ADDR_SCRATCH`] for the LUI+ADD decomposition.
551
fn adjustOffset(e: &mut Emitter, base: gen::Reg, offset: i32) -> AdjustedOffset {
552
    if offset >= super::MIN_IMM and offset <= super::MAX_IMM {
553
        return AdjustedOffset { base, offset };
554
    }
555
    let s = splitImm(offset);
556
    emit(e, encode::lui(super::ADDR_SCRATCH, s.hi));
557
    emit(e, encode::add(super::ADDR_SCRATCH, super::ADDR_SCRATCH, base));
558
559
    return AdjustedOffset { base: super::ADDR_SCRATCH, offset: s.lo };
560
}
561
562
/// Load an immediate value into a register.
563
/// Handles the full range of 64-bit immediates.
564
/// For values fitting in 12 bits, uses a single `ADDI`.
565
/// For values fitting in 32 bits, uses `LUI` + `ADDIW`.
566
/// For wider values, loads upper and lower halves then combines with shift and add.
567
export fn loadImm(e: &mut Emitter, rd: gen::Reg, imm: i64) {
568
    let immMin = super::MIN_IMM as i64;
569
    let immMax = super::MAX_IMM as i64;
570
571
    if imm >= immMin and imm <= immMax {
572
        emit(e, encode::addi(rd, super::ZERO, imm as i32));
573
        return;
574
    }
575
    // Check if the value fits in 32 bits (sign-extended).
576
    let lo32 = imm as i32;
577
    if lo32 as i64 == imm {
578
        let s = splitImm(lo32);
579
        emit(e, encode::lui(rd, s.hi));
580
        if s.lo <> 0 {
581
            emit(e, encode::addiw(rd, rd, s.lo));
582
        }
583
        return;
584
    }
585
    // Full 64-bit immediate: use only rd, no scratch registers.
586
    // Load upper 32 bits first via the 32-bit path (LUI+ADDIW),
587
    // then shift and add lower bits in 11-bit groups to avoid
588
    // sign-extension issues with ADDI's 12-bit signed immediate.
589
    let hi32 = (imm >> 32) as i32;
590
    let lower = imm as i32;
591
592
    // Load upper 32 bits.
593
    loadImm(e, rd, hi32 as i64);
594
    // Shift left by 11, add bits [31:21].
595
    emit(e, encode::slli(rd, rd, 11));
596
    let chunkHi = (lower >> 21) & 0x7FF;
597
    if chunkHi <> 0 {
598
        emit(e, encode::addi(rd, rd, chunkHi));
599
    }
600
    // Shift left by 11, add bits [20:10].
601
    emit(e, encode::slli(rd, rd, 11));
602
    let chunkMid = (lower >> 10) & 0x7FF;
603
    if chunkMid <> 0 {
604
        emit(e, encode::addi(rd, rd, chunkMid));
605
    }
606
    // Shift left by 10, add bits [9:0].
607
    emit(e, encode::slli(rd, rd, 10));
608
    let chunkLo = lower & 0x3FF;
609
    if chunkLo <> 0 {
610
        emit(e, encode::addi(rd, rd, chunkLo));
611
    }
612
}
613
614
/// Emit add-immediate, handling large immediates.
615
export fn emitAddImm(e: &mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) {
616
    if imm >= super::MIN_IMM and imm <= super::MAX_IMM {
617
        emit(e, encode::addi(rd, rs, imm));
618
    } else {
619
        loadImm(e, super::SCRATCH1, imm as i64);
620
        emit(e, encode::add(rd, rs, super::SCRATCH1));
621
    }
622
}
623
624
////////////////////////
625
// Load/Store Helpers //
626
////////////////////////
627
628
/// Emit unsigned load with automatic offset adjustment.
629
export fn emitLoad(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
630
    let adj = adjustOffset(e, base, offset);
631
    match typ {
632
        case il::Type::W8 => emit(e, encode::lbu(rd, adj.base, adj.offset)),
633
        case il::Type::W16 => emit(e, encode::lhu(rd, adj.base, adj.offset)),
634
        case il::Type::W32 => emit(e, encode::lwu(rd, adj.base, adj.offset)),
635
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
636
    }
637
}
638
639
/// Emit signed load with automatic offset adjustment.
640
export fn emitSload(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
641
    let adj = adjustOffset(e, base, offset);
642
    match typ {
643
        case il::Type::W8 => emit(e, encode::lb(rd, adj.base, adj.offset)),
644
        case il::Type::W16 => emit(e, encode::lh(rd, adj.base, adj.offset)),
645
        case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)),
646
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
647
    }
648
}
649
650
/// Emit store with automatic offset adjustment.
651
export fn emitStore(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
652
    let adj = adjustOffset(e, base, offset);
653
    match typ {
654
        case il::Type::W8 => emit(e, encode::sb(rs, adj.base, adj.offset)),
655
        case il::Type::W16 => emit(e, encode::sh(rs, adj.base, adj.offset)),
656
        case il::Type::W32 => emit(e, encode::sw(rs, adj.base, adj.offset)),
657
        case il::Type::W64 => emit(e, encode::sd(rs, adj.base, adj.offset)),
658
    }
659
}
660
661
/// Emit 64-bit load with automatic offset adjustment.
662
export fn emitLd(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
663
    let adj = adjustOffset(e, base, offset);
664
    emit(e, encode::ld(rd, adj.base, adj.offset));
665
}
666
667
/// Emit 64-bit store with automatic offset adjustment.
668
export fn emitSd(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
669
    let adj = adjustOffset(e, base, offset);
670
    emit(e, encode::sd(rs, adj.base, adj.offset));
671
}
672
673
/// Emit 32-bit load with automatic offset adjustment.
674
export fn emitLw(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
675
    let adj = adjustOffset(e, base, offset);
676
    emit(e, encode::lw(rd, adj.base, adj.offset));
677
}
678
679
/// Emit 32-bit store with automatic offset adjustment.
680
export fn emitSw(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
681
    let adj = adjustOffset(e, base, offset);
682
    emit(e, encode::sw(rs, adj.base, adj.offset));
683
}
684
685
/// Emit 8-bit load with automatic offset adjustment.
686
export fn emitLb(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
687
    let adj = adjustOffset(e, base, offset);
688
    emit(e, encode::lb(rd, adj.base, adj.offset));
689
}
690
691
/// Emit 8-bit store with automatic offset adjustment.
692
export fn emitSb(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
693
    let adj = adjustOffset(e, base, offset);
694
    emit(e, encode::sb(rs, adj.base, adj.offset));
695
}
696
697
//////////////////////////
698
// Prologue / Epilogue  //
699
//////////////////////////
700
701
/// Emit function prologue.
702
/// Allocate the frame and save registers. Save FP only for dynamic frames.
703
export fn emitPrologue(e: &mut Emitter, frame: &Frame) {
704
    // Fast path: leaf function with no locals.
705
    if frame.totalSize == 0 {
706
        return;
707
    }
708
    let totalSize = frame.totalSize;
709
710
    // Allocate stack frame.
711
    let negFrame = 0 - totalSize;
712
    if negFrame >= super::MIN_IMM {
713
        emit(e, encode::addi(super::SP, super::SP, negFrame));
714
    } else {
715
        loadImm(e, super::SCRATCH1, totalSize as i64);
716
        emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1));
717
    }
718
    // Save return address.
719
    if not frame.isLeaf {
720
        emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
721
    }
722
    // Save and set FP only when dynamic allocations can move SP.
723
    if frame.isDynamic {
724
        emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
725
        emitAddImm(e, super::FP, super::SP, totalSize);
726
    }
727
    // Save callee-saved registers.
728
    for i in 0..frame.savedRegsLen {
729
        let sr = frame.savedRegs[i];
730
        emitSd(e, sr.reg, super::SP, sr.offset);
731
    }
732
}
733
734
/// Emit a return: jump to epilogue, or emit `ret` directly for leaf functions.
735
export fn emitReturn(e: &mut Emitter, frame: &Frame) {
736
    if frame.totalSize == 0 {
737
        // Leaf function: no frame to tear down, emit ret directly.
738
        emit(e, encode::ret());
739
        return;
740
    }
741
    recordBranch(e, frame.epilogueBlock, BranchKind::Jump);
742
}
743
744
/// Emit function epilogue.
745
/// Restore saved registers and release the frame. Restore FP only for dynamic frames.
746
export fn emitEpilogue(e: &mut Emitter, frame: &Frame) {
747
    // Record epilogue block address for return jumps.
748
    recordBlock(e, frame.epilogueBlock);
749
750
    // Fast path: leaf function with no locals.
751
    if frame.totalSize == 0 {
752
        emit(e, encode::ret());
753
        return;
754
    }
755
    let totalSize = frame.totalSize;
756
757
    // Restore SP to post-prologue value. Only needed when dynamic stack
758
    // allocation may have moved SP.
759
    if frame.isDynamic {
760
        emitAddImm(e, super::SP, super::FP, 0 - totalSize);
761
    }
762
    // Restore callee-saved registers.
763
    for i in 0..frame.savedRegsLen {
764
        let sr = frame.savedRegs[i];
765
        emitLd(e, sr.reg, super::SP, sr.offset);
766
    }
767
    // Restore FP only if the prologue saved and changed it.
768
    if frame.isDynamic {
769
        emitLd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
770
    }
771
    // Restore return address.
772
    if not frame.isLeaf {
773
        emitLd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
774
    }
775
    // Deallocate stack frame.
776
    emitAddImm(e, super::SP, super::SP, totalSize);
777
    emit(e, encode::ret());
778
}
779
780
//////////////////
781
// Code Access  //
782
//////////////////
783
784
/// Get emitted code as a slice.
785
export fn getCode(e: &Emitter) -> *[u32] {
786
    return &e.code[..e.codeLen];
787
}
788
789
/// Record a debug entry mapping the current PC to a source location.
790
/// Deduplicates consecutive entries with the same location.
791
export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) {
792
    if e.error <> nil { return; }
793
    let pc = e.codeLen * super::INSTR_SIZE as u32;
794
795
    // Skip if this is the same location as the previous entry.
796
    if e.debugEntriesLen > 0 {
797
        let prev = &e.debugEntries[e.debugEntriesLen - 1];
798
        if prev.offset == loc.offset and prev.moduleId == loc.moduleId {
799
            return;
800
        }
801
    }
802
    if e.debugEntriesLen == e.debugEntries.len { set e.error = super::Error::Capacity; return; }
803
    set e.debugEntries[e.debugEntriesLen] = types::DebugEntry {
804
        pc,
805
        moduleId: loc.moduleId,
806
        offset: loc.offset,
807
    };
808
    set e.debugEntriesLen += 1;
809
}
810
811
/// Get debug entries as a slice.
812
export fn getDebugEntries(e: &Emitter) -> *[types::DebugEntry] {
813
    return &e.debugEntries[..e.debugEntriesLen];
814
}
815
816
/// Return the first emission failure before any generated output is published.
817
export fn check(e: &Emitter) throws (super::Error) {
818
    if let error = e.error { throw error; }
819
}