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 view = try il::published::publish(func, storage);
315
    let ralloc = try regalloc::allocate(&view, config, storage);
316
    isel::selectFn(&mut generator.e, &ralloc, &view);
317
}
318
319
/// Record an alternate name for the next function emitted.
320
export fn recordFunctionAlias(generator: &mut Generator, name: *[u8]) {
321
    let codeLen = generator.e.codeLen;
322
    emit::recordFuncOffsetAt(&mut generator.e, name, codeLen);
323
}
324
325
/// Add the text section of an assembled program to the generator.
326
///
327
/// This function snapshots the generator's current code length as the base
328
/// index, converts each text symbol's byte offset to an instruction index, adds
329
/// that base, and records the final address for printing. Only `.export` text
330
/// symbols are exported to the emitter's function-offset table for extern call
331
/// resolution. Local labels must not escape their assembly fragment because
332
/// separate assembly inputs may reuse the same local names.
333
///
334
/// Non-text symbols are ignored here because assembled data is not appended to
335
/// the generator's text stream. The driver merges assembled data into the RO data
336
/// prefix separately and passes that data to [`finishProgram`].
337
export fn addAssembly(generator: &mut Generator, program: asm::Program) {
338
    let baseIndex = generator.e.codeLen;
339
340
    for symbol in program.symbols {
341
        if symbol.section == asm::Section::Text {
342
            let index = baseIndex + ((symbol.offset as u32) / INSTR_SIZE as u32);
343
            emit::recordFuncAt(&mut generator.e, symbol.name, index);
344
            if symbol.isExported {
345
                emit::recordFuncOffsetAt(&mut generator.e, symbol.name, index);
346
            }
347
        }
348
    }
349
    for fixup in program.externalFixups {
350
        match fixup.info {
351
            case asm::FixupInfo::Jal { rd, index } => {
352
                emit::recordJumpAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
353
            }
354
            case asm::FixupInfo::Addr { rd, index } => {
355
                emit::recordAddrLoadAt(&mut generator.e, fixup.symbol, rd, baseIndex + index);
356
            }
357
            else => panic "addAssembly: invalid external fixup",
358
        }
359
    }
360
    for word in program.text {
361
        emit::emit(&mut generator.e, word);
362
    }
363
}
364
365
/// Finish RV64 code generation and return the emitted program.
366
export unsafe fn finishProgram(
367
    input: Generator,
368
    globalData: &[il::Data],
369
    storage: Storage,
370
    roDataPrefix: *[u8],
371
    roDataBuf: &mut [u8],
372
    rwDataBuf: &mut [u8]
373
) -> Program throws (Error) {
374
    return try linkProgram(input, globalData, storage, roDataPrefix, roDataBuf, rwDataBuf);
375
}
376
377
/// Lay out data, resolve relocations, and publish the completed program.
378
fn linkProgram(
379
    input: Generator,
380
    globalData: &[il::Data],
381
    storage: Storage,
382
    roDataPrefix: *[u8],
383
    roDataBuf: &mut [u8],
384
    rwDataBuf: &mut [u8]
385
) -> Program throws (Error) {
386
    let mut generator = input;
387
    try emit::check(&generator.e);
388
    let mut roBase: u64 = RO_DATA_BASE as u64;
389
    let mut rwBase: u64 = RW_DATA_BASE as u64;
390
    match generator.placement {
391
        case image::Placement::Physical { roData, rwData, .. } => {
392
            set roBase = roData;
393
            set rwBase = rwData;
394
        },
395
        else => {
396
        },
397
    }
398
    // Build data map after function lowering. Function-local literals can add
399
    // global data while functions are lowered, so final layout belongs here.
400
    let case Storage { dataSyms: symbolBuf, dataSymEntries } = storage
401
        else panic "expected code generation storage";
402
    let mut dataSymCount: u32 = 0;
403
    let roLayoutSize = try data::layoutSectionAtOffset(
404
        globalData, symbolBuf, &mut dataSymCount, roBase, roDataPrefix.len, true
405
    ) catch err {
406
        throw Error::Data(err);
407
    };
408
    let rwLayoutSize = try data::layoutSection(globalData, symbolBuf, &mut dataSymCount, rwBase, false) catch err {
409
        throw Error::Data(err);
410
    };
411
412
    let dataSyms = &symbolBuf[..dataSymCount];
413
    let dataSymMap = try data::buildMap(dataSyms, dataSymEntries) catch err {
414
        throw Error::Data(err);
415
    };
416
    if roBase > 0xffffffffffffffff - roLayoutSize as u64 - 7 {
417
        throw Error::Image(image::Error::Overflow);
418
    }
419
    let mut codeBase: u64 = (roBase + roLayoutSize as u64 + 7) & ~7;
420
    let mut entry = codeBase;
421
    match generator.placement {
422
        case image::Placement::Physical { code, entry: address, .. } => {
423
            set codeBase = code;
424
            set entry = address;
425
        },
426
        else => {
427
        },
428
    }
429
    let codeBytes = generator.e.codeLen * 4;
430
    let mut layout = image::Layout {
431
        entry,
432
        code: image::Segment { address: codeBase, initialized: codeBytes, memory: codeBytes },
433
        roData: image::Segment { address: roBase, initialized: 0, memory: roLayoutSize },
434
        rwData: image::Segment { address: rwBase, initialized: 0, memory: rwLayoutSize },
435
    };
436
    try image::validate(layout) catch err {
437
        throw Error::Image(err);
438
    };
439
440
    match generator.entryPatch {
441
        case EntryPatch::Reserved(targetName) => {
442
            let target = targetName else {
443
                throw Error::Symbol;
444
            };
445
            let offset = emit::branchOffsetToFunc(&mut generator.e, 0, target);
446
            let s = emit::splitImm(offset);
447
448
            emit::patch(&mut generator.e, 0, encode::auipc(SCRATCH1, s.hi));
449
            emit::patch(&mut generator.e, 1, encode::jalr(ZERO, SCRATCH1, s.lo));
450
        }
451
        else => {}
452
    }
453
    // Patch function calls and address loads now that all functions are emitted.
454
    emit::patchJumps(&mut generator.e);
455
    emit::patchCalls(&mut generator.e);
456
    try emit::patchAddrLoads(&mut generator.e, &dataSymMap, codeBase);
457
458
    try emit::check(&generator.e);
459
460
    // Emit data sections.
461
    if roDataPrefix.len > roDataBuf.len {
462
        throw Error::Capacity;
463
    }
464
    try! mem::copy(roDataBuf, roDataPrefix);
465
466
    let roDataSize = try data::emitSectionAtOffset(
467
        globalData, &dataSymMap, &generator.e.labels, codeBase, roDataBuf, true, roDataPrefix.len
468
    ) catch err {
469
        throw Error::Data(err);
470
    };
471
    let rwDataSize = try data::emitSection(
472
        globalData, &dataSymMap, &generator.e.labels, codeBase, rwDataBuf, false
473
    ) catch err {
474
        throw Error::Data(err);
475
    };
476
    set layout.roData.initialized = roDataSize;
477
    set layout.rwData.initialized = rwDataSize;
478
    try image::validate(layout) catch err {
479
        throw Error::Image(err);
480
    };
481
    let case Generator { e, .. } = generator else panic "expected program generator";
482
    let case emit::Emitter {
483
        code, codeLen, funcs, funcsLen, debugEntries, debugEntriesLen, ..
484
    } = e else panic "expected program emitter";
485
    return Program {
486
        code: &code[..codeLen],
487
        funcs: &funcs[..funcsLen],
488
        roDataSize,
489
        rwDataSize,
490
        debugEntries: &debugEntries[..debugEntriesLen],
491
        layout,
492
    };
493
}