Skip to content

Commit b04f545

Browse files
perf(coverage): find a line by table lookup, not by rescanning the file (#963)
cbm_refine_regions_with_pp_lines asks cbm_line_is_toplevel_macro_call about every preprocessor-parsed line in a region. That check walked the source from byte 0 to find the line, so the refinement cost bytes times lines — on exactly the whole-file-ERROR C shape the refinement exists to narrow. Build one line-offset table per file, and hand the byte span to the macro check. The line-based form stays for the two callers that ask about a whole region, where a single walk is cheaper than a table. A file with no table (the allocation failed) still gets the old walk, so the answer never changes. Measured on one C file whose file-scope brace is split by an #ifndef, padded to 5k / 10k / 20k / 40k lines. Release build, one machine, seconds: lines main branch this commit 5000 0.98 1.14 0.98 10000 3.30 4.02 3.41 20000 11.64 14.77 12.56 40000 44.04 58.58 46.90 The branch ran 33% over main at 40k lines and now runs 6.5% over. The reported ranges do not change: 8-8, where main reports the whole file as 5-40000. A profile of the 40k run no longer reaches the macro check. What is left is cbm_line_contains and cbm_span_contains_callable_def, which main carries too and this branch does not touch. The fixture holds 40k definitions, which makes those two cost more here than on a smaller file. parse_coverage, extraction, pipeline, mcp and index_resilience: 964 passed, 4 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Joshua Richter <jrichter5781@gmail.com>
1 parent f871178 commit b04f545

1 file changed

Lines changed: 77 additions & 23 deletions

File tree

‎internal/cbm/cbm.c‎

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,27 +1208,12 @@ static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMD
12081208
* missing from the graph; it's a benign call the grammar can't parse without the
12091209
* preprocessor. True if the [start_line, end_line] span contains a call `NAME(` to
12101210
* a file-defined function-like macro (Macro label + a parameter signature). */
1211-
static bool cbm_span_is_macro_invocation(const char *src, int src_len, uint32_t start_line,
1212-
uint32_t end_line, const CBMDefArray *defs) {
1213-
if (!src || src_len <= 0 || !defs || start_line == 0 || end_line < start_line) {
1214-
return false;
1215-
}
1216-
int span_start = 0;
1217-
uint32_t line = 1;
1218-
while (span_start < src_len && line < start_line) {
1219-
if (src[span_start++] == '\n') {
1220-
line++;
1221-
}
1222-
}
1223-
if (line != start_line) {
1211+
static bool cbm_byte_span_is_macro_invocation(const char *src, int src_len, int span_start,
1212+
int span_end, const CBMDefArray *defs) {
1213+
if (!src || src_len <= 0 || !defs || span_start < 0 || span_end > src_len ||
1214+
span_start >= span_end) {
12241215
return false;
12251216
}
1226-
int span_end = span_start;
1227-
while (span_end < src_len && line <= end_line) {
1228-
if (src[span_end++] == '\n') {
1229-
line++;
1230-
}
1231-
}
12321217
for (int di = 0; di < defs->count; di++) {
12331218
const CBMDefinition *d = &defs->items[di];
12341219
/* Function-like macros only: an object-like macro (#define PI 3.14) has no
@@ -1256,6 +1241,66 @@ static bool cbm_span_is_macro_invocation(const char *src, int src_len, uint32_t
12561241
return false;
12571242
}
12581243

1244+
/* Byte offset where every 1-based line starts, so finding a line's span costs
1245+
* one table read instead of a walk from the start of the file.
1246+
*
1247+
* The table holds line_count + 2 entries. Entry [L] is where line L starts, and
1248+
* the last entry is the end of the source, which gives the final line somewhere
1249+
* to stop. A line the file never reaches starts at the end of the source, so its
1250+
* span is empty and nothing can match inside it — the same answer the walk gives.
1251+
*
1252+
* Returns NULL when the allocation fails; a caller then falls back to the walk. */
1253+
static int *cbm_build_line_offsets(const char *src, int src_len, uint32_t line_count) {
1254+
int *offsets = (int *)malloc(((size_t)line_count + 2) * sizeof(int));
1255+
if (!offsets) {
1256+
return NULL;
1257+
}
1258+
for (uint32_t l = 0; l <= line_count + 1; l++) {
1259+
offsets[l] = src_len;
1260+
}
1261+
offsets[0] = 0;
1262+
offsets[1] = 0;
1263+
uint32_t line = 1;
1264+
for (int i = 0; i < src_len; i++) {
1265+
if (src[i] != '\n') {
1266+
continue;
1267+
}
1268+
line++;
1269+
if (line > line_count + 1) {
1270+
break;
1271+
}
1272+
offsets[line] = i + 1;
1273+
}
1274+
return offsets;
1275+
}
1276+
1277+
/* Same question by line number, for the few callers that ask about one region
1278+
* rather than every line of a file. This form walks the source to find the span,
1279+
* which is why the per-line caller below uses a table instead. */
1280+
static bool cbm_span_is_macro_invocation(const char *src, int src_len, uint32_t start_line,
1281+
uint32_t end_line, const CBMDefArray *defs) {
1282+
if (!src || src_len <= 0 || !defs || start_line == 0 || end_line < start_line) {
1283+
return false;
1284+
}
1285+
int span_start = 0;
1286+
uint32_t line = 1;
1287+
while (span_start < src_len && line < start_line) {
1288+
if (src[span_start++] == '\n') {
1289+
line++;
1290+
}
1291+
}
1292+
if (line != start_line) {
1293+
return false;
1294+
}
1295+
int span_end = span_start;
1296+
while (span_end < src_len && line <= end_line) {
1297+
if (src[span_end++] == '\n') {
1298+
line++;
1299+
}
1300+
}
1301+
return cbm_byte_span_is_macro_invocation(src, src_len, span_start, span_end, defs);
1302+
}
1303+
12591304
/* True if [rs, re] is fully enclosed by an extracted callable definition (a
12601305
* Function/Method body). A macro invocation INSIDE a real function body is an
12611306
* expression-level use where nothing is missing (#1071). A TOP-LEVEL invocation
@@ -1327,9 +1372,12 @@ static void cbm_push_trimmed_run(cbm_error_regions_t *out, uint32_t start, uint3
13271372
* An invocation INSIDE a function body is the benign #1071 case and is left
13281373
* alone here — cbm_subtract_macro_invocation_regions handles it later. */
13291374
static bool cbm_line_is_toplevel_macro_call(const char *src, int src_len, uint32_t line,
1330-
const CBMDefArray *defs) {
1331-
return cbm_span_is_macro_invocation(src, src_len, line, line, defs) &&
1332-
!cbm_region_inside_callable(line, line, defs);
1375+
const int *line_offsets, const CBMDefArray *defs) {
1376+
bool is_call = line_offsets
1377+
? cbm_byte_span_is_macro_invocation(src, src_len, line_offsets[line],
1378+
line_offsets[line + 1], defs)
1379+
: cbm_span_is_macro_invocation(src, src_len, line, line, defs);
1380+
return is_call && !cbm_region_inside_callable(line, line, defs);
13331381
}
13341382

13351383
/* Cut every raw region down to the lines the preprocessed parse could not
@@ -1344,13 +1392,18 @@ static void cbm_refine_regions_with_pp_lines(cbm_error_regions_t *regs, const ui
13441392
uint32_t line_count, const char *src, int src_len,
13451393
const CBMDefArray *defs) {
13461394
cbm_error_regions_t out = {{0}, {0}, 0, regs->dropped};
1395+
/* One offset table for the whole file. The macro check below runs once per
1396+
* line, and without the table each of those calls walks the source from byte
1397+
* 0 to find its line — bytes times lines, on exactly the whole-file-error
1398+
* shape this refinement exists to narrow. */
1399+
int *line_offsets = cbm_build_line_offsets(src, src_len, line_count);
13471400
for (int i = 0; i < regs->count; i++) {
13481401
uint32_t run_start = 0;
13491402
uint32_t run_end = 0;
13501403
uint32_t end = regs->ends[i] < line_count ? regs->ends[i] : line_count;
13511404
for (uint32_t line = regs->starts[i]; line <= end; line++) {
13521405
if ((map[line] & CBM_LINE_PP_PARSED) &&
1353-
!cbm_line_is_toplevel_macro_call(src, src_len, line, defs)) {
1406+
!cbm_line_is_toplevel_macro_call(src, src_len, line, line_offsets, defs)) {
13541407
if (run_start != 0) {
13551408
cbm_push_trimmed_run(&out, run_start, run_end, map, line_count);
13561409
run_start = 0;
@@ -1366,6 +1419,7 @@ static void cbm_refine_regions_with_pp_lines(cbm_error_regions_t *regs, const ui
13661419
cbm_push_trimmed_run(&out, run_start, run_end, map, line_count);
13671420
}
13681421
}
1422+
free(line_offsets);
13691423
*regs = out;
13701424
}
13711425

0 commit comments

Comments
 (0)