Use `assert` instead of `panic`

3df5cf97acb5200ec1fb8c76bcc47d98c125e461c2b401369bd6ca0ef0ce7cdc
Alexis Sellier committed ago 1 parent 4579515f
lib/std/arch/rv64/emit.rad +7 -21
208 208
// Emission Helpers  //
209 209
///////////////////////
210 210
211 211
/// Emit a single instruction.
212 212
pub fn emit(e: *mut Emitter, instr: u32) {
213 -
    if e.codeLen >= e.code.len {
214 -
        panic "emit: code buffer full";
215 -
    }
213 +
    assert e.codeLen < e.code.len, "emit: code buffer full";
216 214
    e.code[e.codeLen] = instr;
217 215
    e.codeLen += 1;
218 216
}
219 217
220 218
/// Compute branch offset to a function by name.
239 237
    dict::insert(&mut e.labels.funcs, name, e.codeLen as i32 * super::INSTR_SIZE);
240 238
}
241 239
242 240
/// Record a function's start position for printing.
243 241
pub fn recordFunc(e: *mut Emitter, name: *[u8]) {
244 -
    if e.funcsLen >= e.funcs.len {
245 -
        panic "recordFunc: funcs buffer full";
246 -
    }
242 +
    assert e.funcsLen < e.funcs.len, "recordFunc: funcs buffer full";
247 243
    e.funcs[e.funcsLen] = types::FuncAddr { name, index: e.codeLen };
248 244
    e.funcsLen += 1;
249 245
}
250 246
251 247
/// Record a local branch needing later patching.
252 248
/// Unconditional jumps use a single slot (J-type, +-1MB range).
253 249
/// Conditional branches use two slots (B-type has only +-4KB range,
254 250
/// so large functions may need the inverted-branch + JAL fallback).
255 251
pub fn recordBranch(e: *mut Emitter, targetBlock: u32, kind: BranchKind) {
256 -
    if e.pendingBranchesLen >= e.pendingBranches.len {
257 -
        panic "recordBranch: buffer full";
258 -
    }
252 +
    assert e.pendingBranchesLen < e.pendingBranches.len, "recordBranch: buffer full";
259 253
    e.pendingBranches[e.pendingBranchesLen] = PendingBranch {
260 254
        index: e.codeLen,
261 255
        target: targetBlock,
262 256
        kind: kind,
263 257
    };
273 267
274 268
/// Record a function call needing later patching.
275 269
/// Emits placeholder instructions that will be patched later.
276 270
/// Uses two slots to support long-distance calls.
277 271
pub fn recordCall(e: *mut Emitter, target: *[u8]) {
278 -
    if e.pendingCallsLen >= e.pendingCalls.len {
279 -
        panic "recordCall: buffer full";
280 -
    }
272 +
    assert e.pendingCallsLen < e.pendingCalls.len, "recordCall: buffer full";
281 273
    e.pendingCalls[e.pendingCallsLen] = PendingCall {
282 274
        index: e.codeLen,
283 275
        target,
284 276
    };
285 277
    e.pendingCallsLen += 1;
290 282
291 283
/// Record a function address load needing later patching.
292 284
/// Emits placeholder instructions that will be patched to load the function's address.
293 285
/// Uses two slots to compute long-distance addresses.
294 286
pub fn recordAddrLoad(e: *mut Emitter, target: *[u8], rd: super::Reg) {
295 -
    if e.pendingAddrLoadsLen >= e.pendingAddrLoads.len {
296 -
        panic "recordAddrLoad: buffer full";
297 -
    }
287 +
    assert e.pendingAddrLoadsLen < e.pendingAddrLoads.len, "recordAddrLoad: buffer full";
298 288
    e.pendingAddrLoads[e.pendingAddrLoadsLen] = PendingAddrLoad {
299 289
        index: e.codeLen,
300 290
        target,
301 291
        rd: rd,
302 292
    };
337 327
                    patch(e, p.index + 1, encode::jal(super::ZERO, adj));
338 328
                }
339 329
            },
340 330
            case BranchKind::Jump => {
341 331
                // Single-slot jump (J-type, +-1MB range).
342 -
                if not encode::isJumpImm(offset) {
343 -
                    panic "patchLocalBranches: jump offset too large";
344 -
                }
332 +
                assert encode::isJumpImm(offset), "patchLocalBranches: jump offset too large";
345 333
                patch(e, p.index, encode::jal(super::ZERO, offset));
346 334
            },
347 335
        }
348 336
    }
349 337
    e.pendingBranchesLen = 0;
675 663
        let prev = &e.debugEntries[e.debugEntriesLen - 1];
676 664
        if prev.offset == loc.offset and prev.moduleId == loc.moduleId {
677 665
            return;
678 666
        }
679 667
    }
680 -
    if e.debugEntriesLen >= e.debugEntries.len {
681 -
        panic "recordSrcLoc: debug entry buffer full";
682 -
    }
668 +
    assert e.debugEntriesLen < e.debugEntries.len, "recordSrcLoc: debug entry buffer full";
683 669
    e.debugEntries[e.debugEntriesLen] = types::DebugEntry {
684 670
        pc,
685 671
        moduleId: loc.moduleId,
686 672
        offset: loc.offset,
687 673
    };
lib/std/arch/rv64/isel.rad +5 -15
439 439
                and regalloc::spill::isSpilled(&s.ralloc.spill, src);
440 440
441 441
            // When both are spilled, offsets must fit 12-bit immediates
442 442
            // since we can't advance base registers (they live in spill
443 443
            // slots, not real registers we can mutate).
444 -
            if bothSpilled and staticSize as i32 > super::MAX_IMM {
445 -
                panic "selectInstr: blit both-spilled with large size";
446 -
            }
444 +
            assert not (bothSpilled and staticSize as i32 > super::MAX_IMM), "selectInstr: blit both-spilled with large size";
447 445
448 446
            // Resolve dst/src base registers.
449 447
            let mut rdst = super::SCRATCH2;
450 448
            let mut rsrc = super::SCRATCH1;
451 449
            let mut srcReload: ?i32 = nil;
662 660
            if let case il::Val::Reg(r) = func {
663 661
                let target = getSrcReg(s, r, super::SCRATCH2);
664 662
                emitMv(s, super::SCRATCH2, target);
665 663
            }
666 664
            // Move arguments to A0-A7 using parallel move resolution.
667 -
            if args.len > super::ARG_REGS.len {
668 -
                panic "selectInstr: too many call arguments";
669 -
            }
665 +
            assert args.len <= super::ARG_REGS.len, "selectInstr: too many call arguments";
670 666
            emitParallelMoves(s, &super::ARG_REGS[..], args);
671 667
672 668
            // Emit call.
673 669
            match func {
674 670
                case il::Val::FnAddr(name) => {
995 991
fn emitParallelMoves(s: *mut Selector, dsts: *[super::Reg], args: *[il::Val]) {
996 992
    let n: u32 = args.len;
997 993
    if n == 0 {
998 994
        return;
999 995
    }
1000 -
    if n > MAX_BLOCK_ARGS {
1001 -
        panic "emitParallelMoves: too many arguments";
1002 -
    }
996 +
    assert n <= MAX_BLOCK_ARGS, "emitParallelMoves: too many arguments";
1003 997
    // Source registers for each arg.
1004 998
    let mut srcRegs: [super::Reg; MAX_BLOCK_ARGS] = [super::ZERO; MAX_BLOCK_ARGS];
1005 999
    // If this is a register-to-register move.
1006 1000
    let mut isRegMove: [bool; MAX_BLOCK_ARGS] = [false; MAX_BLOCK_ARGS];
1007 1001
    // If this move still needs to be executed.
1107 1101
fn emitBlockArgs(s: *mut Selector, func: *il::Fn, target: u32, args: *mut [il::Val]) {
1108 1102
    if args.len == 0 {
1109 1103
        return;
1110 1104
    }
1111 1105
    let block = &func.blocks[target];
1112 -
    if args.len != block.params.len {
1113 -
        panic "emitBlockArgs: argument/parameter count mismatch";
1114 -
    }
1115 -
    if args.len > MAX_BLOCK_ARGS {
1116 -
        panic "emitBlockArgs: too many block arguments";
1117 -
    }
1106 +
    assert args.len == block.params.len, "emitBlockArgs: argument/parameter count mismatch";
1107 +
    assert args.len <= MAX_BLOCK_ARGS, "emitBlockArgs: too many block arguments";
1118 1108
1119 1109
    // Destination registers for each arg.
1120 1110
    // Zero means the destination is spilled or skipped.
1121 1111
    let mut dsts: [super::Reg; MAX_BLOCK_ARGS] = [super::ZERO; MAX_BLOCK_ARGS];
1122 1112
lib/std/collections/dict.rad +1 -3
36 36
    let mut idx = hash(key) & mask;
37 37
38 38
    loop {
39 39
        let entry = m.entries[idx];
40 40
        if entry.key.len == 0 {
41 -
            if m.count >= m.entries.len / 2 {
42 -
                panic "dict::insert: table full";
43 -
            }
41 +
            assert m.count < m.entries.len / 2, "dict::insert: table full";
44 42
            m.entries[idx] = Entry { key, value };
45 43
            m.count += 1;
46 44
            return;
47 45
        }
48 46
        if mem::eq(entry.key, key) {
lib/std/lang/gen/data.rad +1 -3
82 82
83 83
    for i in 0..program.data.len {
84 84
        let data = &program.data[i];
85 85
        if data.readOnly == readOnly and not data.isUndefined {
86 86
            offset = mem::alignUp(offset, data.alignment);
87 -
            if offset + data.size > buf.len {
88 -
                panic "emitSection: buffer overflow";
89 -
            }
87 +
            assert offset + data.size <= buf.len, "emitSection: buffer overflow";
90 88
            for j in 0..data.values.len {
91 89
                let v = &data.values[j];
92 90
                for _ in 0..v.count {
93 91
                    match v.item {
94 92
                        case il::DataItem::Val { typ, val } => {
lib/std/lang/gen/labels.rad +2 -6
36 36
    l.blockCount = 0;
37 37
}
38 38
39 39
/// Record a block's code offset by its index. O(1).
40 40
pub fn recordBlock(l: *mut Labels, blockIdx: u32, offset: i32) {
41 -
    if blockIdx >= l.blockOffsets.len {
42 -
        panic "recordBlock: block index out of range";
43 -
    }
41 +
    assert blockIdx < l.blockOffsets.len, "recordBlock: block index out of range";
44 42
    l.blockOffsets[blockIdx] = offset;
45 43
    l.blockCount += 1;
46 44
}
47 45
48 46
/// Look up a block's byte offset by index. O(1).
49 47
pub fn blockOffset(l: *Labels, blockIdx: u32) -> i32 {
50 -
    if blockIdx >= l.blockCount {
51 -
        panic "blockOffset: block not recorded";
52 -
    }
48 +
    assert blockIdx < l.blockCount, "blockOffset: block not recorded";
53 49
    return l.blockOffsets[blockIdx];
54 50
}
55 51
56 52
/// Look up a function's byte offset by name.
57 53
pub fn funcOffset(l: *Labels, name: *[u8]) -> i32 {
lib/std/lang/gen/regalloc/assign.rad +2 -6
216 216
    return nil;
217 217
}
218 218
219 219
/// Add a mapping to the register map.
220 220
fn rmapSet(rmap: *mut RegMap, virtReg: u32, physReg: u8) {
221 -
    if rmap.n >= MAX_ACTIVE {
222 -
        panic "rmapSet: register map overflow";
223 -
    }
221 +
    assert rmap.n < MAX_ACTIVE, "rmapSet: register map overflow";
224 222
    rmap.virtRegs[rmap.n] = virtReg;
225 223
    rmap.physRegs[rmap.n] = physReg;
226 224
    rmap.n += 1;
227 225
}
228 226
291 289
        if let phys = rmapFind(ctx.current, reg.n) {
292 290
            bitset::clear(ctx.usedRegs, phys as u32);
293 291
            rmapRemove(ctx.current, reg.n);
294 292
        }
295 293
    }
296 -
    if reg.n >= ctx.assignments.len {
297 -
        panic "processInstrRegCb: register out of bounds";
298 -
    }
294 +
    assert reg.n < ctx.assignments.len, "processInstrRegCb: register out of bounds";
299 295
    if spill::isSpilled(ctx.spillInfo, reg) {
300 296
        return; // Spilled values don't get physical registers.
301 297
    }
302 298
    if ctx.assignments[reg.n] == nil {
303 299
        ctx.assignments[reg.n] = rallocReg(
lib/std/lang/gen/regalloc/liveness.rad +1 -3
89 89
            if let dst = il::instrDst(block.instrs[i]) {
90 90
                maxReg = maxRegNum(dst.n, maxReg);
91 91
            }
92 92
        }
93 93
    }
94 -
    if maxReg > MAX_SSA_REGS {
95 -
        panic "analyze: maximum SSA registers exceeded";
96 -
    }
94 +
    assert maxReg <= MAX_SSA_REGS, "analyze: maximum SSA registers exceeded";
97 95
    // Allocate per-block bitsets.
98 96
    let liveIn = try alloc::allocSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *mut [bitset::Bitset];
99 97
    let liveOut = try alloc::allocSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *mut [bitset::Bitset];
100 98
    let defs = try alloc::allocSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *mut [bitset::Bitset];
101 99
    let uses = try alloc::allocSlice(arena, @sizeOf(bitset::Bitset), @alignOf(bitset::Bitset), blockCount) as *mut [bitset::Bitset];
lib/std/lang/gen/regalloc/spill.rad +2 -6
234 234
/// Collect all values from a bitset into a candidates buffer with their costs.
235 235
fn collectCandidates(set: *bitset::Bitset, costs: *[SpillCost]) -> Candidates {
236 236
    let mut c = Candidates { entries: undefined, n: 0 };
237 237
    let mut it = bitset::iter(set);
238 238
    while let reg = bitset::iterNext(&mut it) {
239 -
        if c.n >= MAX_CANDIDATES {
240 -
            panic "collectCandidates: too many live values";
241 -
        }
239 +
        assert c.n < MAX_CANDIDATES, "collectCandidates: too many live values";
242 240
        if reg < costs.len {
243 241
            c.entries[c.n] = CostEntry { reg, cost: costs[reg].defs + costs[reg].uses };
244 242
            c.n += 1;
245 243
        }
246 244
    }
311 309
}
312 310
313 311
/// Callback for [`il::forEachReg`]: increments use count for register.
314 312
fn countRegUseCallback(reg: il::Reg, ctxPtr: *mut opaque) {
315 313
    let ctx = ctxPtr as *mut CountCtx;
316 -
    if reg.n >= ctx.costs.len {
317 -
        panic "countRegUseCallback: register out of bounds";
318 -
    }
314 +
    assert reg.n < ctx.costs.len, "countRegUseCallback: register out of bounds";
319 315
    ctx.costs[reg.n].uses = ctx.costs[reg.n].uses + ctx.weight;
320 316
}
321 317
322 318
/// Callback for [`il::forEachReg`]: adds register to live set.
323 319
fn addRegToSetCallback(reg: il::Reg, ctx: *mut opaque) {
lib/std/lang/lower.rad +5 -15
2364 2364
2365 2365
/// Add a predecessor edge from `pred` to `target`.
2366 2366
/// Must be called before the target block is sealed. Duplicates are ignored.
2367 2367
fn addPredecessor(self: *mut FnLowerer, target: BlockId, pred: BlockId) {
2368 2368
    let blk = getBlockMut(self, target);
2369 -
    if blk.sealState == Sealed::Yes {
2370 -
        panic "addPredecessor: adding predecessor to sealed block";
2371 -
    }
2369 +
    assert blk.sealState != Sealed::Yes, "addPredecessor: adding predecessor to sealed block";
2372 2370
    let preds = &mut blk.preds;
2373 2371
    for i in 0..preds.len {
2374 2372
        if preds[i] == pred.n { // Avoid duplicate predecessor entries.
2375 2373
            return;
2376 2374
        }
2404 2402
/////////////////////
2405 2403
2406 2404
/// Enter a loop context for break/continue handling.
2407 2405
/// `continueBlock` is `nil` when the continue target is created lazily.
2408 2406
fn enterLoop(self: *mut FnLowerer, breakBlock: BlockId, continueBlock: ?BlockId) {
2409 -
    if self.loopDepth >= self.loopStack.len {
2410 -
        panic "enterLoop: loop depth overflow";
2411 -
    }
2407 +
    assert self.loopDepth < self.loopStack.len, "enterLoop: loop depth overflow";
2412 2408
    let slot = &mut self.loopStack[self.loopDepth];
2413 2409
2414 2410
    slot.breakTarget = breakBlock;
2415 2411
    slot.continueTarget = continueBlock;
2416 2412
    self.loopDepth += 1;
2417 2413
}
2418 2414
2419 2415
/// Exit the current loop context.
2420 2416
fn exitLoop(self: *mut FnLowerer) {
2421 -
    if self.loopDepth == 0 {
2422 -
        panic "exitLoop: loopDepth is zero";
2423 -
    }
2417 +
    assert self.loopDepth != 0, "exitLoop: loopDepth is zero";
2424 2418
    self.loopDepth -= 1;
2425 2419
}
2426 2420
2427 2421
/// Get the current loop context.
2428 2422
fn currentLoop(self: *mut FnLowerer) -> ?*mut LoopCtx {
2674 2668
///
2675 2669
/// This function creates a fresh register `%1` as a block parameter, then patches
2676 2670
/// each predecessor's jump to pass its value of `x` as an argument.
2677 2671
fn createBlockParam(self: *mut FnLowerer, block: BlockId, v: Var) -> il::Val throws (LowerError) {
2678 2672
    // Entry block must not have block parameters.
2679 -
    if block == self.entryBlock {
2680 -
        panic "createBlockParam: entry block must not have block parameters";
2681 -
    }
2673 +
    assert block != self.entryBlock, "createBlockParam: entry block must not have block parameters";
2682 2674
    // Allocate a register to hold the merged value.
2683 2675
    let reg = nextReg(self);
2684 2676
    let type = getVar(self, v).type;
2685 2677
2686 2678
    // Create block parameter and add it to the block.
2746 2738
    for predId in blk.preds {
2747 2739
        let pred = BlockId { n: predId };
2748 2740
        // This may recursively trigger more block arg resolution if the
2749 2741
        // predecessor also needs to look up the variable from its predecessors.
2750 2742
        let val = try useVarInBlock(self, pred, v);
2751 -
        if val == il::Val::Undef {
2752 -
            panic "createBlockParam: predecessor provides undef value for block parameter";
2753 -
        }
2743 +
        assert val != il::Val::Undef, "createBlockParam: predecessor provides undef value for block parameter";
2754 2744
        patchTerminatorArg(self, pred, block.n, paramIdx, val);
2755 2745
    }
2756 2746
}
2757 2747
2758 2748
/// Check if a block parameter is trivial, i.e. all predecessors provide
lib/std/lang/resolver.rad +8 -24
939 939
    // Check for an existing scope for this node, and don't allocate a new
940 940
    // one in that case.
941 941
    if let scope = scopeFor(self, owner) {
942 942
        return scope;
943 943
    }
944 -
    if owner.id >= self.nodeData.entries.len {
945 -
        panic "allocScope: node ID out of bounds";
946 -
    }
944 +
    assert owner.id < self.nodeData.entries.len, "allocScope: node ID out of bounds";
947 945
    let p = try! alloc::alloc(&mut self.arena, @sizeOf(Scope), @alignOf(Scope));
948 946
    let entry = p as *mut Scope;
949 947
950 948
    // Allocate symbols from the arena.
951 949
    let ptr = try! alloc::allocSlice(&mut self.arena, @sizeOf(*mut Symbol), @alignOf(*mut Symbol), capacity);
1009 1007
1010 1008
/// Visit the body of a loop while tracking nesting depth.
1011 1009
fn visitLoop(self: *mut Resolver, body: *ast::Node) -> Type
1012 1010
    throws (ResolveError)
1013 1011
{
1014 -
    if self.loopDepth >= MAX_LOOP_DEPTH {
1015 -
        panic "visitLoop: loop nesting depth exceeded";
1016 -
    }
1012 +
    assert self.loopDepth < MAX_LOOP_DEPTH, "visitLoop: loop nesting depth exceeded";
1017 1013
    self.loopStack[self.loopDepth] = LoopCtx { hasBreak: false };
1018 1014
    self.loopDepth += 1;
1019 1015
1020 1016
    let ty = try infer(self, body) catch {
1021 -
        if self.loopDepth == 0 {
1022 -
            panic "visitLoop: loop depth underflow";
1023 -
        }
1017 +
        assert self.loopDepth != 0, "visitLoop: loop depth underflow";
1024 1018
        self.loopDepth -= 1;
1025 1019
        throw ResolveError::Failure;
1026 1020
    };
1027 1021
    // Pop and check if break was encountered.
1028 1022
    self.loopDepth -= 1;
1055 1049
    }
1056 1050
}
1057 1051
1058 1052
/// Set the expected return type for a new function body.
1059 1053
fn enterFn(self: *mut Resolver, node: *ast::Node, ty: *FnType) {
1060 -
    if self.currentFn != nil {
1061 -
        panic "enterFn: already in a function";
1062 -
    }
1054 +
    assert self.currentFn == nil, "enterFn: already in a function";
1063 1055
    self.currentFn = ty;
1064 1056
    enterScope(self, node);
1065 1057
}
1066 1058
1067 1059
/// Clear the expected return type when leaving a function body.
2160 2152
) -> *[*[u8]] throws (ResolveError) {
2161 2153
    let mut out: *[*[u8]] = &[];
2162 2154
2163 2155
    match node.value {
2164 2156
        case ast::NodeValue::Ident(name) if name.len > 0 => {
2165 -
            if buf.len < 1 {
2166 -
                panic "flattenPath: invalid output buffer size";
2167 -
            }
2157 +
            assert buf.len >= 1, "flattenPath: invalid output buffer size";
2168 2158
            buf[0] = name;
2169 2159
            out = &buf[..1];
2170 2160
        }
2171 2161
        case ast::NodeValue::ScopeAccess(access) => {
2172 2162
            // Recursively flatten parent path.
2173 2163
            let parent = try flattenPath(self, access.parent, buf);
2174 -
            if parent.len >= buf.len {
2175 -
                panic "flattenPath: invalid output buffer size";
2176 -
            }
2164 +
            assert parent.len < buf.len, "flattenPath: invalid output buffer size";
2177 2165
            let child = try nodeName(self, access.child);
2178 2166
            buf[parent.len] = child;
2179 2167
            out = &buf[..parent.len + 1];
2180 2168
        }
2181 2169
        case ast::NodeValue::Super => {
2296 2284
    node: *ast::Node,
2297 2285
    access: ast::Access,
2298 2286
    path: *[*[u8]],
2299 2287
    scope: *Scope
2300 2288
) -> *mut Symbol throws (ResolveError) {
2301 -
    if path.len == 0 {
2302 -
        panic "resolvePath: empty path";
2303 -
    }
2289 +
    assert path.len != 0, "resolvePath: empty path";
2304 2290
    // Start by finding the root of the path.
2305 2291
    let root = path[0];
2306 2292
    let sym = findInScopeRecursive(scope, root, isAnySymbol) else
2307 2293
        throw emitError(self, node, ErrorKind::UnresolvedSymbol(root));
2308 2294
    let suffix = &path[1..];
3658 3644
        isAllVoid: true
3659 3645
    });
3660 3646
3661 3647
    try visitList(self, decl.derives);
3662 3648
3663 -
    if decl.variants.len > MAX_UNION_VARIANTS {
3664 -
        panic "resolveUnionBody: maximum union variants exceeded";
3665 -
    }
3649 +
    assert decl.variants.len <= MAX_UNION_VARIANTS, "resolveUnionBody: maximum union variants exceeded";
3666 3650
    let mut iota: u32 = 0;
3667 3651
    for variantNode, i in decl.variants {
3668 3652
        let case ast::NodeValue::UnionDeclVariant(variantDecl) = variantNode.value
3669 3653
            else panic "resolveUnionBody: invalid union variant";
3670 3654
        let variantName = try nodeName(self, variantDecl.name);
lib/std/lang/strings.rad +1 -3
63 63
/// Otherwise, adds the string pointer to the pool and returns it.
64 64
pub fn intern(sp: *mut Pool, str: *[u8]) -> *[u8] {
65 65
    match lookup(sp, str) {
66 66
        case Lookup::Found(entry) => return entry,
67 67
        case Lookup::Empty(idx) => {
68 -
            if sp.count >= TABLE_SIZE / 2 {
69 -
                panic "intern: string pool is full";
70 -
            }
68 +
            assert sp.count < TABLE_SIZE / 2, "intern: string pool is full";
71 69
            sp.table[idx] = str;
72 70
            sp.count += 1;
73 71
74 72
            return str;
75 73
        }