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