lib/std/arch/rv64/bounds.rad 33.4 KiB raw
1
//! Recoverable backend capacity and relocation checks.
2
3
use std::testing;
4
use std::io;
5
use std::lang::alloc;
6
use std::lang::il;
7
use std::lang::gen::data;
8
use std::lang::gen::labels;
9
use std::lang::gen::bitset;
10
use std::lang::gen::regalloc;
11
use std::collections::dict;
12
use super::emit;
13
use super::encode;
14
use super::isel;
15
16
/// Branch forms whose taken edge supplies block parameters.
17
union ParameterEdge: Copy { Jump, Then, Else, Case, Default }
18
19
/// Selection owns parameter definitions, branch instructions, and edge arguments.
20
@test unsafe fn publishedParameters() throws (testing::TestError) {
21
    for edge in [ParameterEdge::Jump, ParameterEdge::Then, ParameterEdge::Else,
22
        ParameterEdge::Case, ParameterEdge::Default]
23
    {
24
        let mut params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }];
25
        let mut targetParams = [il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }];
26
        let mut args = [il::Val::Reg(il::Reg { n: 0 })];
27
        let mut cases = [il::SwitchCase { value: 1, target: 1, args: &mut args[..] }];
28
        let mut entry = [il::Instr::Jmp { target: 1, args: &mut args[..] }];
29
        match edge {
30
            case ParameterEdge::Then => set entry[0] = il::Instr::Br {
31
                op: il::CmpOp::Eq, typ: il::Type::W64, a: il::Val::Imm(1), b: il::Val::Imm(1),
32
                thenTarget: 1, thenArgs: &mut args[..], elseTarget: 2, elseArgs: &mut [],
33
            },
34
            case ParameterEdge::Else => set entry[0] = il::Instr::Br {
35
                op: il::CmpOp::Eq, typ: il::Type::W64, a: il::Val::Imm(1), b: il::Val::Imm(1),
36
                thenTarget: 2, thenArgs: &mut [], elseTarget: 1, elseArgs: &mut args[..],
37
            },
38
            case ParameterEdge::Case => set entry[0] = il::Instr::Switch {
39
                val: il::Val::Imm(1), defaultTarget: 2, defaultArgs: &mut [], cases: &mut cases[..],
40
            },
41
            case ParameterEdge::Default => set entry[0] = il::Instr::Switch {
42
                val: il::Val::Imm(0), defaultTarget: 1, defaultArgs: &mut args[..], cases: &mut [],
43
            },
44
            else => {},
45
        }
46
        let mut target = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 1 }) }];
47
        let mut other = [il::Instr::Ret { val: il::Val::Imm(0) }];
48
        let blocks = [
49
            il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 },
50
            il::Block { label: "target", params: &targetParams[..], instrs: &mut target[..], locs: &[], preds: &[], loopDepth: 0 },
51
            il::Block { label: "other", params: &[], instrs: &mut other[..], locs: &[], preds: &[], loopDepth: 0 },
52
        ];
53
        let func = il::Fn { name: "p::parameters", params: &params[..], returnType: il::Type::W64,
54
            isExtern: false, isLeaf: true, blocks: &blocks[..] };
55
        let mut functionArena = alloc::new(&mut FUNCTION_STORAGE[..]);
56
        let mut scratchArena = alloc::new(&mut SCRATCH[..]);
57
        let mut code = alloc::new(&mut MEMORY[..]);
58
        let mut gen = generator(&mut code);
59
        use functionArena as functionStorage in {
60
            let view = try! il::published::publish(&func, &functionStorage);
61
            let config = super::targetConfig();
62
            use scratchArena as scratchStorage in {
63
                let allocation = try! regalloc::allocate(&view, &config, &scratchStorage);
64
                isel::selectFn(&mut gen.e, &allocation, &view);
65
                assert gen.e.error == nil;
66
                let expected = try! functionStorage.copy(&gen.e.code[..gen.e.codeLen]);
67
                set params[0].value.n = 0xffffffff;
68
                set targetParams[0].value.n = 0xffffffff;
69
                set args[0] = il::Val::Imm(42);
70
                set cases[0].value = 99;
71
                set entry[0] = il::Instr::Unreachable;
72
                set target[0] = il::Instr::Ret { val: il::Val::Imm(99) };
73
                alloc::reset(&mut code);
74
                set gen = generator(&mut code);
75
                isel::selectFn(&mut gen.e, &allocation, &view);
76
                assert gen.e.error == nil and gen.e.codeLen == expected.len;
77
                for word, i in expected {
78
                    assert gen.e.code[i] == word;
79
                }
80
            }
81
            alloc::reset(&mut scratchArena);
82
            assert view.blocks.len == 3;
83
            assert il::published::successors(&view.blocks[0].instructions[0]).len > 0;
84
        }
85
    }
86
}
87
88
/// Reusable emitter allocation storage.
89
static MEMORY: [u8; 16777216] = [0; 16777216];
90
/// Published function storage for disjoint-lifetime selection.
91
static FUNCTION_STORAGE: [u8; 4096] = [0; 4096];
92
93
/// Checked IL construction reaches instruction selection without a raw IL import.
94
@test unsafe fn constructedInstructions() throws (testing::TestError) {
95
    let mut arena = alloc::new(&mut SCRATCH[..]);
96
    let mut code = alloc::new(&mut MEMORY[..]);
97
    let mut e = try! emit::emitter(&mut code, false);
98
    use arena as storage in {
99
        try! selectConstructed(&storage, &mut e);
100
    }
101
}
102
103
/// Construct a published function that returns a constant.
104
fn constructedFunction 'view (storage: &Session 'view)
105
    -> il::published::Function 'view throws (alloc::AllocError) {
106
    let copy = try storage.new(il::published::Code 'view::Fixed(
107
        il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::Imm(7) }));
108
    let ret = try storage.new(il::published::Code 'view::Fixed(
109
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) }));
110
    let first = try il::published::buildInstruction(copy, nil, storage);
111
    let instructions = try storage.fill(first, 2);
112
    set instructions[1] = try il::published::buildInstruction(ret, nil, storage);
113
    let noParams: [il::Param; 0] = [];
114
    let params: &'view [il::Param] = try storage.copy(&noParams[..]);
115
    let noLocations: [il::SrcLoc; 0] = [];
116
    let locations = try storage.copy(&noLocations[..]);
117
    let blocks = try storage.fill(il::published::Block 'view { identity: nil, params,
118
        instructions: &instructions[..], locations, loopDepth: 0 }, 1);
119
    return il::published::Function 'view { name: "p::constructed", isLeaf: true,
120
        params, blocks: &blocks[..] };
121
}
122
123
/// Check the selected constant materialization and register move.
124
fn checkConstructedSelection 'scratch (
125
    e: &emit::Emitter, allocation: &regalloc::AllocResult 'scratch
126
) {
127
    assert e.error == nil;
128
    let assigned = allocation.assignments[0] else panic;
129
    let expected = encode::addi(super::SCRATCH1, super::ZERO, 7);
130
    let move = encode::mv(assigned, super::SCRATCH1);
131
    let output: 'output = &*e in {
132
        let mut found = false;
133
        let mut moved = *assigned == *super::SCRATCH1;
134
        for word in emit::getCode(output) {
135
            if word == expected {
136
                set found = true;
137
            }
138
            if word == move {
139
                set moved = true;
140
            }
141
        }
142
        assert found and moved;
143
    }
144
}
145
146
/// Construct, allocate, and select a function that returns a constant.
147
fn selectConstructed 'view (storage: &Session 'view, e: &mut emit::Emitter)
148
    throws (alloc::AllocError)
149
{
150
    let function = try constructedFunction(storage);
151
    let config = super::targetConfig();
152
    let allocation = try regalloc::allocate(&function, &config, storage);
153
    isel::selectFn(e, &allocation, &function);
154
    checkConstructedSelection(e, &allocation);
155
}
156
157
/// Published IL remains valid after disjoint register-allocation scratch is reclaimed.
158
@test unsafe fn disjointPublishedAndScratchSessions() throws (testing::TestError) {
159
    let mut functionArena = alloc::new(&mut FUNCTION_STORAGE[..]);
160
    let mut scratchArena = alloc::new(&mut SCRATCH[..]);
161
    let mut codeArena = alloc::new(&mut MEMORY[..]);
162
    let mut e = try! emit::emitter(&mut codeArena, false);
163
    use functionArena as functionStorage in {
164
        let function = try! constructedFunction(&functionStorage);
165
        use scratchArena as scratchStorage in {
166
            let config = super::targetConfig();
167
            let allocation = try! regalloc::allocate(&function, &config, &scratchStorage);
168
            isel::selectFn(&mut e, &allocation, &function);
169
            checkConstructedSelection(&e, &allocation);
170
        }
171
        alloc::reset(&mut scratchArena);
172
        let copyInstruction = &function.blocks[0].instructions[0];
173
        let case il::published::Code::Fixed(copyCode) =
174
            il::published::code(copyInstruction) else panic;
175
        let case il::Instr::Copy { dst, val } = copyCode else panic;
176
        assert dst.n == 0 and val == il::Val::Imm(7);
177
        let operands = il::published::registers(&function.blocks[0].instructions[1]);
178
        assert operands.len == 1 and operands[0].n == 0;
179
    }
180
}
181
182
/// Selection owns call arguments and instructions used to size the stack frame.
183
@test unsafe fn publishedCalls() throws (testing::TestError) {
184
    for indirect in [false, true] {
185
        let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }];
186
        let mut args = [il::Val::Imm(7), il::Val::Reg(il::Reg { n: 1 })];
187
        let callee = il::Val::Reg(il::Reg { n: 0 }) if indirect else il::Val::FnAddr("callee");
188
        let mut instructions = [
189
            il::Instr::Reserve { dst: il::Reg { n: 1 }, size: il::Val::Imm(16), alignment: 8 },
190
            il::Instr::Call { retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: callee,
191
                args: &args[..] },
192
            il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 2 }) },
193
        ];
194
        let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..],
195
            locs: &[], preds: &[], loopDepth: 0 }];
196
        let mut func = il::Fn { name: "p::calls", params: &params[..], returnType: il::Type::W64,
197
            isExtern: false, isLeaf: false, blocks: &blocks[..] };
198
        let mut arena = alloc::new(&mut SCRATCH[..]);
199
        let mut code = alloc::new(&mut MEMORY[..]);
200
        let mut gen = generator(&mut code);
201
        use arena as storage in {
202
            let view = try! il::published::publish(&func, &storage);
203
            let config = super::targetConfig();
204
            let allocation = try! regalloc::allocate(&view, &config, &storage);
205
            isel::selectFn(&mut gen.e, &allocation, &view);
206
            assert gen.e.error == nil;
207
            let expected = try! storage.copy(&gen.e.code[..gen.e.codeLen]);
208
            set args[0] = il::Val::Imm(99);
209
            set args[1] = il::Val::Imm(100);
210
            set func.isLeaf = true;
211
            for i in 0..instructions.len {
212
                set instructions[i] = il::Instr::Unreachable;
213
            }
214
            alloc::reset(&mut code);
215
            set gen = generator(&mut code);
216
            isel::selectFn(&mut gen.e, &allocation, &view);
217
            assert gen.e.error == nil and gen.e.codeLen == expected.len;
218
            for word, i in expected {
219
                assert gen.e.code[i] == word;
220
            }
221
        }
222
    }
223
}
224
225
/// Selection retains published locations and consecutive-location deduplication.
226
@test unsafe fn publishedLocations() throws (testing::TestError) {
227
    // Storage for machine instructions, fixups, and source location entries.
228
    static DEBUG_MEMORY: [u8; 33554432] = [0; 33554432];
229
    let mut locations = [il::SrcLoc { moduleId: 1, offset: 10 },
230
        il::SrcLoc { moduleId: 1, offset: 10 }, il::SrcLoc { moduleId: 2, offset: 20 }];
231
    let mut instructions = [
232
        il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::Imm(3) },
233
        il::Instr::Copy { dst: il::Reg { n: 1 }, val: il::Val::Reg(il::Reg { n: 0 }) },
234
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 1 }) },
235
    ];
236
    let blocks = [il::Block { label: "entry", params: &[], instrs: &mut instructions[..],
237
        locs: &locations[..], preds: &[], loopDepth: 0 }];
238
    let func = il::Fn { name: "p::locations", params: &[], returnType: il::Type::W64,
239
        isExtern: false, isLeaf: true, blocks: &blocks[..] };
240
    let mut arena = alloc::new(&mut SCRATCH[..]);
241
    let mut debugCode = alloc::new(&mut DEBUG_MEMORY[..]);
242
    let mut emitter = try emit::emitter(&mut debugCode, true) catch {
243
        io::printLn("publishedLocations: emitter allocation");
244
        throw testing::TestError::Failed;
245
    };
246
    use arena as storage in {
247
        let view = try! il::published::publish(&func, &storage);
248
        let config = super::targetConfig();
249
        let allocation = try! regalloc::allocate(&view, &config, &storage);
250
        for i in 0..locations.len {
251
            set locations[i] = il::SrcLoc { moduleId: 9, offset: 99 };
252
        }
253
        isel::selectFn(&mut emitter, &allocation, &view);
254
        assert emitter.error == nil;
255
        let output: 'output = &emitter in {
256
            let entries = emit::getDebugEntries(output);
257
            assert entries.len == 2;
258
            assert entries[0].moduleId == 1 and entries[0].offset == 10;
259
            assert entries[1].moduleId == 2 and entries[1].offset == 20;
260
            assert entries[0].pc <= entries[1].pc;
261
        }
262
    }
263
}
264
265
/// Function and liveness test storage.
266
static SCRATCH: [u8; 65536] = [0; 65536];
267
/// Code output with guard words for instruction selection checks.
268
static SELECTION_WORDS: [u32; 130] = [0; 130];
269
270
/// Dictionary storage for bounded map tests.
271
unsafe static ENTRIES: [dict::Entry; 4] = undefined;
272
273
/// Build a non-debug generator with a fixed code address.
274
unsafe fn generator(arena: &mut alloc::Arena) -> super::Generator {
275
    return try! super::beginProgram(super::ProgramOptions {
276
        entryPatch: super::EntryPatch::None, debug: false,
277
        placement: super::image::Placement::Physical {
278
            code: 0x80000000, roData: 0x80001000, rwData: 0x80002000, entry: 0x80000000,
279
        },
280
    }, arena);
281
}
282
283
/// Exhaust each emitter storage class and ensure writes stop at the first error.
284
@test unsafe fn emissionCapacity() throws (testing::TestError) {
285
    for kind in 0..8 {
286
        let mut arena = alloc::new(&mut MEMORY[..]);
287
        let mut e = try! emit::emitter(&mut arena, false);
288
        match kind {
289
            case 0 => {
290
                set e.code = &mut e.code[..0];
291
                emit::emit(&mut e, encode::nop());
292
            },
293
            case 1 => {
294
                set e.pendingBranchesLen = e.pendingBranches.len;
295
                emit::recordBranch(&mut e, 0, emit::BranchKind::Jump);
296
            },
297
            case 2 => {
298
                set e.pendingCallsLen = e.pendingCalls.len;
299
                emit::recordCall(&mut e, "p::call");
300
            },
301
            case 3 => {
302
                set e.pendingJumpsLen = e.pendingJumps.len;
303
                emit::recordJumpAt(&mut e, "p::jump", super::ZERO, 0);
304
            },
305
            case 4 => {
306
                set e.pendingAddrLoadsLen = e.pendingAddrLoads.len;
307
                emit::recordDataAddrLoad(&mut e, "p::data", super::A0);
308
            },
309
            case 5 => {
310
                set e.funcsLen = e.funcs.len;
311
                emit::recordFunc(&mut e, "p::call");
312
            },
313
            case 6 => {
314
                let entries = &mut ENTRIES[..2];
315
                set e.labels.funcs = dict::init(&mut entries[..]);
316
                emit::recordFuncOffset(&mut e, "p::first");
317
                emit::recordFuncOffset(&mut e, "p::second");
318
            },
319
            else => {
320
                emit::recordSrcLoc(&mut e, il::SrcLoc { moduleId: 0, offset: 0 });
321
            },
322
        }
323
        try testing::expect(e.error == super::Error::Capacity);
324
        let count = e.codeLen;
325
        emit::emit(&mut e, encode::ebreak());
326
        try testing::expect(e.codeLen == count);
327
        let mut rejected = false;
328
        try emit::check(&e) catch err {
329
            try testing::expect(err == super::Error::Capacity); set rejected = true;
330
        };
331
        try testing::expect(rejected);
332
    }
333
}
334
335
/// Missing labels and long jumps return errors without corrupting instructions.
336
@test unsafe fn relocationFailures() throws (testing::TestError) {
337
    let mut arena = alloc::new(&mut MEMORY[..]);
338
    let mut e = try! emit::emitter(&mut arena, false);
339
    emit::recordCall(&mut e, "missing");
340
    emit::patchCalls(&mut e);
341
    try check(e.error == super::Error::Symbol, "missing function");
342
    alloc::reset(&mut arena);
343
    set e = try! emit::emitter(&mut arena, false);
344
    emit::recordBranch(&mut e, 0, emit::BranchKind::Jump);
345
    labels::recordBlock(&mut e.labels, 0, 0x200000);
346
    emit::patchLocalBranches(&mut e);
347
    try check(e.error == super::Error::Relocation, "long branch");
348
    try check(e.code[0] == encode::nop(), "rejected branch unchanged");
349
    alloc::reset(&mut arena);
350
    set e = try! emit::emitter(&mut arena, false);
351
    let blockCount = e.labels.blockOffsets.len;
352
    emit::recordBlock(&mut e, blockCount);
353
    try check(e.error == super::Error::Capacity, "block capacity");
354
}
355
356
/// Arena setup and per-function failures restore their saved offsets for reuse.
357
@test unsafe fn arenaRecovery() throws (testing::TestError) {
358
    let small = &mut SCRATCH[..64];
359
    let mut arena = alloc::new(&mut small[..]);
360
    set arena.offset = 8;
361
    let mut failed = false;
362
    try super::beginProgram(super::ProgramOptions {
363
        entryPatch: super::EntryPatch::None, debug: false, placement: super::image::Placement::Hosted,
364
    }, &mut arena) catch err {
365
        try check(err == super::Error::Allocation, "generator allocation"); set failed = true;
366
    };
367
    try check(failed and arena.offset == 8, "generator arena restored");
368
    let mut code = alloc::new(&mut MEMORY[..]);
369
    let mut gen = generator(&mut code);
370
    let mut instructions = [
371
        il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::Imm(1) },
372
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) },
373
    ];
374
    let func = il::Fn {
375
        name: "p::one", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
376
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
377
    };
378
    super::generateFunction(&mut gen, &func, &mut arena);
379
    try check(gen.e.error == super::Error::Allocation and arena.offset == 8, "function arena restored");
380
    alloc::reset(&mut code);
381
    set gen = generator(&mut code);
382
    let mut scratch = alloc::new(&mut SCRATCH[..]);
383
    set scratch.offset = 16;
384
    super::generateFunction(&mut gen, &func, &mut scratch);
385
    try check(gen.e.error == nil and gen.e.codeLen > 0 and scratch.offset == 16, "function retry");
386
    alloc::reset(&mut code);
387
    set gen = generator(&mut code);
388
    set instructions[0] = il::Instr::Copy { dst: il::Reg { n: 8192 }, val: il::Val::Imm(1) };
389
    super::generateFunction(&mut gen, &func, &mut scratch);
390
    try check(gen.e.error == super::Error::Allocation and scratch.offset == 16, "SSA capacity");
391
}
392
393
/// Spill candidate storage fails explicitly when too many values are live.
394
@test unsafe fn registerStorage() throws (testing::TestError) {
395
    let mut arena = alloc::new(&mut SCRATCH[..]);
396
    use arena as bits in {
397
        let liveSet = try! bitset::allocate(&bits, 257);
398
        for i in 0..257 {
399
            bitset::put(liveSet, i);
400
        }
401
        let live = regalloc::liveness::LiveInfo 'bits {
402
            liveIn: &liveSet[..0], liveOut: &liveSet[..],
403
            defs: &liveSet[..0], uses: &liveSet[..0],
404
            words: bitset::wordsFor(257), blockCount: 1, maxReg: 257,
405
        };
406
        let func = il::Fn {
407
            name: "p::pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
408
            blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }],
409
        };
410
        let mut failed = false;
411
        let view = try! il::published::publish(&func, &bits);
412
        try regalloc::spill::analyze(&view, &live, 23, 11, 8, &bits) catch {
413
            set failed = true;
414
        };
415
        try testing::expect(failed);
416
    }
417
}
418
419
/// Data output and symbol maps reject insufficient or ambiguous storage.
420
@test unsafe fn dataStorage() throws (testing::TestError) {
421
    let mut arena = alloc::new(&mut MEMORY[..]);
422
    let e = try! emit::emitter(&mut arena, false);
423
    let syms = &[data::DataSym { name: "p::data", addr: 0x80002000 }];
424
    let entries = &mut ENTRIES[..2];
425
    let map = try! data::buildMap(syms, &mut entries[..]);
426
    let items = &[il::Data {
427
        name: "p::data", size: 8, alignment: 8, readOnly: false, isZeroInit: false,
428
        values: &[il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 1 }, count: 1 }],
429
    }];
430
    let mut bytes: [u8; 8] = [255; 8];
431
    let mut failed: u32 = 0;
432
    try data::emitSection(items, &map, &e.labels, 0x80000000, &mut bytes[..7], false) catch err {
433
        try testing::expect(err == data::Error::Capacity); set failed += 1;
434
    };
435
    try data::buildMap(syms, &mut entries[..1]) catch err {
436
        try testing::expect(err == data::Error::Capacity); set failed += 1;
437
    };
438
    let larger = &mut ENTRIES[..4];
439
    unsafe static duplicate: [data::DataSym; 2] = undefined;
440
    set duplicate = [syms[0], syms[0]];
441
    try data::buildMap(&duplicate[..], &mut larger[..]) catch err {
442
        try testing::expect(err == data::Error::Symbol); set failed += 1;
443
    };
444
    try testing::expect(failed == 3);
445
    let length = try data::emitSection(items, &map, &e.labels, 0x80000000, &mut bytes[..], false)
446
        catch {
447
            throw testing::TestError::Failed;
448
        };
449
    try testing::expect(length == 8 and bytes[0] == 1);
450
}
451
452
/// Inline instruction selection respects every shorter output capacity.
453
@test unsafe fn inlineSelectionCapacity() throws (testing::TestError) {
454
    let dst = il::Reg { n: 3 };
455
    let first = il::Reg { n: 0 };
456
    let second = il::Reg { n: 1 };
457
    let a = il::Val::Reg(first);
458
    let b = il::Val::Reg(second);
459
    let instructions = [
460
        il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst, a, b },
461
        il::Instr::UnOp { op: il::UnOp::Neg, typ: il::Type::W32, dst, a },
462
        il::Instr::Load { typ: il::Type::W8, dst, src: first, offset: 4096 },
463
        il::Instr::Sload { typ: il::Type::W16, dst, src: first, offset: -4096 },
464
        il::Instr::Store { typ: il::Type::W32, src: a, dst: second, offset: 4096 },
465
        il::Instr::Copy { dst, val: il::Val::Imm(0x123456789abcdef) },
466
        il::Instr::Reserve { dst, size: il::Val::Imm(32), alignment: 16 },
467
        il::Instr::Reserve { dst, size: a, alignment: 16 },
468
        il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(0) },
469
        il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(8) },
470
        il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(40) },
471
        il::Instr::Zext { typ: il::Type::W16, dst, val: a },
472
        il::Instr::Sext { typ: il::Type::W8, dst, val: a },
473
        il::Instr::Ret { val: a },
474
        il::Instr::Unreachable,
475
        il::Instr::Ecall { dst, num: a, a0: b, a1: a, a2: b, a3: a },
476
        il::Instr::DeviceRead { typ: il::Type::W64, dst, handle: a, offset: b },
477
        il::Instr::DeviceWrite { typ: il::Type::W8, handle: a, offset: b, value: a },
478
        il::Instr::Ebreak,
479
        il::Instr::MemoryFence,
480
    ];
481
    for instr in instructions {
482
        try checkSelectionCapacity(instr, nil);
483
    }
484
}
485
486
/// Direct and indirect calls check argument and output capacities.
487
@test unsafe fn callSelectionCapacity() throws (testing::TestError) {
488
    let a = il::Val::Reg(il::Reg { n: 0 });
489
    let b = il::Val::Reg(il::Reg { n: 1 });
490
    let args = [b, a, b, a, il::Val::Imm(7), a, b, a, a];
491
    for callee in [il::Val::FnAddr("p::callee"), a] {
492
        for dst in [nil as ?il::Reg, il::Reg { n: 3 }] {
493
            for count in [0 as u32, 8, 9] {
494
                let instr = il::Instr::Call {
495
                    retTy: il::Type::W64, dst, func: callee, args: &args[..count],
496
                };
497
                let expected: ?super::Error = super::Error::Capacity if count == 9 else nil;
498
                try checkSelectionCapacity(instr, expected);
499
            }
500
        }
501
    }
502
}
503
504
/// Switch cases and default edges respect every shorter output capacity.
505
@test unsafe fn switchSelectionCapacity() throws (testing::TestError) {
506
    for count in 0..3 {
507
        for argumentMask in 0..8 {
508
            try checkSwitchCapacity(count, argumentMask);
509
        }
510
    }
511
}
512
513
/// Build a switch with independently selected case and default arguments.
514
unsafe fn checkSwitchCapacity(count: u32, argumentMask: u32) throws (testing::TestError) {
515
    let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }];
516
    let firstParams = [il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }];
517
    let secondParams = [il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 }];
518
    let defaultParams = [il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 }];
519
    let firstCount: u32 = 1 if argumentMask & 1 <> 0 else 0;
520
    let secondCount: u32 = 1 if argumentMask & 2 <> 0 else 0;
521
    let defaultCount: u32 = 1 if argumentMask & 4 <> 0 else 0;
522
    let mut firstArgs = [il::Val::Imm(11)];
523
    let mut secondArgs = [il::Val::Imm(22)];
524
    let mut defaultArgs = [il::Val::Reg(il::Reg { n: 0 })];
525
    let mut cases = [
526
        il::SwitchCase { value: 0, target: 1, args: &mut firstArgs[..firstCount] },
527
        il::SwitchCase { value: 0x123456789abcdef, target: 2, args: &mut secondArgs[..secondCount] },
528
    ];
529
    let mut entry = [il::Instr::Switch {
530
        val: il::Val::Reg(il::Reg { n: 0 }), defaultTarget: 3,
531
        defaultArgs: &mut defaultArgs[..defaultCount], cases: &mut cases[..count],
532
    }];
533
    let mut first = [il::Instr::Ret {
534
        val: il::Val::Reg(il::Reg { n: 1 }) if firstCount > 0 else il::Val::Imm(11),
535
    }];
536
    let mut second = [il::Instr::Ret {
537
        val: il::Val::Reg(il::Reg { n: 2 }) if secondCount > 0 else il::Val::Imm(22),
538
    }];
539
    let mut fallback = [il::Instr::Ret {
540
        val: il::Val::Reg(il::Reg { n: 3 }) if defaultCount > 0 else il::Val::Imm(33),
541
    }];
542
    let predecessors = [0 as u32];
543
    let blocks = [
544
        il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 },
545
        il::Block {
546
            label: "first", params: &firstParams[..firstCount], instrs: &mut first[..],
547
            locs: &[], preds: &predecessors[..1 if count > 0 else 0], loopDepth: 0,
548
        },
549
        il::Block {
550
            label: "second", params: &secondParams[..secondCount], instrs: &mut second[..],
551
            locs: &[], preds: &predecessors[..1 if count > 1 else 0], loopDepth: 0,
552
        },
553
        il::Block {
554
            label: "default", params: &defaultParams[..defaultCount], instrs: &mut fallback[..],
555
            locs: &[], preds: &predecessors[..], loopDepth: 0,
556
        },
557
    ];
558
    let func = il::Fn {
559
        name: "p::switch", params: &params[..], returnType: il::Type::W64,
560
        isExtern: false, isLeaf: true, blocks: &blocks[..],
561
    };
562
    try checkFunctionCapacity(&func, nil);
563
}
564
565
/// Jumps preserve argument moves and capacity checks in both block layouts.
566
@test unsafe fn jumpSelectionCapacity() throws (testing::TestError) {
567
    for target in [1 as u32, 2] {
568
        for count in 0..3 {
569
            let params = [
570
                il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 },
571
                il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 },
572
            ];
573
            let targetParams = [
574
                il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 },
575
                il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 },
576
            ];
577
            let mut args = [il::Val::Reg(il::Reg { n: 1 }), il::Val::Reg(il::Reg { n: 0 })];
578
            let mut entry = [il::Instr::Jmp { target, args: &mut args[..count] }];
579
            let mut body = [il::Instr::Ret {
580
                val: il::Val::Reg(il::Reg { n: count + 1 }) if count > 0 else il::Val::Imm(7),
581
            }];
582
            let mut unused = [il::Instr::Ret { val: il::Val::Imm(0) }];
583
            let destination = il::Block {
584
                label: "destination", params: &targetParams[..count], instrs: &mut body[..],
585
                locs: &[], preds: &[0], loopDepth: 0,
586
            };
587
            let other = il::Block {
588
                label: "other", params: &[], instrs: &mut unused[..],
589
                locs: &[], preds: &[], loopDepth: 0,
590
            };
591
            let blocks = [
592
                il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 },
593
                destination if target == 1 else other,
594
                other if target == 1 else destination,
595
            ];
596
            let func = il::Fn {
597
                name: "p::jump", params: &params[..], returnType: il::Type::W64,
598
                isExtern: false, isLeaf: true, blocks: &blocks[..],
599
            };
600
            try checkFunctionCapacity(&func, nil);
601
        }
602
    }
603
}
604
605
/// Conditional edges preserve capacity checks for each comparison and layout.
606
@test unsafe fn branchSelectionCapacity() throws (testing::TestError) {
607
    for op in [il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] {
608
        for typ in [il::Type::W8, il::Type::W16, il::Type::W32, il::Type::W64] {
609
            for layout in 0..2 {
610
                for argumentSide in 0..3 {
611
                    try checkBranchCapacity(op, typ, layout, argumentSide);
612
                }
613
            }
614
        }
615
    }
616
}
617
618
/// Build a branch with no arguments or arguments on exactly one edge.
619
unsafe fn checkBranchCapacity(op: il::CmpOp, typ: il::Type, layout: u32, argumentSide: u32)
620
    throws (testing::TestError)
621
{
622
    let a = il::Val::Reg(il::Reg { n: 0 });
623
    let b = il::Val::Reg(il::Reg { n: 1 });
624
    let params = [
625
        il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 },
626
        il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 },
627
    ];
628
    let thenParams = [il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 }];
629
    let elseParams = [il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 }];
630
    let thenCount: u32 = 1 if argumentSide == 1 else 0;
631
    let elseCount: u32 = 1 if argumentSide == 2 else 0;
632
    let mut thenArgs = [b];
633
    let mut elseArgs = [a];
634
    let mut entry = [il::Instr::Br {
635
        op, typ, a, b: il::Val::Imm(0) if argumentSide == 1 else b,
636
        thenTarget: 1 if layout == 0 else 2, thenArgs: &mut thenArgs[..thenCount],
637
        elseTarget: 2 if layout == 0 else 1, elseArgs: &mut elseArgs[..elseCount],
638
    }];
639
    let mut thenBody = [il::Instr::Ret {
640
        val: il::Val::Reg(il::Reg { n: 2 }) if thenCount > 0 else il::Val::Imm(10),
641
    }];
642
    let mut elseBody = [il::Instr::Ret {
643
        val: il::Val::Reg(il::Reg { n: 3 }) if elseCount > 0 else il::Val::Imm(20),
644
    }];
645
    let thenBlock = il::Block {
646
        label: "then", params: &thenParams[..thenCount], instrs: &mut thenBody[..],
647
        locs: &[], preds: &[0], loopDepth: 0,
648
    };
649
    let elseBlock = il::Block {
650
        label: "else", params: &elseParams[..elseCount], instrs: &mut elseBody[..],
651
        locs: &[], preds: &[0], loopDepth: 0,
652
    };
653
    let blocks = [
654
        il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 },
655
        thenBlock if layout == 0 else elseBlock,
656
        elseBlock if layout == 0 else thenBlock,
657
    ];
658
    let func = il::Fn {
659
        name: "p::branch", params: &params[..], returnType: il::Type::W64,
660
        isExtern: false, isLeaf: true, blocks: &blocks[..],
661
    };
662
    try checkFunctionCapacity(&func, nil);
663
}
664
665
/// Verify instruction errors, exact-fit output, and shorter-buffer canaries.
666
unsafe fn checkSelectionCapacity(instr: il::Instr, expectedError: ?super::Error) throws (testing::TestError) {
667
    let mut body = [instr, il::Instr::Ret { val: il::Val::Imm(0) }];
668
    let mut count: u32 = 2;
669
    if let case il::Instr::Ret { .. } = instr {
670
        set count = 1;
671
    }
672
    let params = [
673
        il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 },
674
        il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 },
675
    ];
676
    let func = il::Fn {
677
        name: "p::inline", params: &params[..], returnType: il::Type::W64,
678
        isExtern: false, isLeaf: not il::isCall(instr),
679
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..count], locs: &[], preds: &[], loopDepth: 0 }],
680
    };
681
    try checkFunctionCapacity(&func, expectedError);
682
}
683
684
/// Verify function output capacity and failure propagation with guard words.
685
unsafe fn checkFunctionCapacity(func: &il::Fn, expectedError: ?super::Error) throws (testing::TestError) {
686
    let mut arena = alloc::new(&mut MEMORY[..]);
687
    let mut gen = generator(&mut arena);
688
    let mut scratch = alloc::new(&mut SCRATCH[..]);
689
    super::generateFunction(&mut gen, func, &mut scratch);
690
    assert gen.e.error == expectedError;
691
    if expectedError <> nil {
692
        let count = gen.e.codeLen;
693
        emit::emit(&mut gen.e, encode::nop());
694
        assert gen.e.codeLen == count;
695
        return;
696
    }
697
    let length = gen.e.codeLen;
698
    let mut expected: [u32; 128] = [0; 128];
699
    assert length > 0 and length <= expected.len;
700
    for i in 0..length {
701
        set expected[i] = gen.e.code[i];
702
    }
703
    for capacity in 0..(length + 1) {
704
        let words = &mut SELECTION_WORDS[..];
705
        for i in 0..words.len {
706
            set words[i] = 0xdeadbeef;
707
        }
708
        alloc::reset(&mut arena);
709
        set gen = generator(&mut arena);
710
        set gen.e.code = &mut words[1..capacity + 1];
711
        super::generateFunction(&mut gen, func, &mut scratch);
712
        assert gen.e.codeLen <= capacity;
713
        assert words[0] == 0xdeadbeef;
714
        for i in (capacity + 1)..words.len {
715
            assert words[i] == 0xdeadbeef;
716
        }
717
        if capacity == length {
718
            assert gen.e.error == nil;
719
            assert gen.e.codeLen == length;
720
            for i in 0..length {
721
                assert words[i + 1] == expected[i];
722
            }
723
        } else {
724
            assert gen.e.error == super::Error::Capacity;
725
        }
726
    }
727
}
728
729
/// Name the failed invariant before returning to the test runner.
730
fn check(condition: bool, name: *[u8]) throws (testing::TestError) {
731
    if not condition {
732
        io::printLn(name);
733
        throw testing::TestError::Failed;
734
    }
735
}