test/backend.rad 22.0 KiB raw
1
//! Executable checks for recoverable backend bounds.
2
use std::lang::alloc;
3
use std::lang::il;
4
use std::lang::gen::bitset;
5
use std::lang::gen::data;
6
use std::lang::gen::regalloc;
7
use std::collections::dict;
8
use std::arch::rv64;
9
use std::arch::rv64::encode;
10
11
/// A backend check could not obtain its required test storage.
12
union TestError: Copy {
13
    /// Test setup failed.
14
    Failed,
15
}
16
17
/// Reusable emitter storage.
18
static ASSEMBLY_ARENA_STORAGE: [u8; 16777216] = [0; 16777216];
19
20
/// Run all backend boundary checks.
21
@default unsafe fn main() -> u32 {
22
    try testBackendBounds() catch {
23
        panic "backend bounds failed";
24
    };
25
    return 0;
26
}
27
28
/// Register limit checks use bounded scratch storage.
29
static REGISTER_SCRATCH: [u8; 65536] = [0; 65536];
30
31
/// Register numbers at or above the supported count return allocation errors.
32
unsafe fn testRegisterLimit() throws (TestError) {
33
    for number in [8191, 8192, 0xffffffff] {
34
        let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]);
35
        let mut instructions = [il::Instr::Ret { val: il::Val::Reg(il::Reg { n: number }) }];
36
        let func = il::Fn {
37
            name: "limit", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
38
            blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
39
        };
40
        let mut failed = false;
41
        try regalloc::liveness::analyze(&func, &mut arena) catch {
42
            set failed = true;
43
        };
44
        assert failed == (number >= 8192);
45
        if failed {
46
            assert arena.offset == 0;
47
        }
48
    }
49
}
50
51
/// Excess live values return an error before the spill candidate table overflows.
52
unsafe fn testSpillCandidateLimit() throws (TestError) {
53
    let mut arena = alloc::new(&mut REGISTER_SCRATCH[..]);
54
    let mut liveSet = try! bitset::allocate(&mut arena, 257);
55
    for i in 0..257 {
56
        bitset::put(&mut liveSet, i);
57
    }
58
    let mut out = [liveSet];
59
    let live = regalloc::liveness::LiveInfo {
60
        liveIn: &mut [], liveOut: &mut out[..], defs: &mut [], uses: &mut [], blockCount: 1, maxReg: 257,
61
    };
62
    let func = il::Fn {
63
        name: "pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
64
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }],
65
    };
66
    let mut failed = false;
67
    try regalloc::spill::analyze(&func, &live, 23, 11, 8, &mut arena) catch {
68
        set failed = true;
69
    };
70
    assert failed;
71
}
72
73
/// Frame size arithmetic must not wrap into a negative stack allocation.
74
unsafe fn testFrameSizeOverflow() throws (TestError) {
75
    for size in [0x7fffffff, -1] {
76
        let mut failed = false;
77
        try rv64::emit::computeFrame(size, 0, 0, false, false) catch error {
78
            assert error == rv64::Error::Capacity;
79
            set failed = true;
80
        };
81
        assert failed;
82
    }
83
    let frame = try rv64::emit::computeFrame(16, 0, 0, false, false) catch {
84
        throw TestError::Failed;
85
    };
86
    assert frame.totalSize == 32;
87
}
88
89
/// Oversized reserves must fail before narrowing their byte counts.
90
unsafe fn testReserveOverflow() throws (TestError) {
91
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
92
    let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..]);
93
    let mut generator = try! rv64::beginProgram(
94
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
95
    );
96
    let mut instructions = [
97
        il::Instr::Reserve { dst: il::Reg { n: 0 }, size: il::Val::Imm(0x100000000), alignment: 8 },
98
        il::Instr::Ret { val: nil },
99
    ];
100
    let func = il::Fn {
101
        name: "reserve", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
102
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
103
    };
104
    rv64::generateFunction(&mut generator, &func, &mut scratch);
105
    assert generator.e.error == rv64::Error::Capacity;
106
    assert scratch.offset == 0;
107
108
    let config = rv64::targetConfig();
109
    let ralloc = try! regalloc::allocate(&func, &config, &mut scratch);
110
    set generator.e.error = rv64::Error::Symbol;
111
    rv64::isel::selectFn(&mut generator.e, &ralloc, &func);
112
    assert generator.e.error == rv64::Error::Symbol;
113
}
114
115
/// An exhausted instruction buffer records failure and stops writes.
116
unsafe fn testCodeCapacity() throws (TestError) {
117
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
118
    let mut e = try rv64::emit::emitter(&mut arena, false) catch {
119
        throw TestError::Failed;
120
    };
121
    set e.code = &mut e.code[..0];
122
    rv64::emit::emit(&mut e, encode::nop());
123
    assert e.error == rv64::Error::Capacity;
124
    rv64::emit::emit(&mut e, encode::ebreak());
125
    assert e.codeLen == 0;
126
}
127
128
/// Generator initialization must report exhausted storage without trapping.
129
unsafe fn testGeneratorAllocationFailure() throws (TestError) {
130
    let mut arena = alloc::new(&mut REGISTER_SCRATCH[..64]);
131
    set arena.offset = 8;
132
    let mut failed = false;
133
    try rv64::beginProgram(
134
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
135
    ) catch error {
136
        assert error == rv64::Error::Allocation;
137
        set failed = true;
138
    };
139
    assert failed;
140
    assert arena.offset == 8;
141
}
142
143
/// Function allocation failure preserves the scratch checkpoint.
144
unsafe fn testFunctionAllocationFailure() throws (TestError) {
145
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
146
    let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..8]);
147
    set scratch.offset = 8;
148
    let mut generator = try! rv64::beginProgram(
149
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
150
    );
151
    let mut instructions = [il::Instr::Ret { val: il::Val::Imm(0) }];
152
    let func = il::Fn {
153
        name: "allocation", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
154
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
155
    };
156
    rv64::generateFunction(&mut generator, &func, &mut scratch);
157
    assert generator.e.error == rv64::Error::Allocation;
158
    assert scratch.offset == 8;
159
}
160
161
/// Full metadata tables reject appends before requesting more storage.
162
unsafe fn testMetadataCapacity() throws (TestError) {
163
    for kind in 0..5 {
164
        let mut e = boundsEmitter();
165
        match kind {
166
            case 0 => {
167
                set e.pendingBranches.len = 0;
168
                set e.pendingBranches.cap = 0;
169
                rv64::emit::recordBranch(&mut e, 0, rv64::emit::BranchKind::Jump);
170
            }
171
            case 1 => {
172
                set e.pendingCalls.len = 0;
173
                set e.pendingCalls.cap = 0;
174
                rv64::emit::recordCall(&mut e, "call");
175
            }
176
            case 2 => {
177
                set e.pendingJumps.len = 0;
178
                set e.pendingJumps.cap = 0;
179
                rv64::emit::recordJumpAt(&mut e, "jump", rv64::ZERO, 0);
180
            }
181
            case 3 => {
182
                set e.pendingAddrLoads.len = 0;
183
                set e.pendingAddrLoads.cap = 0;
184
                rv64::emit::recordDataAddrLoad(&mut e, "data", rv64::A0);
185
            }
186
            else => {
187
                set e.funcs.len = 0;
188
                set e.funcs.cap = 0;
189
                rv64::emit::recordFunc(&mut e, "function");
190
            }
191
        }
192
        assert e.error == rv64::Error::Capacity;
193
        let length = e.codeLen;
194
        rv64::emit::emit(&mut e, encode::nop());
195
        assert e.codeLen == length;
196
    }
197
}
198
199
/// Debug entries report exhausted storage without writing beyond the buffer.
200
unsafe fn testDebugCapacity() throws (TestError) {
201
    let mut e = boundsEmitter();
202
    rv64::emit::recordSrcLoc(&mut e, il::SrcLoc { moduleId: 0, offset: 0 });
203
    assert e.error == rv64::Error::Capacity;
204
    assert e.debugEntriesLen == 0;
205
}
206
207
/// Block labels report an out-of-range table index.
208
unsafe fn testBlockCapacity() throws (TestError) {
209
    let mut e = boundsEmitter();
210
    let count = e.labels.blockOffsets.len;
211
    rv64::emit::recordBlock(&mut e, count);
212
    assert e.error == rv64::Error::Capacity;
213
    assert e.labels.blockCount == 0;
214
}
215
216
/// Unresolved calls preserve their placeholder instructions and report a symbol error.
217
unsafe fn testMissingCallSymbol() throws (TestError) {
218
    let mut e = boundsEmitter();
219
    rv64::emit::recordCall(&mut e, "missing");
220
    rv64::emit::patchCalls(&mut e);
221
    assert e.error == rv64::Error::Symbol;
222
    assert e.code[0] == encode::nop();
223
    assert e.code[1] == encode::nop();
224
}
225
226
/// Local jumps outside their immediate range preserve the reserved instruction.
227
unsafe fn testLocalJumpRange() throws (TestError) {
228
    let mut e = boundsEmitter();
229
    rv64::emit::recordBranch(&mut e, 0, rv64::emit::BranchKind::Jump);
230
    set e.labels.blockOffsets[0] = 0x200000;
231
    set e.labels.blockCount = 1;
232
    rv64::emit::patchLocalBranches(&mut e);
233
    assert e.error == rv64::Error::Relocation;
234
    assert e.code[0] == encode::nop();
235
}
236
237
/// Assembly jumps reject targets beyond the J-type displacement range.
238
unsafe fn testAssemblyJumpRange() throws (TestError) {
239
    let mut e = boundsEmitter();
240
    rv64::emit::emit(&mut e, encode::nop());
241
    rv64::emit::recordJumpAt(&mut e, "far", rv64::ZERO, 0);
242
    rv64::emit::recordFuncOffsetAt(&mut e, "far", 0x80000);
243
    rv64::emit::patchJumps(&mut e);
244
    assert e.error == rv64::Error::Relocation;
245
    assert e.code[0] == encode::nop();
246
}
247
248
/// Missing data symbols preserve address-load placeholders.
249
unsafe fn testMissingDataSymbol() throws (TestError) {
250
    let mut e = boundsEmitter();
251
    static entries: [dict::Entry; 4] = undefined;
252
    let map = try! data::buildMap(&[], &mut entries[..]);
253
    rv64::emit::recordDataAddrLoad(&mut e, "missing", rv64::A0);
254
    rv64::emit::patchAddrLoads(&mut e, &map);
255
    assert e.error == rv64::Error::Symbol;
256
    assert e.code[0] == encode::nop();
257
    assert e.code[1] == encode::nop();
258
}
259
260
/// Data addresses outside the supported signed address range are rejected.
261
unsafe fn testDataAddressRange() throws (TestError) {
262
    let mut e = boundsEmitter();
263
    static entries: [dict::Entry; 4] = undefined;
264
    static symbols: [data::DataSym; 1] = [data::DataSym { name: "far", addr: 0x80000000 }];
265
    let map = try! data::buildMap(&symbols[..], &mut entries[..]);
266
    rv64::emit::recordDataAddrLoad(&mut e, "far", rv64::A0);
267
    rv64::emit::patchAddrLoads(&mut e, &map);
268
    assert e.error == rv64::Error::Relocation;
269
    assert e.code[0] == encode::nop();
270
    assert e.code[1] == encode::nop();
271
}
272
273
/// Data section sizes must not wrap when alignment exceeds the address space.
274
unsafe fn testDataLayoutOverflow() throws (TestError) {
275
    let items = [il::Data { name: "value", size: 8, alignment: 8, readOnly: true, isZeroInit: false, values: &[] }];
276
    let mut symbols: [data::DataSym; 1] = undefined;
277
    let mut count: u32 = 0;
278
    let mut failed = false;
279
    try data::layoutSectionAtOffset(&items[..], &mut symbols[..], &mut count, 0, 0xfffffffc, true) catch error {
280
        assert error == data::Error::Overflow;
281
        set failed = true;
282
    };
283
    assert failed;
284
    assert count == 0;
285
}
286
287
/// Initializers must not write past their declared data slot.
288
unsafe fn testDataInitializerBounds() throws (TestError) {
289
    try testDataMapCapacity();
290
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
291
    let e = try! rv64::emit::emitter(&mut arena, false);
292
    static entries: [dict::Entry; 4] = undefined;
293
    let map = try! data::buildMap(&[], &mut entries[..]);
294
    let mut items = [il::Data {
295
        name: "small", size: 1, alignment: 1, readOnly: true, isZeroInit: false,
296
        values: &[il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 7 }, count: 1 }],
297
    }];
298
    let mut bytes = [0xaa as u8; 16];
299
    let mut failed = false;
300
    try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error {
301
        assert error == data::Error::Capacity;
302
        set failed = true;
303
    };
304
    assert failed;
305
    assert bytes[0] == 0xaa and bytes[1] == 0xaa;
306
307
    set items[0].size = 8;
308
    let written = try! data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..8], true);
309
    assert written == 8;
310
    assert bytes[0] == 7;
311
    for i in 1..8 {
312
        assert bytes[i] == 0;
313
    }
314
    assert bytes[8] == 0xaa;
315
}
316
317
/// Data maps must reject inputs beyond the backing dictionary capacity.
318
unsafe fn testDataMapCapacity() throws (TestError) {
319
    static mapCapacityEntries: [dict::Entry; 2] = undefined;
320
    static mapCapacitySymbols: [data::DataSym; 2] = [
321
        data::DataSym { name: "first", addr: 0 },
322
        data::DataSym { name: "second", addr: 8 },
323
    ];
324
    let mut failed = false;
325
    try data::buildMap(&mapCapacitySymbols[..], &mut mapCapacityEntries[..]) catch error {
326
        assert error == data::Error::Capacity;
327
        set failed = true;
328
    };
329
    assert failed;
330
}
331
332
/// Create an emitter in reusable static storage for bounds checks.
333
unsafe fn boundsEmitter() -> rv64::emit::Emitter {
334
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
335
    return try! rv64::emit::emitter(&mut arena, false);
336
}
337
338
/// Verify recoverable backend bounds and allocation failures.
339
unsafe fn testBackendBounds() throws (TestError) {
340
    try testFunctionMapCapacity();
341
    try testRegisterLimit();
342
    try testSpillCandidateLimit();
343
    try testFrameSizeOverflow();
344
    try testReserveOverflow();
345
    try testCallArgumentCapacity();
346
    try testBlockArgumentCapacity();
347
    try testCodeCapacity();
348
    try testGeneratorAllocationFailure();
349
    try testFunctionAllocationFailure();
350
    try testMetadataCapacity();
351
    try testDebugCapacity();
352
    try testBlockCapacity();
353
    try testMissingCallSymbol();
354
    try testLocalJumpRange();
355
    try testAssemblyJumpRange();
356
    try testMissingDataSymbol();
357
    try testDataAddressRange();
358
    try testDataLayoutOverflow();
359
    try testDataAlignment();
360
    try testDataInitializerBounds();
361
    try testDataEmissionSymbol();
362
    try testFunctionInitializerSymbol();
363
    try testOutputPrefixCapacity();
364
    try testMissingEntry();
365
    try testCodeBaseOverflow();
366
}
367
368
/// Function labels reject a full dictionary before insertion.
369
unsafe fn testFunctionMapCapacity() throws (TestError) {
370
    let mut e = boundsEmitter();
371
    static entries: [dict::Entry; 2] = undefined;
372
    set e.labels.funcs = dict::init(&mut entries[..]);
373
    rv64::emit::recordFuncOffsetAt(&mut e, "first", 0);
374
    rv64::emit::recordFuncOffsetAt(&mut e, "second", 1);
375
    assert e.error == rv64::Error::Capacity;
376
    assert e.labels.funcs.count == 1;
377
}
378
379
/// Data initializers report unresolved symbols before writing the slot.
380
unsafe fn testDataEmissionSymbol() throws (TestError) {
381
    let e = boundsEmitter();
382
    static entries: [dict::Entry; 4] = undefined;
383
    let map = try! data::buildMap(&[], &mut entries[..]);
384
    let items = [il::Data { name: "symbol", size: 8, alignment: 8, readOnly: true, isZeroInit: false,
385
        values: &[il::DataValue { item: il::DataItem::Sym("missing"), count: 1 }] }];
386
    let mut bytes = [0xaa as u8; 8];
387
    let mut failed = false;
388
    try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error {
389
        assert error == data::Error::Symbol;
390
        set failed = true;
391
    };
392
    assert failed;
393
    assert bytes[0] == 0xaa;
394
}
395
396
/// Function initializers report unresolved symbols before writing the slot.
397
unsafe fn testFunctionInitializerSymbol() throws (TestError) {
398
    let e = boundsEmitter();
399
    static entries: [dict::Entry; 4] = undefined;
400
    let map = try! data::buildMap(&[], &mut entries[..]);
401
    let items = [il::Data { name: "symbol", size: 8, alignment: 8, readOnly: true, isZeroInit: false,
402
        values: &[il::DataValue { item: il::DataItem::Fn("missing"), count: 1 }] }];
403
    let mut bytes = [0xaa as u8; 8];
404
    let mut failed = false;
405
    try data::emitSection(&items[..], &map, &e.labels, 0, &mut bytes[..], true) catch error {
406
        assert error == data::Error::Symbol;
407
        set failed = true;
408
    };
409
    assert failed;
410
    assert bytes[0] == 0xaa;
411
}
412
413
/// The read-only prefix must fit its output buffer.
414
unsafe fn testOutputPrefixCapacity() throws (TestError) {
415
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
416
    let mut generator = try! rv64::beginProgram(
417
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
418
    );
419
    static symbols: [data::DataSym; 1] = undefined;
420
    static entries: [dict::Entry; 4] = undefined;
421
    let storage = rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut entries[..] };
422
    let mut failed = false;
423
    try rv64::finishProgram(&mut generator, &[], storage, "x", &mut [], &mut []) catch error {
424
        assert error == rv64::Error::Capacity;
425
        set failed = true;
426
    };
427
    assert failed;
428
}
429
430
/// A reserved entry jump requires an entry symbol.
431
unsafe fn testMissingEntry() throws (TestError) {
432
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
433
    let mut generator = try! rv64::beginProgram(
434
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::Reserved(nil), debug: false }, &mut arena
435
    );
436
    static symbols: [data::DataSym; 1] = undefined;
437
    static entries: [dict::Entry; 4] = undefined;
438
    let storage = rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut entries[..] };
439
    let mut failed = false;
440
    try rv64::finishProgram(&mut generator, &[], storage, "", &mut [], &mut []) catch error {
441
        assert error == rv64::Error::Symbol;
442
        set failed = true;
443
    };
444
    assert failed;
445
}
446
447
/// Code placement must not wrap after the read-only section.
448
unsafe fn testCodeBaseOverflow() throws (TestError) {
449
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
450
    let mut generator = try! rv64::beginProgram(
451
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
452
    );
453
    static symbols: [data::DataSym; 1] = undefined;
454
    static entries: [dict::Entry; 4] = undefined;
455
    let storage = rv64::Storage { dataSyms: &mut symbols[..], dataSymEntries: &mut entries[..] };
456
    let items = [il::Data { name: "large", size: 0xfffeffff, alignment: 1, readOnly: true, isZeroInit: true, values: &[] }];
457
    let mut failed = false;
458
    try rv64::finishProgram(&mut generator, &items[..], storage, "", &mut [], &mut []) catch error {
459
        assert error == rv64::Error::Data(data::Error::Overflow);
460
        set failed = true;
461
    };
462
    assert failed;
463
}
464
465
/// Call arguments must fit the architectural argument register bank.
466
unsafe fn testCallArgumentCapacity() throws (TestError) {
467
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
468
    let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..]);
469
    let mut generator = try! rv64::beginProgram(
470
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
471
    );
472
    let args = [il::Val::Imm(0); 9];
473
    let mut instructions = [
474
        il::Instr::Call { retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("target"), args: &args[..] },
475
        il::Instr::Ret { val: nil },
476
    ];
477
    let func = il::Fn {
478
        name: "call", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: false,
479
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
480
    };
481
    rv64::generateFunction(&mut generator, &func, &mut scratch);
482
    assert generator.e.error == rv64::Error::Capacity;
483
    assert scratch.offset == 0;
484
485
    set generator.e.error = nil;
486
    set instructions[0] = il::Instr::Call {
487
        retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("target"), args: &args[..8],
488
    };
489
    rv64::generateFunction(&mut generator, &func, &mut scratch);
490
    assert generator.e.error == nil;
491
    assert scratch.offset == 0;
492
}
493
494
/// Block argument lists must fit the parallel move storage.
495
unsafe fn testBlockArgumentCapacity() throws (TestError) {
496
    let mut arena = alloc::new(&mut ASSEMBLY_ARENA_STORAGE[..]);
497
    let mut scratch = alloc::new(&mut REGISTER_SCRATCH[..]);
498
    let mut generator = try! rv64::beginProgram(
499
        rv64::ProgramOptions { entryPatch: rv64::EntryPatch::None, debug: false }, &mut arena
500
    );
501
    let mut args = [il::Val::Imm(0); 17];
502
    let mut params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }; 17];
503
    for i in 0..params.len {
504
        set params[i].value.n = i;
505
    }
506
    let mut entry = [il::Instr::Jmp { target: 1, args: &mut args[..] }];
507
    let mut exit = [il::Instr::Ret { val: nil }];
508
    let mut blocks = [
509
        il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 },
510
        il::Block { label: "exit", params: &params[..], instrs: &mut exit[..], locs: &[], preds: &[0], loopDepth: 0 },
511
    ];
512
    let func = il::Fn {
513
        name: "block", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
514
        blocks: &blocks[..],
515
    };
516
    rv64::generateFunction(&mut generator, &func, &mut scratch);
517
    assert generator.e.error == rv64::Error::Capacity;
518
    assert scratch.offset == 0;
519
520
    set generator.e.error = nil;
521
    set entry[0] = il::Instr::Jmp { target: 1, args: &mut args[..16] };
522
    set blocks[1].params = &params[..16];
523
    rv64::generateFunction(&mut generator, &func, &mut scratch);
524
    assert generator.e.error == nil;
525
    assert scratch.offset == 0;
526
}
527
528
/// Initialized and zero-filled data require power-of-two alignment.
529
unsafe fn testDataAlignment() throws (TestError) {
530
    for zeroInit in [false, true] {
531
        for alignment in [0, 3, 8] {
532
            let items = [il::Data {
533
                name: "aligned", size: 8, alignment, readOnly: true,
534
                isZeroInit: zeroInit, values: &[],
535
            }];
536
            let mut symbols: [data::DataSym; 1] = undefined;
537
            let mut count: u32 = 0;
538
            let mut failed = false;
539
            let size = try data::layoutSectionAtOffset(
540
                &items[..], &mut symbols[..], &mut count, 16, 1, true
541
            ) catch error {
542
                assert error == data::Error::Alignment;
543
                set failed = true;
544
                0;
545
            };
546
            assert failed == (alignment <> 8);
547
            if not failed {
548
                assert size == 16 and count == 1;
549
                assert symbols[0].addr == 24;
550
            } else {
551
                assert count == 0;
552
            }
553
        }
554
    }
555
}