[LLVMdev] RFC: Proposal to Remove Poison

Sanjoy Das sanjoy at playingwithpointers.com
Sun Feb 8 21:20:56 PST 2015


> I agree with Hal that the right way to assign semantics to undef is
> that say that it is some N bit value at each *use*; and the N bit
> value undef pretends to be at each use may be a different one.  I

I take back what I said -- deciding what value undef will correspond
to at each use is fairly counter-intuitive.  For instance, this means
inlining is not meaning-preserving:

declare i32 @use(i32, i32)

define i32 @f(i32 %x) alwaysinline {
 entry:
  %r = call i32 @use(i32 %x, i32 %x)
  ret i32 %r
}

define i32 @g() {
 entry:
  %r = call i32 @f(i32 undef)
  ret i32 %r
}

on `opt -always-inline` is transformed to

declare i32 @use(i32, i32)

; Function Attrs: alwaysinline
define i32 @f(i32 %x) #0 {
entry:
  %r = call i32 @use(i32 %x, i32 %x)
  ret i32 %r
}

define i32 @g() {
entry:
  %r.i = call i32 @use(i32 undef, i32 undef)
  ret i32 %r.i
}

attributes #0 = { alwaysinline }


If we say "undef chooses any value at each use", the first program
always calls @use with both parameters equal (so if @use was printing
the xor of its two parameters, it would always print 0) while the
second program calls @use with two arbitrary values.

Another example related to, but slightly different from David's
motivating example:

define i32 @s(i32* %p) {
 entry:
  store i32 undef, i32* %p
  %r = load i32* %p
  %m = call i32 @use(i32 %r, i32 %r)
  ret i32 %r
}

opt -O3 transforms this to

define i32 @s(i32* nocapture readnone %p) {
entry:
  %m = tail call i32 @use(i32 undef, i32 undef)
  ret i32 undef
}

This example has the same problem as the first one -- the value of i32
undef was supposed to have been "decided" at its only use, the store;
but instead LLVM has increased the program's degree of freedom (I'm
using the term informally).

As far as I can tell, there are two ways to fix this:

 1. teach all of LLVM to treat undef specially.  This may be difficult
because unlike every other SSA value, undef's value is decided at
uses, not defs.  For instance, a "fixed" version of the inliner pass
could coerce the "i32 undef" to, say, "i32 0" before inlining the body
of the callee.

 2. represent undef as an instruction, not as a value.  Then the first
example becomes

define i32 @f(i32 %x) alwaysinline {
 entry:
  %r = call i32 @use(i32 %x, i32 %x)
  ret i32 %r
}

define i32 @g() {
 entry:
  %u = undef
  %r = call i32 @f(i32 %u)
  ret i32 %r
}

which opt transforms to

define i32 @g() {
entry:
  %u = undef
  %r.i = call i32 @use(i32 %u, i32 %u)
  ret i32 %r.i
}

I do not want to derail this thread by taking off on a tangent, but if
there is interest in (2) [or (1), but I think (1) is too hard to do
correctly] I can write up a proposal and start a separate thread on
llvmdev.

-- Sanjoy



More information about the llvm-dev mailing list