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