lib/std/lang/il/binary/decodeTests.rad 32.7 KiB raw
1
//! Package reconstruction, malformed input, and arena rollback tests.
2
3
use std::testing;
4
use std::lang::alloc;
5
use std::lang::il;
6
use std::lang::il::binary;
7
use std::lang::il::binary::reader;
8
use std::lang::il::binary::program;
9
use std::lang::il::binary::collect;
10
11
/// Decode arena backing storage. Tests reset it before each use.
12
static MEMORY: [u8; 2048] = [0; 2048];
13
14
/// Structural limits for small test programs.
15
constant LIMITS: binary::Limits = binary::Limits { registers: 16, blocks: 4 };
16
17
/// Check exact-fit package encoding and every shorter output window.
18
unsafe fn encodingCapacity(package: &binary::Package, expected: &[u8]) throws (testing::TestError) {
19
    for capacity in 0..expected.len + 1 {
20
        let mut buffer: [u8; 2050] = [0xa5; 2050];
21
        let mut failed = false;
22
        let count = try program::encode(&mut buffer[1..capacity + 1], package) catch err {
23
            assert err == binary::Error::Capacity;
24
            set failed = true;
25
            0
26
        };
27
        assert failed == (capacity < expected.len);
28
        assert buffer[0] == 0xa5;
29
        for i in capacity + 1..buffer.len { assert buffer[i] == 0xa5; }
30
        if not failed {
31
            assert count == expected.len;
32
            try testing::expectBytesEq(&buffer[1..count + 1], expected);
33
        }
34
    }
35
}
36
37
/// Check a package round trip and failure at every truncated byte length.
38
unsafe fn roundTrip(package: &binary::Package) throws (testing::TestError) {
39
    let mut buffer: [u8; 2048] = [0; 2048];
40
    let length = try program::encode(&mut buffer[..], package) catch {
41
        throw testing::TestError::Failed;
42
    };
43
    try encodingCapacity(package, &buffer[..length]);
44
    let memory = &mut MEMORY[..2048];
45
    let mut arena = alloc::new(&mut memory[..]);
46
    set arena.offset = 8;
47
    let decoded = try program::decode(&buffer[..length], &mut arena, LIMITS) catch {
48
        throw testing::TestError::Failed;
49
    };
50
    try testing::expectBytesEq(decoded.name, package.name);
51
    try testing::expect(decoded.dependencies.len == package.dependencies.len);
52
    try testing::expect(decoded.exports.len == package.exports.len);
53
    try testing::expect(decoded.program.fns.len == package.program.fns.len);
54
    for original, i in package.program.fns {
55
        let restored = decoded.program.fns[i];
56
        assert restored.params.len == original.params.len;
57
        for param, j in original.params {
58
            assert restored.params[j].value.n == param.value.n;
59
            assert restored.params[j].type == param.type;
60
        }
61
        assert restored.blocks.len == original.blocks.len;
62
        let mut isLeaf = true;
63
        for block, j in original.blocks {
64
            for instr in block.instrs {
65
                if il::isCall(instr) { set isLeaf = false; }
66
            }
67
            let rebuilt = &restored.blocks[j];
68
            try testing::expectBytesEq(rebuilt.label, block.label);
69
            assert rebuilt.loopDepth == block.loopDepth;
70
            assert rebuilt.params.len == block.params.len;
71
            for param, k in block.params {
72
                assert rebuilt.params[k].value.n == param.value.n;
73
                assert rebuilt.params[k].type == param.type;
74
            }
75
            assert rebuilt.preds.len == block.preds.len;
76
            for pred, k in block.preds { assert rebuilt.preds[k] == pred; }
77
        }
78
        assert restored.isLeaf == isLeaf;
79
    }
80
    let mut encoded: [u8; 2048] = [0; 2048];
81
    let repeated = try program::encode(&mut encoded[..], &decoded) catch {
82
        throw testing::TestError::Failed;
83
    };
84
    try testing::expectBytesEq(&buffer[..length], &encoded[..repeated]);
85
    // Decoded names and string initializers retain arena ownership.
86
    for i in 0..length {
87
        set buffer[i] = 0;
88
    }
89
    let copied = try program::encode(&mut buffer[..], &decoded) catch {
90
        throw testing::TestError::Failed;
91
    };
92
    try testing::expectBytesEq(&buffer[..copied], &encoded[..repeated]);
93
    for end in 0..length {
94
        set arena.offset = 8;
95
        let mut failed = false;
96
        try program::decode(&buffer[..end], &mut arena, LIMITS) catch err {
97
            try testing::expect(err == binary::Error::Truncated);
98
            set failed = true;
99
        };
100
        try testing::expect(failed);
101
        try testing::expect(arena.offset == 8);
102
    }
103
    set arena.offset = 8;
104
    let namesTable: [*[u8]; 0] = [];
105
    let source: 'input = &buffer[..0], names = &namesTable[..] in {
106
        let mut input = reader::new(source, &mut arena, names);
107
        let mut exhausted = false;
108
        try reader::storage(&mut input, 0xffffffff, 8, 0xffffffff) catch err {
109
            try testing::expect(err == binary::Error::Storage);
110
            set exhausted = true;
111
        };
112
        try testing::expect(exhausted);
113
        try testing::expect(arena.offset == 8);
114
    }
115
    // Exercise every smaller arena extent, including alignment boundaries.
116
    let mut usedArena = alloc::new(&mut memory[..]);
117
    let _ = try program::decode(&buffer[..length], &mut usedArena, LIMITS) catch {
118
        throw testing::TestError::Failed;
119
    };
120
    let required = usedArena.offset;
121
    for capacity in 0..required {
122
        let mut short = alloc::new(&mut MEMORY[..capacity]);
123
        let mut failed = false;
124
        try program::decode(&buffer[..length], &mut short, LIMITS) catch err {
125
            try testing::expect(err == binary::Error::Storage);
126
            set failed = true;
127
        };
128
        try testing::expect(failed);
129
        try testing::expect(short.offset == 0);
130
    }
131
}
132
133
/// Check an empty program with a package identity.
134
@test unsafe fn emptyPackage() throws (testing::TestError) {
135
    let package = binary::Package {
136
        symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
137
        program: il::Program { data: &[], fns: &[] },
138
    };
139
    try roundTrip(&package);
140
    let mut buffer: [u8; 64] = [0; 64];
141
    let length = try program::encode(&mut buffer[..], &package) catch {
142
        throw testing::TestError::Failed;
143
    };
144
    try testing::expectBytesEq(&buffer[..length], &[
145
        82, 73, 76, 0, 1, 0, 0, 0, 1, 0, 0, 0,
146
        1, 0, 0, 0, 112, 0, 0, 0, 0,
147
        0, 0, 0, 0, 0, 0, 0, 0, 0,
148
        0, 0, 0, 0, 0, 0, 0, 0,
149
    ]);
150
}
151
152
/// Check functions, dependencies, exports, initializers, and block metadata.
153
@test unsafe fn fullPackage() throws (testing::TestError) {
154
    let params: *unsafe [il::Param] = &[
155
        il::Param { value: il::Reg { n: 1 }, type: il::Type::W8 },
156
        il::Param { value: il::Reg { n: 4 }, type: il::Type::W16 },
157
        il::Param { value: il::Reg { n: 5 }, type: il::Type::W32 },
158
        il::Param { value: il::Reg { n: 6 }, type: il::Type::W64 },
159
    ];
160
    let mut instrs = [
161
        il::Instr::Call {
162
            retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("dep::fn"),
163
            args: &[il::Val::Reg(il::Reg { n: 1 })],
164
        },
165
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 2 }) },
166
    ];
167
    let func = il::Fn {
168
        name: "p::main", params, returnType: il::Type::W64, isExtern: false, isLeaf: false,
169
        blocks: &[il::Block {
170
            label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[0], loopDepth: 2,
171
        }, il::Block {
172
            label: "empty", params: &[il::Param { value: il::Reg { n: 3 }, type: il::Type::W32 }],
173
            instrs: &mut [], locs: &[], preds: &[0, 1], loopDepth: 0,
174
        }],
175
    };
176
    let external = il::Fn {
177
        name: "dep::fn", params, returnType: il::Type::W64, isExtern: true, isLeaf: true, blocks: &[],
178
    };
179
    let data = il::Data {
180
        name: "p::data", size: 64, alignment: 8, readOnly: false, isZeroInit: false,
181
        values: &[
182
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W8, val: -1 }, count: 2 },
183
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, val: -2 }, count: 1 },
184
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W32, val: -3 }, count: 1 },
185
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: -4 }, count: 1 },
186
            il::DataValue { item: il::DataItem::Sym("p::data"), count: 1 },
187
            il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 },
188
            il::DataValue { item: il::DataItem::Str("bytes"), count: 2 },
189
            il::DataValue { item: il::DataItem::Str(""), count: 0 },
190
            il::DataValue { item: il::DataItem::Undef, count: 1 },
191
        ],
192
    };
193
    let zero = il::Data {
194
        name: "p::zero", size: 4096, alignment: 4096, readOnly: false, isZeroInit: true, values: &[],
195
    };
196
    let package = binary::Package {
197
        symbols: &["p", "dep", "p::main", "dep::fn", "p::data", "p::zero"], name: "p",
198
        dependencies: &["dep"],
199
        exports: &[
200
            binary::Export { name: "p::main", kind: binary::ExportKind::Function },
201
            binary::Export { name: "p::data", kind: binary::ExportKind::Data },
202
        ],
203
        entry: "p::main", program: il::Program { data: retainData(&[data, zero]), fns: &[&func, &external] },
204
    };
205
    try roundTrip(&package);
206
}
207
208
/// Reject an invalid encoded instruction without a panic.
209
unsafe fn badInstruction(bytes: *[u8], expected: binary::Error) throws (testing::TestError) {
210
    let memory = &mut MEMORY[..64];
211
    let mut arena = alloc::new(&mut memory[..]);
212
    let namesTable: [*[u8]; 1] = ["p"];
213
    let source: 'input = &bytes[..], names = &namesTable[..] in {
214
        let mut input = reader::new(source, &mut arena, names);
215
        set input.registers = 2;
216
        set input.blocks = 1;
217
        let mut failed = false;
218
        try reader::instr(&mut input) catch err {
219
            try testing::expect(err == expected);
220
            set failed = true;
221
        };
222
        try testing::expect(failed);
223
    }
224
}
225
226
/// Check instruction tags, operand tags, types, optional flags, and indices.
227
@test unsafe fn malformedInstructions() throws (testing::TestError) {
228
    try badInstruction(&[255], binary::Error::Invalid);
229
    try badInstruction(&[5, 0, 0, 0, 0, 255], binary::Error::Invalid);
230
    try badInstruction(&[1, 3], binary::Error::Invalid);
231
    try badInstruction(&[6, 255], binary::Error::Invalid);
232
    try badInstruction(&[7, 255], binary::Error::Invalid);
233
    try badInstruction(&[13, 255], binary::Error::Invalid);
234
    try badInstruction(&[11, 2], binary::Error::Invalid);
235
    try badInstruction(&[10, 8, 2], binary::Error::Invalid);
236
    try badInstruction(&[5, 2, 0, 0, 0], binary::Error::Invalid);
237
    try badInstruction(&[5, 0, 0, 0, 0, 0, 2, 0, 0, 0], binary::Error::Invalid);
238
    try badInstruction(&[12, 1, 0, 0, 0], binary::Error::Invalid);
239
    try badInstruction(&[5, 0, 0, 0, 0, 2, 1, 0, 0, 0], binary::Error::Symbol);
240
    try badInstruction(&[12, 0, 0, 0, 0, 255, 255, 255, 255], binary::Error::Truncated);
241
}
242
243
/// Check header validation and package symbol indices with arena rollback.
244
@test unsafe fn malformedPackages() throws (testing::TestError) {
245
    let package = binary::Package {
246
        symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
247
        program: il::Program { data: &[], fns: &[] },
248
    };
249
    let mut buffer: [u8; 64] = [0; 64];
250
    let length = try program::encode(&mut buffer[..], &package) catch {
251
        throw testing::TestError::Failed;
252
    };
253
    let memory = &mut MEMORY[..64];
254
    let mut arena = alloc::new(&mut memory[..]);
255
    for offset in &[0, 4, 17, 29] {
256
        let original = buffer[offset];
257
        set buffer[offset] = 255;
258
        set arena.offset = 8;
259
        let mut failed = false;
260
        try program::decode(&buffer[..length], &mut arena, LIMITS) catch {
261
            set failed = true;
262
        };
263
        try testing::expect(failed);
264
        try testing::expect(arena.offset == 8);
265
        set buffer[offset] = original;
266
    }
267
    let mut trailing = false;
268
    try program::decode(&buffer[..length + 1], &mut arena, LIMITS) catch err {
269
        try testing::expect(err == binary::Error::Invalid);
270
        set trailing = true;
271
    };
272
    try testing::expect(trailing);
273
    try testing::expect(arena.offset == 8);
274
}
275
276
/// Reject invalid dependency and export entries after partial table decoding.
277
@test unsafe fn malformedHeaderTables() throws (testing::TestError) {
278
    let package = binary::Package {
279
        symbols: &["p"], name: "p", dependencies: &["p", "p"],
280
        exports: &[
281
            binary::Export { name: "p", kind: binary::ExportKind::Function },
282
            binary::Export { name: "p", kind: binary::ExportKind::Data },
283
        ],
284
        entry: nil, program: il::Program { data: &[], fns: &[] },
285
    };
286
    let mut buffer: [u8; 64] = [0; 64];
287
    let length = try program::encode(&mut buffer[..], &package) catch { throw testing::TestError::Failed; };
288
    for offset in [25, 29, 37, 42, 41, 46] {
289
        let original = buffer[offset];
290
        set buffer[offset] = 2;
291
        let mut arena = alloc::new(&mut MEMORY[..]);
292
        set arena.offset = 8;
293
        let mut failed = false;
294
        try program::decode(&buffer[..length], &mut arena, LIMITS) catch err {
295
            assert err == (binary::Error::Invalid if offset == 41 or offset == 46 else binary::Error::Symbol);
296
            set failed = true;
297
        };
298
        assert failed;
299
        assert arena.offset == 8;
300
        set buffer[offset] = original;
301
    }
302
}
303
304
/// Encode a malformed package and check rejection with arena rollback.
305
unsafe fn rejected(package: &binary::Package, limits: binary::Limits) throws (testing::TestError) {
306
    let mut buffer: [u8; 512] = [0; 512];
307
    let length = try program::encode(&mut buffer[..], package) catch {
308
        throw testing::TestError::Failed;
309
    };
310
    let memory = &mut MEMORY[..256];
311
    let mut arena = alloc::new(&mut memory[..]);
312
    set arena.offset = 8;
313
    let mut failed = false;
314
    try program::decode(&buffer[..length], &mut arena, limits) catch err {
315
        try testing::expect(err == binary::Error::Invalid);
316
        set failed = true;
317
    };
318
    try testing::expect(failed);
319
    try testing::expect(arena.offset == 8);
320
}
321
322
/// Check initializer extent arithmetic, alignments, and duplicate symbols.
323
@test unsafe fn invalidData() throws (testing::TestError) {
324
    let mut item = il::Data {
325
        name: "p", size: 0xffffffff, alignment: 8, readOnly: false, isZeroInit: false,
326
        values: &[
327
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 0 }, count: 0xffffffff },
328
        ],
329
    };
330
    let mut package = binary::Package {
331
        symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
332
        program: il::Program { data: retainData(&[item]), fns: &[] },
333
    };
334
    try rejected(&package, LIMITS);
335
    set item.size = 1;
336
    set item.values = &[
337
        il::DataValue { item: il::DataItem::Undef, count: 1 },
338
        il::DataValue { item: il::DataItem::Undef, count: 1 },
339
    ];
340
    set package.program = il::Program { data: retainData(&[item]), fns: &[] };
341
    try rejected(&package, LIMITS);
342
    set item.values = &[];
343
    for alignment in &[0, 3] {
344
        set item.alignment = alignment;
345
        set package.program = il::Program { data: retainData(&[item]), fns: &[] };
346
        try rejected(&package, LIMITS);
347
    }
348
    set package.program = il::Program { data: &[], fns: &[] };
349
    set package.symbols = &["p", "p"];
350
    try rejected(&package, LIMITS);
351
    set package.symbols = &["p", ""];
352
    try rejected(&package, LIMITS);
353
}
354
355
/// Initializer extents include every item kind and cumulative size boundaries.
356
@test unsafe fn initializerExtents() throws (testing::TestError) {
357
    for typ in [il::Type::W8, il::Type::W16, il::Type::W32, il::Type::W64] {
358
        try checkInitializerExtents(il::DataItem::Val { typ, val: 0 }, il::typeSize(typ));
359
    }
360
    try checkInitializerExtents(il::DataItem::Sym("p"), 8);
361
    try checkInitializerExtents(il::DataItem::Fn("p"), 8);
362
    try checkInitializerExtents(il::DataItem::Str("abc"), 3);
363
    try checkInitializerExtents(il::DataItem::Str(""), 0);
364
    try checkInitializerExtents(il::DataItem::Undef, 1);
365
}
366
367
/// Test one repeated item followed by a byte at exact-fit and shorter sizes.
368
unsafe fn checkInitializerExtents(item: il::DataItem, width: u32) throws (testing::TestError) {
369
    /// Stable initialized values retained by the fixture's data definition.
370
    static values: [il::DataValue; 2] = [il::DataValue { item: il::DataItem::Undef, count: 1 }; 2];
371
    for count in [0 as u32, 1, 3, 0xffffffff] {
372
        set values[0] = il::DataValue { item, count };
373
        let extent = width as u64 * count as u64 + 1;
374
        let size = extent as u32 if extent <= 0xffffffff else 0xffffffff;
375
        for declared in [size, size - 1] {
376
            let definition = il::Data {
377
                name: "p", size: declared, alignment: 1, readOnly: false, isZeroInit: false,
378
                values: &values[..],
379
            };
380
            let package = binary::Package {
381
                symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
382
                program: il::Program { data: retainData(&[definition]), fns: &[] },
383
            };
384
            let mut bytes: [u8; 256] = [0; 256];
385
            let length = try program::encode(&mut bytes[..], &package) catch { throw testing::TestError::Failed; };
386
            let mut arena = alloc::new(&mut MEMORY[..]);
387
            set arena.offset = 8;
388
            let mut failed = false;
389
            try program::decode(&bytes[..length], &mut arena, LIMITS) catch err {
390
                assert err == binary::Error::Invalid;
391
                assert arena.offset == 8;
392
                set failed = true;
393
            };
394
            assert failed == (extent > declared as u64);
395
        }
396
    }
397
}
398
399
/// Check function block limits, parameter indices, and predecessor indices.
400
@test unsafe fn invalidFunctions() throws (testing::TestError) {
401
    let mut instrs = [il::Instr::Ret { val: nil }];
402
    let mut block = il::Block {
403
        label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[], loopDepth: 0,
404
    };
405
    let mut func = il::Fn {
406
        name: "p", params: &[], returnType: il::Type::W64,
407
        isExtern: false, isLeaf: true, blocks: &[block],
408
    };
409
    let package = binary::Package {
410
        symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
411
        program: il::Program { data: &[], fns: &[&func] },
412
    };
413
    try rejected(&package, binary::Limits { registers: 16, blocks: 0 });
414
    set func.isExtern = true;
415
    try rejected(&package, LIMITS);
416
    set func.isExtern = false;
417
    set func.params = &[il::Param { value: il::Reg { n: 16 }, type: il::Type::W64 }];
418
    try rejected(&package, LIMITS);
419
    set func.params = &[];
420
    set block.params = &[il::Param { value: il::Reg { n: 16 }, type: il::Type::W32 }];
421
    set func.blocks = &[block];
422
    try rejected(&package, LIMITS);
423
    set block.params = &[];
424
    set block.preds = &[1];
425
    set func.blocks = &[block];
426
    try rejected(&package, LIMITS);
427
}
428
429
/// Reject unknown initializer tags and widths before reading their payloads.
430
@test unsafe fn malformedInitializers() throws (testing::TestError) {
431
    let memory = &mut MEMORY[..8];
432
    let mut arena = alloc::new(&mut memory[..]);
433
    let fixtures: [*[u8]; 2] = [&[255], &[0, 3]];
434
    for bytes in &fixtures[..] {
435
        let namesTable: [*[u8]; 0] = [];
436
        let source: 'input = &bytes[..], names = &namesTable[..] in {
437
            let mut input = reader::new(source, &mut arena, names);
438
            let mut failed = false;
439
            try reader::dataValue(&mut input) catch err {
440
                try testing::expect(err == binary::Error::Invalid);
441
                set failed = true;
442
            };
443
            try testing::expect(failed);
444
        }
445
    }
446
}
447
448
/// Check symbol collection capacity and package ownership failures.
449
@test unsafe fn collectionBounds() throws (testing::TestError) {
450
    let mut symbols: [*[u8]; 8] = [""; 8];
451
    let mut dependencies: [*[u8]; 1] = [""; 1];
452
    let empty = il::Program { data: &[], fns: &[] };
453
    let symbolTable: 'empty = &mut symbols[..0], dependencyTable = &mut dependencies[..] in {
454
        let mut names = collect::new(symbolTable, dependencyTable);
455
        let mut failed = false;
456
        try collect::package(&mut names, "p", empty, &[], nil) catch err {
457
            try testing::expect(err == binary::Error::Capacity);
458
            set failed = true;
459
        };
460
        try testing::expect(failed);
461
    }
462
    let item = il::Data {
463
        name: "p::data", size: 8, alignment: 8, readOnly: false, isZeroInit: false,
464
        values: &[il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 }],
465
    };
466
    let local = il::Program { data: retainData(&[item]), fns: &[] };
467
    let symbolTable: 'capacity = &mut symbols[..], dependencyTable = &mut dependencies[..0] in {
468
        let mut names = collect::new(symbolTable, dependencyTable);
469
        let mut failed = false;
470
        try collect::package(&mut names, "p", local, &[], nil) catch err {
471
            try testing::expect(err == binary::Error::Capacity);
472
            set failed = true;
473
        };
474
        try testing::expect(failed);
475
    }
476
    let symbolTable: 'owner = &mut symbols[..], dependencyTable = &mut dependencies[..] in {
477
        let mut names = collect::new(symbolTable, dependencyTable);
478
        let mut failed = false;
479
        try collect::package(&mut names, "other", local, &[], nil) catch err {
480
            try testing::expect(err == binary::Error::Invalid);
481
            set failed = true;
482
        };
483
        try testing::expect(failed);
484
    }
485
    let symbolTable: 'valid = &mut symbols[..], dependencyTable = &mut dependencies[..] in {
486
        let mut names = collect::new(symbolTable, dependencyTable);
487
        let package = try collect::package(&mut names, "p", local, &[], nil) catch {
488
            throw testing::TestError::Failed;
489
        };
490
        try testing::expect(package.dependencies.len == 1);
491
        try testing::expectBytesEq(package.dependencies[0], "dep");
492
        try testing::expect(package.symbols.len == 4);
493
    }
494
}
495
496
/// Fixed instruction operands retain symbol order and bounded table writes.
497
@test unsafe fn fixedInstructionCollection() throws (testing::TestError) {
498
    let r = il::Reg { n: 0 };
499
    let a = il::Val::DataSym("z::data");
500
    let b = il::Val::FnAddr("a::fn");
501
    let c = il::Val::DataSym("c::data");
502
    let d = il::Val::FnAddr("d::fn");
503
    let e = il::Val::DataSym("e::data");
504
    let single: [*[u8]; 2] = ["z::data", "z"];
505
    let pair: [*[u8]; 4] = ["z::data", "z", "a::fn", "a"];
506
    for instr in [
507
        il::Instr::Reserve { dst: r, size: a, alignment: 8 },
508
        il::Instr::Blit { dst: r, src: r, size: a },
509
        il::Instr::Store { typ: il::Type::W64, src: a, dst: r, offset: 0 },
510
        il::Instr::Copy { dst: r, val: a },
511
        il::Instr::Zext { typ: il::Type::W8, dst: r, val: a },
512
        il::Instr::Sext { typ: il::Type::W8, dst: r, val: a },
513
        il::Instr::UnOp { op: il::UnOp::Neg, typ: il::Type::W64, dst: r, a },
514
        il::Instr::Ret { val: a },
515
        il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: r, a, b: a },
516
    ] { try checkInstructionNames(instr, &single[..]); }
517
    for instr in [
518
        il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: r, a, b },
519
        il::Instr::DeviceRead { typ: il::Type::W8, dst: r, handle: a, offset: b },
520
    ] { try checkInstructionNames(instr, &pair[..]); }
521
    try checkInstructionNames(il::Instr::Ecall { dst: r, num: a, a0: b, a1: c, a2: d, a3: e },
522
        &["z::data", "z", "a::fn", "a", "c::data", "c", "d::fn", "d", "e::data", "e"]);
523
    try checkInstructionNames(il::Instr::DeviceWrite { typ: il::Type::W8, handle: a, offset: b, value: c },
524
        &["z::data", "z", "a::fn", "a", "c::data", "c"]);
525
    for instr in [
526
        il::Instr::Load { typ: il::Type::W8, dst: r, src: r, offset: 0 },
527
        il::Instr::Sload { typ: il::Type::W8, dst: r, src: r, offset: 0 },
528
        il::Instr::Ret { val: nil },
529
        il::Instr::Copy { dst: r, val: il::Val::Imm(9) },
530
        il::Instr::Unreachable, il::Instr::Ebreak, il::Instr::MemoryFence,
531
    ] { try checkInstructionNames(instr, &[]); }
532
}
533
534
/// Variable operand groups retain first-use order and deduplicate names.
535
@test unsafe fn variableInstructionCollection() throws (testing::TestError) {
536
    let r = il::Reg { n: 0 };
537
    let a = il::Val::DataSym("z::data");
538
    let b = il::Val::FnAddr("a::fn");
539
    let c = il::Val::DataSym("c::data");
540
    let imm = il::Val::Imm(0);
541
    let mut args = [imm, a, a, b];
542
    let mut fallback = [c, a];
543
    try checkInstructionNames(il::Instr::Call { retTy: il::Type::W64, dst: r,
544
        func: b, args: &args[..] }, &["a::fn", "a", "z::data", "z"]);
545
    try checkInstructionNames(il::Instr::Call { retTy: il::Type::W64, dst: nil,
546
        func: il::Val::Reg(r), args: &[] }, &[]);
547
    try checkInstructionNames(il::Instr::Jmp { target: 0, args: &mut args[..] },
548
        &["z::data", "z", "a::fn", "a"]);
549
    try checkInstructionNames(il::Instr::Br { op: il::CmpOp::Eq, typ: il::Type::W64,
550
        a, b: imm, thenTarget: 0, thenArgs: &mut [], elseTarget: 0, elseArgs: &mut args[..] },
551
        &["z::data", "z", "a::fn", "a"]);
552
    try checkInstructionNames(il::Instr::Br { op: il::CmpOp::Eq, typ: il::Type::W64,
553
        a: imm, b: imm, thenTarget: 0, thenArgs: &mut args[..], elseTarget: 0, elseArgs: &mut fallback[..] },
554
        &["z::data", "z", "a::fn", "a", "c::data", "c"]);
555
    let mut cases = [il::SwitchCase { value: 0, target: 0, args: &mut [] },
556
        il::SwitchCase { value: 1, target: 0, args: &mut args[..] }];
557
    try checkInstructionNames(il::Instr::Switch { val: b, defaultTarget: 0,
558
        defaultArgs: &mut fallback[..], cases: &mut cases[..] },
559
        &["a::fn", "a", "c::data", "c", "z::data", "z"]);
560
    try checkInstructionNames(il::Instr::Switch { val: imm, defaultTarget: 0,
561
        defaultArgs: &mut [], cases: &mut [] }, &[]);
562
}
563
564
/// Collect one instruction at exact-fit and every shorter symbol capacity.
565
unsafe fn checkInstructionNames(instr: il::Instr, expected: &[*[u8]]) throws (testing::TestError) {
566
    let mut body = [instr];
567
    let function = il::Fn {
568
        name: "p::f", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
569
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..], locs: &[], preds: &[], loopDepth: 0 }],
570
    };
571
    let local = il::Program { data: &[], fns: &[&function] };
572
    for capacity in 0..expected.len + 3 {
573
        let mut symbols: [*[u8]; 16] = [""; 16];
574
        let mut dependencies: [*[u8]; 5] = [""; 5];
575
        let symbolTable: 'tables = &mut symbols[..capacity], dependencyTable = &mut dependencies[..] in {
576
            let mut names = collect::new(symbolTable, dependencyTable);
577
            let mut failed = false;
578
            try collect::package(&mut names, "p", local, &[], nil) catch err {
579
                assert err == binary::Error::Capacity;
580
                set failed = true;
581
            };
582
            assert failed == (capacity < expected.len + 2);
583
            assert names.symbolCount <= capacity;
584
            if not failed {
585
                assert names.symbolCount == expected.len + 2;
586
                try testing::expectBytesEq(names.symbols[0], "p");
587
                try testing::expectBytesEq(names.symbols[1], "p::f");
588
                for name, i in expected { try testing::expectBytesEq(names.symbols[i + 2], name); }
589
                assert names.dependencyCount == expected.len / 2;
590
            }
591
        }
592
        for i in capacity..symbols.len { assert symbols[i].len == 0; }
593
    }
594
}
595
596
/// Function definitions precede their block references in table order.
597
@test unsafe fn functionCollectionOrder() throws (testing::TestError) {
598
    let external = il::Fn { name: "p::external", params: &[], returnType: il::Type::W64,
599
        isExtern: true, isLeaf: true, blocks: &[] };
600
    let mut first = [il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::DataSym("z::data") }];
601
    let mut second = [il::Instr::Ret { val: il::Val::FnAddr("a::fn") }];
602
    let blocks = [
603
        il::Block { label: "first", params: &[], instrs: &mut first[..], locs: &[], preds: &[], loopDepth: 0 },
604
        il::Block { label: "empty", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 },
605
        il::Block { label: "second", params: &[], instrs: &mut second[..], locs: &[], preds: &[], loopDepth: 0 },
606
    ];
607
    let function = il::Fn { name: "p::main", params: &[], returnType: il::Type::W64,
608
        isExtern: false, isLeaf: true, blocks: &blocks[..] };
609
    let local = il::Program { data: &[], fns: &[&external, &function] };
610
    let expected: [*[u8]; 7] = ["p", "p::external", "p::main", "z::data", "z", "a::fn", "a"];
611
    for capacity in 0..expected.len + 1 {
612
        let mut symbols: [*[u8]; 8] = [""; 8];
613
        let mut dependencies: [*[u8]; 2] = [""; 2];
614
        let symbolTable: 'tables = &mut symbols[..capacity], dependencyTable = &mut dependencies[..] in {
615
            let mut names = collect::new(symbolTable, dependencyTable);
616
            let mut failed = false;
617
            try collect::package(&mut names, "p", local, &[], nil) catch err {
618
                assert err == binary::Error::Capacity;
619
                set failed = true;
620
            };
621
            assert failed == (capacity < expected.len);
622
            assert names.symbolCount <= capacity;
623
            if not failed {
624
                assert names.symbolCount == expected.len;
625
                for name, i in expected { try testing::expectBytesEq(names.symbols[i], name); }
626
                assert names.dependencyCount == 2;
627
                try testing::expectBytesEq(names.dependencies[0], "z");
628
                try testing::expectBytesEq(names.dependencies[1], "a");
629
            }
630
        }
631
        for i in capacity..symbols.len { assert symbols[i].len == 0; }
632
    }
633
}
634
635
/// Exports precede the entry name, and entry definitions belong to the package.
636
@test unsafe fn entryCollectionOrder() throws (testing::TestError) {
637
    let local = il::Program { data: &[], fns: &[] };
638
    let exports = [
639
        binary::Export { name: "p::second", kind: binary::ExportKind::Function },
640
        binary::Export { name: "p::first", kind: binary::ExportKind::Function },
641
        binary::Export { name: "p::second", kind: binary::ExportKind::Function },
642
    ];
643
    let entries: [?*[u8]; 5] = [nil, "p::first", "p::start", "other::start", "p::"];
644
    for entry, index in entries {
645
        let mut symbols: [*[u8]; 4] = [""; 4];
646
        let mut dependencies: [*[u8]; 1] = [""; 1];
647
        let symbolTable: 'tables = &mut symbols[..], dependencyTable = &mut dependencies[..] in {
648
            let mut names = collect::new(symbolTable, dependencyTable);
649
            let package = try collect::package(&mut names, "p", local, &exports[..], entry) catch err {
650
                assert index >= 3;
651
                assert err == binary::Error::Invalid;
652
                continue;
653
            };
654
            assert index < 3;
655
            assert package.symbols.len == (4 if index == 2 else 3);
656
            for name, i in ["p", "p::second", "p::first"] {
657
                try testing::expectBytesEq(package.symbols[i], name);
658
            }
659
            if index == 2 { try testing::expectBytesEq(package.symbols[3], "p::start"); }
660
            assert package.dependencies.len == 0;
661
            assert package.exports.len == 3;
662
            assert package.entry == entry;
663
        }
664
    }
665
}
666
667
/// Data collection retains first-use order and unique dependencies.
668
@test unsafe fn dataCollectionOrder() throws (testing::TestError) {
669
    let item = il::Data {
670
        name: "p::data", size: 64, alignment: 8, readOnly: true, isZeroInit: false,
671
        values: &[
672
            il::DataValue { item: il::DataItem::Fn("z::fn"), count: 1 },
673
            il::DataValue { item: il::DataItem::Sym("a::data"), count: 1 },
674
            il::DataValue { item: il::DataItem::Fn("z::fn"), count: 1 },
675
            il::DataValue { item: il::DataItem::Sym("p::data"), count: 1 },
676
            il::DataValue { item: il::DataItem::Str("not::a::symbol"), count: 1 },
677
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, val: 9 }, count: 2 },
678
        ],
679
    };
680
    let zero = il::Data {
681
        name: "p::zero", size: 32, alignment: 8, readOnly: false, isZeroInit: true, values: &[],
682
    };
683
    let local = il::Program { data: retainData(&[item, zero]), fns: &[] };
684
    let mut symbols: [*[u8]; 7] = [""; 7];
685
    let mut dependencies: [*[u8]; 2] = [""; 2];
686
    let symbolTable: 'tables = &mut symbols[..], dependencyTable = &mut dependencies[..] in {
687
        let mut names = collect::new(symbolTable, dependencyTable);
688
        let package = try collect::package(&mut names, "p", local,
689
            &[binary::Export { name: "p::data", kind: binary::ExportKind::Data }], nil
690
        ) catch { throw testing::TestError::Failed; };
691
        assert package.symbols.len == 7;
692
        for expected, index in ["p", "p::data", "z::fn", "z", "a::data", "a", "p::zero"] {
693
            try testing::expectBytesEq(package.symbols[index], expected);
694
        }
695
        assert package.dependencies.len == 2;
696
        try testing::expectBytesEq(package.dependencies[0], "z");
697
        try testing::expectBytesEq(package.dependencies[1], "a");
698
        try roundTrip(&package);
699
    }
700
}
701
702
/// Copy data fixtures into stable storage for the package descriptor.
703
unsafe fn retainData(items: &[il::Data]) -> *[il::Data] {
704
    unsafe static DATA: [il::Data; 2] = undefined;
705
    assert items.len <= DATA.len;
706
    for item, i in items {
707
        set DATA[i] = item;
708
    }
709
    return &DATA[..items.len];
710
}