lib/std/arch/rv64/bounds.rad 21.0 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
15
/// Reusable emitter allocation storage.
16
static MEMORY: [u8; 16777216] = [0; 16777216];
17
/// Function and liveness test storage.
18
static SCRATCH: [u8; 65536] = [0; 65536];
19
/// Code output with guard words for instruction selection checks.
20
static SELECTION_WORDS: [u32; 130] = [0; 130];
21
22
/// Dictionary storage for bounded map tests.
23
unsafe static ENTRIES: [dict::Entry; 4] = undefined;
24
25
/// Build a non-debug generator with a fixed code address.
26
unsafe fn generator(arena: &mut alloc::Arena) -> super::Generator {
27
    return try! super::beginProgram(super::ProgramOptions {
28
        entryPatch: super::EntryPatch::None, debug: false,
29
        placement: super::image::Placement::Physical {
30
            code: 0x80000000, roData: 0x80001000, rwData: 0x80002000, entry: 0x80000000,
31
        },
32
    }, arena);
33
}
34
35
/// Exhaust each emitter storage class and ensure writes stop at the first error.
36
@test unsafe fn emissionCapacity() throws (testing::TestError) {
37
    for kind in 0..8 {
38
        let mut arena = alloc::new(&mut MEMORY[..]);
39
        let mut e = try! emit::emitter(&mut arena, false);
40
        match kind {
41
            case 0 => {
42
                set e.code = &mut e.code[..0];
43
                emit::emit(&mut e, encode::nop());
44
            },
45
            case 1 => {
46
                set e.pendingBranchesLen = e.pendingBranches.len;
47
                emit::recordBranch(&mut e, 0, emit::BranchKind::Jump);
48
            },
49
            case 2 => {
50
                set e.pendingCallsLen = e.pendingCalls.len;
51
                emit::recordCall(&mut e, "p::call");
52
            },
53
            case 3 => {
54
                set e.pendingJumpsLen = e.pendingJumps.len;
55
                emit::recordJumpAt(&mut e, "p::jump", super::ZERO, 0);
56
            },
57
            case 4 => {
58
                set e.pendingAddrLoadsLen = e.pendingAddrLoads.len;
59
                emit::recordDataAddrLoad(&mut e, "p::data", super::A0);
60
            },
61
            case 5 => {
62
                set e.funcsLen = e.funcs.len;
63
                emit::recordFunc(&mut e, "p::call");
64
            },
65
            case 6 => {
66
                let entries = &mut ENTRIES[..2];
67
                set e.labels.funcs = dict::init(&mut entries[..]);
68
                emit::recordFuncOffset(&mut e, "p::first");
69
                emit::recordFuncOffset(&mut e, "p::second");
70
            },
71
            else => {
72
                emit::recordSrcLoc(&mut e, il::SrcLoc { moduleId: 0, offset: 0 });
73
            },
74
        }
75
        try testing::expect(e.error == super::Error::Capacity);
76
        let count = e.codeLen;
77
        emit::emit(&mut e, encode::ebreak());
78
        try testing::expect(e.codeLen == count);
79
        let mut rejected = false;
80
        try emit::check(&e) catch err {
81
            try testing::expect(err == super::Error::Capacity); set rejected = true;
82
        };
83
        try testing::expect(rejected);
84
    }
85
}
86
87
/// Missing labels and long jumps return errors without corrupting instructions.
88
@test unsafe fn relocationFailures() throws (testing::TestError) {
89
    let mut arena = alloc::new(&mut MEMORY[..]);
90
    let mut e = try! emit::emitter(&mut arena, false);
91
    emit::recordCall(&mut e, "missing");
92
    emit::patchCalls(&mut e);
93
    try check(e.error == super::Error::Symbol, "missing function");
94
    alloc::reset(&mut arena);
95
    set e = try! emit::emitter(&mut arena, false);
96
    emit::recordBranch(&mut e, 0, emit::BranchKind::Jump);
97
    labels::recordBlock(&mut e.labels, 0, 0x200000);
98
    emit::patchLocalBranches(&mut e);
99
    try check(e.error == super::Error::Relocation, "long branch");
100
    try check(e.code[0] == encode::nop(), "rejected branch unchanged");
101
    alloc::reset(&mut arena);
102
    set e = try! emit::emitter(&mut arena, false);
103
    let blockCount = e.labels.blockOffsets.len;
104
    emit::recordBlock(&mut e, blockCount);
105
    try check(e.error == super::Error::Capacity, "block capacity");
106
}
107
108
/// Arena setup and per-function failures restore their saved offsets for reuse.
109
@test unsafe fn arenaRecovery() throws (testing::TestError) {
110
    let small = &mut SCRATCH[..64];
111
    let mut arena = alloc::new(&mut small[..]);
112
    set arena.offset = 8;
113
    let mut failed = false;
114
    try super::beginProgram(super::ProgramOptions {
115
        entryPatch: super::EntryPatch::None, debug: false, placement: super::image::Placement::Hosted,
116
    }, &mut arena) catch err {
117
        try check(err == super::Error::Allocation, "generator allocation"); set failed = true;
118
    };
119
    try check(failed and arena.offset == 8, "generator arena restored");
120
    let mut code = alloc::new(&mut MEMORY[..]);
121
    let mut gen = generator(&mut code);
122
    let mut instructions = [
123
        il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::Imm(1) },
124
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) },
125
    ];
126
    let func = il::Fn {
127
        name: "p::one", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
128
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut instructions[..], locs: &[], preds: &[], loopDepth: 0 }],
129
    };
130
    super::generateFunction(&mut gen, &func, &mut arena);
131
    try check(gen.e.error == super::Error::Allocation and arena.offset == 8, "function arena restored");
132
    alloc::reset(&mut code);
133
    set gen = generator(&mut code);
134
    let mut scratch = alloc::new(&mut SCRATCH[..]);
135
    set scratch.offset = 16;
136
    super::generateFunction(&mut gen, &func, &mut scratch);
137
    try check(gen.e.error == nil and gen.e.codeLen > 0 and scratch.offset == 16, "function retry");
138
    alloc::reset(&mut code);
139
    set gen = generator(&mut code);
140
    set instructions[0] = il::Instr::Copy { dst: il::Reg { n: 8192 }, val: il::Val::Imm(1) };
141
    super::generateFunction(&mut gen, &func, &mut scratch);
142
    try check(gen.e.error == super::Error::Allocation and scratch.offset == 16, "SSA capacity");
143
}
144
145
/// Spill candidate storage fails explicitly when too many values are live.
146
@test unsafe fn registerStorage() throws (testing::TestError) {
147
    let mut arena = alloc::new(&mut SCRATCH[..]);
148
    use arena as bits in {
149
        let liveSet = try! bitset::allocate(&bits, 257);
150
        for i in 0..257 {
151
            bitset::put(liveSet, i);
152
        }
153
        let live = regalloc::liveness::LiveInfo 'bits {
154
            liveIn: &liveSet[..0], liveOut: &liveSet[..],
155
            defs: &liveSet[..0], uses: &liveSet[..0],
156
            words: bitset::wordsFor(257), blockCount: 1, maxReg: 257,
157
        };
158
        let func = il::Fn {
159
            name: "p::pressure", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
160
            blocks: &[il::Block { label: "entry", params: &[], instrs: &mut [], locs: &[], preds: &[], loopDepth: 0 }],
161
        };
162
        let mut failed = false;
163
        try regalloc::spill::analyze(&func, &live, 23, 11, 8, &bits) catch {
164
            set failed = true;
165
        };
166
        try testing::expect(failed);
167
    }
168
}
169
170
/// Data output and symbol maps reject insufficient or ambiguous storage.
171
@test unsafe fn dataStorage() throws (testing::TestError) {
172
    let mut arena = alloc::new(&mut MEMORY[..]);
173
    let e = try! emit::emitter(&mut arena, false);
174
    let syms = &[data::DataSym { name: "p::data", addr: 0x80002000 }];
175
    let entries = &mut ENTRIES[..2];
176
    let map = try! data::buildMap(syms, &mut entries[..]);
177
    let items = &[il::Data {
178
        name: "p::data", size: 8, alignment: 8, readOnly: false, isZeroInit: false,
179
        values: &[il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 1 }, count: 1 }],
180
    }];
181
    let mut bytes: [u8; 8] = [255; 8];
182
    let mut failed: u32 = 0;
183
    try data::emitSection(items, &map, &e.labels, 0x80000000, &mut bytes[..7], false) catch err {
184
        try testing::expect(err == data::Error::Capacity); set failed += 1;
185
    };
186
    try data::buildMap(syms, &mut entries[..1]) catch err {
187
        try testing::expect(err == data::Error::Capacity); set failed += 1;
188
    };
189
    let larger = &mut ENTRIES[..4];
190
    unsafe static duplicate: [data::DataSym; 2] = undefined;
191
    set duplicate = [syms[0], syms[0]];
192
    try data::buildMap(&duplicate[..], &mut larger[..]) catch err {
193
        try testing::expect(err == data::Error::Symbol); set failed += 1;
194
    };
195
    try testing::expect(failed == 3);
196
    let length = try data::emitSection(items, &map, &e.labels, 0x80000000, &mut bytes[..], false)
197
        catch {
198
            throw testing::TestError::Failed;
199
        };
200
    try testing::expect(length == 8 and bytes[0] == 1);
201
}
202
203
/// Inline instruction selection respects every shorter output capacity.
204
@test unsafe fn inlineSelectionCapacity() throws (testing::TestError) {
205
    let dst = il::Reg { n: 3 };
206
    let first = il::Reg { n: 0 };
207
    let second = il::Reg { n: 1 };
208
    let a = il::Val::Reg(first);
209
    let b = il::Val::Reg(second);
210
    let instructions = [
211
        il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst, a, b },
212
        il::Instr::UnOp { op: il::UnOp::Neg, typ: il::Type::W32, dst, a },
213
        il::Instr::Load { typ: il::Type::W8, dst, src: first, offset: 4096 },
214
        il::Instr::Sload { typ: il::Type::W16, dst, src: first, offset: -4096 },
215
        il::Instr::Store { typ: il::Type::W32, src: a, dst: second, offset: 4096 },
216
        il::Instr::Copy { dst, val: il::Val::Imm(0x123456789abcdef) },
217
        il::Instr::Reserve { dst, size: il::Val::Imm(32), alignment: 16 },
218
        il::Instr::Reserve { dst, size: a, alignment: 16 },
219
        il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(0) },
220
        il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(8) },
221
        il::Instr::Blit { dst: first, src: second, size: il::Val::Imm(40) },
222
        il::Instr::Zext { typ: il::Type::W16, dst, val: a },
223
        il::Instr::Sext { typ: il::Type::W8, dst, val: a },
224
        il::Instr::Ret { val: a },
225
        il::Instr::Unreachable,
226
        il::Instr::Ecall { dst, num: a, a0: b, a1: a, a2: b, a3: a },
227
        il::Instr::DeviceRead { typ: il::Type::W64, dst, handle: a, offset: b },
228
        il::Instr::DeviceWrite { typ: il::Type::W8, handle: a, offset: b, value: a },
229
        il::Instr::Ebreak,
230
        il::Instr::MemoryFence,
231
    ];
232
    for instr in instructions {
233
        try checkSelectionCapacity(instr, nil);
234
    }
235
}
236
237
/// Direct and indirect calls check argument and output capacities.
238
@test unsafe fn callSelectionCapacity() throws (testing::TestError) {
239
    let a = il::Val::Reg(il::Reg { n: 0 });
240
    let b = il::Val::Reg(il::Reg { n: 1 });
241
    let args = [b, a, b, a, il::Val::Imm(7), a, b, a, a];
242
    for callee in [il::Val::FnAddr("p::callee"), a] {
243
        for dst in [nil as ?il::Reg, il::Reg { n: 3 }] {
244
            for count in [0 as u32, 8, 9] {
245
                let instr = il::Instr::Call {
246
                    retTy: il::Type::W64, dst, func: callee, args: &args[..count],
247
                };
248
                let expected: ?super::Error = super::Error::Capacity if count == 9 else nil;
249
                try checkSelectionCapacity(instr, expected);
250
            }
251
        }
252
    }
253
}
254
255
/// Switch cases and default edges respect every shorter output capacity.
256
@test unsafe fn switchSelectionCapacity() throws (testing::TestError) {
257
    for count in 0..3 {
258
        for argumentMask in 0..8 {
259
            try checkSwitchCapacity(count, argumentMask);
260
        }
261
    }
262
}
263
264
/// Build a switch with independently selected case and default arguments.
265
unsafe fn checkSwitchCapacity(count: u32, argumentMask: u32) throws (testing::TestError) {
266
    let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }];
267
    let firstParams = [il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }];
268
    let secondParams = [il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 }];
269
    let defaultParams = [il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 }];
270
    let firstCount: u32 = 1 if argumentMask & 1 <> 0 else 0;
271
    let secondCount: u32 = 1 if argumentMask & 2 <> 0 else 0;
272
    let defaultCount: u32 = 1 if argumentMask & 4 <> 0 else 0;
273
    let mut firstArgs = [il::Val::Imm(11)];
274
    let mut secondArgs = [il::Val::Imm(22)];
275
    let mut defaultArgs = [il::Val::Reg(il::Reg { n: 0 })];
276
    let mut cases = [
277
        il::SwitchCase { value: 0, target: 1, args: &mut firstArgs[..firstCount] },
278
        il::SwitchCase { value: 0x123456789abcdef, target: 2, args: &mut secondArgs[..secondCount] },
279
    ];
280
    let mut entry = [il::Instr::Switch {
281
        val: il::Val::Reg(il::Reg { n: 0 }), defaultTarget: 3,
282
        defaultArgs: &mut defaultArgs[..defaultCount], cases: &mut cases[..count],
283
    }];
284
    let mut first = [il::Instr::Ret {
285
        val: il::Val::Reg(il::Reg { n: 1 }) if firstCount > 0 else il::Val::Imm(11),
286
    }];
287
    let mut second = [il::Instr::Ret {
288
        val: il::Val::Reg(il::Reg { n: 2 }) if secondCount > 0 else il::Val::Imm(22),
289
    }];
290
    let mut fallback = [il::Instr::Ret {
291
        val: il::Val::Reg(il::Reg { n: 3 }) if defaultCount > 0 else il::Val::Imm(33),
292
    }];
293
    let predecessors = [0 as u32];
294
    let blocks = [
295
        il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 },
296
        il::Block {
297
            label: "first", params: &firstParams[..firstCount], instrs: &mut first[..],
298
            locs: &[], preds: &predecessors[..1 if count > 0 else 0], loopDepth: 0,
299
        },
300
        il::Block {
301
            label: "second", params: &secondParams[..secondCount], instrs: &mut second[..],
302
            locs: &[], preds: &predecessors[..1 if count > 1 else 0], loopDepth: 0,
303
        },
304
        il::Block {
305
            label: "default", params: &defaultParams[..defaultCount], instrs: &mut fallback[..],
306
            locs: &[], preds: &predecessors[..], loopDepth: 0,
307
        },
308
    ];
309
    let func = il::Fn {
310
        name: "p::switch", params: &params[..], returnType: il::Type::W64,
311
        isExtern: false, isLeaf: true, blocks: &blocks[..],
312
    };
313
    try checkFunctionCapacity(&func, nil);
314
}
315
316
/// Jumps preserve argument moves and capacity checks in both block layouts.
317
@test unsafe fn jumpSelectionCapacity() throws (testing::TestError) {
318
    for target in [1 as u32, 2] {
319
        for count in 0..3 {
320
            let params = [
321
                il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 },
322
                il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 },
323
            ];
324
            let targetParams = [
325
                il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 },
326
                il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 },
327
            ];
328
            let mut args = [il::Val::Reg(il::Reg { n: 1 }), il::Val::Reg(il::Reg { n: 0 })];
329
            let mut entry = [il::Instr::Jmp { target, args: &mut args[..count] }];
330
            let mut body = [il::Instr::Ret {
331
                val: il::Val::Reg(il::Reg { n: count + 1 }) if count > 0 else il::Val::Imm(7),
332
            }];
333
            let mut unused = [il::Instr::Ret { val: il::Val::Imm(0) }];
334
            let destination = il::Block {
335
                label: "destination", params: &targetParams[..count], instrs: &mut body[..],
336
                locs: &[], preds: &[0], loopDepth: 0,
337
            };
338
            let other = il::Block {
339
                label: "other", params: &[], instrs: &mut unused[..],
340
                locs: &[], preds: &[], loopDepth: 0,
341
            };
342
            let blocks = [
343
                il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 },
344
                destination if target == 1 else other,
345
                other if target == 1 else destination,
346
            ];
347
            let func = il::Fn {
348
                name: "p::jump", params: &params[..], returnType: il::Type::W64,
349
                isExtern: false, isLeaf: true, blocks: &blocks[..],
350
            };
351
            try checkFunctionCapacity(&func, nil);
352
        }
353
    }
354
}
355
356
/// Conditional edges preserve capacity checks for each comparison and layout.
357
@test unsafe fn branchSelectionCapacity() throws (testing::TestError) {
358
    for op in [il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] {
359
        for typ in [il::Type::W8, il::Type::W16, il::Type::W32, il::Type::W64] {
360
            for layout in 0..2 {
361
                for argumentSide in 0..3 {
362
                    try checkBranchCapacity(op, typ, layout, argumentSide);
363
                }
364
            }
365
        }
366
    }
367
}
368
369
/// Build a branch with no arguments or arguments on exactly one edge.
370
unsafe fn checkBranchCapacity(op: il::CmpOp, typ: il::Type, layout: u32, argumentSide: u32)
371
    throws (testing::TestError)
372
{
373
    let a = il::Val::Reg(il::Reg { n: 0 });
374
    let b = il::Val::Reg(il::Reg { n: 1 });
375
    let params = [
376
        il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 },
377
        il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 },
378
    ];
379
    let thenParams = [il::Param { value: il::Reg { n: 2 }, type: il::Type::W64 }];
380
    let elseParams = [il::Param { value: il::Reg { n: 3 }, type: il::Type::W64 }];
381
    let thenCount: u32 = 1 if argumentSide == 1 else 0;
382
    let elseCount: u32 = 1 if argumentSide == 2 else 0;
383
    let mut thenArgs = [b];
384
    let mut elseArgs = [a];
385
    let mut entry = [il::Instr::Br {
386
        op, typ, a, b: il::Val::Imm(0) if argumentSide == 1 else b,
387
        thenTarget: 1 if layout == 0 else 2, thenArgs: &mut thenArgs[..thenCount],
388
        elseTarget: 2 if layout == 0 else 1, elseArgs: &mut elseArgs[..elseCount],
389
    }];
390
    let mut thenBody = [il::Instr::Ret {
391
        val: il::Val::Reg(il::Reg { n: 2 }) if thenCount > 0 else il::Val::Imm(10),
392
    }];
393
    let mut elseBody = [il::Instr::Ret {
394
        val: il::Val::Reg(il::Reg { n: 3 }) if elseCount > 0 else il::Val::Imm(20),
395
    }];
396
    let thenBlock = il::Block {
397
        label: "then", params: &thenParams[..thenCount], instrs: &mut thenBody[..],
398
        locs: &[], preds: &[0], loopDepth: 0,
399
    };
400
    let elseBlock = il::Block {
401
        label: "else", params: &elseParams[..elseCount], instrs: &mut elseBody[..],
402
        locs: &[], preds: &[0], loopDepth: 0,
403
    };
404
    let blocks = [
405
        il::Block { label: "entry", params: &[], instrs: &mut entry[..], locs: &[], preds: &[], loopDepth: 0 },
406
        thenBlock if layout == 0 else elseBlock,
407
        elseBlock if layout == 0 else thenBlock,
408
    ];
409
    let func = il::Fn {
410
        name: "p::branch", params: &params[..], returnType: il::Type::W64,
411
        isExtern: false, isLeaf: true, blocks: &blocks[..],
412
    };
413
    try checkFunctionCapacity(&func, nil);
414
}
415
416
/// Verify instruction errors, exact-fit output, and shorter-buffer canaries.
417
unsafe fn checkSelectionCapacity(instr: il::Instr, expectedError: ?super::Error) throws (testing::TestError) {
418
    let mut body = [instr, il::Instr::Ret { val: il::Val::Imm(0) }];
419
    let mut count: u32 = 2;
420
    if let case il::Instr::Ret { .. } = instr {
421
        set count = 1;
422
    }
423
    let params = [
424
        il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 },
425
        il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 },
426
    ];
427
    let func = il::Fn {
428
        name: "p::inline", params: &params[..], returnType: il::Type::W64,
429
        isExtern: false, isLeaf: not il::isCall(instr),
430
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..count], locs: &[], preds: &[], loopDepth: 0 }],
431
    };
432
    try checkFunctionCapacity(&func, expectedError);
433
}
434
435
/// Verify function output capacity and failure propagation with guard words.
436
unsafe fn checkFunctionCapacity(func: &il::Fn, expectedError: ?super::Error) throws (testing::TestError) {
437
    let mut arena = alloc::new(&mut MEMORY[..]);
438
    let mut gen = generator(&mut arena);
439
    let mut scratch = alloc::new(&mut SCRATCH[..]);
440
    super::generateFunction(&mut gen, func, &mut scratch);
441
    assert gen.e.error == expectedError;
442
    if expectedError <> nil {
443
        let count = gen.e.codeLen;
444
        emit::emit(&mut gen.e, encode::nop());
445
        assert gen.e.codeLen == count;
446
        return;
447
    }
448
    let length = gen.e.codeLen;
449
    let mut expected: [u32; 128] = [0; 128];
450
    assert length > 0 and length <= expected.len;
451
    for i in 0..length {
452
        set expected[i] = gen.e.code[i];
453
    }
454
    for capacity in 0..(length + 1) {
455
        let words = &mut SELECTION_WORDS[..];
456
        for i in 0..words.len {
457
            set words[i] = 0xdeadbeef;
458
        }
459
        alloc::reset(&mut arena);
460
        set gen = generator(&mut arena);
461
        set gen.e.code = &mut words[1..capacity + 1];
462
        super::generateFunction(&mut gen, func, &mut scratch);
463
        assert gen.e.codeLen <= capacity;
464
        assert words[0] == 0xdeadbeef;
465
        for i in (capacity + 1)..words.len {
466
            assert words[i] == 0xdeadbeef;
467
        }
468
        if capacity == length {
469
            assert gen.e.error == nil;
470
            assert gen.e.codeLen == length;
471
            for i in 0..length {
472
                assert words[i + 1] == expected[i];
473
            }
474
        } else {
475
            assert gen.e.error == super::Error::Capacity;
476
        }
477
    }
478
}
479
480
/// Name the failed invariant before returning to the test runner.
481
fn check(condition: bool, name: *[u8]) throws (testing::TestError) {
482
    if not condition {
483
        io::printLn(name);
484
        throw testing::TestError::Failed;
485
    }
486
}