259 changes: 90 additions & 169 deletions clang/test/CodeGen/attr-counted-by.c

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,38 +32,68 @@ void foo(int *p){}
namespace std{
template <typename T> class span {

T *elements;
T *elements;

span(T *, unsigned){}
span(T *, unsigned){}

public:
public:

constexpr span<T> subspan(size_t offset, size_t count) const {
return span<T> (elements+offset, count); // expected-warning{{unsafe pointer arithmetic}}
}
constexpr span<T> subspan(size_t offset, size_t count) const {
return span<T> (elements+offset, count); // expected-warning{{unsafe pointer arithmetic}}
}

constexpr T* data() const noexcept {
return elements;
}
constexpr T* data() const noexcept {
return elements;
}

constexpr T* hello() const noexcept {
return elements;
}
};

template <typename T> class vector {

T *elements;

public:

vector(size_t n) {
elements = new T[n];
}

constexpr T* data() const noexcept {
return elements;
}

~vector() {
delete[] elements;
}
};

template <class T, size_t N>
class array {
T elements[N];

public:

constexpr const T* data() const noexcept {
return elements;
}

};


constexpr T* hello() const noexcept {
return elements;
}
};

template <typename T> class span_duplicate {
span_duplicate(T *, unsigned){}
span_duplicate(T *, unsigned){}

T array[10];
T array[10];

public:
public:

T* data() {
return array;
}
T* data() {
return array;
}

};
};
}

using namespace std;
Expand All @@ -89,21 +119,28 @@ void cast_without_data(int *ptr) {
float *p = (float*) ptr;
}

void warned_patterns(std::span<int> span_ptr, std::span<Base> base_span, span<int> span_without_qual) {
A *a1 = (A*)span_ptr.data(); // expected-warning{{unsafe invocation of span::data}}
a1 = (A*)span_ptr.data(); // expected-warning{{unsafe invocation of span::data}}
void warned_patterns_span(std::span<int> span_ptr, std::span<Base> base_span, span<int> span_without_qual) {
A *a1 = (A*)span_ptr.data(); // expected-warning{{unsafe invocation of 'data'}}
a1 = (A*)span_ptr.data(); // expected-warning{{unsafe invocation of 'data'}}

a1 = (A*)(span_ptr.data()); // expected-warning{{unsafe invocation of span::data}}
A *a2 = (A*) (span_without_qual.data()); // expected-warning{{unsafe invocation of span::data}}
a1 = (A*)(span_ptr.data()); // expected-warning{{unsafe invocation of 'data'}}
A *a2 = (A*) (span_without_qual.data()); // expected-warning{{unsafe invocation of 'data'}}

a2 = (A*) span_without_qual.data(); // expected-warning{{unsafe invocation of span::data}}
a2 = (A*) span_without_qual.data(); // expected-warning{{unsafe invocation of 'data'}}

// TODO:: Should we warn when we cast from base to derived type?
Derived *b = dynamic_cast<Derived*> (base_span.data());// expected-warning{{unsafe invocation of span::data}}
Derived *b = dynamic_cast<Derived*> (base_span.data());// expected-warning{{unsafe invocation of 'data'}}

// TODO:: This pattern is safe. We can add special handling for it, if we decide this
// is the recommended fixit for the unsafe invocations.
A *a3 = (A*)span_ptr.subspan(0, sizeof(A)).data(); // expected-warning{{unsafe invocation of span::data}}
A *a3 = (A*)span_ptr.subspan(0, sizeof(A)).data(); // expected-warning{{unsafe invocation of 'data'}}
}

void warned_patterns_array(std::array<int, 5> array_ptr, std::array<Base, 10> base_span, span<int> span_without_qual) {
const A *a1 = (A*)array_ptr.data(); // expected-warning{{unsafe invocation of 'data'}}
a1 = (A*)array_ptr.data(); // expected-warning{{unsafe invocation of 'data'}}

a1 = (A*)(array_ptr.data()); // expected-warning{{unsafe invocation of 'data'}}
}

void not_warned_patterns(std::span<A> span_ptr, std::span<Base> base_span) {
Expand Down
8 changes: 5 additions & 3 deletions compiler-rt/lib/sanitizer_common/sanitizer_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,12 @@ class ScopedErrorReportLock {
extern uptr stoptheworld_tracer_pid;
extern uptr stoptheworld_tracer_ppid;

// Returns true if we can read a memory range.
// Returns true if the entire range can be read.
bool IsAccessibleMemoryRange(uptr beg, uptr size);
// Returns true if we can read a memory range starting at `src`, and copies
// content into `dest`.
// Attempts to copy `n` bytes from memory range starting at `src` to `dest`.
// Returns true if the entire range can be read. Returns `false` if any part of
// the source range cannot be read, in which case the contents of `dest` are
// undefined.
bool TryMemCpy(void *dest, const void *src, uptr n);

// Error report formatting.
Expand Down
82 changes: 38 additions & 44 deletions lldb/source/Plugins/ObjectFile/Minidump/MinidumpFileBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ Status MinidumpFileBuilder::AddHeaderAndCalculateDirectories() {
StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
if (stop_info_sp) {
const StopReason &stop_reason = stop_info_sp->GetStopReason();
if (stop_reason == StopReason::eStopReasonException ||
stop_reason == StopReason::eStopReasonSignal)
if (stop_reason != lldb::eStopReasonInvalid)
m_expected_directories++;
}
}
Expand Down Expand Up @@ -685,50 +684,45 @@ Status MinidumpFileBuilder::AddExceptions() {
Status error;
for (const ThreadSP &thread_sp : thread_list) {
StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
bool add_exception = false;
if (stop_info_sp) {
switch (stop_info_sp->GetStopReason()) {
case eStopReasonSignal:
case eStopReasonException:
add_exception = true;
break;
default:
break;
}
}
if (add_exception) {
constexpr size_t minidump_exception_size =
sizeof(llvm::minidump::ExceptionStream);
error = AddDirectory(StreamType::Exception, minidump_exception_size);
if (error.Fail())
return error;
// If we don't have a stop info, or if it's invalid, skip.
if (!stop_info_sp ||
stop_info_sp->GetStopReason() == lldb::eStopReasonInvalid)
continue;

StopInfoSP stop_info_sp = thread_sp->GetStopInfo();
RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
Exception exp_record = {};
exp_record.ExceptionCode =
static_cast<llvm::support::ulittle32_t>(stop_info_sp->GetValue());
exp_record.ExceptionFlags = static_cast<llvm::support::ulittle32_t>(0);
exp_record.ExceptionRecord = static_cast<llvm::support::ulittle64_t>(0);
exp_record.ExceptionAddress = reg_ctx_sp->GetPC();
exp_record.NumberParameters = static_cast<llvm::support::ulittle32_t>(0);
exp_record.UnusedAlignment = static_cast<llvm::support::ulittle32_t>(0);
// exp_record.ExceptionInformation;

ExceptionStream exp_stream;
exp_stream.ThreadId =
static_cast<llvm::support::ulittle32_t>(thread_sp->GetID());
exp_stream.UnusedAlignment = static_cast<llvm::support::ulittle32_t>(0);
exp_stream.ExceptionRecord = exp_record;
auto Iter = m_tid_to_reg_ctx.find(thread_sp->GetID());
if (Iter != m_tid_to_reg_ctx.end()) {
exp_stream.ThreadContext = Iter->second;
} else {
exp_stream.ThreadContext.DataSize = 0;
exp_stream.ThreadContext.RVA = 0;
}
m_data.AppendData(&exp_stream, minidump_exception_size);
constexpr size_t minidump_exception_size =
sizeof(llvm::minidump::ExceptionStream);
error = AddDirectory(StreamType::Exception, minidump_exception_size);
if (error.Fail())
return error;

RegisterContextSP reg_ctx_sp(thread_sp->GetRegisterContext());
Exception exp_record = {};
exp_record.ExceptionCode =
static_cast<llvm::support::ulittle32_t>(stop_info_sp->GetValue());
exp_record.ExceptionFlags =
static_cast<llvm::support::ulittle32_t>(Exception::LLDB_FLAG);
exp_record.ExceptionRecord = static_cast<llvm::support::ulittle64_t>(0);
exp_record.ExceptionAddress = reg_ctx_sp->GetPC();
exp_record.NumberParameters = static_cast<llvm::support::ulittle32_t>(1);
std::string description = stop_info_sp->GetDescription();
// We have 120 bytes to work with and it's unlikely description will
// overflow, but we gotta check.
memcpy(&exp_record.ExceptionInformation, description.c_str(),
std::max(description.size(), Exception::MaxParameterBytes));
exp_record.UnusedAlignment = static_cast<llvm::support::ulittle32_t>(0);
ExceptionStream exp_stream;
exp_stream.ThreadId =
static_cast<llvm::support::ulittle32_t>(thread_sp->GetID());
exp_stream.UnusedAlignment = static_cast<llvm::support::ulittle32_t>(0);
exp_stream.ExceptionRecord = exp_record;
auto Iter = m_tid_to_reg_ctx.find(thread_sp->GetID());
if (Iter != m_tid_to_reg_ctx.end()) {
exp_stream.ThreadContext = Iter->second;
} else {
exp_stream.ThreadContext.DataSize = 0;
exp_stream.ThreadContext.RVA = 0;
}
m_data.AppendData(&exp_stream, minidump_exception_size);
}

return error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,4 @@ class MinidumpFileBuilder {
lldb::FileUP m_core_file;
lldb_private::SaveCoreOptions m_save_core_options;
};

#endif // LLDB_SOURCE_PLUGINS_OBJECTFILE_MINIDUMP_MINIDUMPFILEBUILDER_H
12 changes: 10 additions & 2 deletions lldb/source/Plugins/Process/minidump/ProcessMinidump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,16 @@ void ProcessMinidump::RefreshStateAfterStop() {
// No stop.
return;
}

stop_info = StopInfo::CreateStopReasonWithSignal(*stop_thread, signo);
const char *description = nullptr;
if (exception_stream.ExceptionRecord.ExceptionFlags ==
llvm::minidump::Exception::LLDB_FLAG)
description = reinterpret_cast<const char *>(
exception_stream.ExceptionRecord.ExceptionInformation);

llvm::StringRef description_str(description,
Exception::MaxParameterBytes);
stop_info = StopInfo::CreateStopReasonWithSignal(
*stop_thread, signo, description_str.str().c_str());
} else if (arch.GetTriple().getVendor() == llvm::Triple::Apple) {
stop_info = StopInfoMachException::CreateStopReasonWithMachException(
*stop_thread, exception_stream.ExceptionRecord.ExceptionCode, 2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -524,3 +524,23 @@ def test_multiple_exceptions_or_signals(self):
self.assertStopReason(thread.GetStopReason(), lldb.eStopReasonSignal)
stop_description = thread.GetStopDescription(256)
self.assertIn("SIGSEGV", stop_description)

def test_breakpoint_on_minidump(self):
"""
Test that LLDB breakpoints are recorded in Minidumps
"""
yaml = "linux-x86_64-exceptiondescription.yaml"
core = self.getBuildArtifact("breakpoint.core.dmp")
self.yaml2obj(yaml, core)
try:
# Create a target with the object file we just created from YAML
target = self.dbg.CreateTarget(None)
self.assertTrue(target, VALID_TARGET)
process = target.LoadCore(core)
self.assertTrue(process, VALID_PROCESS)
thread = process.GetThreadAtIndex(0)
stop_reason = thread.GetStopDescription(256)
self.assertIn("breakpoint 1.1", stop_reason)
finally:
if os.path.isfile(core):
os.unlink(core)
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--- !minidump
Streams:
- Type: SystemInfo
Processor Arch: AMD64
Processor Level: 6
Processor Revision: 15876
Number of Processors: 40
Platform ID: Linux
CSD Version: 'Linux 3.13.0-91-generic'
CPU:
Vendor ID: GenuineIntel
Version Info: 0x00000000
Feature Info: 0x00000000
- Type: ThreadList
Threads:
- Thread Id: 0x31F222
Context: 00000000000000
Stack:
Start of Memory Range: 0x7FFFFFFFD660
Content: ''
- Type: Exception
Thread ID: 0x31F222
Exception Record:
Exception Code: 0x2
Exception Flags: 0x4C4C4442
Exception Address: 0x555555556671
Number of Parameters: 1
Parameter 0: 0x696F706B61657262
Parameter 1: 0x312E3120746E
Parameter 2: 0x1
Parameter 3: 0x8000000000000000
Parameter 4: 0x200000002
Parameter 5: 0x8000000000000002
Parameter 7: 0x555555556671
Parameter 8: 0x1
Thread Context: ''
...
2 changes: 2 additions & 0 deletions llvm/include/llvm/BinaryFormat/Minidump.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ static_assert(sizeof(Thread) == 48);

struct Exception {
static constexpr size_t MaxParameters = 15;
static constexpr size_t MaxParameterBytes = MaxParameters * sizeof(uint64_t);
static const uint32_t LLDB_FLAG = 'LLDB';

support::ulittle32_t ExceptionCode;
support::ulittle32_t ExceptionFlags;
Expand Down
5 changes: 5 additions & 0 deletions llvm/include/llvm/Transforms/Utils/ScalarEvolutionExpander.h
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ class SCEVExpander : public SCEVVisitor<SCEVExpander, Value *> {
/// "expanded" form.
bool LSRMode;

/// When true, rewrite any divisors of UDiv expressions that may be 0 to
/// umax(Divisor, 1) to avoid introducing UB. If the divisor may be poison,
/// freeze it first.
bool SafeUDivMode = false;

typedef IRBuilder<InstSimplifyFolder, IRBuilderCallbackInserter> BuilderType;
BuilderType Builder;

Expand Down
20 changes: 19 additions & 1 deletion llvm/lib/Transforms/Utils/ScalarEvolutionExpander.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,21 @@ Value *SCEVExpander::visitUDivExpr(const SCEVUDivExpr *S) {
SCEV::FlagAnyWrap, /*IsSafeToHoist*/ true);
}

Value *RHS = expand(S->getRHS());
const SCEV *RHSExpr = S->getRHS();
Value *RHS = expand(RHSExpr);
if (SafeUDivMode) {
bool GuaranteedNotPoison =
ScalarEvolution::isGuaranteedNotToBePoison(RHSExpr);
if (!GuaranteedNotPoison)
RHS = Builder.CreateFreeze(RHS);

// We need an umax if either RHSExpr is not known to be zero, or if it is
// not guaranteed to be non-poison. In the later case, the frozen poison may
// be 0.
if (!SE.isKnownNonZero(RHSExpr) || !GuaranteedNotPoison)
RHS = Builder.CreateIntrinsic(RHS->getType(), Intrinsic::umax,
{RHS, ConstantInt::get(RHS->getType(), 1)});
}
return InsertBinop(Instruction::UDiv, LHS, RHS, SCEV::FlagAnyWrap,
/*IsSafeToHoist*/ SE.isKnownNonZero(S->getRHS()));
}
Expand Down Expand Up @@ -1376,11 +1390,14 @@ Value *SCEVExpander::visitSignExtendExpr(const SCEVSignExtendExpr *S) {
Value *SCEVExpander::expandMinMaxExpr(const SCEVNAryExpr *S,
Intrinsic::ID IntrinID, Twine Name,
bool IsSequential) {
bool PrevSafeMode = SafeUDivMode;
SafeUDivMode |= IsSequential;
Value *LHS = expand(S->getOperand(S->getNumOperands() - 1));
Type *Ty = LHS->getType();
if (IsSequential)
LHS = Builder.CreateFreeze(LHS);
for (int i = S->getNumOperands() - 2; i >= 0; --i) {
SafeUDivMode = (IsSequential && i != 0) || PrevSafeMode;
Value *RHS = expand(S->getOperand(i));
if (IsSequential && i != 0)
RHS = Builder.CreateFreeze(RHS);
Expand All @@ -1395,6 +1412,7 @@ Value *SCEVExpander::expandMinMaxExpr(const SCEVNAryExpr *S,
}
LHS = Sel;
}
SafeUDivMode = PrevSafeMode;
return LHS;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,12 +459,12 @@ exit:
ret i64 %p
}

; FIXME: currently the expansion of the loop bounds may introduce UB through the division.
define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch(ptr %dst, i64 %N) {
; CHECK-LABEL: define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch(
; CHECK-SAME: ptr [[DST:%.*]], i64 [[N:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[TMP0:%.*]] = udiv i64 42, [[N]]
; CHECK-NEXT: [[TMP10:%.*]] = call i64 @llvm.umax.i64(i64 [[N]], i64 1)
; CHECK-NEXT: [[TMP0:%.*]] = udiv i64 42, [[TMP10]]
; CHECK-NEXT: [[TMP8:%.*]] = freeze i64 [[TMP0]]
; CHECK-NEXT: [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[N]], i64 0)
; CHECK-NEXT: [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP8]], i64 [[SMAX]])
Expand Down Expand Up @@ -529,13 +529,14 @@ exit:

declare void @foo()

; FIXME: currently the expansion of the loop bounds may introduce UB through the division.
define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch_call_before_loop(ptr %dst, i64 %N) {
; CHECK-LABEL: define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch_call_before_loop(
; CHECK-SAME: ptr [[DST:%.*]], i64 [[N:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: call void @foo()
; CHECK-NEXT: [[TMP2:%.*]] = udiv i64 42, [[N]]
; CHECK-NEXT: [[TMP0:%.*]] = freeze i64 [[N]]
; CHECK-NEXT: [[TMP1:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP0]], i64 1)
; CHECK-NEXT: [[TMP2:%.*]] = udiv i64 42, [[TMP1]]
; CHECK-NEXT: [[TMP3:%.*]] = freeze i64 [[TMP2]]
; CHECK-NEXT: [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[N]], i64 0)
; CHECK-NEXT: [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP3]], i64 [[SMAX]])
Expand Down Expand Up @@ -599,14 +600,15 @@ exit:
ret i64 %p
}

; FIXME: currently the expansion of the loop bounds may introduce UB through the division.
define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch_loop_may_not_execute(ptr %dst, i64 %N, i1 %c) {
; CHECK-LABEL: define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch_loop_may_not_execute(
; CHECK-SAME: ptr [[DST:%.*]], i64 [[N:%.*]], i1 [[C:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: br i1 [[C]], label [[LOOP_HEADER_PREHEADER:%.*]], label [[EXIT:%.*]]
; CHECK: loop.header.preheader:
; CHECK-NEXT: [[TMP2:%.*]] = udiv i64 42, [[N]]
; CHECK-NEXT: [[TMP0:%.*]] = freeze i64 [[N]]
; CHECK-NEXT: [[TMP1:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP0]], i64 1)
; CHECK-NEXT: [[TMP2:%.*]] = udiv i64 42, [[TMP1]]
; CHECK-NEXT: [[TMP3:%.*]] = freeze i64 [[TMP2]]
; CHECK-NEXT: [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[N]], i64 0)
; CHECK-NEXT: [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP3]], i64 [[SMAX]])
Expand Down Expand Up @@ -672,12 +674,13 @@ exit:
ret i64 %p
}

; FIXME: currently the expansion of the loop bounds may introduce UB through the division.
define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch_different_bounds(ptr %dst, i64 %N, i64 %M) {
; CHECK-LABEL: define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch_different_bounds(
; CHECK-SAME: ptr [[DST:%.*]], i64 [[N:%.*]], i64 [[M:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[TMP2:%.*]] = udiv i64 42, [[M]]
; CHECK-NEXT: [[TMP0:%.*]] = freeze i64 [[M]]
; CHECK-NEXT: [[TMP1:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP0]], i64 1)
; CHECK-NEXT: [[TMP2:%.*]] = udiv i64 42, [[TMP1]]
; CHECK-NEXT: [[TMP3:%.*]] = freeze i64 [[TMP2]]
; CHECK-NEXT: [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[N]], i64 0)
; CHECK-NEXT: [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP3]], i64 [[SMAX]])
Expand Down Expand Up @@ -740,13 +743,13 @@ exit:
ret i64 %p
}

; FIXME: currently the expansion of the loop bounds may introduce UB through the division.
define i64 @multi_exit_4_exit_count_with_udiv_by_frozen_value_in_latch(ptr %dst, i64 %N) {
; CHECK-LABEL: define i64 @multi_exit_4_exit_count_with_udiv_by_frozen_value_in_latch(
; CHECK-SAME: ptr [[DST:%.*]], i64 [[N:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[FR_N:%.*]] = freeze i64 [[N]]
; CHECK-NEXT: [[TMP2:%.*]] = udiv i64 42, [[FR_N]]
; CHECK-NEXT: [[TMP1:%.*]] = call i64 @llvm.umax.i64(i64 [[FR_N]], i64 1)
; CHECK-NEXT: [[TMP2:%.*]] = udiv i64 42, [[TMP1]]
; CHECK-NEXT: [[TMP10:%.*]] = freeze i64 [[TMP2]]
; CHECK-NEXT: [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[N]], i64 0)
; CHECK-NEXT: [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP10]], i64 [[SMAX]])
Expand Down Expand Up @@ -931,12 +934,12 @@ exit:
ret void
}

; FIXME: currently the expansion of the loop bounds may introduce UB through the division.
define i64 @multi_exit_4_exit_count_with_urem_by_value_in_latch(ptr %dst, i64 %N) {
; CHECK-LABEL: define i64 @multi_exit_4_exit_count_with_urem_by_value_in_latch(
; CHECK-SAME: ptr [[DST:%.*]], i64 [[N:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[TMP0:%.*]] = udiv i64 42, [[N]]
; CHECK-NEXT: [[TMP12:%.*]] = call i64 @llvm.umax.i64(i64 [[N]], i64 1)
; CHECK-NEXT: [[TMP0:%.*]] = udiv i64 42, [[TMP12]]
; CHECK-NEXT: [[TMP1:%.*]] = mul nuw i64 [[N]], [[TMP0]]
; CHECK-NEXT: [[TMP2:%.*]] = sub i64 42, [[TMP1]]
; CHECK-NEXT: [[SMAX1:%.*]] = call i64 @llvm.smax.i64(i64 [[TMP2]], i64 0)
Expand Down Expand Up @@ -1002,7 +1005,6 @@ exit:
ret i64 %p
}

; FIXME: currently the expansion of the loop bounds may introduce UB through the division.
define i64 @multi_exit_4_exit_count_with_urem_by_constant_in_latch(ptr %dst, i64 %N) {
; CHECK-LABEL: define i64 @multi_exit_4_exit_count_with_urem_by_constant_in_latch(
; CHECK-SAME: ptr [[DST:%.*]], i64 [[N:%.*]]) {
Expand Down Expand Up @@ -1156,7 +1158,8 @@ define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch1(ptr %dst, i64 %
; CHECK-LABEL: define i64 @multi_exit_4_exit_count_with_udiv_by_value_in_latch1(
; CHECK-SAME: ptr [[DST:%.*]], i64 [[N:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[TMP9:%.*]] = udiv i64 42, [[N]]
; CHECK-NEXT: [[TMP8:%.*]] = call i64 @llvm.umax.i64(i64 [[N]], i64 1)
; CHECK-NEXT: [[TMP9:%.*]] = udiv i64 42, [[TMP8]]
; CHECK-NEXT: [[TMP10:%.*]] = freeze i64 [[TMP9]]
; CHECK-NEXT: [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[N]], i64 0)
; CHECK-NEXT: [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP10]], i64 [[SMAX]])
Expand Down Expand Up @@ -1262,13 +1265,14 @@ exit:
ret i64 %p
}

; FIXME: currently the expansion of the loop bounds may introduce UB through the division.
define i64 @multi_exit_count_with_udiv_by_value_in_latch_different_bounds_divisor_non_zero_may_be_poison(ptr %dst, i64 %N, i64 %M) {
; CHECK-LABEL: define i64 @multi_exit_count_with_udiv_by_value_in_latch_different_bounds_divisor_non_zero_may_be_poison(
; CHECK-SAME: ptr [[DST:%.*]], i64 [[N:%.*]], i64 [[M:%.*]]) {
; CHECK-NEXT: entry:
; CHECK-NEXT: [[M_1:%.*]] = call i64 @llvm.umax.i64(i64 [[M]], i64 1)
; CHECK-NEXT: [[TMP0:%.*]] = udiv i64 42, [[M_1]]
; CHECK-NEXT: [[TMP9:%.*]] = freeze i64 [[M_1]]
; CHECK-NEXT: [[TMP10:%.*]] = call i64 @llvm.umax.i64(i64 [[TMP9]], i64 1)
; CHECK-NEXT: [[TMP0:%.*]] = udiv i64 42, [[TMP10]]
; CHECK-NEXT: [[TMP1:%.*]] = freeze i64 [[TMP0]]
; CHECK-NEXT: [[SMAX:%.*]] = call i64 @llvm.smax.i64(i64 [[N]], i64 0)
; CHECK-NEXT: [[UMIN:%.*]] = call i64 @llvm.umin.i64(i64 [[TMP1]], i64 [[SMAX]])
Expand Down