Use `assert` keyword throughout compiler

7a8ab6ad4fb655460a4afe56c055efc1b4d73b4322bae437a51d56884d6d25e0
Alexis Sellier committed ago 1 parent 16d67a26
lib/std.rad +0 -1
2 2
3 3
pub mod io;
4 4
pub mod collections;
5 5
pub mod lang;
6 6
pub mod sys;
7 -
pub mod debug;
8 7
pub mod arch;
9 8
pub mod fmt;
10 9
pub mod mem;
11 10
pub mod vec;
12 11
pub mod intrinsics;
lib/std/arch/rv64.rad +1 -2
16 16
pub mod isel;
17 17
pub mod printer;
18 18
19 19
@test mod tests;
20 20
21 -
use std::debug;
22 21
use std::mem;
23 22
use std::lang::il;
24 23
use std::lang::alloc;
25 24
use std::lang::gen::labels;
26 25
use std::lang::gen::regalloc;
66 65
pub const T5:   Reg = { n: 30 };  /// Temporary.
67 66
pub const T6:   Reg = { n: 31 };  /// Temporary.
68 67
69 68
/// Create a register from a number. Panics if `n > 31`.
70 69
pub fn reg(n: u8) -> Reg {
71 -
    debug::check(n < 32);
70 +
    assert n < 32;
72 71
    return Reg { n };
73 72
}
74 73
75 74
////////////////////////////
76 75
// Architecture constants //
lib/std/arch/rv64/emit.rad +2 -3
1 1
//! RV64 binary emission.
2 2
//!
3 3
//! Emits RV64 machine code as `u32` list.
4 4
5 -
use std::debug;
6 5
use std::lang::il;
7 6
use std::lang::alloc;
8 7
use std::lang::gen::labels;
9 8
use std::collections::dict;
10 9
use std::mem;
232 231
    e.code[index] = instr;
233 232
}
234 233
235 234
/// Record a block's address for branch resolution.
236 235
pub fn recordBlock(e: *mut Emitter, blockIdx: u32) {
237 -
    debug::check(e.codeLen <= MAX_CODE_LEN);
236 +
    assert e.codeLen <= MAX_CODE_LEN;
238 237
    labels::recordBlock(&mut e.labels, blockIdx, e.codeLen as i32 * super::INSTR_SIZE);
239 238
}
240 239
241 240
/// Record a function's code offset for call resolution.
242 241
pub fn recordFuncOffset(e: *mut Emitter, name: *[u8]) {
243 -
    debug::check(e.codeLen <= MAX_CODE_LEN);
242 +
    assert e.codeLen <= MAX_CODE_LEN;
244 243
    dict::insert(&mut e.labels.funcs, name, e.codeLen as i32 * super::INSTR_SIZE);
245 244
}
246 245
247 246
/// Record a function's start position for printing.
248 247
pub fn recordFunc(e: *mut Emitter, name: *[u8]) {
lib/std/arch/rv64/encode.rad +10 -12
1 1
//! RISC-V RV64I+M instruction encoding.
2 2
//!
3 3
//! Provides type-safe functions for encoding RV64 instructions.
4 4
5 -
use std::debug;
6 -
7 5
//////////////////////
8 6
// Opcode Constants //
9 7
//////////////////////
10 8
11 9
pub const OP_LOAD:   u32 = 0x03;
104 102
         | ((funct7       & 0x7F) << 25);
105 103
}
106 104
107 105
/// Encode an I-type instruction.
108 106
fn encodeI(opcode: u32, rd: super::Reg, rs1: super::Reg, funct3: u32, imm: i32) -> u32 {
109 -
    debug::check(isSmallImm(imm));
107 +
    assert isSmallImm(imm);
110 108
111 109
    return (opcode        & 0x7F)
112 110
         | ((rd.n  as u32 & 0x1F)  << 7)
113 111
         | ((funct3       & 0x07)  << 12)
114 112
         | ((rs1.n as u32 & 0x1F)  << 15)
115 113
         | ((imm as u32   & 0xFFF) << 20);
116 114
}
117 115
118 116
/// Encode an S-type instruction.
119 117
fn encodeS(opcode: u32, rs1: super::Reg, rs2: super::Reg, funct3: u32, imm: i32) -> u32 {
120 -
    debug::check(isSmallImm(imm));
118 +
    assert isSmallImm(imm);
121 119
122 120
    return (opcode  & 0x7F)
123 121
         | ((imm as u32        & 0x1F) << 7)
124 122
         | ((funct3            & 0x07) << 12)
125 123
         | ((rs1.n as u32      & 0x1F) << 15)
127 125
         | ((imm as u32 >> 5   & 0x7F) << 25);
128 126
}
129 127
130 128
/// Encode a B-type (branch) instruction.
131 129
fn encodeB(opcode: u32, rs1: super::Reg, rs2: super::Reg, funct3: u32, imm: i32) -> u32 {
132 -
    debug::check(isBranchImm(imm));
130 +
    assert isBranchImm(imm);
133 131
134 132
    let imm11   = (imm as u32 >> 11) & 0x1;
135 133
    let imm4_1  = (imm as u32 >> 1)  & 0xF;
136 134
    let imm10_5 = (imm as u32 >> 5)  & 0x3F;
137 135
    let imm12   = (imm as u32 >> 12) & 0x1;
153 151
         | ((imm as u32  & 0xFFFFF) << 12);
154 152
}
155 153
156 154
/// Encode a J-type (jump) instruction.
157 155
fn encodeJ(opcode: u32, rd: super::Reg, imm: i32) -> u32 {
158 -
    debug::check(isJumpImm(imm));
156 +
    assert isJumpImm(imm);
159 157
160 158
    let imm20    = (imm as u32 >> 20) & 0x1;
161 159
    let imm10_1  = (imm as u32 >> 1)  & 0x3FF;
162 160
    let imm11    = (imm as u32 >> 11) & 0x1;
163 161
    let imm19_12 = (imm as u32 >> 12) & 0xFF;
204 202
    return encodeI(OP_IMM, rd, rs1, F3_AND, imm);
205 203
}
206 204
207 205
/// Shift left logical immediate: `rd = rs1 << shamt`.
208 206
pub fn slli(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
209 -
    debug::check(shamt >= 0 and shamt < 64);
207 +
    assert shamt >= 0 and shamt < 64;
210 208
    return encodeI(OP_IMM, rd, rs1, F3_SLL, shamt & 0x3F);
211 209
}
212 210
213 211
/// Shift right logical immediate: `rd = rs1 >> shamt` (zero-extend).
214 212
pub fn srli(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
215 -
    debug::check(shamt >= 0 and shamt < 64);
213 +
    assert shamt >= 0 and shamt < 64;
216 214
    return encodeI(OP_IMM, rd, rs1, F3_SRL, shamt & 0x3F);
217 215
}
218 216
219 217
/// Shift right arithmetic immediate: `rd = rs1 >> shamt` (sign-extend).
220 218
pub fn srai(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
221 -
    debug::check(shamt >= 0 and shamt < 64);
219 +
    assert shamt >= 0 and shamt < 64;
222 220
    // SRAI has bit 10 set in immediate field (becomes bit 30 in instruction)
223 221
    return encodeI(OP_IMM, rd, rs1, F3_SRL, (shamt & 0x3F) | 0b10000000000);
224 222
}
225 223
226 224
///////////////////////////
330 328
    return encodeI(OP_IMM32, rd, rs1, F3_ADD, imm);
331 329
}
332 330
333 331
/// Shift left logical immediate word: `rd = sign_ext((rs1 << shamt)[31:0])`.
334 332
pub fn slliw(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
335 -
    debug::check(shamt >= 0 and shamt < 32);
333 +
    assert shamt >= 0 and shamt < 32;
336 334
    return encodeI(OP_IMM32, rd, rs1, F3_SLL, shamt & 0x1F);
337 335
}
338 336
339 337
/// Shift right logical immediate word: `rd = sign_ext((rs1[31:0] >> shamt))`.
340 338
pub fn srliw(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
341 -
    debug::check(shamt >= 0 and shamt < 32);
339 +
    assert shamt >= 0 and shamt < 32;
342 340
    return encodeI(OP_IMM32, rd, rs1, F3_SRL, shamt & 0x1F);
343 341
}
344 342
345 343
/// Shift right arithmetic immediate word: `rd = sign_ext((rs1[31:0] >> shamt))` (sign-extended).
346 344
pub fn sraiw(rd: super::Reg, rs1: super::Reg, shamt: i32) -> u32 {
347 -
    debug::check(shamt >= 0 and shamt < 32);
345 +
    assert shamt >= 0 and shamt < 32;
348 346
    return encodeI(OP_IMM32, rd, rs1, F3_SRL, (shamt & 0x1F) | 0b10000000000);
349 347
}
350 348
351 349
/// Add word: `rd = sign_ext((rs1 + rs2)[31:0])`.
352 350
pub fn addw(rd: super::Reg, rs1: super::Reg, rs2: super::Reg) -> u32 {
lib/std/arch/rv64/isel.rad +1 -2
26 26
//!   loadVal(rd, val) -> Reg
27 27
//!     Force an [`il::Val`] into a specific register `rd`. Built on [`resolveVal`] + [`emitMv`].
28 28
//!     Used when the instruction requires the value in `rd` (e.g. `sub rd, rd, rs2`).
29 29
30 30
use std::mem;
31 -
use std::debug;
32 31
use std::lang::il;
33 32
use std::lang::gen::regalloc;
34 33
use std::lang::gen::labels;
35 34
36 35
use super::encode;
389 388
390 389
                    emit::emit(s.e, encode::sub(super::SP, super::SP, rs));
391 390
392 391
                    if alignment > 1 {
393 392
                        let mask = 0 - alignment as i32;
394 -
                        debug::check(encode::isSmallImm(mask));
393 +
                        assert encode::isSmallImm(mask);
395 394
396 395
                        emit::emit(s.e, encode::andi(super::SP, super::SP, mask));
397 396
                    }
398 397
                    emit::emit(s.e, encode::mv(rd, super::SP));
399 398
                },
lib/std/arch/rv64/tests/assert.false.rad added +6 -0
1 +
//! returns: 133
2 +
//! Test that assert with a false condition triggers EBREAK.
3 +
@default fn main() -> i32 {
4 +
    assert false;
5 +
    return 0;
6 +
}
lib/std/arch/rv64/tests/assert.true.rad added +8 -0
1 +
2 +
@default fn main() -> i32 {
3 +
    assert true;
4 +
    assert 1 == 1;
5 +
    assert 5 > 3;
6 +
7 +
    return 0;
8 +
}
lib/std/arch/rv64/tests/debug.assert.true.rad deleted +0 -8
1 -
2 -
@default fn main() -> i32 {
3 -
    if not (true) { panic "assert"; }
4 -
    if not (1 == 1) { panic "assert"; }
5 -
    if not (5 > 3) { panic "assert"; }
6 -
7 -
    return 0;
8 -
}
lib/std/arch/rv64/tests/error.try.catch.binding.rad +6 -7
1 1
2 -
3 2
union TestError { Boom, Bust }
4 3
5 4
@default fn main() -> u32 {
6 5
    // Test catching and using the error value.
7 6
    let mut caught: u32 = 0;
10 9
            caught = 1;
11 10
        } else {
12 11
            caught = 2;
13 12
        }
14 13
    };
15 -
    if not (caught == 1) { panic "assert"; }
14 +
    assert caught == 1;
16 15
17 16
    // Test catching a different error variant.
18 17
    caught = 0;
19 18
    try failWithBust() catch e {
20 19
        if (e == TestError::Bust) {
21 20
            caught = 10;
22 21
        } else {
23 22
            caught = 20;
24 23
        }
25 24
    };
26 -
    if not (caught == 10) { panic "assert"; }
25 +
    assert caught == 10;
27 26
28 27
    // Test that success path doesn't execute catch block.
29 28
    caught = 0;
30 29
    try succeed() catch e {
31 30
        caught = 99;
32 31
    };
33 -
    if not (caught == 0) { panic "assert"; }
32 +
    assert caught == 0;
34 33
35 34
    // Test catch block that returns early from function.
36 35
    let early: u32 = testCatchReturn();
37 -
    if not (early == 100) { panic "assert"; }
36 +
    assert early == 100;
38 37
39 38
    // Test using success value when catch diverges.
40 39
    let success: u32 = testCatchDiverges();
41 -
    if not (success == 77) { panic "assert"; }
40 +
    assert success == 77;
42 41
43 42
    // Test error value is correctly bound.
44 43
    let errVal: u32 = testErrorValue();
45 -
    if not (errVal == 1) { panic "assert"; }
44 +
    assert errVal == 1;
46 45
47 46
    return 0;
48 47
}
49 48
50 49
fn failWithBoom() throws (TestError) {
lib/std/arch/rv64/tests/debug.assert.false.rad → lib/std/arch/rv64/tests/panic.rad renamed +0 -0
lib/std/arch/rv64/tests/string.escape.rad +7 -21
1 1
//! Test string escape sequences.
2 2
@default fn main() -> i32 {
3 3
    let s1: *[u8] = "Hello\tWorld!\n";
4 -
    if s1.len != 13 {
5 -
        panic;
6 -
    }
4 +
    assert s1.len == 13;
7 5
8 6
    let s2: *[u8] = "\"";
9 -
    if s2.len != 1 {
10 -
        panic;
11 -
    }
12 -
    if s2[0] != '"' {
13 -
        panic;
14 -
    }
7 +
    assert s2.len == 1;
8 +
    assert s2[0] == '"';
15 9
16 10
    let s3: *[u8] = "\"\\\"";
17 -
    if s3.len != 3 {
18 -
        panic;
19 -
    }
20 -
    if s3[0] != '"' {
21 -
        panic;
22 -
    }
23 -
    if s3[1] != '\\' {
24 -
        panic;
25 -
    }
26 -
    if s3[2] != '"' {
27 -
        panic;
28 -
    }
11 +
    assert s3.len == 3;
12 +
    assert s3[0] == '"';
13 +
    assert s3[1] == '\\';
14 +
    assert s3[2] == '"';
29 15
    return 0;
30 16
}
lib/std/arch/rv64/tests/var.infer.rad +5 -6
1 1
2 -
3 2
record Pair {
4 3
    left: bool,
5 4
    right: bool,
6 5
}
7 6
17 16
    return 5;
18 17
}
19 18
20 19
@default fn main() -> i32 {
21 20
    let inferredFlag = alwaysTrue();
22 -
    if not (inferredFlag) { panic "assert"; }
21 +
    assert inferredFlag;
23 22
24 23
    let mut counter = returnsCount();
25 24
    counter += 1;
26 -
    if not (counter == 6) { panic "assert"; }
25 +
    assert counter == 6;
27 26
28 27
    let pair = loadPair();
29 28
    let left = pair.left;
30 29
31 -
    if not (left) { panic "assert"; }
32 -
    if not (not pair.right) { panic "assert"; }
30 +
    assert left;
31 +
    assert not pair.right;
33 32
34 33
    let copy = inferredFlag;
35 -
    if not (copy) { panic "assert"; }
34 +
    assert copy;
36 35
37 36
    return 0;
38 37
}
lib/std/debug.rad deleted +0 -14
1 -
// TODO: Re-export `core::debug::*`.
2 -
use super::intrinsics;
3 -
4 -
/// Check that a condition is true, otherwise trigger a breakpoint.
5 -
pub fn check(cond: bool) {
6 -
    if not cond {
7 -
        intrinsics::ebreak();
8 -
    }
9 -
}
10 -
11 -
/// Check that a condition is true with a message, otherwise trigger a breakpoint.
12 -
pub fn checkMsg(cond: bool, msg: *[u8]) {
13 -
    check(cond);
14 -
}
lib/std/fmt.rad +4 -5
1 1
//! Formatting utilities for converting values to strings.
2 2
use super::mem;
3 -
use super::debug;
4 3
5 4
/// Maximum string length for a formatted u32 (eg. "4294967295").
6 5
pub const U32_STR_LEN: u32 = 10;
7 6
/// Maximum string length for a formatted i32 (eg. "-2147483648").
8 7
pub const I32_STR_LEN: u32 = 11;
13 12
/// Maximum string length for a formatted bool (eg. "false").
14 13
pub const BOOL_STR_LEN: u32 = 5;
15 14
16 15
/// Format a u32 by writing it to the provided buffer.
17 16
pub fn formatU32(val: u32, buffer: *mut [u8]) -> *[u8] {
18 -
    debug::check(buffer.len >= U32_STR_LEN);
17 +
    assert buffer.len >= U32_STR_LEN;
19 18
20 19
    let mut x: u32 = val;
21 20
    let mut i: u32 = buffer.len;
22 21
23 22
    // Handle the zero case separately to ensure a single '0' is written.
37 36
    return &buffer[i..];
38 37
}
39 38
40 39
/// Format a i32 by writing it to the provided buffer.
41 40
pub fn formatI32(val: i32, buffer: *mut [u8]) -> *[u8] {
42 -
    debug::check(buffer.len >= I32_STR_LEN);
41 +
    assert buffer.len >= I32_STR_LEN;
43 42
44 43
    let neg: bool = val < 0;
45 44
    let mut x: u32 = -val as u32 if neg else val as u32;
46 45
    let mut i: u32 = buffer.len;
47 46
    // Handle the zero case separately to ensure a single '0' is written.
64 63
    return &buffer[i..];
65 64
}
66 65
67 66
/// Format a u64 by writing it to the provided buffer.
68 67
pub fn formatU64(val: u64, buffer: *mut [u8]) -> *[u8] {
69 -
    debug::check(buffer.len >= U64_STR_LEN);
68 +
    assert buffer.len >= U64_STR_LEN;
70 69
71 70
    let mut x: u64 = val;
72 71
    let mut i: u32 = buffer.len;
73 72
74 73
    if x == 0 {
84 83
    return &buffer[i..];
85 84
}
86 85
87 86
/// Format a i64 by writing it to the provided buffer.
88 87
pub fn formatI64(val: i64, buffer: *mut [u8]) -> *[u8] {
89 -
    debug::check(buffer.len >= I64_STR_LEN);
88 +
    assert buffer.len >= I64_STR_LEN;
90 89
91 90
    let neg: bool = val < 0;
92 91
    let mut x: u64 = -val as u64 if neg else val as u64;
93 92
    let mut i: u32 = buffer.len;
94 93
    if x == 0 {
lib/std/lang/alloc.rad +2 -3
4 4
//! byte buffer. Memory is never freed individually - the entire arena is
5 5
//! reset at once. This is ideal for compiler passes where all allocations
6 6
//! have the same lifetime.
7 7
@test mod tests;
8 8
9 -
use std::debug;
10 9
use std::mem;
11 10
12 11
/// Error thrown by allocator.
13 12
pub union AllocError {
14 13
    /// Allocator is out of memory.
35 34
///
36 35
/// Returns an opaque pointer to the allocated memory. Throws `AllocError` if
37 36
/// the arena is exhausted. The caller is responsible for casting to the
38 37
/// appropriate type and initializing the memory.
39 38
pub fn alloc(arena: *mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
40 -
    debug::check(alignment > 0);
41 -
    debug::check(size > 0);
39 +
    assert alignment > 0;
40 +
    assert size > 0;
42 41
43 42
    let aligned = mem::alignUp(arena.offset, alignment);
44 43
    let newOffset = aligned + size;
45 44
46 45
    if newOffset > arena.data.len as u32 {
lib/std/lang/ast.rad +1 -2
1 1
//! Radiance AST modules.
2 2
pub mod printer;
3 3
4 4
use std::io;
5 -
use std::debug;
6 5
use std::lang::alloc;
7 6
8 7
/// Maximum number of trait methods.
9 8
pub const MAX_TRAIT_METHODS: u32 = 8;
10 9
107 106
    return self.list.len;
108 107
}
109 108
110 109
/// Fetch the attribute node at the specified index.
111 110
pub fn attributesGet(self: *Attributes, index: u32) -> *Node {
112 -
    debug::checkMsg(index < self.list.len, "attributesGet: invalid index");
111 +
    assert index < self.list.len, "attributesGet: invalid index";
113 112
    return self.list.list[index];
114 113
}
115 114
116 115
/// Check if an attributes list contains an attribute.
117 116
pub fn attributesContains(self: *Attributes, attr: Attribute) -> bool {
lib/std/lang/lower.rad +13 -14
85 85
//!       โ†“
86 86
//!   il::DataValue
87 87
//!       โ†“
88 88
//!   il::Data
89 89
//!
90 -
use std::debug;
91 90
use std::fmt;
92 91
use std::io;
93 92
use std::lang::alloc;
94 93
use std::mem;
95 94
use std::lang::ast;
1040 1039
    } else {
1041 1040
        func.returnType = ilType(self, *fnType.returnType);
1042 1041
    }
1043 1042
    let body = decl.body else {
1044 1043
        // Extern functions have no body.
1045 -
        debug::check(isExtern);
1044 +
        assert isExtern;
1046 1045
        return func;
1047 1046
    };
1048 1047
    func.blocks = try lowerFnBody(&mut fnLow, body);
1049 1048
1050 1049
    return func;
2112 2111
    switchToBlock(self, target);
2113 2112
}
2114 2113
2115 2114
/// Emit a conditional branch based on `cond`.
2116 2115
fn emitBr(self: *mut FnLowerer, cond: il::Reg, thenBlock: BlockId, elseBlock: BlockId) throws (LowerError) {
2117 -
    debug::check(thenBlock != elseBlock);
2116 +
    assert thenBlock != elseBlock;
2118 2117
    emit(self, il::Instr::Br {
2119 2118
        op: il::CmpOp::Ne,
2120 2119
        typ: il::Type::W32,
2121 2120
        a: il::Val::Reg(cond),
2122 2121
        b: il::Val::Imm(0),
2137 2136
    a: il::Val,
2138 2137
    b: il::Val,
2139 2138
    thenBlock: BlockId,
2140 2139
    elseBlock: BlockId
2141 2140
) throws (LowerError) {
2142 -
    debug::check(thenBlock != elseBlock);
2141 +
    assert thenBlock != elseBlock;
2143 2142
    emit(self, il::Instr::Br {
2144 2143
        op, typ, a, b,
2145 2144
        thenTarget: thenBlock.n, thenArgs: &mut [],
2146 2145
        elseTarget: elseBlock.n, elseArgs: &mut [],
2147 2146
    });
2380 2379
2381 2380
                try emitBr(self, eqReg, matchBlock, fallthrough);
2382 2381
            }
2383 2382
        }
2384 2383
        case MatchSubjectKind::Union(unionInfo) => {
2385 -
            debug::check(not isNil);
2384 +
            assert not isNil;
2386 2385
2387 2386
            let case resolver::NodeExtra::UnionVariant { tag: variantTag, .. } =
2388 2387
                resolver::nodeData(self.low.resolver, pattern).extra
2389 2388
            else {
2390 2389
                throw LowerError::ExpectedVariant;
2408 2407
2409 2408
                try emitBrCmp(self, il::CmpOp::Eq, il::Type::W8, il::Val::Reg(tagReg), il::Val::Imm(variantTag as i64), matchBlock, fallthrough);
2410 2409
            }
2411 2410
        }
2412 2411
        else => { // Scalar comparison.
2413 -
            debug::check(not isNil);
2412 +
            assert not isNil;
2414 2413
            let pattVal = try lowerExpr(self, pattern);
2415 2414
            try emitBrCmp(self, il::CmpOp::Eq, subject.ilType, subject.val, pattVal, matchBlock, fallthrough);
2416 2415
        }
2417 2416
    }
2418 2417
}
2425 2424
    subject: *MatchSubject,
2426 2425
    patterns: ast::NodeList,
2427 2426
    matchBlock: BlockId,
2428 2427
    fallthrough: BlockId
2429 2428
) throws (LowerError) {
2430 -
    debug::check(patterns.len > 0);
2429 +
    assert patterns.len > 0;
2431 2430
2432 2431
    for i in 0..(patterns.len - 1) {
2433 2432
        let pattern = patterns.list[i];
2434 2433
        let nextArm = try createBlock(self, "arm");
2435 2434
        try emitPatternMatch(self, subject, pattern, matchBlock, nextArm);
2735 2734
/// Define (write) a variable. Record the SSA value of a variable in the
2736 2735
/// current block. Called when a variable is assigned or initialized (`let`
2737 2736
/// bindings, assignments, loop updates). When [`useVar`] is later called,
2738 2737
/// it will retrieve this value.
2739 2738
fn defVar(self: *mut FnLowerer, v: Var, val: il::Val) {
2740 -
    debug::check(v.id < self.varsLen);
2739 +
    assert v.id < self.varsLen;
2741 2740
    getBlockMut(self, currentBlock(self)).vars[v.id] = val;
2742 2741
}
2743 2742
2744 2743
/// Use (read) the current value of a variable in the current block.
2745 2744
/// May insert block parameters if the value must come from predecessors.
2752 2751
/// Given a variable and a block where it's used, this function finds the
2753 2752
/// correct [`il::Val`] that holds the variable's value at that program point.
2754 2753
/// When control flow merges from multiple predecessors with different
2755 2754
/// definitions, it creates a block parameter to unify them.
2756 2755
fn useVarInBlock(self: *mut FnLowerer, block: BlockId, v: Var) -> il::Val throws (LowerError) {
2757 -
    debug::check(v.id < self.varsLen);
2756 +
    assert v.id < self.varsLen;
2758 2757
2759 2758
    let blk = getBlockMut(self, block);
2760 2759
    if let val = blk.vars[v.id] {
2761 2760
        return val;
2762 2761
    }
2819 2818
    self.varsLen = savedVarsLen;
2820 2819
}
2821 2820
2822 2821
/// Get the metadata for a variable.
2823 2822
fn getVar(self: *FnLowerer, v: Var) -> *VarData {
2824 -
    debug::check(v.id < self.varsLen);
2823 +
    assert v.id < self.varsLen;
2825 2824
    return &self.vars[v.id];
2826 2825
}
2827 2826
2828 2827
/// Create a block parameter to merge a variable's value from multiple
2829 2828
/// control flow paths.
3051 3050
    let offset: u32 = 1 if self.returnReg != nil else 0;
3052 3051
    let totalLen = fnType.paramTypesLen + offset;
3053 3052
    if totalLen == 0 {
3054 3053
        return &[];
3055 3054
    }
3056 -
    debug::check(fnType.paramTypesLen <= resolver::MAX_FN_PARAMS);
3055 +
    assert fnType.paramTypesLen <= resolver::MAX_FN_PARAMS;
3057 3056
3058 3057
    let params = try! alloc::allocSlice(
3059 3058
        self.low.arena, @sizeOf(il::Param), @alignOf(il::Param), totalLen
3060 3059
    ) as *mut [il::Param];
3061 3060
3487 3486
///       jmp else#0;                     // guard failed, fallthrough to `else`
3488 3487
///   else#0:
3489 3488
///       ret 0;                          // `else` body
3490 3489
///
3491 3490
fn lowerMatch(self: *mut FnLowerer, node: *ast::Node, m: ast::Match) throws (LowerError) {
3492 -
    debug::check(m.prongs.len > 0);
3491 +
    assert m.prongs.len > 0;
3493 3492
3494 3493
    let prongs = &m.prongs;
3495 3494
    // Lower the subject expression once; reused across all arms.
3496 3495
    let subject = try lowerMatchSubject(self, m.subject);
3497 3496
    // Merge block created lazily if any arm needs it (i.e., doesn't diverge).
4406 4405
    let tagBlock = try createBlock(self, "eq#tag");
4407 4406
4408 4407
    // Compare tags: if they differ, jump to merge with `false`; otherwise check payloads.
4409 4408
    let falseArgs = try allocVal(self, il::Val::Imm(0));
4410 4409
4411 -
    debug::check(tagBlock != mergeBlock);
4410 +
    assert tagBlock != mergeBlock;
4412 4411
4413 4412
    // TODO: Use the helper once the compiler supports more than eight function params.
4414 4413
    emit(self, il::Instr::Br {
4415 4414
        op: il::CmpOp::Eq, typ: il::Type::W8, a: tagA, b: tagB,
4416 4415
        thenTarget: tagBlock.n, thenArgs: &mut [],
5474 5473
    try emitRetVal(self, val);
5475 5474
}
5476 5475
5477 5476
/// Lower a throw statement.
5478 5477
fn lowerThrowStmt(self: *mut FnLowerer, expr: *ast::Node) throws (LowerError) {
5479 -
    debug::check(self.fnType.throwListLen > 0);
5478 +
    assert self.fnType.throwListLen > 0;
5480 5479
5481 5480
    let errType = resolver::typeFor(self.low.resolver, expr) else {
5482 5481
        throw LowerError::MissingType(expr);
5483 5482
    };
5484 5483
    let tag = getOrAssignErrorTag(self.low, errType) as i64;
lib/std/lang/module.rad +7 -8
7 7
pub mod printer;
8 8
9 9
@test mod tests;
10 10
11 11
use std::mem;
12 -
use std::debug;
13 12
use std::lang::alloc;
14 13
use std::lang::ast;
15 14
use std::lang::strings;
16 15
17 16
/// Maximum number of modules tracked in a single compilation graph.
149 148
    graph: *mut ModuleGraph,
150 149
    parentId: u16,
151 150
    name: *[u8],
152 151
    filePath: *[u8]
153 152
) -> u16 throws (ModuleError) {
154 -
    debug::checkMsg(name.len > 0, "registerChild: name must not be empty");
155 -
    debug::checkMsg(filePath.len > 0, "registerChild: file path must not be empty");
153 +
    assert name.len > 0, "registerChild: name must not be empty";
154 +
    assert filePath.len > 0, "registerChild: file path must not be empty";
156 155
157 156
    let parent = getMut(graph, parentId)
158 157
        else throw ModuleError::NotFound(parentId);
159 158
160 159
    // If the child already exists under this parent, return it.
193 192
    return &mut graph.entries[id as u32];
194 193
}
195 194
196 195
/// Return the identifier of the child stored at `index`.
197 196
pub fn childAt(m: *ModuleEntry, index: u32) -> u16 {
198 -
    debug::checkMsg(index < m.childrenLen, "childAt: index must be valid");
197 +
    assert index < m.childrenLen, "childAt: index must be valid";
199 198
    return m.children[index];
200 199
}
201 200
202 201
/// Public accessor for a module's directory prefix.
203 202
pub fn moduleDir(m: *ModuleEntry) -> *[u8] {
204 203
    return &m.filePath[..m.dirLen];
205 204
}
206 205
207 206
/// Public accessor to the logical module path segments.
208 207
pub fn moduleQualifiedPath(m: *ModuleEntry) -> *[*[u8]] {
209 -
    debug::checkMsg(m.pathDepth > 0, "moduleQualifiedPath: path must not be empty");
208 +
    assert m.pathDepth > 0, "moduleQualifiedPath: path must not be empty";
210 209
    return &m.path[..m.pathDepth];
211 210
}
212 211
213 212
/// Update the recorded lifecycle state for `id`.
214 213
pub fn setState(graph: *mut ModuleGraph, id: u16, state: ModuleState) throws (ModuleError) {
237 236
    m.source = source;
238 237
}
239 238
240 239
/// Look up a child module by name under the given parent.
241 240
pub fn findChild(graph: *ModuleGraph, name: *[u8], parentId: u16) -> ?*ModuleEntry {
242 -
    debug::checkMsg(isValidId(graph, parentId), "findChild: parent identifier is valid");
241 +
    assert isValidId(graph, parentId), "findChild: parent identifier is valid";
243 242
244 243
    let parent = &graph.entries[parentId as u32];
245 244
    for i in 0..parent.childrenLen {
246 245
        let childId = parent.children[i];
247 246
        let child = &graph.entries[childId as u32];
261 260
262 261
    // Split on '/' to extract all but the last component.
263 262
    for i in 0..filePath.len {
264 263
        if filePath[i] == PATH_SEP {
265 264
            if i > last {
266 -
                debug::checkMsg(count < components.len, "parsePath: output slice is large enough");
265 +
                assert count < components.len, "parsePath: output slice is large enough";
267 266
                components[count] = &filePath[last..i];
268 267
                count += 1;
269 268
            }
270 269
            last = i + 1;
271 270
        }
273 272
    if last >= filePath.len {
274 273
        return nil; // Path ends with separator or is empty.
275 274
    }
276 275
277 276
    // Handle the last component by removing extension.
278 -
    debug::checkMsg(count < components.len, "parsePath: output slice is large enough");
277 +
    assert count < components.len, "parsePath: output slice is large enough";
279 278
    if let name = trimExtension(&filePath[last..]) {
280 279
        components[count] = name;
281 280
    } else {
282 281
        return nil;
283 282
    };
lib/std/lang/parser.rad +2 -3
1 1
//! Recursive descent parser for the Radiance programming language.
2 2
@test pub mod tests;
3 3
4 4
use std::mem;
5 5
use std::io;
6 -
use std::debug;
7 6
use std::lang::alloc;
8 7
use std::lang::ast;
9 8
use std::lang::strings;
10 9
use std::lang::scanner;
11 10
162 161
    return node(p, ast::NodeValue::Bool(value));
163 162
}
164 163
165 164
/// Convert a single ASCII digit into its numeric value for the given radix.
166 165
pub fn digitFromAscii(ch: u8, radix: u32) -> ?u32 {
167 -
    debug::check(radix >= 2 and radix <= 36);
166 +
    assert radix >= 2 and radix <= 36;
168 167
169 168
    // Default to an out-of-range value so non-digits fall through to `nil`.
170 169
    let mut value: u32 = 36;
171 170
172 171
    if ch >= '0' and ch <= '9' {
1142 1141
    }
1143 1142
}
1144 1143
1145 1144
/// Report a parser error.
1146 1145
fn reportError(p: *mut Parser, token: scanner::Token, message: *[u8]) {
1147 -
    debug::check(message.len > 0);
1146 +
    assert message.len > 0;
1148 1147
1149 1148
    // Ignore errors once the error list is full.
1150 1149
    if p.errors.count < p.errors.list.len {
1151 1150
        p.errors.list[p.errors.count] = Error { message, token };
1152 1151
        p.errors.count += 1;
lib/std/lang/resolver.rad +9 -10
12 12
// TODO: When a function declaration fails to typecheck, it should still "exist".
13 13
// TODO: `ensureNominalResolved` should just run when you call `typeFor`.
14 14
// TODO: Have different types for positional vs. named field records.
15 15
16 16
use std::mem;
17 -
use std::debug;
18 17
use std::io;
19 18
use std::lang::alloc;
20 19
use std::lang::ast;
21 20
use std::lang::parser;
22 21
use std::lang::module;
2831 2830
    if let a = decl.alignment {
2832 2831
        let case ast::NodeValue::Align { value } = a.value
2833 2832
            else panic "resolveLet: expected Align node";
2834 2833
        alignment = try checkSizeInt(self, value);
2835 2834
    }
2836 -
    debug::check(bindingTy != Type::Unknown);
2835 +
    assert bindingTy != Type::Unknown;
2837 2836
2838 2837
    // Alignment must be zero or a power of two.
2839 2838
    if alignment != 0 and (alignment & (alignment - 1)) != 0 {
2840 2839
        throw emitError(self, decl.value, ErrorKind::InvalidAlignmentValue(alignment));
2841 2840
    }
2992 2991
2993 2992
    // Validate it fits within u32 range.
2994 2993
    if not validateConstIntRange(value, Type::U32) {
2995 2994
        throw emitError(self, node, ErrorKind::NumericLiteralOverflow);
2996 2995
    }
2997 -
    debug::check(not int.negative);
2996 +
    assert not int.negative;
2998 2997
    try setNodeType(self, node, Type::U32);
2999 2998
3000 2999
    return int.magnitude as u32;
3001 3000
}
3002 3001
4724 4723
            expected: info.paramTypesLen,
4725 4724
            actual: call.args.len,
4726 4725
        }));
4727 4726
    }
4728 4727
    for i in 0..call.args.len {
4729 -
        debug::check(i < MAX_FN_PARAMS);
4728 +
        assert i < MAX_FN_PARAMS;
4730 4729
4731 4730
        let argNode = call.args.list[i];
4732 4731
        let expectedTy = *info.paramTypes[i];
4733 4732
4734 4733
        try checkAssignable(self, argNode, expectedTy);
5113 5112
        expectedTy = *ary.item;
5114 5113
    };
5115 5114
    for i in 0..length {
5116 5115
        let itemNode = items.list[i];
5117 5116
        let itemTy = try visit(self, itemNode, expectedTy);
5118 -
        debug::check(itemTy != Type::Unknown);
5117 +
        assert itemTy != Type::Unknown;
5119 5118
5120 5119
        // Set the expected type to the first type we encounter.
5121 5120
        if expectedTy == Type::Unknown {
5122 5121
            expectedTy = itemTy;
5123 5122
        } else {
5510 5509
    throws (ResolveError)
5511 5510
{
5512 5511
    let targetTy = try infer(self, expr.type);
5513 5512
    let sourceTy = try visit(self, expr.value, targetTy);
5514 5513
5515 -
    debug::check(sourceTy != Type::Unknown);
5516 -
    debug::check(targetTy != Type::Unknown);
5514 +
    assert sourceTy != Type::Unknown;
5515 +
    assert targetTy != Type::Unknown;
5517 5516
5518 5517
    if isValidCast(sourceTy, targetTy) {
5519 5518
        return try setNodeType(self, node, targetTy);
5520 5519
    }
5521 5520
    throw emitError(self, node, ErrorKind::InvalidAsCast(InvalidAsCast {
6266 6265
    let case ast::NodeValue::Block(block) = node.value
6267 6266
        else panic "resolvePackage: expected block for module root";
6268 6267
6269 6268
    // Module graph analysis phase: bind all module name symbols and scopes.
6270 6269
    try resolveModuleGraph(self, &block) catch {
6271 -
        debug::checkMsg(self.errors.listLen > 0, "resolvePackage: failure should have diagnostics");
6270 +
        assert self.errors.listLen > 0, "resolvePackage: failure should have diagnostics";
6272 6271
        return Diagnostics { errors: self.errors };
6273 6272
    };
6274 6273
6275 6274
    // Declaration phase: bind all names and analyze top-level declarations.
6276 6275
    try resolveModuleDecls(self, &block) catch {
6277 -
        debug::checkMsg(self.errors.listLen > 0, "resolvePackage: failure should have diagnostics");
6276 +
        assert self.errors.listLen > 0, "resolvePackage: failure should have diagnostics";
6278 6277
    };
6279 6278
    if self.errors.listLen > 0 {
6280 6279
        return Diagnostics { errors: self.errors };
6281 6280
    }
6282 6281
6283 6282
    // Definition phase: analyze function bodies and sub-module definitions.
6284 6283
    try resolveModuleDefs(self, &block) catch {
6285 -
        debug::checkMsg(self.errors.listLen > 0, "resolvePackage: failure should have diagnostics");
6284 +
        assert self.errors.listLen > 0, "resolvePackage: failure should have diagnostics";
6286 6285
    };
6287 6286
    try setNodeType(self, node, Type::Void);
6288 6287
6289 6288
    return Diagnostics { errors: self.errors };
6290 6289
}
lib/std/lang/scanner.rad +0 -1
2 2
//!
3 3
//! This module implements a hand-written scanner that tokenizes Radiance
4 4
//! source code into a stream of tokens for consumption by the parser.
5 5
@test mod tests;
6 6
7 -
use std::debug;
8 7
use std::mem;
9 8
use std::lang::strings;
10 9
11 10
/// Token kinds representing all lexical elements in Radiance.
12 11
///
lib/std/vec.rad +4 -6
2 2
//!
3 3
//! Users provide their own arena (static array) and the vector manages
4 4
//! element count within that arena. The arena should be aligned according
5 5
//! to the element type's requirements.
6 6
7 -
use super::debug;
8 -
9 7
/// Raw vector metadata structure.
10 8
///
11 9
/// Does not own storage, points to user-provided arena.
12 10
pub record RawVec {
13 11
    /// Pointer to user-provided byte arena.
24 22
///
25 23
/// * `arena` is a pointer to static array backing storage.
26 24
/// * `stride` is the size of each element.
27 25
/// * `alignment` is the required alignment for elements.
28 26
pub fn new(arena: *mut [u8], stride: u32, alignment: u32) -> RawVec {
29 -
    debug::check(stride > 0);
30 -
    debug::check(alignment > 0);
31 -
    debug::check((arena.ptr as u32) % alignment == 0);
32 -
    debug::check((arena.len % stride) == 0);
27 +
    assert stride > 0;
28 +
    assert alignment > 0;
29 +
    assert (arena.ptr as u32) % alignment == 0;
30 +
    assert (arena.len % stride) == 0;
33 31
34 32
    return RawVec { data: arena, len: 0, stride, alignment };
35 33
}
36 34
37 35
/// Get the current number of elements in the vector.
std.lib +0 -1
2 2
lib/std/fmt.rad
3 3
lib/std/mem.rad
4 4
lib/std/vec.rad
5 5
lib/std/io.rad
6 6
lib/std/intrinsics.rad
7 -
lib/std/debug.rad
8 7
lib/std/sys.rad
9 8
lib/std/collections.rad
10 9
lib/std/collections/dict.rad
11 10
lib/std/sys/unix.rad
12 11
lib/std/arch.rad