lib/std/arch/rv64.rad 13.0 KiB raw
1
//! RV64 code generation backend.
2
//!
3
//! Generates RISC-V 64-bit machine code from IL (intermediate language).
4
//!
5
//! # Submodules
6
//!
7
//! * encode: Instruction encoding functions
8
//! * decode: Instruction decoding (for disassembly/printing)
9
//! * emit: Binary emission context and branch patching
10
//! * isel: Instruction selection (IL to RV64 instructions)
11
//! * printer: Assembly text output
12
13
export mod encode;
14
export mod decode;
15
export mod emit;
16
export mod isel;
17
export mod printer;
18
export mod asm;
19
20
@test mod tests;
21
22
use std::mem;
23
use std::collections::dict;
24
use std::lang::il;
25
use std::lang::alloc;
26
use std::lang::gen;
27
use std::lang::gen::labels;
28
use std::lang::gen::regalloc;
29
use std::lang::gen::data;
30
use std::lang::gen::types;
31
32
////////////////
33
// Registers  //
34
////////////////
35
36
export constant ZERO: gen::Reg = gen::Reg(0);   /// Hard-wired zero.
37
export constant RA:   gen::Reg = gen::Reg(1);   /// Return address.
38
export constant SP:   gen::Reg = gen::Reg(2);   /// Stack pointer.
39
export constant GP:   gen::Reg = gen::Reg(3);   /// Global pointer.
40
export constant TP:   gen::Reg = gen::Reg(4);   /// Thread pointer.
41
export constant T0:   gen::Reg = gen::Reg(5);   /// Temporary/alternate link register.
42
export constant T1:   gen::Reg = gen::Reg(6);   /// Temporary.
43
export constant T2:   gen::Reg = gen::Reg(7);   /// Temporary.
44
export constant S0:   gen::Reg = gen::Reg(8);   /// Saved register/frame pointer.
45
export constant FP:   gen::Reg = gen::Reg(8);   /// Frame pointer (alias for S0).
46
export constant S1:   gen::Reg = gen::Reg(9);   /// Saved register.
47
export constant A0:   gen::Reg = gen::Reg(10);  /// Function argument/return.
48
export constant A1:   gen::Reg = gen::Reg(11);  /// Function argument/return.
49
export constant A2:   gen::Reg = gen::Reg(12);  /// Function argument.
50
export constant A3:   gen::Reg = gen::Reg(13);  /// Function argument.
51
export constant A4:   gen::Reg = gen::Reg(14);  /// Function argument.
52
export constant A5:   gen::Reg = gen::Reg(15);  /// Function argument.
53
export constant A6:   gen::Reg = gen::Reg(16);  /// Function argument.
54
export constant A7:   gen::Reg = gen::Reg(17);  /// Function argument.
55
export constant S2:   gen::Reg = gen::Reg(18);  /// Saved register.
56
export constant S3:   gen::Reg = gen::Reg(19);  /// Saved register.
57
export constant S4:   gen::Reg = gen::Reg(20);  /// Saved register.
58
export constant S5:   gen::Reg = gen::Reg(21);  /// Saved register.
59
export constant S6:   gen::Reg = gen::Reg(22);  /// Saved register.
60
export constant S7:   gen::Reg = gen::Reg(23);  /// Saved register.
61
export constant S8:   gen::Reg = gen::Reg(24);  /// Saved register.
62
export constant S9:   gen::Reg = gen::Reg(25);  /// Saved register.
63
export constant S10:  gen::Reg = gen::Reg(26);  /// Saved register.
64
export constant S11:  gen::Reg = gen::Reg(27);  /// Saved register.
65
export constant T3:   gen::Reg = gen::Reg(28);  /// Temporary.
66
export constant T4:   gen::Reg = gen::Reg(29);  /// Temporary.
67
export constant T5:   gen::Reg = gen::Reg(30);  /// Temporary.
68
export constant T6:   gen::Reg = gen::Reg(31);  /// Temporary.
69
70
/// Create a register from a number. Panics if `n > 31`.
71
export fn reg(n: u8) -> gen::Reg {
72
    assert n < 32;
73
    return gen::Reg(n);
74
}
75
76
////////////////////////////
77
// Architecture constants //
78
////////////////////////////
79
80
/// Total number of general-purpose registers.
81
export constant NUM_REGISTERS: u8 = 32;
82
/// Number of saved registers.
83
export constant NUM_SAVED_REGISTERS: u8 = 11;
84
/// Word size in bytes (32-bit).
85
export constant WORD_SIZE: i32 = 4;
86
/// Doubleword size in bytes (64-bit).
87
export constant DWORD_SIZE: i32 = 8;
88
/// Instruction size in bytes.
89
export constant INSTR_SIZE: i32 = 4;
90
/// Stack alignment requirement in bytes.
91
export constant STACK_ALIGNMENT: i32 = 16;
92
93
/// Minimum blit size (in bytes) to use a loop instead of inline copy.
94
/// Blits below this threshold are fully unrolled as LD/SD pairs.
95
export constant BLIT_LOOP_THRESHOLD: i32 = 256;
96
97
/////////////////////////
98
// Codegen Allocation  //
99
/////////////////////////
100
101
/// Argument registers for function calls.
102
export constant ARG_REGS: [gen::Reg; 8] = [A0, A1, A2, A3, A4, A5, A6, A7];
103
104
/// Scratch register for code gen. Never allocated to user values.
105
export constant SCRATCH1: gen::Reg = T5;
106
107
/// Second scratch register for operations needing two temporaries.
108
export constant SCRATCH2: gen::Reg = T6;
109
110
/// Dedicated scratch for address offset adjustment. Never allocated to user
111
/// values and never used for operand materialization, so it can never
112
/// conflict with `rd`, `rs`, or `base` in load/store helpers.
113
export constant ADDR_SCRATCH: gen::Reg = T4;
114
115
/// Callee-saved registers that need save/restore if used.
116
export constant CALLEE_SAVED: [gen::Reg; NUM_SAVED_REGISTERS] = [S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11];
117
118
/// Maximum 12-bit signed immediate value.
119
export constant MAX_IMM: i32 = 2047;
120
121
/// Minimum 12-bit signed immediate value.
122
export constant MIN_IMM: i32 = -2048;
123
124
/// Allocatable registers for register allocation.
125
constant ALLOCATABLE_REGS: [gen::Reg; 23] = [
126
    T0, T1, T2, T3,                             // Temporaries
127
    A0, A1, A2, A3, A4, A5, A6, A7,             // Arguments
128
    S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, // Saved
129
];
130
131
/// Get target configuration for register allocation.
132
// TODO: This should be a constant variable.
133
export fn targetConfig() -> regalloc::TargetConfig {
134
    return regalloc::TargetConfig {
135
        allocatable: &ALLOCATABLE_REGS[..],
136
        argRegs: &ARG_REGS[..],
137
        calleeSaved: &CALLEE_SAVED[..],
138
        slotSize: DWORD_SIZE,
139
    };
140
}
141
142
///////////////////////
143
// Codegen Constants //
144
///////////////////////
145
146
/// Base address where read-only data is loaded.
147
export constant RO_DATA_BASE: u32 = 0x10000;
148
149
/// Base address where read-write data is loaded.
150
export constant RW_DATA_BASE: u32 = 0xFFFFF0;
151
152
/// Single-file RV64 image magic, "RAD0" as a little-endian u32.
153
export constant IMAGE_MAGIC: u32 = 0x30444152;
154
155
/// Single-file RV64 image format version.
156
export constant IMAGE_VERSION: u32 = 1;
157
158
/// Build a single-file RV64 image header.
159
export fn imageHeader(codeBytes: u32, roDataBytes: u32, rwDataBytes: u32) -> [u32; 5] {
160
    return [IMAGE_MAGIC, IMAGE_VERSION, codeBytes, roDataBytes, rwDataBytes];
161
}
162
163
/// Storage buffers passed from driver for code generation.
164
export record Storage {
165
    /// Buffer for data symbols.
166
    dataSyms: *mut [data::DataSym],
167
    /// Hash table entries for data symbol lookup.
168
    dataSymEntries: *mut [dict::Entry],
169
}
170
171
/// Result of code generation.
172
export record Program {
173
    /// Slice of emitted code.
174
    code: *[u32],
175
    /// Slice of function addresses (name + start index).
176
    funcs: *[types::FuncAddr],
177
    /// Number of read-only data bytes emitted.
178
    roDataSize: u32,
179
    /// Number of read-write data bytes emitted.
180
    rwDataSize: u32,
181
    /// Debug entries mapping PCs to source locations. Empty when debug is off.
182
    debugEntries: *[types::DebugEntry],
183
}
184
185
/// Entry jump patching requested for the generated program.
186
export union EntryPatch {
187
    /// No entry jump is emitted.
188
    None,
189
    /// Reserve code slot zero for a jump to the default function.
190
    Reserved(?*[u8]),
191
}
192
193
/// Options controlling incremental RV64 program generation.
194
export record ProgramOptions {
195
    /// Entry jump patching mode.
196
    entryPatch: EntryPatch,
197
    /// Whether to emit debug source locations.
198
    debug: bool,
199
}
200
201
/// State for incremental RV64 program generation.
202
///
203
/// The generator owns global codegen state that must survive across function
204
/// emission. Function-local scratch stays outside this record so callers can
205
/// reclaim it after each function.
206
export record Generator {
207
    /// Binary emitter and relocation state.
208
    e: emit::Emitter,
209
    /// Entry jump patching state.
210
    entryPatch: EntryPatch,
211
}
212
213
/// Begin RV64 code generation for a program's global state.
214
export fn beginProgram(
215
    options: ProgramOptions,
216
    arena: *mut alloc::Arena
217
) -> Generator {
218
    let mut e = try! emit::emitter(arena, options.debug);
219
220
    // Emit placeholder entry jump when requested.
221
    // We'll patch this at the end once we know where the function is.
222
    match options.entryPatch {
223
        case EntryPatch::Reserved(_) => {
224
            emit::emit(&mut e, encode::nop()); // Placeholder for two-instruction jump.
225
            emit::emit(&mut e, encode::nop()); //
226
        }
227
        else => {}
228
    }
229
230
    return Generator {
231
        e,
232
        entryPatch: options.entryPatch,
233
    };
234
}
235
236
/// Generate code for one IL function.
237
export fn generateFunction(
238
    generator: *mut Generator,
239
    func: *il::Fn,
240
    arena: *mut alloc::Arena
241
) {
242
    if func.isExtern {
243
        return;
244
    }
245
    let checkpoint = alloc::save(arena);
246
    let config = targetConfig();
247
    let ralloc = try! regalloc::allocate(func, &config, arena);
248
249
    isel::selectFn(&mut generator.e, &ralloc, func);
250
251
    // Reclaim unused memory after instruction selection.
252
    alloc::restore(arena, checkpoint);
253
}
254
255
/// Record an alternate name for the next function emitted.
256
export fn recordFunctionAlias(generator: *mut Generator, name: *[u8]) {
257
    emit::recordFuncOffsetAt(&mut generator.e, name, generator.e.codeLen);
258
}
259
260
/// Add the text section of an assembled program to the generator.
261
///
262
/// This function snapshots the generator's current code length as the base
263
/// index, converts each text symbol's byte offset to an instruction index, adds
264
/// that base, and records the final address for printing. Only `.export` text
265
/// symbols are exported to the emitter's function-offset table for extern call
266
/// resolution. Local labels must not escape their assembly fragment because
267
/// separate assembly inputs may reuse the same local names.
268
///
269
/// Non-text symbols are ignored here because assembled data is not appended to
270
/// the generator's text stream. The driver merges assembled data into the RO data
271
/// prefix separately and passes that data to [`finishProgram`].
272
export fn addAssembly(generator: *mut Generator, program: asm::Program) {
273
    let baseIndex = generator.e.codeLen;
274
275
    for symbol in program.symbols {
276
        if symbol.section == asm::Section::Text {
277
            let index = baseIndex + ((symbol.offset as u32) / INSTR_SIZE as u32);
278
            emit::recordFuncAt(&mut generator.e, symbol.name, index);
279
            if symbol.isExported {
280
                emit::recordFuncOffsetAt(&mut generator.e, symbol.name, index);
281
            }
282
        }
283
    }
284
    for fixup in program.externalFixups {
285
        match fixup.info {
286
            case asm::FixupInfo::Jal { rd, index } => {
287
                emit::recordJumpAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
288
            }
289
            case asm::FixupInfo::Addr { rd, index } => {
290
                emit::recordAddrLoadAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
291
            }
292
            else => panic "addAssembly: invalid external fixup",
293
        }
294
    }
295
    for word in program.text {
296
        emit::emit(&mut generator.e, word);
297
    }
298
}
299
300
/// Finish RV64 code generation and return the emitted program.
301
export fn finishProgram(
302
    generator: *mut Generator,
303
    globalData: *[il::Data],
304
    storage: Storage,
305
    roDataPrefix: *[u8],
306
    roDataBuf: *mut [u8],
307
    rwDataBuf: *mut [u8]
308
) -> Program {
309
    // Build data map after function lowering. Function-local literals can add
310
    // global data while functions are lowered, so final layout belongs here.
311
    let mut dataSymCount: u32 = 0;
312
    let roLayoutSize = data::layoutSectionAtOffset(
313
        globalData, storage.dataSyms, &mut dataSymCount, RO_DATA_BASE, roDataPrefix.len, true
314
    );
315
    data::layoutSection(globalData, storage.dataSyms, &mut dataSymCount, RW_DATA_BASE, false);
316
317
    let dataSyms = &storage.dataSyms[..dataSymCount];
318
    let dataSymMap = data::buildMap(dataSyms, storage.dataSymEntries);
319
    let codeBase = mem::alignUp(RO_DATA_BASE + roLayoutSize, DWORD_SIZE as u32);
320
321
    match generator.entryPatch {
322
        case EntryPatch::Reserved(targetName) => {
323
            let target = targetName else {
324
                panic "finishProgram: entry jump reserved without default function";
325
            };
326
            let offset = emit::branchOffsetToFunc(&generator.e, 0, target);
327
            let s = emit::splitImm(offset);
328
329
            emit::patch(&mut generator.e, 0, encode::auipc(SCRATCH1, s.hi));
330
            emit::patch(&mut generator.e, 1, encode::jalr(ZERO, SCRATCH1, s.lo));
331
        }
332
        else => {}
333
    }
334
    // Patch function calls and address loads now that all functions are emitted.
335
    emit::patchJumps(&mut generator.e);
336
    emit::patchCalls(&mut generator.e);
337
    emit::patchAddrLoads(&mut generator.e, &dataSymMap);
338
339
    // Emit data sections.
340
    assert roDataPrefix.len <= roDataBuf.len, "finishProgram: rodata prefix buffer overflow";
341
    try! mem::copy(roDataBuf, roDataPrefix);
342
343
    let roDataSize = data::emitSectionAtOffset(
344
        globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true, roDataPrefix.len
345
    );
346
    let rwDataSize = data::emitSection(
347
        globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false
348
    );
349
    return Program {
350
        code: emit::getCode(&generator.e),
351
        funcs: generator.e.funcs,
352
        roDataSize,
353
        rwDataSize,
354
        debugEntries: emit::getDebugEntries(&generator.e),
355
    };
356
}