lib/std/lang/il.rad 18.0 KiB raw
1
//! Radiance Intermediate Language (RIL).
2
//!
3
//! A minimal, low-level, SSA-based intermediate language.
4
//!
5
//! > The single-assignment property simplifies reasoning about variables,
6
//! > since for every value instance its (single) defining assignment is known.
7
//! > Because every assignment creates a new value name it cannot kill
8
//! > (i.e., invalidate) expressions previously computed from other values.
9
//! > In particular, if two expressions are textually the same, they are sure
10
//! > to evaluate the same result.
11
//! >
12
//! >   -- "Single-Pass Generation of Static Single-Assignment Form for
13
//! >       Structured Languages", by Marc M. Brandis and Hanspeter Mossenbock
14
//!
15
//! # Type System
16
//!
17
//! Primitive types: `W8`, `W16`, `W32`, `W64` (word sizes) and `Ptr`.
18
//! Signedness is encoded in operations (e.g., `Sdiv` vs `Udiv`), not types.
19
//!
20
//! `Ptr` is the same physical width as `W64` on RV64 but is semantically
21
//! distinct: it marks values with pointer provenance. A verifier treats
22
//! `Ptr` and `W64` as incompatible types.
23
//!
24
//! Types on arithmetic operations indicate logical width but don't
25
//! change code generation. Arithmetic is always machine word sized,
26
//! no 8-bit or 16-bit add instruction.
27
//!
28
//! # Truncation and Narrowing
29
//!
30
//! There is no truncation instruction.
31
//! * Narrow values are stored in word-sized registers with undefined high bits.
32
//! * Truncation happens implicitly via narrow store instructions.
33
//! * To normalize a narrow value, mask explicitly via a bitwise `and`.
34
//!
35
//! # Widerning
36
//!
37
//! `Zext`/`Sext` widen values when loading from memory.
38
//!
39
//! # Aggregate Types
40
//!
41
//! The IL provides no aggregate types. Records, slices, optionals, and unions
42
//! are represented as bytes in memory with explicit layout computed during
43
//! lowering. Access is via explicit address arithmetic plus load/store/blit.
44
//!
45
//! # Lowering Strategy
46
//!
47
//! * Slices lower to a pointer+length layout in memory.
48
//! * Optionals lower to a tag+value layout in memory.
49
//! * Tagged unions lower to a tag+payload layout in memory.
50
//! * Results lower to a tag+payload layout in memory.
51
//!
52
//! Field access is expressed as address arithmetic plus load/store.
53
//!
54
//! Bounds check elimination and nullable pointer optimization happen during
55
//! this lowering phase.
56
//!
57
//! # Pointer Provenance
58
//!
59
//! A `Ptr`-typed register may only be produced by the following closed
60
//! set of operations:
61
//!
62
//! | Instruction            | Provenance                              |
63
//! |------------------------|------------------------------------------|
64
//! | `Reserve`              | Stack allocation (local provenance)      |
65
//! | `Copy(DataSym)`        | Global/static data address               |
66
//! | `Copy(FnAddr)`         | Function address (code section)          |
67
//! | `Load` with `typ: Ptr` | Derived from an existing pointer         |
68
//! | `Elem`                 | Bounds-checked element pointer           |
69
//! | `BinOp::Add` on `Ptr`  | Pointer arithmetic (derived)             |
70
//! | `Call` returning `Ptr`  | Callee-produced pointer                 |
71
//! | Block parameter         | Merges pointer values from predecessors  |
72
//! | `MakePtr`              | Explicit escape hatch (unsafe code)      |
73
//!
74
//! Any other instruction producing a register used as a memory base is
75
//! a verifier error.
76
//!
77
//! Pointer arithmetic (`BinOp::Add` with `typ: Ptr`) requires exactly
78
//! one `Ptr` operand and one integer operand. The result is `Ptr` with
79
//! the same provenance as the pointer operand.
80
//!
81
//! # Control Flow
82
//!
83
//! Uses block parameters instead of phi nodes (like MLIR/SIL). Each block
84
//! can declare parameters, and jumps/branches pass arguments to their targets.
85
86
// TODO: Labels should have their own type.
87
// TODO: Blocks should have an instruction in `Instr`.
88
89
pub mod printer;
90
pub mod verify;
91
92
use std::mem;
93
use std::lang::alloc;
94
95
/// Source location for debug info.
96
///
97
/// Associates an IL instruction with its originating source module and
98
/// byte offset.
99
pub record SrcLoc {
100
    /// Module identifier.
101
    moduleId: u16,
102
    /// Byte offset into the module's source file.
103
    offset: u32,
104
}
105
106
///////////////////////
107
// Name Formatting   //
108
///////////////////////
109
110
/// Separator for qualified symbol names.
111
pub const PATH_SEPARATOR: *[u8] = "::";
112
113
/// Format a qualified symbol name: `pkg::mod::path::name`.
114
pub fn formatQualifiedName(arena: *mut alloc::Arena, path: *[*[u8]], name: *[u8]) -> *[u8] {
115
    let mut totalLen: u32 = name.len;
116
    for segment in path {
117
        totalLen += segment.len + PATH_SEPARATOR.len;
118
    }
119
    let buf = try! alloc::allocSlice(arena, 1, 1, totalLen) as *mut [u8];
120
    let mut pos: u32 = 0;
121
122
    for segment in path {
123
        pos += try! mem::copy(&mut buf[pos..], segment);
124
        pos += try! mem::copy(&mut buf[pos..], PATH_SEPARATOR);
125
    }
126
    try! mem::copy(&mut buf[pos..], name);
127
128
    return &buf[..totalLen];
129
}
130
131
///////////
132
// Types //
133
///////////
134
135
/// IL type representation. Integer signedness is encoded in operations, not types.
136
pub union Type {
137
    /// 8-bit value.
138
    W8,
139
    /// 16-bit value.
140
    W16,
141
    /// 32-bit value.
142
    W32,
143
    /// 64-bit value.
144
    W64,
145
    /// Pointer-width value with provenance.
146
    /// Same physical width as W64 on RV64, but semantically distinct:
147
    /// a verifier treats Ptr and W64 as incompatible types.
148
    Ptr,
149
}
150
151
/// Get the size of a type in bytes.
152
pub fn typeSize(t: Type) -> u32 {
153
    match t {
154
        case Type::W8 => return 1,
155
        case Type::W16 => return 2,
156
        case Type::W32 => return 4,
157
        case Type::W64,
158
             Type::Ptr => return 8,
159
    }
160
}
161
162
/// SSA register reference.
163
pub record Reg { n: u32 }
164
165
/// Instruction value.
166
pub union Val {
167
    /// Register reference.
168
    Reg(Reg),
169
    /// Immediate integer value.
170
    Imm(i64),
171
    /// Data symbol address (globals, constants, string literals).
172
    DataSym(*[u8]),
173
    /// Function address by symbol name. Used directly in call instructions
174
    /// for direct calls, or stored first for indirect calls.
175
    FnAddr(*[u8]),
176
    /// Undefined value. Used when the value is to be discarded,
177
    /// eg. in void returns.
178
    Undef,
179
}
180
181
/// Comparison operation for compare-and-branch.
182
pub union CmpOp { Eq, Ne, Slt, Ult }
183
184
/// Binary ALU operation kind.
185
pub union BinOp {
186
    // Arithmetic.
187
    Add, Sub, Mul, Sdiv, Udiv, Srem, Urem,
188
    // Comparison.
189
    Eq, Ne, Slt, Sge, Ult, Uge,
190
    // Bitwise.
191
    And, Or, Xor, Shl, Sshr, Ushr,
192
}
193
194
/// Unary ALU operation kind.
195
pub union UnOp {
196
    Neg, Not,
197
}
198
199
//////////////////
200
// Instructions //
201
//////////////////
202
203
/// Block parameter.
204
pub record Param {
205
    /// SSA register.
206
    value: Reg,
207
    /// Parameter type.
208
    type: Type,
209
}
210
211
/// Switch case mapping a constant value to a branch target.
212
pub record SwitchCase {
213
    /// The constant value to match against.
214
    value: i64,
215
    /// The target block index.
216
    target: u32,
217
    /// Arguments to pass to the target block.
218
    args: *mut [Val],
219
}
220
221
/// IL instruction.
222
/// SSA registers are represented as `Reg`, values as `Val`.
223
pub union Instr {
224
    ///////////////////////
225
    // Memory operations //
226
    ///////////////////////
227
228
    /// Allocate space on the stack: `reserve %dst <size> <alignment>;`
229
    /// Size can be a register for dynamic stack allocation (eg. VLAs).
230
    /// The result is always `Ptr` (stack provenance).
231
    Reserve { dst: Reg, size: Val, alignment: u32 },
232
    /// Load a value from memory (zero-extending): `load <type> %dst %src <offset>;`
233
    Load { typ: Type, dst: Reg, src: Reg, offset: i32 },
234
    /// Signed load from memory (sign-extending): `sload <type> %dst %src <offset>;`
235
    Sload { typ: Type, dst: Reg, src: Reg, offset: i32 },
236
    /// Store a value to memory: `store <type> <src> %dst <offset>;`
237
    Store {
238
        /// Type being stored.
239
        typ: Type,
240
        /// Value being stored.
241
        src: Val,
242
        /// Destination address.
243
        dst: Reg,
244
        /// Byte offset from the base address.
245
        offset: i32
246
    },
247
    /// Copy memory region: `blit %dst %src <size>;`
248
    /// Size can be an immediate or a register (eg. for generics).
249
    Blit {
250
        /// Destination address.
251
        dst: Reg,
252
        /// Source address.
253
        src: Reg,
254
        /// Size to copy in bytes.
255
        size: Val
256
    },
257
    /// Copy a value into a register: `copy %dst <val>;`.
258
    /// When the source is `DataSym` or `FnAddr`, the result is `Ptr`.
259
    Copy { dst: Reg, val: Val },
260
261
    /////////////////////
262
    // ALU operations  //
263
    /////////////////////
264
265
    /// Binary ALU operation: `<op> <type> %dst <a> <b>;`
266
    BinOp { op: BinOp, typ: Type, dst: Reg, a: Val, b: Val },
267
    /// Unary ALU operation: `<op> <type> %dst <a>;`
268
    UnOp { op: UnOp, typ: Type, dst: Reg, a: Val },
269
270
    /////////////////
271
    // Conversions //
272
    /////////////////
273
274
    /// Zero-extend from narrower type to word: `zext <type> %dst <val>;`
275
    Zext {
276
        /// Source type (I8 or I16).
277
        typ: Type,
278
        /// Destination register, always I32.
279
        dst: Reg,
280
        /// Source value.
281
        val: Val,
282
    },
283
    /// Sign-extend from narrower type to word: `sext <type> %dst <val>;`
284
    Sext {
285
        /// Source type (I8 or I16).
286
        typ: Type,
287
        /// Destination register, always I32.
288
        dst: Reg,
289
        /// Source value.
290
        val: Val,
291
    },
292
    /// Construct a pointer from a word: `ptr %dst <val>;`
293
    /// The result is Ptr. Only valid in unsafe code.
294
    MakePtr { dst: Reg, val: Val },
295
296
    ////////////////////
297
    // Function calls //
298
    ////////////////////
299
300
    /// Call a function: `call <type> %dst $func(<arg>...);`
301
    Call {
302
        /// Return type.
303
        retTy: Type,
304
        /// Holds return value, for non-void function.
305
        dst: ?Reg,
306
        /// Function value, either a symbol or an address in a register.
307
        func: Val,
308
        /// Function arguments.
309
        args: *[Val]
310
    },
311
312
    /////////////////
313
    // Terminators //
314
    /////////////////
315
316
    /// Return from function: `ret <val>;` or `ret;`
317
    Ret { val: ?Val },
318
    /// Unconditional jump: `jmp @block(<arg>...);`
319
    Jmp { target: u32, args: *mut [Val] },
320
    /// Compare-and-branch: `br.<op> <type> <a> <b> @then @else;`
321
    Br { op: CmpOp, typ: Type, a: Val, b: Val, thenTarget: u32, thenArgs: *mut [Val], elseTarget: u32, elseArgs: *mut [Val] },
322
    /// Multi-way branch: `switch <val> (<n> @block) ... @default;`
323
    Switch { val: Val, defaultTarget: u32, defaultArgs: *mut [Val], cases: *mut [SwitchCase] },
324
    /// Unreachable code marker: `unreachable;`
325
    /// Indicates control flow cannot reach this point to allow for optimizations.
326
    Unreachable,
327
328
    /////////////////
329
    // Intrinsics  //
330
    /////////////////
331
332
    /// Environment call: `ecall %dst <num> <a0> <a1> <a2> <a3>;`
333
    Ecall { dst: Reg, num: Val, a0: Val, a1: Val, a2: Val, a3: Val },
334
    /// Environment break: `ebreak;`.
335
    /// Triggers a breakpoint exception for debugging.
336
    Ebreak,
337
338
    //////////////////////
339
    // Checked memory   //
340
    //////////////////////
341
342
    /// Bounds-checked element pointer: `elem %dst %base <idx> <len> <stride>;`
343
    /// Computes `base + idx * stride` after asserting `idx < len`.
344
    /// Traps if the index is out of bounds. Result is always `Ptr`.
345
    /// The verifier can check this instruction locally without CFG analysis.
346
    Elem {
347
        /// Destination register (element pointer).
348
        dst: Reg,
349
        /// Base pointer (array/slice data).
350
        base: Reg,
351
        /// Index value.
352
        idx: Val,
353
        /// Length bound (element count).
354
        len: Val,
355
        /// Element stride in bytes.
356
        stride: u32,
357
    },
358
}
359
360
//////////////////////////
361
// Blocks and Functions //
362
//////////////////////////
363
364
/// A basic block in a function.
365
///
366
/// Basic blocks are instruction sequences with a single entry point and no branch
367
/// instruction, except possibly at the end of the sequence, where a terminator
368
/// may be found, ie. an instruction that terminates the sequence by jumping
369
/// to another sequence.
370
pub record Block {
371
    /// Block label.
372
    label: *[u8],
373
    /// Block parameters.
374
    params: *[Param],
375
    /// Instructions in the block. The last instruction must be a terminator.
376
    instrs: *mut [Instr],
377
    /// Source locations for debugging and error reporting.
378
    /// One entry per instruction when debug info is enabled, empty otherwise.
379
    locs: *[SrcLoc],
380
    /// Predecessor block indices. Used for control flow analysis.
381
    preds: *[u32],
382
    /// Loop nesting depth.
383
    /// Used for spill cost weighting in register allocation.
384
    loopDepth: u32,
385
}
386
387
/// An IL function.
388
pub record Fn {
389
    /// Qualified function name (e.g. `$mod$path$func`).
390
    name: *[u8],
391
    /// Function parameters.
392
    params: *[Param],
393
    /// Return type.
394
    returnType: Type,
395
    /// Whether the function is extern (no body).
396
    isExtern: bool,
397
    /// Whether the function is a leaf (contains no call or ecall instructions).
398
    isLeaf: bool,
399
    /// Basic blocks. Empty for extern functions.
400
    blocks: *[Block],
401
}
402
403
/////////////
404
// Program //
405
/////////////
406
407
/// Data initializer item.
408
pub union DataItem {
409
    /// Typed value: `w32 42;` or `w8 255;`
410
    Val { typ: Type, val: i64 },
411
    /// Symbol reference: `$symbol`
412
    Sym(*[u8]),
413
    /// Function reference: `$fnName`
414
    Fn(*[u8]),
415
    /// String literal bytes: `"hello"` (no auto null-terminator)
416
    Str(*[u8]),
417
    /// Undefined value. Used for padding and `void` returns.
418
    Undef,
419
}
420
421
/// Data initializer value with optional repeat count.
422
pub record DataValue {
423
    /// The item contained in the value.
424
    item: DataItem,
425
    /// The number of times the item should be repeated.
426
    count: u32,
427
}
428
429
/// Global data definition.
430
pub record Data {
431
    /// Data name.
432
    name: *[u8],
433
    /// Size in bytes.
434
    size: u32,
435
    /// Alignment requirement.
436
    alignment: u32,
437
    /// Whether this is read-only data.
438
    /// Typically, `const` declaration are read-only, while `static`
439
    /// declarations are not.
440
    readOnly: bool,
441
    /// Whether the data is entirely undefined.
442
    /// Undefined data doesn't need to be written since memory is zero-initialized.
443
    isUndefined: bool,
444
    /// Initializer values.
445
    values: *[DataValue],
446
}
447
448
/// An IL program (compilation unit).
449
pub record Program {
450
    /// Global data.
451
    data: *[Data],
452
    /// Functions.
453
    fns: *[*Fn],
454
    /// Index of entry point function, if any.
455
    defaultFnIdx: ?u32,
456
}
457
458
///////////////////////
459
// Utility Functions //
460
///////////////////////
461
462
/// Get the destination register of an instruction, if any.
463
pub fn instrDst(instr: Instr) -> ?Reg {
464
    match instr {
465
        case Instr::Reserve { dst, .. } => return dst,
466
        case Instr::Load { dst, .. } => return dst,
467
        case Instr::Sload { dst, .. } => return dst,
468
        case Instr::Copy { dst, .. } => return dst,
469
        case Instr::BinOp { dst, .. } => return dst,
470
        case Instr::UnOp { dst, .. } => return dst,
471
        case Instr::Zext { dst, .. } => return dst,
472
        case Instr::Sext { dst, .. } => return dst,
473
        case Instr::MakePtr { dst, .. } => return dst,
474
        case Instr::Call { dst, .. } => return dst,
475
        case Instr::Ecall { dst, .. } => return dst,
476
        case Instr::Elem { dst, .. } => return dst,
477
        else => return nil,
478
    }
479
}
480
481
/// Check if an instruction is a function call.
482
pub fn isCall(instr: Instr) -> bool {
483
    match instr {
484
        case Instr::Call { .. },
485
             Instr::Ecall { .. } => return true,
486
        else => return false,
487
    }
488
}
489
490
/// Call a function for each register used by an instruction.
491
/// This is called by the register allocator to analyze register usage.
492
pub fn forEachReg(instr: Instr, f: fn(Reg, *mut opaque), ctx: *mut opaque) {
493
    match instr {
494
        case Instr::Reserve { size, .. } =>
495
            withReg(size, f, ctx),
496
        case Instr::Load { src, .. } => f(src, ctx),
497
        case Instr::Sload { src, .. } => f(src, ctx),
498
        case Instr::Store { src, dst, .. } => {
499
            withReg(src, f, ctx);
500
            f(dst, ctx);
501
        },
502
        case Instr::Blit { dst, src, size } => {
503
            f(dst, ctx);
504
            f(src, ctx);
505
            withReg(size, f, ctx);
506
        },
507
        case Instr::Copy { val, .. } =>
508
            withReg(val, f, ctx),
509
        case Instr::BinOp { a, b, .. } => {
510
            withReg(a, f, ctx);
511
            withReg(b, f, ctx);
512
        },
513
        case Instr::UnOp { a, .. } =>
514
            withReg(a, f, ctx),
515
        case Instr::Zext { val, .. } =>
516
            withReg(val, f, ctx),
517
        case Instr::Sext { val, .. } =>
518
            withReg(val, f, ctx),
519
        case Instr::MakePtr { val, .. } =>
520
            withReg(val, f, ctx),
521
        case Instr::Call { func, args, .. } => {
522
            withReg(func, f, ctx);
523
            for arg in args {
524
                withReg(arg, f, ctx);
525
            }
526
        },
527
        case Instr::Ret { val } => {
528
            if let v = val {
529
                withReg(v, f, ctx);
530
            }
531
        },
532
        case Instr::Jmp { args, .. } => {
533
            for arg in args {
534
                withReg(arg, f, ctx);
535
            }
536
        },
537
        case Instr::Br { a, b, thenArgs, elseArgs, .. } => {
538
            withReg(a, f, ctx);
539
            withReg(b, f, ctx);
540
            for arg in thenArgs {
541
                withReg(arg, f, ctx);
542
            }
543
            for arg in elseArgs {
544
                withReg(arg, f, ctx);
545
            }
546
        },
547
        case Instr::Switch { val, defaultArgs, cases, .. } => {
548
            withReg(val, f, ctx);
549
            for arg in defaultArgs {
550
                withReg(arg, f, ctx);
551
            }
552
            for c in cases {
553
                for arg in c.args {
554
                    withReg(arg, f, ctx);
555
                }
556
            }
557
        },
558
        case Instr::Ecall { num, a0, a1, a2, a3, .. } => {
559
            withReg(num, f, ctx);
560
            withReg(a0, f, ctx);
561
            withReg(a1, f, ctx);
562
            withReg(a2, f, ctx);
563
            withReg(a3, f, ctx);
564
        },
565
        case Instr::Unreachable,
566
             Instr::Ebreak => {},
567
        case Instr::Elem { base, idx, len, .. } => {
568
            f(base, ctx);
569
            withReg(idx, f, ctx);
570
            withReg(len, f, ctx);
571
        },
572
    }
573
}
574
575
/// Call callback if value is a register.
576
fn withReg(val: Val, callback: fn(Reg, *mut opaque), ctx: *mut opaque) {
577
    if let case Val::Reg(r) = val {
578
        callback(r, ctx);
579
    }
580
}