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