compiler: Select instructions from owned regional IL

25d7de6e0dca5ab7787de21dbb32824da800579cf3178af6701b9188e21f90b1
Alexis Sellier committed ago 1 parent c69442cd
lib/std/arch/rv64.rad +1 -1
311 311
    config: &regalloc::TargetConfig,
312 312
    storage: &Session 'scratch
313 313
) throws (alloc::AllocError) {
314 314
    let view = try il::published::publish(func, storage);
315 315
    let ralloc = try regalloc::allocate(&view, config, storage);
316 -
    isel::selectFn(&mut generator.e, &ralloc, func, &view);
316 +
    isel::selectFn(&mut generator.e, &ralloc, &view);
317 317
}
318 318
319 319
/// Record an alternate name for the next function emitted.
320 320
export fn recordFunctionAlias(generator: &mut Generator, name: *[u8]) {
321 321
    let codeLen = generator.e.codeLen;
lib/std/arch/rv64/bounds.rad +51 -4
14 14
use super::isel;
15 15
16 16
/// Branch forms whose taken edge supplies block parameters.
17 17
union ParameterEdge: Copy { Jump, Then, Else, Case, Default }
18 18
19 -
/// Selection uses published parameter definitions after source tables change.
19 +
/// Selection owns parameter definitions, branch instructions, and edge arguments.
20 20
@test unsafe fn publishedParameters() throws (testing::TestError) {
21 21
    for edge in [ParameterEdge::Jump, ParameterEdge::Then, ParameterEdge::Else,
22 22
        ParameterEdge::Case, ParameterEdge::Default]
23 23
    {
24 24
        let mut params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }];
57 57
        let mut gen = generator(&mut code);
58 58
        use arena as storage in {
59 59
            let view = try! il::published::publish(&func, &storage);
60 60
            let config = super::targetConfig();
61 61
            let allocation = try! regalloc::allocate(&view, &config, &storage);
62 -
            isel::selectFn(&mut gen.e, &allocation, &func, &view);
62 +
            isel::selectFn(&mut gen.e, &allocation, &view);
63 63
            assert gen.e.error == nil;
64 64
            let expected = try! storage.copy(&gen.e.code[..gen.e.codeLen]);
65 65
            set params[0].value.n = 0xffffffff;
66 66
            set targetParams[0].value.n = 0xffffffff;
67 +
            set args[0] = il::Val::Imm(42);
68 +
            set cases[0].value = 99;
69 +
            set entry[0] = il::Instr::Unreachable;
70 +
            set target[0] = il::Instr::Ret { val: il::Val::Imm(99) };
67 71
            alloc::reset(&mut code);
68 72
            set gen = generator(&mut code);
69 -
            isel::selectFn(&mut gen.e, &allocation, &func, &view);
73 +
            isel::selectFn(&mut gen.e, &allocation, &view);
70 74
            assert gen.e.error == nil and gen.e.codeLen == expected.len;
71 75
            for word, i in expected {
72 76
                assert gen.e.code[i] == word;
73 77
            }
74 78
        }
76 80
}
77 81
78 82
/// Reusable emitter allocation storage.
79 83
static MEMORY: [u8; 16777216] = [0; 16777216];
80 84
85 +
/// Selection owns call arguments and instructions used to size the stack frame.
86 +
@test unsafe fn publishedCalls() throws (testing::TestError) {
87 +
    for indirect in [false, true] {
88 +
        let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }];
89 +
        let mut args = [il::Val::Imm(7), il::Val::Reg(il::Reg { n: 1 })];
90 +
        let callee = il::Val::Reg(il::Reg { n: 0 }) if indirect else il::Val::FnAddr("callee");
91 +
        let mut instructions = [
92 +
            il::Instr::Reserve { dst: il::Reg { n: 1 }, size: il::Val::Imm(16), alignment: 8 },
93 +
            il::Instr::Call { retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: callee,
94 +
                args: &args[..] },
95 +
            il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 2 }) },
96 +
        ];
97 +
        let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..],
98 +
            locs: &[], preds: &[], loopDepth: 0 }];
99 +
        let mut func = il::Fn { name: "p::calls", params: &params[..], returnType: il::Type::W64,
100 +
            isExtern: false, isLeaf: false, blocks: &blocks[..] };
101 +
        let mut arena = alloc::new(&mut SCRATCH[..]);
102 +
        let mut code = alloc::new(&mut MEMORY[..]);
103 +
        let mut gen = generator(&mut code);
104 +
        use arena as storage in {
105 +
            let view = try! il::published::publish(&func, &storage);
106 +
            let config = super::targetConfig();
107 +
            let allocation = try! regalloc::allocate(&view, &config, &storage);
108 +
            isel::selectFn(&mut gen.e, &allocation, &view);
109 +
            assert gen.e.error == nil;
110 +
            let expected = try! storage.copy(&gen.e.code[..gen.e.codeLen]);
111 +
            set args[0] = il::Val::Imm(99);
112 +
            set args[1] = il::Val::Imm(100);
113 +
            set func.isLeaf = true;
114 +
            for i in 0..instructions.len {
115 +
                set instructions[i] = il::Instr::Unreachable;
116 +
            }
117 +
            alloc::reset(&mut code);
118 +
            set gen = generator(&mut code);
119 +
            isel::selectFn(&mut gen.e, &allocation, &view);
120 +
            assert gen.e.error == nil and gen.e.codeLen == expected.len;
121 +
            for word, i in expected {
122 +
                assert gen.e.code[i] == word;
123 +
            }
124 +
        }
125 +
    }
126 +
}
127 +
81 128
/// Selection retains published locations and consecutive-location deduplication.
82 129
@test unsafe fn publishedLocations() throws (testing::TestError) {
83 130
    // Storage for machine instructions, fixups, and source location entries.
84 131
    static DEBUG_MEMORY: [u8; 33554432] = [0; 33554432];
85 132
    let mut locations = [il::SrcLoc { moduleId: 1, offset: 10 },
104 151
        let config = super::targetConfig();
105 152
        let allocation = try! regalloc::allocate(&view, &config, &storage);
106 153
        for i in 0..locations.len {
107 154
            set locations[i] = il::SrcLoc { moduleId: 9, offset: 99 };
108 155
        }
109 -
        isel::selectFn(&mut emitter, &allocation, &func, &view);
156 +
        isel::selectFn(&mut emitter, &allocation, &view);
110 157
        assert emitter.error == nil;
111 158
        let output: 'output = &emitter in {
112 159
            let entries = emit::getDebugEntries(output);
113 160
            assert entries.len == 2;
114 161
            assert entries[0].moduleId == 1 and entries[0].offset == 10;
lib/std/arch/rv64/isel.rad +23 -23
272 272
    isDynamic: bool,
273 273
}
274 274
275 275
/// Add a block's reserve instructions to the accumulated frame requirements.
276 276
/// Returns the total size needed for all static reserves, respecting alignment.
277 -
fn computeReserveInfo(instrs: &[il::Instr], previous: ReserveInfo) -> ?ReserveInfo {
277 +
fn computeReserveInfo 'view (instrs: &[il::published::Instruction 'view], previous: ReserveInfo) -> ?ReserveInfo {
278 278
    let mut offset = previous.size;
279 279
    let mut isDynamic = previous.isDynamic;
280 280
    for instr in instrs {
281 -
        match instr {
281 +
        let case il::published::Code::Fixed(fixed) = il::published::code(&instr) else continue;
282 +
        match fixed {
282 283
            case il::Instr::Reserve { size, alignment, .. } => {
283 284
                if let case il::Val::Imm(sz) = size {
284 285
                    if alignment == 0 or (alignment & (alignment - 1)) <> 0 or sz < 0 {
285 286
                        return nil;
286 287
                    }
297 298
        }
298 299
    }
299 300
    return ReserveInfo { size: offset, isDynamic };
300 301
}
301 302
302 -
/// Select instructions with published parameter definitions and assignments.
303 -
/// Published inputs and assignments must describe the selected instruction tables.
304 -
export unsafe fn selectFn 'scratch (
303 +
/// Select instructions from published IL and its register assignments.
304 +
export fn selectFn 'scratch (
305 305
    e: &mut emit::Emitter,
306 306
    ralloc: &regalloc::AllocResult 'scratch,
307 -
    func: &il::Fn,
308 307
    view: &il::published::Function 'scratch
309 308
) {
310 309
    if e.error <> nil {
311 310
        return;
312 311
    }
313 312
    // Reset block offsets for this function.
314 313
    labels::resetBlocks(&mut e.labels);
315 314
    // Pre-scan for constant-sized reserves to promote to fixed frame slots.
316 315
    let mut reserveInfo = ReserveInfo { size: 0, isDynamic: false };
317 -
    for block in func.blocks {
318 -
        let next = computeReserveInfo(&block.instrs[..], reserveInfo) else {
316 +
    for block in view.blocks {
317 +
        let next = computeReserveInfo(block.instructions, reserveInfo) else {
319 318
            set e.error = super::Error::Capacity;
320 319
            return;
321 320
        };
322 321
        set reserveInfo = next;
323 322
    }
324 323
    if reserveInfo.size as u64 + ralloc.spill.frameSize as u64 > 0x7fffff00 {
325 324
        set e.error = super::Error::Capacity; return;
326 325
    }
327 -
    let isLeaf = func.isLeaf;
326 +
    let isLeaf = view.isLeaf;
328 327
    // Compute frame layout from spill slots, reserve slots, and used callee-saved registers.
329 328
    let frame = emit::computeFrame(
330 329
        ralloc.spill.frameSize + reserveInfo.size,
331 330
        ralloc.usedCalleeSaved,
332 331
        view.blocks.len,
342 341
            reserveOffset: 0, pendingSpill: nil,
343 342
            nextSynthBlock: view.blocks.len + 1,
344 343
            isDynamic: frame.isDynamic,
345 344
        };
346 345
        // Record function name for printing.
347 -
        emit::recordFunc(s.e, func.name);
346 +
        emit::recordFunc(s.e, view.name);
348 347
        // Record function code offset for call patching.
349 -
        emit::recordFuncOffset(s.e, func.name);
348 +
        emit::recordFuncOffset(s.e, view.name);
350 349
        // Emit prologue.
351 350
        emit::emitPrologue(s.e, &frame);
352 351
353 352
        // Move function params from arg registers to assigned registers.
354 353
        // Cross-call params may have been assigned to callee-saved registers
371 370
        // Emit each block.
372 371
        for i in 0..view.blocks.len {
373 372
            if s.e.error <> nil {
374 373
                return;
375 374
            }
376 -
            selectBlock(&mut s, i, &func.blocks[i], &frame, view);
375 +
            selectBlock(&mut s, i, &frame, view);
377 376
        }
378 377
        // Emit epilogue.
379 378
        emit::emitEpilogue(s.e, &frame);
380 379
        // Patch local branches now that all blocks are emitted.
381 380
        emit::patchLocalBranches(s.e);
382 381
    }
383 382
}
384 383
385 384
/// Select instructions for a block.
386 -
unsafe fn selectBlock 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, block: &il::Block, frame: &emit::Frame, view: &il::published::Function 'scratch) where 'scratch: 'selection {
385 +
fn selectBlock 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, frame: &emit::Frame, view: &il::published::Function 'scratch) where 'scratch: 'selection {
387 386
    // Record block address for branch patching.
388 387
    emit::recordBlock(s.e, blockIdx);
389 388
390 389
    // Block parameters are handled at jump sites (in `Jmp`/`Br`).
391 390
    // By the time we enter the block, the arguments have already been
392 391
    // moved to the parameter registers by the predecessor's terminator.
393 392
394 393
    // Process each instruction, auto-committing any pending spill after each.
395 394
    let locations = view.blocks[blockIdx].locations;
396 395
    let hasLocs = locations.len > 0;
397 -
    for instr, i in block.instrs {
396 +
    for instr, i in view.blocks[blockIdx].instructions {
398 397
        if s.e.error <> nil {
399 398
            return;
400 399
        }
401 400
        // Record debug location before emitting machine instructions.
402 401
        if hasLocs {
403 402
            emit::recordSrcLoc(s.e, locations[i]);
404 403
        }
405 404
        set s.pendingSpill = nil;
406 -
        selectInstr(s, blockIdx, &instr, frame, view);
405 +
        let code = il::published::code(&instr);
406 +
        selectInstr(s, blockIdx, code, frame, view);
407 407
408 408
        // Flush the pending spill store, if any.
409 409
        if let p = s.pendingSpill {
410 410
            if let slot = regalloc::spill::spillSlot(&s.ralloc.spill, p.ssa) {
411 411
                emit::emitSd(s.e, p.rd, spillBase(s), spillOffset(s, slot));
414 414
        }
415 415
    }
416 416
}
417 417
418 418
/// Select instructions for a single IL instruction.
419 -
unsafe fn selectInstr 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, instr: &il::Instr, frame: &emit::Frame, view: &il::published::Function 'scratch) where 'scratch: 'selection {
419 +
fn selectInstr 'scratch 'selection (s: &mut Selector 'scratch 'selection, blockIdx: u32, instr: &il::published::Code 'scratch, frame: &emit::Frame, view: &il::published::Function 'scratch) where 'scratch: 'selection {
420 420
    match *instr {
421 -
        case il::Instr::Jmp { target, args } => {
421 +
        case il::published::Code::Jmp { target, args } => {
422 422
            selectJump(s, blockIdx, target, view.blocks[target].params, args);
423 423
        },
424 -
        case il::Instr::Br { thenTarget, thenArgs, elseTarget, elseArgs, .. } => {
424 +
        case il::published::Code::Br { thenTarget, thenArgs, elseTarget, elseArgs, .. } => {
425 425
            let params: &'scratch [il::Param] = view.blocks[thenTarget].params
426 426
                if thenArgs.len > 0 and elseArgs.len == 0
427 427
                else view.blocks[elseTarget].params if elseArgs.len > 0 and thenArgs.len == 0
428 428
                else &view.params[..0];
429 429
            selectBranch(s, blockIdx, instr, params, thenArgs, elseArgs);
430 430
        },
431 -
        case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => {
431 +
        case il::published::Code::Switch { val, defaultTarget, defaultArgs, cases } => {
432 432
            let rs1 = resolveVal(s, super::SCRATCH1, val);
433 433
            for c in cases {
434 434
                let params: &'scratch [il::Param] = view.blocks[c.target].params
435 435
                    if c.args.len > 0 else &view.params[..0];
436 436
                selectSwitchCase(s, rs1, c.value, c.target, params, c.args);
437 437
            }
438 438
            // Fall through to default.
439 439
            emitBlockArgs(s, view.blocks[defaultTarget].params, defaultArgs);
440 440
            emit::recordBranch(s.e, defaultTarget, emit::BranchKind::Jump);
441 441
        },
442 -
        case il::Instr::Call { dst, func, args, .. } => selectCall(s, &func, args, dst),
443 -
        else => selectFixedInstr(s, blockIdx, instr, frame),
442 +
        case il::published::Code::Call { dst, func, args, .. } => selectCall(s, &func, args, dst),
443 +
        case il::published::Code::Fixed(fixed) => selectFixedInstr(s, blockIdx, &fixed, frame),
444 444
    }
445 445
}
446 446
447 447
/// Compare one switch case and emit its argument moves only on a match.
448 448
fn selectSwitchCase 'scratch 'selection (
492 492
493 493
/// Select a conditional branch with parameters for its argument-bearing edge.
494 494
fn selectBranch 'scratch 'selection (
495 495
    s: &mut Selector 'scratch 'selection,
496 496
    blockIdx: u32,
497 -
    instr: &il::Instr,
497 +
    instr: &il::published::Code 'scratch,
498 498
    params: &[il::Param],
499 499
    thenArgs: &[il::Val],
500 500
    elseArgs: &[il::Val]
501 501
) where 'scratch: 'selection {
502 -
    let case il::Instr::Br { op, typ, a, b, thenTarget, elseTarget, .. } = *instr
502 +
    let case il::published::Code::Br { op, typ, a, b, thenTarget, elseTarget, .. } = *instr
503 503
        else panic "selectBranch: expected conditional branch";
504 504
    // Use zero register directly for immediate `0` operands.
505 505
    let aIsZero = isZeroImm(a);
506 506
    let bIsZero = isZeroImm(b);
507 507
lib/std/lang/il/published.rad +123 -8
1 -
//! Immutable, region-bound views of IL used by function analyses.
1 +
//! Immutable, region-bound IL for analysis and instruction selection.
2 2
//!
3 -
//! Publication owns analysis tables and retains original node pointers as
4 -
//! identities. Analysis reads do not access the source tables.
3 +
//! Publication owns instruction and analysis tables. Original node pointers
4 +
//! identify source nodes. Symbol names use immutable IL symbol storage.
5 5
6 6
use std::lang::alloc;
7 7
use std::lang::il;
8 8
9 9
/// Largest element count represented by an analysis slice.
10 10
constant MAX_ELEMENTS: u32 = 0xffffffff;
11 11
12 12
/// Published function tables within one analysis lifetime.
13 13
export record Function: 'view + Copy {
14 +
    /// Immutable symbol name.
15 +
    name: *[u8],
16 +
    /// Whether the function makes no calls.
17 +
    isLeaf: bool,
14 18
    /// Function parameter definitions.
15 19
    params: &'view [il::Param],
16 20
    /// Blocks in source order.
17 21
    blocks: &'view [Block 'view],
18 22
}
29 33
    locations: &'view [il::SrcLoc],
30 34
    /// Loop nesting depth for cost weighting.
31 35
    loopDepth: u32,
32 36
}
33 37
34 -
/// An instruction's immutable register-allocation inputs.
38 +
/// An instruction and its immutable register-allocation inputs.
35 39
export opaque record Instruction: 'view + Copy {
36 40
    /// Original instruction pointer, used only for identity comparisons.
37 41
    identity: *unsafe il::Instr,
42 +
    /// Instruction operands with owned variable-length tables.
43 +
    code: &'view Code 'view,
38 44
    /// Destination register, when the instruction defines a value.
39 45
    destination: ?il::Reg,
40 46
    /// Whether the instruction has call-clobber semantics.
41 47
    isCall: bool,
42 48
    /// Source registers in operand order, including repeated uses.
43 49
    operands: &'view [il::Reg],
44 50
    /// Successor block positions in branch order.
45 51
    targets: &'view [u32],
46 52
}
47 53
48 -
/// Copy analysis tables from valid IL into session-owned immutable storage.
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.
49 119
/// All source tables and nested arrays must remain valid during this call.
50 120
/// Original node pointers are identity values, not published storage borrows.
51 121
export unsafe fn publish 'view (function: &il::Fn, storage: &Session 'view)
52 122
    -> Function 'view throws (alloc::AllocError)
53 123
{
54 124
    let params = try storage.copy(function.params);
55 125
    let blocks: &[il::Block] = function.blocks;
56 126
    if blocks.len == 0 {
57 127
        let none: [Block 'view; 0] = [];
58 -
        return Function 'view { params, blocks: try storage.copy(&none[..]) };
128 +
        return Function 'view { name: function.name, isLeaf: function.isLeaf,
129 +
            params, blocks: try storage.copy(&none[..]) };
59 130
    }
60 131
    let first = try publishBlock(&blocks[0], storage);
61 132
    let views = try storage.fill(first, blocks.len);
62 133
    for i in 1..blocks.len {
63 134
        set views[i] = try publishBlock(&blocks[i], storage);
64 135
    }
65 -
    return Function 'view { params, blocks: &views[..] };
136 +
    return Function 'view { name: function.name, isLeaf: function.isLeaf,
137 +
        params, blocks: &views[..] };
66 138
}
67 139
68 140
/// Copy block definitions and each instruction's analysis tables.
69 141
unsafe fn publishBlock 'view (block: &il::Block, storage: &Session 'view)
70 142
    -> Block 'view throws (alloc::AllocError)
127 199
    for i in 0..count {
128 200
        let reg = il::nextReg(&mut cursor, source)
129 201
            else panic "publishInstruction: operand count changed";
130 202
        set operands[i] = reg;
131 203
    }
132 -
    return Instruction 'view { identity: source as *unsafe il::Instr,
204 +
    let code = try storage.new(try publishCode(source, storage));
205 +
    return Instruction 'view { identity: source as *unsafe il::Instr, code,
133 206
        destination: il::instrDst(*source), isCall: il::isCall(*source),
134 207
        operands: &operands[..], targets };
135 208
}
136 209
210 +
/// Copy the variable-length operands of an instruction into the session.
211 +
unsafe fn publishCode 'view (source: &il::Instr, storage: &Session 'view)
212 +
    -> Code 'view throws (alloc::AllocError)
213 +
{
214 +
    match *source {
215 +
        case il::Instr::Jmp { target, args } => {
216 +
            return Code 'view::Jmp { target, args: try storage.copy(args) };
217 +
        },
218 +
        case il::Instr::Br { op, typ, a, b, thenTarget, thenArgs, elseTarget, elseArgs } => {
219 +
            return Code 'view::Br { op, typ, a, b, thenTarget,
220 +
                thenArgs: try storage.copy(thenArgs), elseTarget,
221 +
                elseArgs: try storage.copy(elseArgs) };
222 +
        },
223 +
        case il::Instr::Call { retTy, dst, func, args } => {
224 +
            return Code 'view::Call { retTy, dst, func, args: try storage.copy(args) };
225 +
        },
226 +
        case il::Instr::Switch { val, defaultTarget, defaultArgs, cases } => {
227 +
            let defaultArgs = try storage.copy(defaultArgs);
228 +
            let cases: &[il::SwitchCase] = cases;
229 +
            if cases.len == 0 {
230 +
                let none: [SwitchCase 'view; 0] = [];
231 +
                return Code 'view::Switch { val, defaultTarget, defaultArgs,
232 +
                    cases: try storage.copy(&none[..]) };
233 +
            }
234 +
            let first = SwitchCase 'view { value: cases[0].value, target: cases[0].target,
235 +
                args: try storage.copy(cases[0].args) };
236 +
            let views = try storage.fill(first, cases.len);
237 +
            for i in 1..cases.len {
238 +
                set views[i] = SwitchCase 'view { value: cases[i].value, target: cases[i].target,
239 +
                    args: try storage.copy(cases[i].args) };
240 +
            }
241 +
            return Code 'view::Switch { val, defaultTarget, defaultArgs, cases: &views[..] };
242 +
        },
243 +
        else => return Code 'view::Fixed(*source),
244 +
    }
245 +
}
246 +
247 +
/// Return the instruction data with session-owned operand tables.
248 +
export fn code 'view (instruction: &Instruction 'view) -> &'view Code 'view {
249 +
    return instruction.code;
250 +
}
251 +
137 252
/// Return the original instruction pointer for identity comparisons.
138 253
export fn identity 'view (instruction: &Instruction 'view) -> *unsafe il::Instr {
139 254
    return instruction.identity;
140 255
}
141 256
lib/std/lang/il/publishedTests.rad +27 -6
40 40
        assert not published::isCall(&block.instructions[0]);
41 41
        let returned = published::registers(&block.instructions[1]);
42 42
        assert returned.len == 1 and returned[0].n == 9;
43 43
        assert published::destination(&block.instructions[1]) == nil;
44 44
        assert published::successors(&block.instructions[1]).len == 0;
45 +
        let case published::Code::Fixed(fixed) = published::code(&block.instructions[1])
46 +
            else panic;
47 +
        let case il::Instr::Ret { val } = fixed else panic;
48 +
        let value = val else panic;
49 +
        let case il::Val::Reg(reg) = value else panic;
50 +
        assert reg.n == 9;
45 51
    }
46 52
}
47 53
48 54
/// Empty functions require no analysis storage.
49 55
@test unsafe fn testEmptyPublication() throws (testing::TestError) {
86 92
            for reg in registers {
87 93
                assert reg.n == 7;
88 94
            }
89 95
            let targets = published::successors(instruction);
90 96
            assert targets.len == 2 and targets[0] == 0 and targets[1] == 0;
97 +
            let case published::Code::Switch { cases, defaultArgs, .. } = published::code(instruction)
98 +
                else panic;
99 +
            assert defaultArgs.len == 0 and cases.len == 1;
100 +
            assert cases[0].value == 1 and cases[0].target == 0;
101 +
            assert cases[0].args.len == 3;
102 +
            let case il::Val::Reg(reg) = cases[0].args[1] else panic;
103 +
            assert reg.n == 7;
91 104
        }
92 105
    }
93 106
}
94 107
95 108
/// Allocation failures leave no borrowed source storage in published results.
96 109
@test unsafe fn testPublicationAllocationFailure() throws (testing::TestError) {
97 -
    let args = [il::Val::Reg(il::Reg { n: 4 })];
98 -
    let locations = [il::SrcLoc { moduleId: 5, offset: 17 }];
110 +
    let mut args = [il::Val::Reg(il::Reg { n: 4 })];
111 +
    let locations = [il::SrcLoc { moduleId: 5, offset: 17 }, il::SrcLoc { moduleId: 5, offset: 18 }];
112 +
    let mut cases = [il::SwitchCase { value: 1, target: 0, args: &mut args[..] },
113 +
        il::SwitchCase { value: 2, target: 0, args: &mut args[..] }];
99 114
    let mut instructions = [il::Instr::Call { retTy: il::Type::W64, dst: nil,
100 -
        func: il::Val::FnAddr("callee"), args: &args[..] }];
115 +
        func: il::Val::FnAddr("callee"), args: &args[..] },
116 +
        il::Instr::Switch { val: il::Val::Imm(1), defaultTarget: 0,
117 +
            defaultArgs: &mut args[..], cases: &mut cases[..] }];
101 118
    let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..],
102 119
        locs: &locations[..], preds: &[], loopDepth: 0 }];
103 120
    let function = il::Fn { name: "allocation", params: &[], returnType: il::Type::W64,
104 121
        isExtern: false, isLeaf: false, blocks: &blocks[..] };
105 122
    let mut succeeded = false;
106 -
    for capacity in 0..513 {
107 -
        static DATA: [u8; 512] = [0; 512];
123 +
    for capacity in 0..2049 {
124 +
        static DATA: [u8; 2048] = [0; 2048];
108 125
        let mut arena = alloc::new(&mut DATA[..capacity]);
109 126
        let input: 'input = &function in {
110 127
            use arena as storage in {
111 128
                if let view = try? published::publish(input, &storage) {
112 129
                    let registers = published::registers(&view.blocks[0].instructions[0]);
113 130
                    assert registers.len == 1 and registers[0].n == 4;
114 -
                    assert view.blocks[0].locations.len == 1;
131 +
                    assert view.blocks[0].locations.len == 2;
115 132
                    assert view.blocks[0].locations[0].offset == 17;
133 +
                    let case published::Code::Switch { cases, defaultArgs, .. } =
134 +
                        *published::code(&view.blocks[0].instructions[1]) else panic;
135 +
                    assert cases.len == 2 and cases[1].value == 2;
136 +
                    assert cases[1].args.len == 1 and defaultArgs.len == 1;
116 137
                    set succeeded = true;
117 138
                } else {
118 139
                    assert not succeeded;
119 140
                }
120 141
            }