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