lib/std/arch/rv64.rad 17.5 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
    return beginProgramWithEmitter(options, e);
253
}
254
255
/// Begin RV64 code generation with caller-owned emitter storage.
256
export fn beginProgramWithStorage(
257
    options: ProgramOptions,
258
    storage: emit::Storage
259
) -> Generator {
260
    let e = emit::emitterWithStorage(storage, options.debug);
261
    return beginProgramWithEmitter(options, e);
262
}
263
264
/// Initialize a program generator from an emitter.
265
fn beginProgramWithEmitter(options: ProgramOptions, input: emit::Emitter) -> Generator {
266
    let mut e = input;
267
268
    // Emit placeholder entry jump when requested.
269
    // We'll patch this at the end once we know where the function is.
270
    match options.entryPatch {
271
        case EntryPatch::Reserved(_) => {
272
            emit::emit(&mut e, encode::nop()); // Placeholder for two-instruction jump.
273
            emit::emit(&mut e, encode::nop()); //
274
        }
275
        else => {}
276
    }
277
278
    return Generator {
279
        e,
280
        entryPatch: options.entryPatch,
281
        placement: options.placement,
282
    };
283
}
284
285
/// Generate code for one IL function.
286
/// Record failures in the emitter and restore the function arena offset.
287
export unsafe fn generateFunction(
288
    generator: &mut Generator,
289
    func: &il::Fn,
290
    arena: &mut alloc::Arena
291
) {
292
    if generator.e.error <> nil or func.isExtern {
293
        return;
294
    }
295
    let checkpoint = alloc::save(arena);
296
    let config = targetConfig();
297
    use *arena as scratch in {
298
        try generateFunctionWithStorage(generator, func, &config, &scratch) catch {
299
            set generator.e.error = Error::Allocation;
300
        };
301
    }
302
303
    // Reclaim unused memory after instruction selection.
304
    alloc::restore(arena, checkpoint);
305
}
306
307
/// Allocate registers and select one function within a scratch session.
308
unsafe fn generateFunctionWithStorage 'scratch (
309
    generator: &mut Generator,
310
    func: &il::Fn,
311
    config: &regalloc::TargetConfig,
312
    storage: &Session 'scratch
313
) throws (alloc::AllocError) {
314
    let ralloc = try regalloc::allocate(func, config, storage);
315
    isel::selectFn(&mut generator.e, &ralloc, func);
316
}
317
318
/// Record an alternate name for the next function emitted.
319
export fn recordFunctionAlias(generator: &mut Generator, name: *[u8]) {
320
    let codeLen = generator.e.codeLen;
321
    emit::recordFuncOffsetAt(&mut generator.e, name, codeLen);
322
}
323
324
/// Add the text section of an assembled program to the generator.
325
///
326
/// This function snapshots the generator's current code length as the base
327
/// index, converts each text symbol's byte offset to an instruction index, adds
328
/// that base, and records the final address for printing. Only `.export` text
329
/// symbols are exported to the emitter's function-offset table for extern call
330
/// resolution. Local labels must not escape their assembly fragment because
331
/// separate assembly inputs may reuse the same local names.
332
///
333
/// Non-text symbols are ignored here because assembled data is not appended to
334
/// the generator's text stream. The driver merges assembled data into the RO data
335
/// prefix separately and passes that data to [`finishProgram`].
336
export fn addAssembly(generator: &mut Generator, program: asm::Program) {
337
    let baseIndex = generator.e.codeLen;
338
339
    for symbol in program.symbols {
340
        if symbol.section == asm::Section::Text {
341
            let index = baseIndex + ((symbol.offset as u32) / INSTR_SIZE as u32);
342
            emit::recordFuncAt(&mut generator.e, symbol.name, index);
343
            if symbol.isExported {
344
                emit::recordFuncOffsetAt(&mut generator.e, symbol.name, index);
345
            }
346
        }
347
    }
348
    for fixup in program.externalFixups {
349
        match fixup.info {
350
            case asm::FixupInfo::Jal { rd, index } => {
351
                emit::recordJumpAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
352
            }
353
            case asm::FixupInfo::Addr { rd, index } => {
354
                emit::recordAddrLoadAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
355
            }
356
            else => panic "addAssembly: invalid external fixup",
357
        }
358
    }
359
    for word in program.text {
360
        emit::emit(&mut generator.e, word);
361
    }
362
}
363
364
/// Finish RV64 code generation and return the emitted program.
365
export unsafe fn finishProgram(
366
    input: Generator,
367
    globalData: &[il::Data],
368
    storage: Storage,
369
    roDataPrefix: *[u8],
370
    roDataBuf: &mut [u8],
371
    rwDataBuf: &mut [u8]
372
) -> Program throws (Error) {
373
    return try linkProgram(input, globalData, storage, roDataPrefix, roDataBuf, rwDataBuf);
374
}
375
376
/// Lay out data, resolve relocations, and publish the completed program.
377
fn linkProgram(
378
    input: Generator,
379
    globalData: &[il::Data],
380
    storage: Storage,
381
    roDataPrefix: *[u8],
382
    roDataBuf: &mut [u8],
383
    rwDataBuf: &mut [u8]
384
) -> Program throws (Error) {
385
    let mut generator = input;
386
    try emit::check(&generator.e);
387
    let mut roBase: u64 = RO_DATA_BASE as u64;
388
    let mut rwBase: u64 = RW_DATA_BASE as u64;
389
    match generator.placement {
390
        case image::Placement::Physical { roData, rwData, .. } => {
391
            set roBase = roData;
392
            set rwBase = rwData;
393
        },
394
        else => {
395
        },
396
    }
397
    // Build data map after function lowering. Function-local literals can add
398
    // global data while functions are lowered, so final layout belongs here.
399
    let case Storage { dataSyms: symbolBuf, dataSymEntries } = storage
400
        else panic "expected code generation storage";
401
    let mut dataSymCount: u32 = 0;
402
    let roLayoutSize = try data::layoutSectionAtOffset(
403
        globalData, symbolBuf, &mut dataSymCount, roBase, roDataPrefix.len, true
404
    ) catch err {
405
        throw Error::Data(err);
406
    };
407
    let rwLayoutSize = try data::layoutSection(globalData, symbolBuf, &mut dataSymCount, rwBase, false) catch err {
408
        throw Error::Data(err);
409
    };
410
411
    let dataSyms = &symbolBuf[..dataSymCount];
412
    let dataSymMap = try data::buildMap(dataSyms, dataSymEntries) catch err {
413
        throw Error::Data(err);
414
    };
415
    if roBase > 0xffffffffffffffff - roLayoutSize as u64 - 7 {
416
        throw Error::Image(image::Error::Overflow);
417
    }
418
    let mut codeBase: u64 = (roBase + roLayoutSize as u64 + 7) & ~7;
419
    let mut entry = codeBase;
420
    match generator.placement {
421
        case image::Placement::Physical { code, entry: address, .. } => {
422
            set codeBase = code;
423
            set entry = address;
424
        },
425
        else => {
426
        },
427
    }
428
    let codeBytes = generator.e.codeLen * 4;
429
    let mut layout = image::Layout {
430
        entry,
431
        code: image::Segment { address: codeBase, initialized: codeBytes, memory: codeBytes },
432
        roData: image::Segment { address: roBase, initialized: 0, memory: roLayoutSize },
433
        rwData: image::Segment { address: rwBase, initialized: 0, memory: rwLayoutSize },
434
    };
435
    try image::validate(layout) catch err {
436
        throw Error::Image(err);
437
    };
438
439
    match generator.entryPatch {
440
        case EntryPatch::Reserved(targetName) => {
441
            let target = targetName else {
442
                throw Error::Symbol;
443
            };
444
            let offset = emit::branchOffsetToFunc(&mut generator.e, 0, target);
445
            let s = emit::splitImm(offset);
446
447
            emit::patch(&mut generator.e, 0, encode::auipc(SCRATCH1, s.hi));
448
            emit::patch(&mut generator.e, 1, encode::jalr(ZERO, SCRATCH1, s.lo));
449
        }
450
        else => {}
451
    }
452
    // Patch function calls and address loads now that all functions are emitted.
453
    emit::patchJumps(&mut generator.e);
454
    emit::patchCalls(&mut generator.e);
455
    try emit::patchAddrLoads(&mut generator.e, &dataSymMap, codeBase);
456
457
    try emit::check(&generator.e);
458
459
    // Emit data sections.
460
    if roDataPrefix.len > roDataBuf.len {
461
        throw Error::Capacity;
462
    }
463
    try! mem::copy(roDataBuf, roDataPrefix);
464
465
    let roDataSize = try data::emitSectionAtOffset(
466
        globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true, roDataPrefix.len
467
    ) catch err {
468
        throw Error::Data(err);
469
    };
470
    let rwDataSize = try data::emitSection(
471
        globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false
472
    ) catch err {
473
        throw Error::Data(err);
474
    };
475
    set layout.roData.initialized = roDataSize;
476
    set layout.rwData.initialized = rwDataSize;
477
    try image::validate(layout) catch err {
478
        throw Error::Image(err);
479
    };
480
    let case Generator { e, .. } = generator else panic "expected program generator";
481
    let case emit::Emitter {
482
        code, codeLen, funcs, funcsLen, debugEntries, debugEntriesLen, ..
483
    } = e else panic "expected program emitter";
484
    return Program {
485
        code: &code[..codeLen],
486
        funcs: &funcs[..funcsLen],
487
        roDataSize,
488
        rwDataSize,
489
        debugEntries: &debugEntries[..debugEntriesLen],
490
        layout,
491
    };
492
}