Skip to content

Move comparison before umul.with.overflow [instcombine?] #73306

Open
@scottmcm

Description

@scottmcm

A bunch of containers have code enforcing object size limits that ends up looking like

let Some(total_bytes) = element_size.checked_mul(element_count) else { panic!() };
if total_bytes > (isize::MAX as usize) { panic!() };

in Rust, or in C++ something like

unsigned long total_bytes;
if __builtin_uaddl_overflow(element_size, element_count, &total_bytes) { throw bad_alloc(); }
if total_bytes > (size_t)PTR_DIFF_MAX { throw bad_alloc(); }

It would be nice if LLVM could -- when element_size is a constant, as it often ends up being in templated code -- move the check before the multiplication so that the overflow handling could be removed, and it just be a normal multiplication instead. (Perhaps even an nuw one, by sinking it into the branch that cares about the result.)

An example in Alive2: https://alive2.llvm.org/ce/z/X9ipzy

define {i64, i64} @src(i64 noundef %x) {
start:
  %#0 = umul_overflow i64 noundef %x, 26
  %_11.0.i = extractvalue {i64, i1, i24} %#0, 0
  %_11.1.i = extractvalue {i64, i1, i24} %#0, 1
  %_7.i.i = icmp ugt i64 %_11.0.i, 9223372036854775806
  %_0.sroa.0.0.i.i = select i1 %_7.i.i, i64 0, i64 2
  %#1 = insertvalue {i64, i64} poison, i64 %_0.sroa.0.0.i.i, 0
  %#2 = insertvalue {i64, i64} %#1, i64 %_11.0.i, 1
  %.pn.i = select i1 %_11.1.i, {i64, i64} { 0, -1 }, {i64, i64} %#2
  %.fca.0.extract = extractvalue {i64, i64} %.pn.i, 0
  %#3 = icmp eq i64 %.fca.0.extract, 0
  br i1 %#3, label %bb2, label %bb4

bb4:
  ret {i64, i64} %.pn.i

bb2:
  call void #trap() noreturn nothrow memory(inaccessiblemem: write)
  assume i1 0
}
=>
define {i64, i64} @tgt(i64 noundef %x) {
start:
  %_11.0.i = mul i64 noundef %x, 26
  %_7.i.i = icmp ugt i64 noundef %x, 354745078340568300
  %#1 = insertvalue {i64, i64} poison, i64 2, 0
  %#2 = insertvalue {i64, i64} %#1, i64 %_11.0.i, 1
  br i1 %_7.i.i, label %bb2, label %bb4

bb4:
  ret {i64, i64} %#2

bb2:
  call void #trap() noreturn nothrow memory(inaccessiblemem: write)
  assume i1 0
}
Transformation seems to be correct!

(It's based on https://rust.godbolt.org/z/47WWG4b4T, which was inspired by rust-lang/rust#118228.)

This issue is basically the same as #56563 but for umul.with.overflow rather than for ordinary mul.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions