Clarification about undefined behaviour of `invariant.load` instructions

Looking at the test/Analysis/MemoryDependenceAnalysis/InvariantLoad.ll:

define i8 @test(i1 %cmp) {
  %p = alloca i8
  %addr = getelementptr inbounds i8, i8* %p, i64 0
  store i8 5, i8* %addr
  br label %header
  %i = phi i8 [0, %entry], [, %backedge]
  br i1 %cmp, label %alive, label %dead
  call void @foo(i8* %p)
  %v = load i8, i8* %addr, !invariant.load !1
  %i.1 = add i8 %i, %v
  br label %alive
  %i.2 = phi i8 [%i, %header], [%i.1, %dead]
  store i8 -5, i8* %addr
  br label %backedge
  call void @llvm.memset.p0i8.i8(i8 * align 1 %p, i8 0, i32 1, i1 false) = add i8 %i.2, 1
  %cmp.loop = icmp ugt i8, 100
  br i1 %cmp.loop, label %exit, label %header
  %res = load i8, i8* %addr
  ret i8 %res

we have the invariant load %v = load i8, i8* %addr, !invariant.load !1. The memory location
is initialised by store i8 5, i8* %addr and, following the execution of the load, is modified by
store i8 -5, i8* %addr ?

Is this undefined behaviour?

The LangRef says:

If a load instruction tagged with the !invariant.load
metadata is executed, the memory location referenced by the load has
to contain the same value at all points in the program where the
memory location is dereferenceable; otherwise, the behavior is

Or should we interpret this to mean that the load is never executed? (The basic block label dead suggests so, but it’s not entirely clear).

Could we optimise the load to 5, i.e. delete it and change %i.1 = ... into %i.1 = add i8 %i, 5

Yep there’s UB on the path header → dead → alive, but not on header → alive → backedge

So LLVM can’t assume the header → dead edge is never executed, and thus you can remove the whole dead BB. You can also replace the load with 5, if you want, yes.