Issue 202573
Summary Fold `strlen` of Reachable Constant String Table Entries
Labels new issue
Assignees
Reporter ParkHanbum
    ## Fold `strlen(table[idx])` when `table[idx]` is a reachable constant string entry

LLVM can miss a small constant-string table idiom where a pointer is loaded from
a constant table, passed to `strlen`, and the resulting length is used as the
length argument of a string comparison.

There are already related LLVM transforms with the same general shape:

- `strlen(select ? "foo" : "bars")` can be folded to a select of constant
  lengths.
- SimplifyCFG can turn a switch that selects among constant results into a
  private constant lookup table, loading `table[idx]` instead of preserving the
  original control flow.

This issue is similar in spirit. The selected result is not written directly in
a switch; it is derived from a constant string table entry through `strlen`.
When the index range proves that only constant string entries are reachable,
LLVM could materialize the derived length either as a small select/switch or as
an indexable constant length table.

Representative shape:

```llvm
@.str.high = private unnamed_addr constant [6 x i8] c",high\00"
@.str.low = private unnamed_addr constant [5 x i8] c",low\00"
@suffix_tbl = internal unnamed_addr constant [3 x ptr]
  [ptr @.str.high, ptr @.str.low, ptr null]

loop:
  %idx = phi i64 [ 0, %entry ], [ 1, %next ]
  %slot = getelementptr [8 x i8], ptr @suffix_tbl, i64 %idx
  %suffix = load ptr, ptr %slot
  %len = call i64 @strlen(ptr %suffix)
  %cmp = call i32 @strncmp(ptr %input, ptr %suffix, i64 %len)
```

In this example the table has a null sentinel, but the reachable index range is
only `0..1`. Both reachable entries are constant strings with known first-NUL
positions, so the runtime `strlen` can be replaced with a constant-length
selection:

```llvm
  %slot = getelementptr [8 x i8], ptr @suffix_tbl, i64 %idx
  %suffix = load ptr, ptr %slot
  %is.low = icmp eq i64 %idx, 1
  %len = select i1 %is.low, i64 4, i64 5
  %cmp = call i32 @strncmp(ptr %input, ptr %suffix, i64 %len)
```

This is not a request for general dynamic `strlen` folding. The intended scope is
narrow:

- the table object is constant;
- the loaded entries are constant strings with statically known first-NUL
  lengths;
- the reachable index range excludes null sentinel entries;
- the `strlen` result is used as a compare length, such as `strncmp`,
  `strncasecmp`, or `bcmp`;
- all uses of the `strlen` result are replaced consistently.

A compact C source shape that can leave this kind of IR is:

```c
static const char *const suffix_tbl[] = {
    ",high",
    ",low",
 NULL,
};

int f(const char *input, size_t idx) {
  if (idx >= 2)
 return -1;

  const char *suffix = suffix_tbl[idx];
  size_t len = strlen(suffix);
  return strncmp(input, suffix, len) == 0 ? (int)idx : -1;
}
```

This is different from a fully unrolled source-level case. If the optimizer can
split each table entry into a separate path, it can produce direct calls such as
`strncmp(input, ",high", 5)`.

The missed residual case is when the selected string pointer remains dynamic:

```c
suffix = suffix_tbl[idx];
```

but the index is range-checked, so the length is still derivable from the same
index. LLVM does not need to know the loaded pointer as one constant value; it
only needs to preserve the correlation:

```text
idx == 0 -> strlen(suffix_tbl[idx]) == 5
idx == 1 -> strlen(suffix_tbl[idx]) == 4
```

That allows replacing the `strlen` call with a small select or switch on `idx`,
or with an index-based constant length lookup, while leaving the loaded string
pointer itself dynamic.

Observed benchmark-backed instances include:

- `linux/optimized/crash_core.ll:1130`: suffix table feeding `strncmp`
- `linux/optimized/libata-sata.ll:1894`: policy-name table feeding `strncmp`
- `linux/optimized/nf_conntrack_irc.ll:353`: protocol table feeding `bcmp`
- `linux/optimized/trace_events_filter.ll:2715`: operator table feeding
 `strncmp`
- `linux/optimized/workqueue.ll:12409`: affinity-name table feeding
  `strncasecmp`
- `linux/optimized/workqueue.ll:12934`: affinity-name table feeding
  `strncasecmp`

One related instance uses the same constant table length idiom for allocation
size calculation rather than a string-compare length:

- `linux/optimized/hid-sony.ll:1872`

That allocation-size variant may be worth considering separately, but the
compare-length form is the clearer initial target.

Profitability should be guarded by a table-size and cost threshold. There are at
least two possible lowering choices:

- for very small reachable tables, use a compact select or switch on `idx`;
- for larger reachable tables, consider generating or reusing an indexable
  constant length table, conceptually:

```llvm
@suffix_tbl_len = private unnamed_addr constant [2 x i64] [i64 5, i64 4]

%len.slot = getelementptr [8 x i8], ptr @suffix_tbl_len, i64 %idx
%len = load i64, ptr %len.slot
```

The length-table form can avoid a long compare/select chain for 5-7 entry
tables, which matches several observed instances. However, it has fixed
overhead: a new global constant, possible alignment/data-section growth, address
materialization, and a load. For two or three entries, that overhead may be worse
than a small select. For cold or size-optimized code, profitability should be
judged on combined code and data size, not only on IR instruction count.

_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to