lib/std/arch/rv64/emit.rad 31.6 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 {
101
    /// First failure recorded during generation.
102
    error: ?super::Error,
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 throws (super::Error) {
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
    if localSize < 0 or usedCalleeSaved >> (super::NUM_SAVED_REGISTERS as u32) <> 0 {
163
        throw super::Error::Capacity;
164
    }
165
    let total64 = (localSize as u64 + savedRegs as u64 * super::DWORD_SIZE as u64
166
        + super::STACK_ALIGNMENT as u64 - 1) & ~(super::STACK_ALIGNMENT as u64 - 1);
167
    if total64 > 0x7fffffff {
168
        throw super::Error::Capacity;
169
    }
170
    let totalSize = total64 as i32;
171
    set frame.totalSize = totalSize;
172
173
    // Build list of callee-saved registers with offsets.
174
    let mut offset = totalSize - (super::DWORD_SIZE * 3);
175
    for reg, i in super::CALLEE_SAVED {
176
        // Check if this register is in use.
177
        if (usedCalleeSaved & (1 << i)) <> 0 {
178
            set frame.savedRegs[frame.savedRegsLen] = SavedReg {
179
                reg,
180
                offset,
181
            };
182
            set frame.savedRegsLen += 1;
183
            set offset -= super::DWORD_SIZE;
184
        }
185
    }
186
    return frame;
187
}
188
189
/// Create a new emitter.
190
export unsafe fn emitter(arena: &mut alloc::Arena, debug: bool) -> Emitter throws (alloc::AllocError) {
191
    let code = try alloc::allocSlice(arena, @sizeOf(u32), @alignOf(u32), MAX_INSTRS);
192
    let pendingBranches = try alloc::allocSlice(arena, @sizeOf(PendingBranch), @alignOf(PendingBranch), MAX_PENDING);
193
    let pendingCalls = try alloc::allocSlice(arena, @sizeOf(PendingCall), @alignOf(PendingCall), MAX_PENDING);
194
    let pendingJumps = try alloc::allocSlice(arena, @sizeOf(PendingJump), @alignOf(PendingJump), MAX_PENDING);
195
    let pendingAddrLoads = try alloc::allocSlice(arena, @sizeOf(PendingAddrLoad), @alignOf(PendingAddrLoad), MAX_PENDING);
196
    let blockOffsets = try alloc::allocSlice(arena, @sizeOf(i32), @alignOf(i32), labels::MAX_BLOCKS_PER_FN);
197
    let funcEntries = try alloc::allocSlice(arena, @sizeOf(dict::Entry), @alignOf(dict::Entry), labels::FUNC_TABLE_SIZE);
198
    let funcs = try alloc::allocSlice(arena, @sizeOf(types::FuncAddr), @alignOf(types::FuncAddr), MAX_FUNCS);
199
200
    let mut debugEntries: *mut [types::DebugEntry] = &mut [];
201
    if debug {
202
        set debugEntries = try alloc::allocSlice(
203
            arena, @sizeOf(types::DebugEntry), @alignOf(types::DebugEntry), MAX_DEBUG_ENTRIES
204
        ) as *mut [types::DebugEntry];
205
    }
206
    let pendingBranchesBuf = pendingBranches as *mut [PendingBranch];
207
    let pendingCallsBuf = pendingCalls as *mut [PendingCall];
208
    let pendingJumpsBuf = pendingJumps as *mut [PendingJump];
209
    let pendingAddrLoadsBuf = pendingAddrLoads as *mut [PendingAddrLoad];
210
    let funcsBuf = funcs as *mut [types::FuncAddr];
211
    return Emitter {
212
        error: nil,
213
        code: code as *mut [u32],
214
        codeLen: 0,
215
        pendingBranches: &mut pendingBranchesBuf[..0],
216
        pendingCalls: &mut pendingCallsBuf[..0],
217
        pendingJumps: &mut pendingJumpsBuf[..0],
218
        pendingAddrLoads: &mut pendingAddrLoadsBuf[..0],
219
        labels: labels::init(blockOffsets as *mut [i32], funcEntries as *mut [dict::Entry]),
220
        funcs: &mut funcsBuf[..0],
221
        debugEntries,
222
        debugEntriesLen: 0,
223
    };
224
}
225
226
///////////////////////
227
// Emission Helpers  //
228
///////////////////////
229
230
/// Emit a single instruction.
231
export fn emit(e: &mut Emitter, instr: u32) {
232
    if e.error <> nil {
233
        return;
234
    }
235
    if e.codeLen >= e.code.len {
236
        set e.error = super::Error::Capacity;
237
        return;
238
    }
239
    set e.code[e.codeLen] = instr;
240
    set e.codeLen += 1;
241
}
242
243
/// Compute branch offset to a function by name.
244
export fn branchOffsetToFunc(e: &mut Emitter, srcIndex: u32, name: *[u8]) -> i32 {
245
    if e.error <> nil {
246
        return 0;
247
    }
248
    let target = dict::get(&e.labels.funcs, name) else {
249
        set e.error = super::Error::Symbol;
250
        return 0;
251
    };
252
    let offset = target as i64 - srcIndex as i64 * super::INSTR_SIZE as i64;
253
    if offset < -0x80000000 or offset > 0x7fffffff {
254
        set e.error = super::Error::Relocation;
255
        return 0;
256
    }
257
    return offset as i32;
258
}
259
260
/// Patch an instruction at a given index.
261
export fn patch(e: &mut Emitter, index: u32, instr: u32) {
262
    if e.error <> nil {
263
        return;
264
    }
265
    if index >= e.codeLen or index >= e.code.len {
266
        set e.error = super::Error::Capacity;
267
        return;
268
    }
269
    set e.code[index] = instr;
270
}
271
272
/// Record a block's address for branch resolution.
273
export fn recordBlock(e: &mut Emitter, blockIdx: u32) {
274
    if e.error <> nil {
275
        return;
276
    }
277
    if e.codeLen > MAX_CODE_LEN or blockIdx >= e.labels.blockOffsets.len {
278
        set e.error = super::Error::Capacity;
279
        return;
280
    }
281
    labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE);
282
}
283
284
/// Record a function's code offset for call resolution.
285
export fn recordFuncOffset(e: &mut Emitter, name: *[u8]) {
286
    let codeLen = e.codeLen;
287
    recordFuncOffsetAt(e, name, codeLen);
288
}
289
290
/// Record a function's code offset at `index` for call resolution.
291
export fn recordFuncOffsetAt(e: &mut Emitter, name: *[u8], index: u32) {
292
    if e.error <> nil {
293
        return;
294
    }
295
    if index > MAX_CODE_LEN or e.labels.funcs.entries.len < 2 {
296
        set e.error = super::Error::Capacity;
297
        return;
298
    }
299
    if name.len == 0 {
300
        set e.error = super::Error::Symbol;
301
        return;
302
    }
303
    if dict::get(&e.labels.funcs, name) == nil and
304
        e.labels.funcs.count >= e.labels.funcs.entries.len / 2 {
305
        set e.error = super::Error::Capacity;
306
        return;
307
    }
308
    dict::insert(&mut e.labels.funcs, name, index as i32 * super::INSTR_SIZE);
309
}
310
311
/// Record a function's start position for printing.
312
export fn recordFunc(e: &mut Emitter, name: *[u8]) {
313
    let codeLen = e.codeLen;
314
    recordFuncAt(e, name, codeLen);
315
}
316
317
/// Record a function's start position at `index` for printing.
318
export fn recordFuncAt(e: &mut Emitter, name: *[u8], index: u32) {
319
    if e.error <> nil {
320
        return;
321
    }
322
    if e.funcs.len >= e.funcs.cap {
323
        set e.error = super::Error::Capacity;
324
        return;
325
    }
326
    let count = e.funcs.len;
327
    unsafe {
328
        set e.funcs.len = count + 1;
329
    }
330
    set e.funcs[count] = types::FuncAddr { name, index };
331
}
332
333
/// Record a local branch needing later patching.
334
/// Unconditional jumps use a single slot (J-type, +-1MB range).
335
/// Conditional branches use two slots (B-type has only +-4KB range,
336
/// so large functions may need the inverted-branch + JAL fallback).
337
export fn recordBranch(e: &mut Emitter, targetBlock: u32, kind: BranchKind) {
338
    if e.error <> nil {
339
        return;
340
    }
341
    if e.pendingBranches.len >= e.pendingBranches.cap {
342
        set e.error = super::Error::Capacity;
343
        return;
344
    }
345
    let count = e.pendingBranches.len;
346
    unsafe {
347
        set e.pendingBranches.len = count + 1;
348
    }
349
    set e.pendingBranches[count] = PendingBranch {
350
        index: e.codeLen,
351
        target: targetBlock,
352
        kind: kind,
353
    };
354
355
    emit(e, encode::nop()); // First slot, always needed.
356
357
    match kind {
358
        case BranchKind::Jump => {},
359
        else => emit(e, encode::nop()), // Second slot for conditional branches.
360
    }
361
}
362
363
/// Record a function call needing later patching.
364
/// Emits placeholder instructions that will be patched later.
365
/// Uses two slots to support long-distance calls.
366
export fn recordCall(e: &mut Emitter, target: *[u8]) {
367
    if e.error <> nil {
368
        return;
369
    }
370
    if e.pendingCalls.len >= e.pendingCalls.cap {
371
        set e.error = super::Error::Capacity;
372
        return;
373
    }
374
    let count = e.pendingCalls.len;
375
    unsafe {
376
        set e.pendingCalls.len = count + 1;
377
    }
378
    set e.pendingCalls[count] = PendingCall {
379
        index: e.codeLen,
380
        target,
381
    };
382
383
    emit(e, encode::nop()); // Placeholder for AUIPC.
384
    emit(e, encode::nop()); // Placeholder for JALR.
385
}
386
387
/// Record a jump emitted by assembly that needs whole-program patching.
388
export fn recordJumpAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
389
    if e.error <> nil {
390
        return;
391
    }
392
    if e.pendingJumps.len >= e.pendingJumps.cap {
393
        set e.error = super::Error::Capacity;
394
        return;
395
    }
396
    let count = e.pendingJumps.len;
397
    unsafe {
398
        set e.pendingJumps.len = count + 1;
399
    }
400
    set e.pendingJumps[count] = PendingJump {
401
        index,
402
        target,
403
        rd,
404
    };
405
}
406
407
/// Record a function address load needing later patching.
408
/// Emits placeholder instructions that will be patched to load the function's address.
409
/// Uses two slots to compute long-distance addresses.
410
export fn recordAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) {
411
    let codeLen = e.codeLen;
412
    recordAddrLoadAt(e, target, rd, codeLen);
413
414
    emit(e, encode::nop()); // Placeholder for AUIPC.
415
    emit(e, encode::nop()); // Placeholder for ADDI.
416
}
417
418
/// Record a function address load already reserved by assembly.
419
export fn recordAddrLoadAt(e: &mut Emitter, target: *[u8], rd: gen::Reg, index: u32) {
420
    if e.error <> nil {
421
        return;
422
    }
423
    if e.pendingAddrLoads.len >= e.pendingAddrLoads.cap {
424
        set e.error = super::Error::Capacity;
425
        return;
426
    }
427
    let count = e.pendingAddrLoads.len;
428
    unsafe {
429
        set e.pendingAddrLoads.len = count + 1;
430
    }
431
    set e.pendingAddrLoads[count] = PendingAddrLoad {
432
        index,
433
        target,
434
        rd: rd,
435
        isData: false,
436
    };
437
}
438
439
/// Record a data address load needing later patching.
440
/// Uses an absolute 32-bit load sequence matching the current data memory map.
441
export fn recordDataAddrLoad(e: &mut Emitter, target: *[u8], rd: gen::Reg) {
442
    if e.error <> nil {
443
        return;
444
    }
445
    if e.pendingAddrLoads.len >= e.pendingAddrLoads.cap {
446
        set e.error = super::Error::Capacity;
447
        return;
448
    }
449
    let count = e.pendingAddrLoads.len;
450
    unsafe {
451
        set e.pendingAddrLoads.len = count + 1;
452
    }
453
    set e.pendingAddrLoads[count] = PendingAddrLoad {
454
        index: e.codeLen,
455
        target,
456
        rd: rd,
457
        isData: true,
458
    };
459
460
    emit(e, encode::nop()); // Placeholder for LUI.
461
    emit(e, encode::nop()); // Placeholder for ADDIW.
462
}
463
464
/// Patch local branches and clear the pending list.
465
///
466
/// Called after each function.
467
///
468
/// Uses two-instruction sequences: short branches use `branch` and `nop`,
469
/// long branches use inverted branch  and `jal` or `auipc` and `jalr`.
470
export unsafe fn patchLocalBranches(e: &mut Emitter) {
471
    for i in 0..e.pendingBranches.len {
472
        let p = e.pendingBranches[i];
473
        if e.error <> nil {
474
            return;
475
        }
476
        if p.target >= e.labels.blockCount or p.target >= e.labels.blockOffsets.len {
477
            set e.error = super::Error::Symbol;
478
            return;
479
        }
480
        let offset64 = e.labels.blockOffsets[p.target] as i64 - p.index as i64 * super::INSTR_SIZE as i64;
481
        if offset64 < -0x80000000 or offset64 > 0x7fffffff {
482
            set e.error = super::Error::Relocation;
483
            return;
484
        }
485
        let offset = offset64 as i32;
486
        match p.kind {
487
            case BranchKind::Cond { op, rs1, rs2 } => {
488
                if encode::isBranchImm(offset) {
489
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, offset));
490
                    patch(e, p.index + 1, encode::nop());
491
                } else {
492
                    let adjusted = offset64 - super::INSTR_SIZE as i64;
493
                    if adjusted < -0x100000 or adjusted > 0xffffe {
494
                        set e.error = super::Error::Relocation;
495
                        return;
496
                    }
497
                    let adj = adjusted as i32;
498
                    if not encode::isJumpImm(adj) {
499
                        set e.error = super::Error::Relocation;
500
                        return;
501
                    }
502
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
503
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
504
                }
505
            },
506
            case BranchKind::InvertedCond { op, rs1, rs2 } => {
507
                if encode::isBranchImm(offset) {
508
                    patch(e, p.index, encodeInvertedBranch(op, rs1, rs2, offset));
509
                    patch(e, p.index + 1, encode::nop());
510
                } else {
511
                    let adjusted = offset64 - super::INSTR_SIZE as i64;
512
                    if adjusted < -0x100000 or adjusted > 0xffffe {
513
                        set e.error = super::Error::Relocation;
514
                        return;
515
                    }
516
                    let adj = adjusted as i32;
517
                    if not encode::isJumpImm(adj) {
518
                        set e.error = super::Error::Relocation;
519
                        return;
520
                    }
521
                    patch(e, p.index, encodeCondBranch(op, rs1, rs2, super::INSTR_SIZE * 2));
522
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
523
                }
524
            },
525
            case BranchKind::Jump => {
526
                // Single-slot jump (J-type, +-1MB range).
527
                if not encode::isJumpImm(offset) {
528
                    set e.error = super::Error::Relocation;
529
                    return;
530
                }
531
                patch(e, p.index, encode::jal(super::ZERO, offset));
532
            },
533
        }
534
    }
535
    set e.pendingBranches.len = 0;
536
}
537
538
/// Encode a conditional branch instruction.
539
fn encodeCondBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 {
540
    match op {
541
        case il::CmpOp::Eq => return encode::beq(rs1, rs2, offset),
542
        case il::CmpOp::Ne => return encode::bne(rs1, rs2, offset),
543
        case il::CmpOp::Slt => return encode::blt(rs1, rs2, offset),
544
        case il::CmpOp::Ult => return encode::bltu(rs1, rs2, offset),
545
    }
546
}
547
548
/// Encode an inverted conditional branch instruction.
549
fn encodeInvertedBranch(op: il::CmpOp, rs1: gen::Reg, rs2: gen::Reg, offset: i32) -> u32 {
550
    match op {
551
        case il::CmpOp::Eq => return encode::bne(rs1, rs2, offset),
552
        case il::CmpOp::Ne => return encode::beq(rs1, rs2, offset),
553
        case il::CmpOp::Slt => return encode::bge(rs1, rs2, offset),
554
        case il::CmpOp::Ult => return encode::bgeu(rs1, rs2, offset),
555
    }
556
}
557
558
/// Patch all pending function calls.
559
/// Called after all functions have been generated.
560
export fn patchCalls(e: &mut Emitter) {
561
    for i in 0..e.pendingCalls.len {
562
        let p = e.pendingCalls[i];
563
        let offset = branchOffsetToFunc(e, p.index, p.target);
564
        let s = splitImm(offset);
565
566
        // `AUIPC scratch, hi(offset)`.
567
        patch(e, p.index, encode::auipc(super::SCRATCH1, s.hi));
568
        // `JALR ra, scratch, lo(offset)`.
569
        patch(e, p.index + 1, encode::jalr(super::RA, super::SCRATCH1, s.lo));
570
    }
571
}
572
573
/// Patch all pending assembly jumps.
574
export fn patchJumps(e: &mut Emitter) {
575
    for i in 0..e.pendingJumps.len {
576
        let p = e.pendingJumps[i];
577
        let offset = branchOffsetToFunc(e, p.index, p.target);
578
579
        if e.error <> nil {
580
            return;
581
        }
582
        if not encode::isJumpImm(offset) {
583
            set e.error = super::Error::Relocation;
584
            return;
585
        }
586
        patch(e, p.index, encode::jal(p.rd, offset));
587
    }
588
}
589
590
/// Patch all pending function and data address loads.
591
/// Called after all functions have been generated and data layout is known.
592
export fn patchAddrLoads(e: &mut Emitter, dataSymMap: &data::DataSymMap) {
593
    for i in 0..e.pendingAddrLoads.len {
594
        if e.error <> nil {
595
            return;
596
        }
597
        let p = e.pendingAddrLoads[i];
598
        if p.isData {
599
            let addr = data::lookupAddr(dataSymMap, p.target) else {
600
                set e.error = super::Error::Symbol;
601
                return;
602
            };
603
            if addr > MAX_I32_ADDR {
604
                set e.error = super::Error::Relocation;
605
                return;
606
            }
607
            let s = splitImm(addr as i32);
608
609
            patch(e, p.index, encode::lui(p.rd, s.hi));
610
            patch(e, p.index + 1, encode::addiw(p.rd, p.rd, s.lo));
611
612
            continue;
613
        }
614
615
        let offset = branchOffsetToFunc(e, p.index, p.target);
616
        let s = splitImm(offset);
617
        // `AUIPC rd, hi(offset)`.
618
        patch(e, p.index, encode::auipc(p.rd, s.hi));
619
        // `ADDI rd, rd, lo(offset)`.
620
        patch(e, p.index + 1, encode::addi(p.rd, p.rd, s.lo));
621
    }
622
}
623
624
/////////////////////////
625
// Immediate Handling  //
626
/////////////////////////
627
628
/// Split immediate into `hi` and `lo` bits.
629
export record SplitImm: Copy {
630
    /// Upper 20 bits.
631
    hi: i32,
632
    /// Lower 12 bits.
633
    lo: i32,
634
}
635
636
/// Split a 32-bit immediate for `AUIPC, ADDI` / `JALR` sequences.
637
/// Handles sign extension: if *lo* is negative, increment *hi*.
638
export fn splitImm(imm: i32) -> SplitImm {
639
    let lo = imm & 0xFFF;
640
    let mut hi = (imm >> 12) & 0xFFFFF;
641
    // If `lo`'s sign bit is set, it will be sign-extended to negative.
642
    // Compensate by incrementing `hi`.
643
    if (lo & 0x800) <> 0 {
644
        set hi += 1;
645
        return SplitImm { hi, lo: lo | 0xFFFFF000 as i32 };
646
    }
647
    return SplitImm { hi, lo };
648
}
649
650
/// Adjust a large offset by loading *hi* bits into [`super::ADDR_SCRATCH`].
651
/// Returns adjusted base register and remaining offset.
652
///
653
/// When the offset fits a 12-bit signed immediate, returns it unchanged.
654
/// Otherwise uses [`super::ADDR_SCRATCH`] for the LUI+ADD decomposition.
655
fn adjustOffset(e: &mut Emitter, base: gen::Reg, offset: i32) -> AdjustedOffset {
656
    if offset >= super::MIN_IMM and offset <= super::MAX_IMM {
657
        return AdjustedOffset { base, offset };
658
    }
659
    let s = splitImm(offset);
660
    emit(e, encode::lui(super::ADDR_SCRATCH, s.hi));
661
    emit(e, encode::add(super::ADDR_SCRATCH, super::ADDR_SCRATCH, base));
662
663
    return AdjustedOffset { base: super::ADDR_SCRATCH, offset: s.lo };
664
}
665
666
/// Load an immediate value into a register.
667
/// Handles the full range of 64-bit immediates.
668
/// For values fitting in 12 bits, uses a single `ADDI`.
669
/// For values fitting in 32 bits, uses `LUI` + `ADDIW`.
670
/// For wider values, loads upper and lower halves then combines with shift and add.
671
export fn loadImm(e: &mut Emitter, rd: gen::Reg, imm: i64) {
672
    let immMin = super::MIN_IMM as i64;
673
    let immMax = super::MAX_IMM as i64;
674
675
    if imm >= immMin and imm <= immMax {
676
        emit(e, encode::addi(rd, super::ZERO, imm as i32));
677
        return;
678
    }
679
    // Check if the value fits in 32 bits (sign-extended).
680
    let lo32 = imm as i32;
681
    if lo32 as i64 == imm {
682
        let s = splitImm(lo32);
683
        emit(e, encode::lui(rd, s.hi));
684
        if s.lo <> 0 {
685
            emit(e, encode::addiw(rd, rd, s.lo));
686
        }
687
        return;
688
    }
689
    // Full 64-bit immediate: use only rd, no scratch registers.
690
    // Load upper 32 bits first via the 32-bit path (LUI+ADDIW),
691
    // then shift and add lower bits in 11-bit groups to avoid
692
    // sign-extension issues with ADDI's 12-bit signed immediate.
693
    let hi32 = (imm >> 32) as i32;
694
    let lower = imm as i32;
695
696
    // Load upper 32 bits.
697
    loadImm(e, rd, hi32 as i64);
698
    // Shift left by 11, add bits [31:21].
699
    emit(e, encode::slli(rd, rd, 11));
700
    let chunkHi = (lower >> 21) & 0x7FF;
701
    if chunkHi <> 0 {
702
        emit(e, encode::addi(rd, rd, chunkHi));
703
    }
704
    // Shift left by 11, add bits [20:10].
705
    emit(e, encode::slli(rd, rd, 11));
706
    let chunkMid = (lower >> 10) & 0x7FF;
707
    if chunkMid <> 0 {
708
        emit(e, encode::addi(rd, rd, chunkMid));
709
    }
710
    // Shift left by 10, add bits [9:0].
711
    emit(e, encode::slli(rd, rd, 10));
712
    let chunkLo = lower & 0x3FF;
713
    if chunkLo <> 0 {
714
        emit(e, encode::addi(rd, rd, chunkLo));
715
    }
716
}
717
718
/// Emit add-immediate, handling large immediates.
719
export fn emitAddImm(e: &mut Emitter, rd: gen::Reg, rs: gen::Reg, imm: i32) {
720
    if imm >= super::MIN_IMM and imm <= super::MAX_IMM {
721
        emit(e, encode::addi(rd, rs, imm));
722
    } else {
723
        loadImm(e, super::SCRATCH1, imm as i64);
724
        emit(e, encode::add(rd, rs, super::SCRATCH1));
725
    }
726
}
727
728
////////////////////////
729
// Load/Store Helpers //
730
////////////////////////
731
732
/// Emit unsigned load with automatic offset adjustment.
733
export fn emitLoad(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
734
    let adj = adjustOffset(e, base, offset);
735
    match typ {
736
        case il::Type::W8 => emit(e, encode::lbu(rd, adj.base, adj.offset)),
737
        case il::Type::W16 => emit(e, encode::lhu(rd, adj.base, adj.offset)),
738
        case il::Type::W32 => emit(e, encode::lwu(rd, adj.base, adj.offset)),
739
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
740
    }
741
}
742
743
/// Emit signed load with automatic offset adjustment.
744
export fn emitSload(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
745
    let adj = adjustOffset(e, base, offset);
746
    match typ {
747
        case il::Type::W8 => emit(e, encode::lb(rd, adj.base, adj.offset)),
748
        case il::Type::W16 => emit(e, encode::lh(rd, adj.base, adj.offset)),
749
        case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)),
750
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
751
    }
752
}
753
754
/// Emit store with automatic offset adjustment.
755
export fn emitStore(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32, typ: il::Type) {
756
    let adj = adjustOffset(e, base, offset);
757
    match typ {
758
        case il::Type::W8 => emit(e, encode::sb(rs, adj.base, adj.offset)),
759
        case il::Type::W16 => emit(e, encode::sh(rs, adj.base, adj.offset)),
760
        case il::Type::W32 => emit(e, encode::sw(rs, adj.base, adj.offset)),
761
        case il::Type::W64 => emit(e, encode::sd(rs, adj.base, adj.offset)),
762
    }
763
}
764
765
/// Emit 64-bit load with automatic offset adjustment.
766
export fn emitLd(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
767
    let adj = adjustOffset(e, base, offset);
768
    emit(e, encode::ld(rd, adj.base, adj.offset));
769
}
770
771
/// Emit 64-bit store with automatic offset adjustment.
772
export fn emitSd(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
773
    let adj = adjustOffset(e, base, offset);
774
    emit(e, encode::sd(rs, adj.base, adj.offset));
775
}
776
777
/// Emit 32-bit load with automatic offset adjustment.
778
export fn emitLw(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
779
    let adj = adjustOffset(e, base, offset);
780
    emit(e, encode::lw(rd, adj.base, adj.offset));
781
}
782
783
/// Emit 32-bit store with automatic offset adjustment.
784
export fn emitSw(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
785
    let adj = adjustOffset(e, base, offset);
786
    emit(e, encode::sw(rs, adj.base, adj.offset));
787
}
788
789
/// Emit 8-bit load with automatic offset adjustment.
790
export fn emitLb(e: &mut Emitter, rd: gen::Reg, base: gen::Reg, offset: i32) {
791
    let adj = adjustOffset(e, base, offset);
792
    emit(e, encode::lb(rd, adj.base, adj.offset));
793
}
794
795
/// Emit 8-bit store with automatic offset adjustment.
796
export fn emitSb(e: &mut Emitter, rs: gen::Reg, base: gen::Reg, offset: i32) {
797
    let adj = adjustOffset(e, base, offset);
798
    emit(e, encode::sb(rs, adj.base, adj.offset));
799
}
800
801
//////////////////////////
802
// Prologue / Epilogue  //
803
//////////////////////////
804
805
/// Emit function prologue.
806
/// Allocate the frame and save registers. Save FP only for dynamic frames.
807
export fn emitPrologue(e: &mut Emitter, frame: &Frame) {
808
    // Fast path: leaf function with no locals.
809
    if frame.totalSize == 0 {
810
        return;
811
    }
812
    let totalSize = frame.totalSize;
813
814
    // Allocate stack frame.
815
    let negFrame = 0 - totalSize;
816
    if negFrame >= super::MIN_IMM {
817
        emit(e, encode::addi(super::SP, super::SP, negFrame));
818
    } else {
819
        loadImm(e, super::SCRATCH1, totalSize as i64);
820
        emit(e, encode::sub(super::SP, super::SP, super::SCRATCH1));
821
    }
822
    // Save return address.
823
    if not frame.isLeaf {
824
        emitSd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
825
    }
826
    // Save and set FP only when dynamic allocations can move SP.
827
    if frame.isDynamic {
828
        emitSd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
829
        emitAddImm(e, super::FP, super::SP, totalSize);
830
    }
831
    // Save callee-saved registers.
832
    for i in 0..frame.savedRegsLen {
833
        let sr = frame.savedRegs[i];
834
        emitSd(e, sr.reg, super::SP, sr.offset);
835
    }
836
}
837
838
/// Emit a return: jump to epilogue, or emit `ret` directly for leaf functions.
839
export fn emitReturn(e: &mut Emitter, frame: &Frame) {
840
    if frame.totalSize == 0 {
841
        // Leaf function: no frame to tear down, emit ret directly.
842
        emit(e, encode::ret());
843
        return;
844
    }
845
    recordBranch(e, frame.epilogueBlock, BranchKind::Jump);
846
}
847
848
/// Emit function epilogue.
849
/// Restore saved registers and release the frame. Restore FP only for dynamic frames.
850
export fn emitEpilogue(e: &mut Emitter, frame: &Frame) {
851
    // Record epilogue block address for return jumps.
852
    recordBlock(e, frame.epilogueBlock);
853
854
    // Fast path: leaf function with no locals.
855
    if frame.totalSize == 0 {
856
        emit(e, encode::ret());
857
        return;
858
    }
859
    let totalSize = frame.totalSize;
860
861
    // Restore SP to post-prologue value. Only needed when dynamic stack
862
    // allocation may have moved SP.
863
    if frame.isDynamic {
864
        emitAddImm(e, super::SP, super::FP, 0 - totalSize);
865
    }
866
    // Restore callee-saved registers.
867
    for i in 0..frame.savedRegsLen {
868
        let sr = frame.savedRegs[i];
869
        emitLd(e, sr.reg, super::SP, sr.offset);
870
    }
871
    // Restore FP only if the prologue saved and changed it.
872
    if frame.isDynamic {
873
        emitLd(e, super::FP, super::SP, totalSize - super::DWORD_SIZE * 2);
874
    }
875
    // Restore return address.
876
    if not frame.isLeaf {
877
        emitLd(e, super::RA, super::SP, totalSize - super::DWORD_SIZE);
878
    }
879
    // Deallocate stack frame.
880
    emitAddImm(e, super::SP, super::SP, totalSize);
881
    emit(e, encode::ret());
882
}
883
884
//////////////////
885
// Code Access  //
886
//////////////////
887
888
/// Get emitted code as a slice.
889
export fn getCode(e: &Emitter) -> *[u32] {
890
    return &e.code[..e.codeLen];
891
}
892
893
/// Record a debug entry mapping the current PC to a source location.
894
/// Deduplicates consecutive entries with the same location.
895
export fn recordSrcLoc(e: &mut Emitter, loc: il::SrcLoc) {
896
    if e.error <> nil {
897
        return;
898
    }
899
    let pc = e.codeLen * super::INSTR_SIZE as u32;
900
901
    // Skip if this is the same location as the previous entry.
902
    if e.debugEntriesLen > 0 {
903
        let prev = &e.debugEntries[e.debugEntriesLen - 1];
904
        if prev.offset == loc.offset and prev.moduleId == loc.moduleId {
905
            return;
906
        }
907
    }
908
    if e.debugEntriesLen >= e.debugEntries.len {
909
        set e.error = super::Error::Capacity;
910
        return;
911
    }
912
    set e.debugEntries[e.debugEntriesLen] = types::DebugEntry {
913
        pc,
914
        moduleId: loc.moduleId,
915
        offset: loc.offset,
916
    };
917
    set e.debugEntriesLen += 1;
918
}
919
920
/// Get debug entries as a slice.
921
export fn getDebugEntries(e: &Emitter) -> *[types::DebugEntry] {
922
    return &e.debugEntries[..e.debugEntriesLen];
923
}
924
925
/// Return the first error recorded during emission.
926
export fn check(e: &Emitter) throws (super::Error) {
927
    if let error = e.error {
928
        throw error;
929
    }
930
}