lib/std/lang/il/published.rad 13.4 KiB raw
1
//! Immutable, region-bound IL for analysis and instruction selection.
2
//!
3
//! Publication owns instruction and analysis tables. Original node pointers
4
//! identify source nodes. Symbol names use immutable IL symbol storage.
5
6
use std::lang::alloc;
7
use std::lang::il;
8
9
/// Largest element count represented by an analysis slice.
10
constant MAX_ELEMENTS: u32 = 0xffffffff;
11
12
/// Published function tables within one analysis lifetime.
13
export record Function: 'view + Copy {
14
    /// Immutable symbol name.
15
    name: *[u8],
16
    /// Whether the function makes no calls.
17
    isLeaf: bool,
18
    /// Function parameter definitions.
19
    params: &'view [il::Param],
20
    /// Blocks in source order.
21
    blocks: &'view [Block 'view],
22
}
23
24
/// Read-only block tables and their original identity.
25
export record Block: 'view + Copy {
26
    /// Original block pointer for imported code, used only for identity comparisons.
27
    identity: ?*unsafe il::Block,
28
    /// Block parameter definitions.
29
    params: &'view [il::Param],
30
    /// Published instruction descriptors.
31
    instructions: &'view [Instruction 'view],
32
    /// Source locations in instruction order, or an empty slice without debug data.
33
    locations: &'view [il::SrcLoc],
34
    /// Loop nesting depth for cost weighting.
35
    loopDepth: u32,
36
}
37
38
/// An instruction and its immutable register-allocation inputs.
39
export opaque record Instruction: 'view + Copy {
40
    /// Original instruction pointer for imported code, used only for identity comparisons.
41
    identity: ?*unsafe il::Instr,
42
    /// Instruction operands with owned variable-length tables.
43
    code: &'view Code 'view,
44
    /// Destination register, when the instruction defines a value.
45
    destination: ?il::Reg,
46
    /// Whether the instruction has call-clobber semantics.
47
    isCall: bool,
48
    /// Source registers in operand order, including repeated uses.
49
    operands: &'view [il::Reg],
50
    /// Successor block positions in branch order.
51
    targets: &'view [u32],
52
}
53
54
/// Instruction data with region-bound call and branch tables.
55
export union Code: 'view + Copy {
56
    /// An instruction whose operands are all inline.
57
    Fixed(il::Instr),
58
    /// An unconditional branch and its argument values.
59
    Jmp {
60
        /// Destination block position.
61
        target: u32,
62
        /// Values supplied to the destination parameters.
63
        args: &'view [il::Val],
64
    },
65
    /// A comparison and its two outgoing edges.
66
    Br {
67
        /// Comparison operation.
68
        op: il::CmpOp,
69
        /// Comparison width.
70
        typ: il::Type,
71
        /// Left comparison operand.
72
        a: il::Val,
73
        /// Right comparison operand.
74
        b: il::Val,
75
        /// Destination when the comparison succeeds.
76
        thenTarget: u32,
77
        /// Values for the successful edge.
78
        thenArgs: &'view [il::Val],
79
        /// Destination when the comparison fails.
80
        elseTarget: u32,
81
        /// Values for the failed edge.
82
        elseArgs: &'view [il::Val],
83
    },
84
    /// A value-based branch table.
85
    Switch {
86
        /// Value to compare with the cases.
87
        val: il::Val,
88
        /// Destination when no case matches.
89
        defaultTarget: u32,
90
        /// Values supplied to the default destination.
91
        defaultArgs: &'view [il::Val],
92
        /// Cases in comparison order.
93
        cases: &'view [SwitchCase 'view],
94
    },
95
    /// A function call and its argument values.
96
    Call {
97
        /// Return value width.
98
        retTy: il::Type,
99
        /// Register for the return value, if used.
100
        dst: ?il::Reg,
101
        /// Function symbol or address.
102
        func: il::Val,
103
        /// Values supplied to the function.
104
        args: &'view [il::Val],
105
    },
106
}
107
108
/// One switch comparison and its outgoing edge.
109
export record SwitchCase: 'view + Copy {
110
    /// Value that selects this case.
111
    value: i64,
112
    /// Destination block position.
113
    target: u32,
114
    /// Values supplied to the destination parameters.
115
    args: &'view [il::Val],
116
}
117
118
/// Copy IL tables from valid IL into session-owned immutable storage.
119
/// All source tables and nested arrays must remain valid during this call.
120
/// Original node pointers are identity values, not published storage borrows.
121
export unsafe fn publish 'view (function: &il::Fn, storage: &Session 'view)
122
    -> Function 'view throws (alloc::AllocError)
123
{
124
    let params = try storage.copy(function.params);
125
    let blocks: &[il::Block] = function.blocks;
126
    if blocks.len == 0 {
127
        let none: [Block 'view; 0] = [];
128
        return Function 'view { name: function.name, isLeaf: function.isLeaf,
129
            params, blocks: try storage.copy(&none[..]) };
130
    }
131
    let first = try publishBlock(&blocks[0], storage);
132
    let views = try storage.fill(first, blocks.len);
133
    for i in 1..blocks.len {
134
        set views[i] = try publishBlock(&blocks[i], storage);
135
    }
136
    return Function 'view { name: function.name, isLeaf: function.isLeaf,
137
        params, blocks: &views[..] };
138
}
139
140
/// Copy block definitions and each instruction's analysis tables.
141
unsafe fn publishBlock 'view (block: &il::Block, storage: &Session 'view)
142
    -> Block 'view throws (alloc::AllocError)
143
{
144
    let params = try storage.copy(block.params);
145
    let locations = try storage.copy(block.locs);
146
    let instructions: &[il::Instr] = block.instrs;
147
    if instructions.len == 0 {
148
        let none: [Instruction 'view; 0] = [];
149
        return Block 'view { identity: block as *unsafe il::Block, params,
150
            instructions: try storage.copy(&none[..]), locations, loopDepth: block.loopDepth };
151
    }
152
    let first = try publishInstruction(&instructions[0], storage);
153
    let views = try storage.fill(first, instructions.len);
154
    for i in 1..instructions.len {
155
        set views[i] = try publishInstruction(&instructions[i], storage);
156
    }
157
    return Block 'view { identity: block as *unsafe il::Block, params,
158
        instructions: &views[..], locations, loopDepth: block.loopDepth };
159
}
160
161
/// Import an instruction and construct its checked analysis tables.
162
unsafe fn publishInstruction 'view (source: &il::Instr, storage: &Session 'view)
163
    -> Instruction 'view throws (alloc::AllocError)
164
{
165
    let code = try storage.new(try publishCode(source, storage));
166
    return try buildInstruction(code, source as *unsafe il::Instr, storage);
167
}
168
169
/// Construct instruction metadata from immutable, region-owned operands.
170
/// The source identity is retained as a value and is never dereferenced.
171
export fn buildInstruction 'view (
172
    code: &'view Code 'view,
173
    identity: ?*unsafe il::Instr,
174
    storage: &Session 'view
175
) -> Instruction 'view throws (alloc::AllocError) {
176
    let noTargets: [u32; 0] = [];
177
    let mut targets: &'view [u32] = try storage.copy(&noTargets[..]);
178
    let mut count: u64 = 0;
179
    let mut destination: ?il::Reg = nil;
180
    let mut isCall = false;
181
    match code {
182
        case Code::Fixed(fixed) => {
183
            let cursor = il::registers(fixed);
184
            assert not cursor.arguments, "buildInstruction: expected inline operands";
185
            set count = cursor.count as u64;
186
            set destination = il::instrDst(*fixed);
187
            set isCall = il::isCall(*fixed);
188
        },
189
        case Code::Call { dst, func, args, .. } => {
190
            let prefix = [*func];
191
            set count = registerCount(&prefix[..]) + registerCount(*args);
192
            set destination = *dst;
193
            set isCall = true;
194
        },
195
        case Code::Jmp { target, args } => {
196
            set count = registerCount(*args);
197
            set targets = try storage.fill(*target, 1);
198
        },
199
        case Code::Br { a, b, thenTarget, thenArgs, elseTarget, elseArgs, .. } => {
200
            let prefix = [*a, *b];
201
            set count = registerCount(&prefix[..]) + registerCount(*thenArgs) + registerCount(*elseArgs);
202
            let edges = try storage.fill(*thenTarget, 2);
203
            set edges[1] = *elseTarget;
204
            set targets = &edges[..];
205
        },
206
        case Code::Switch { val, defaultTarget, defaultArgs, cases } => {
207
            let prefix = [*val];
208
            set count = registerCount(&prefix[..]) + registerCount(*defaultArgs);
209
            if cases.len == MAX_ELEMENTS {
210
                throw alloc::AllocError::OutOfMemory;
211
            }
212
            let edges = try storage.fill(*defaultTarget, cases.len + 1);
213
            for item, i in *cases {
214
                set edges[i + 1] = item.target;
215
                set count += registerCount(item.args);
216
            }
217
            set targets = &edges[..];
218
        },
219
    }
220
    if count > MAX_ELEMENTS as u64 {
221
        throw alloc::AllocError::OutOfMemory;
222
    }
223
    let operands = try storage.fill(il::Reg { n: 0 }, count as u32);
224
    let mut written: u32 = 0;
225
    match code {
226
        case Code::Fixed(fixed) => {
227
            let cursor = il::registers(fixed);
228
            for i in 0..cursor.count {
229
                set operands[i] = cursor.fixed[i];
230
            }
231
            set written = cursor.count;
232
        },
233
        case Code::Call { func, args, .. } => {
234
            let prefix = [*func];
235
            set written = copyRegisters(&prefix[..], operands, 0);
236
            set written = copyRegisters(*args, operands, written);
237
        },
238
        case Code::Jmp { args, .. } => set written = copyRegisters(*args, operands, 0),
239
        case Code::Br { a, b, thenArgs, elseArgs, .. } => {
240
            let prefix = [*a, *b];
241
            set written = copyRegisters(&prefix[..], operands, 0);
242
            set written = copyRegisters(*thenArgs, operands, written);
243
            set written = copyRegisters(*elseArgs, operands, written);
244
        },
245
        case Code::Switch { val, defaultArgs, cases, .. } => {
246
            let prefix = [*val];
247
            set written = copyRegisters(&prefix[..], operands, 0);
248
            set written = copyRegisters(*defaultArgs, operands, written);
249
            for item in *cases {
250
                set written = copyRegisters(item.args, operands, written);
251
            }
252
        },
253
    }
254
    assert written == operands.len, "buildInstruction: operand count changed";
255
    return Instruction 'view { identity, code, destination, isCall,
256
        operands: &operands[..], targets };
257
}
258
259
/// Count source registers in one operand group, including repeated uses.
260
fn registerCount(values: &[il::Val]) -> u64 {
261
    let mut count: u64 = 0;
262
    for value in values {
263
        if let case il::Val::Reg(_) = value {
264
            set count += 1;
265
        }
266
    }
267
    return count;
268
}
269
270
/// Write source registers at an offset and return the next free position.
271
fn copyRegisters(values: &[il::Val], output: &mut [il::Reg], offset: u32) -> u32 {
272
    let mut next = offset;
273
    for value in values {
274
        if let case il::Val::Reg(reg) = value {
275
            set output[next] = reg;
276
            set next += 1;
277
        }
278
    }
279
    return next;
280
}
281
282
/// Copy the variable-length operands of an instruction into the session.
283
unsafe fn publishCode 'view (source: &il::Instr, storage: &Session 'view)
284
    -> Code 'view throws (alloc::AllocError)
285
{
286
    match *source {
287
        case il::Instr::Jmp { target, args } => {
288
            return Code 'view::Jmp { target, args: try storage.copy(args) };
289
        },
290
        case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => {
291
            return Code 'view::Br { op, typ, a, b, thenTarget,
292
                thenArgs: try storage.copy(thenArgs), elseTarget,
293
                elseArgs: try storage.copy(elseArgs) };
294
        },
295
        case il::Instr::Call { retTy, dst, func, args } => {
296
            return Code 'view::Call { retTy, dst, func, args: try storage.copy(args) };
297
        },
298
        case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => {
299
            let defaultArgs = try storage.copy(defaultArgs);
300
            let cases: &[il::SwitchCase] = cases;
301
            if cases.len == 0 {
302
                let none: [SwitchCase 'view; 0] = [];
303
                return Code 'view::Switch { val, defaultTarget, defaultArgs,
304
                    cases: try storage.copy(&none[..]) };
305
            }
306
            let first = SwitchCase 'view { value: cases[0].value, target: cases[0].target,
307
                args: try storage.copy(cases[0].args) };
308
            let views = try storage.fill(first, cases.len);
309
            for i in 1..cases.len {
310
                set views[i] = SwitchCase 'view { value: cases[i].value, target: cases[i].target,
311
                    args: try storage.copy(cases[i].args) };
312
            }
313
            return Code 'view::Switch { val, defaultTarget, defaultArgs, cases: &views[..] };
314
        },
315
        else => return Code 'view::Fixed(*source),
316
    }
317
}
318
319
/// Return the instruction data with session-owned operand tables.
320
export fn code 'view (instruction: &Instruction 'view) -> &'view Code 'view {
321
    return instruction.code;
322
}
323
324
/// Return the original instruction pointer when the instruction was imported.
325
export fn identity 'view (instruction: &Instruction 'view) -> ?*unsafe il::Instr {
326
    return instruction.identity;
327
}
328
329
/// Return an instruction's destination register.
330
export fn destination 'view (instruction: &Instruction 'view) -> ?il::Reg {
331
    return instruction.destination;
332
}
333
334
/// Check whether an instruction has call-clobber semantics.
335
export fn isCall 'view (instruction: &Instruction 'view) -> bool {
336
    return instruction.isCall;
337
}
338
339
/// Borrow successor positions in branch order, including repeated targets.
340
export fn successors 'view (instruction: &Instruction 'view) -> &'view [u32] {
341
    return instruction.targets;
342
}
343
344
/// Borrow source registers in operand order, including repeated uses.
345
export fn registers 'view (instruction: &Instruction 'view) -> &'view [il::Reg] {
346
    return instruction.operands;
347
}