nested GEP in a static initializer fails

Hi,

Given this LLVM assembly:

  @a = global i8* getelementptr (i8* null, i64 mul (i64 ptrtoint (i32* getelementptr (i32* null, i32 1) to i64), i64 2))

llc fails an assertion:

  llc: /home/jdenny/llvm-svn/include/llvm/Support/Casting.h:202: typename llvm::cast_retty<To, From>::ret_type llvm::cast(const Y&) [with X = llvm::ConstantInt, Y = llvm::Value*]: Assertion `isa<X>(Val) && "cast<Ty>() argument of incompatible type!"' failed.

This has been reported previously as a bug and was resolved as invalid:

  http://llvm.org/bugs/show_bug.cgi?id=2672

The explanation was:

"I don't think we should try to fold arbitrary expressions in the code
generator. Instead, we should accept that targets have limitations on
these. We can't ever support things like "void *X = &G / &H;" for
example."

Are the limitations on what can be placed in a static initializer
documented somewhere? When those limitations aren't obeyed, would it be
possible for llc to print a more intelligible error message for the user?

Thanks.

Joel E. Denny wrote:

Hi,

Given this LLVM assembly:

   @a = global i8* getelementptr (i8* null, i64 mul (i64 ptrtoint (i32* getelementptr (i32* null, i32 1) to i64), i64 2))

llc fails an assertion:

   llc: /home/jdenny/llvm-svn/include/llvm/Support/Casting.h:202: typename llvm::cast_retty<To, From>::ret_type llvm::cast(const Y&) [with X = llvm::ConstantInt, Y = llvm::Value*]: Assertion `isa<X>(Val)&& "cast<Ty>() argument of incompatible type!"' failed.

This has been reported previously as a bug and was resolved as invalid:

   http://llvm.org/bugs/show_bug.cgi?id=2672

The explanation was:

"I don't think we should try to fold arbitrary expressions in the code
generator. Instead, we should accept that targets have limitations on
these. We can't ever support things like "void *X =&G /&H;" for
example."

Are the limitations on what can be placed in a static initializer
documented somewhere?

No.

   When those limitations aren't obeyed, would it be

possible for llc to print a more intelligible error message for the user?

The limitations are a product of the ABI and on-disk .o file format. For example, given "int X = (int)&G / (int)&H;", llc will happily generate "X: .long G/H" which is invalid assembly, but llc doesn't know that.

Maybe with the MC infrastructure we could start to figure out what is and isn't representable. That would be nice since the frontend may need to change its behaviour, for example, GCC lowers the example code into a C++ static initializer function to calculate the value for X at load time before main() starts. As of today, llvm-gcc and clang both get this wrong.

Nick

Hi Nick,

Joel E. Denny wrote:

> Given this LLVM assembly:
>
> @a = global i8* getelementptr (i8* null, i64 mul (i64 ptrtoint (i32*
> getelementptr (i32* null, i32 1) to i64), i64 2))
>
> llc fails an assertion:
>
> llc: /home/jdenny/llvm-svn/include/llvm/Support/Casting.h:202: typename
> llvm::cast_retty<To, From>::ret_type llvm::cast(const Y&) [with X =
> llvm::ConstantInt, Y = llvm::Value*]: Assertion `isa<X>(Val)&& "cast<Ty>()
> argument of incompatible type!"' failed.

The limitations are a product of the ABI and on-disk .o file format. For
example, given "int X = (int)&G / (int)&H;", llc will happily generate "X:
.long G/H" which is invalid assembly, but llc doesn't know that.

Maybe with the MC infrastructure we could start to figure out what is and
isn't representable.

llc is already able to determine that something has gone wrong. It fails
an assertion. Could that be turned into a user-friendly error message?

However, I'm not sure that the target assembly's exact limitations are
really the issue in the cases I've been encountering. I think the issue
is how far LLVM is able to go to fold constant expressions, in general.

For example, for my platform, llc folds the add but not the getelementptr
in:

  @a = global i8* getelementptr (i8* null, i64 add (i64 3, i64 2))

so we get:

  .quad 0+5

However, it folds neither of those in:

  @b = global i64 add (i64 ptrtoint (i8* getelementptr (i8* null, i64 3) to i64), i64 2)

so we get:

  .quad ((0+1)*3)+2

In other words, it seems llc knows when it must fold an add, but it
doesn't do it if it doesn't have to. Why can't it use the same logic for
getelementptr? For example, if a getelementptr invocation has the
following form:

  <result> = getelementptr (<pty>* null{, <ty> <idx>}*)

where every idx is a constant, is there any reason this getelementptr
cannot be folded? This would allow more flexibility in building
expressions that use getelementptr to compute sizeof or offsetof.

In case it isn't clear, my goal is more about understanding constant
folding in LLVM than in seeing this particular functionality implemented.

Thanks.

For example, for my platform, llc folds the add but not the getelementptr
in:

@a = global i8* getelementptr (i8* null, i64 add (i64 3, i64 2))

This gets folded when it's read in; there's no other way to represent
a constant 'i64 add (i64 3, i64 2)' in LLVM than 'i64 5'. (i.e. if you
try to create the add() constant manually you get i64 5 automatically)

You can see easily see this by running this command from a terminal:
  echo "@a = global i8* getelementptr (i8* null, i64 add (i64 3, i64
2))" | llvm-as | llvm-dis
which outputs
  @a = global i8* getelementptr (i8* null, i64 5)

The getelementptr expression can't be folded like this in general
because it depends on the sizes and alignments of types, which depend
on targetdata. This is probably not really the case for i8*, but there
could be a platform where i8 has a size > 1 (unless LLVM defines this
problem away by explicitly not supporting that).

so we get:

.quad 0+5

However, it folds neither of those in:

@b = global i64 add (i64 ptrtoint (i8* getelementptr (i8* null, i64 3) to i64), i64 2)

so we get:

.quad ((0+1)*3)+2

In other words, it seems llc knows when it must fold an add, but it
doesn't do it if it doesn't have to. Why can't it use the same logic for
getelementptr? For example, if a getelementptr invocation has the
following form:

<result> = getelementptr (<pty>* null{, <ty> <idx>}*)

where every idx is a constant, is there any reason this getelementptr
cannot be folded? This would allow more flexibility in building
expressions that use getelementptr to compute sizeof or offsetof.

In case it isn't clear, my goal is more about understanding constant
folding in LLVM than in seeing this particular functionality implemented.

It could certainly be folded if you happen to have the TargetData for
the relevant target architecture at hand.
I know there's llvm/Support/TargetFolder.h for use with IRBuilder,
which uses ConstantFoldConstantExpression() from
llvm/Analysis/ConstantFolding.h. That handles GEPs of null pointers.
As for transformation passes, grepping for
ConstantFoldConstantExpression tells me that instruction combining
(-instcombine) calls this method on all operands of instructions, and
-globalopt seems to calls it on initializers of globals. (But in a
quick test with opt -globalopt it didn't fold, so perhaps it prefers
not to create a inttoptr constant?)

Hi Frits,

> For example, for my platform, llc folds the add but not the getelementptr
> in:
>
> @a = global i8* getelementptr (i8* null, i64 add (i64 3, i64 2))

This gets folded when it's read in; there's no other way to represent
a constant 'i64 add (i64 3, i64 2)' in LLVM than 'i64 5'. (i.e. if you
try to create the add() constant manually you get i64 5 automatically)

Thanks, I didn't realize this.

If I understand you correctly, then, in order to determine how my LLVM
assembly will be handled, I must understand the internal LLVM API and data
structures. That is, I must understand what LLVM can represent so I can
determine what will be folded, what the resulting expression will be, and
thus whether the target assembly can represent the expression.

As for transformation passes, grepping for
ConstantFoldConstantExpression tells me that instruction combining
(-instcombine) calls this method on all operands of instructions, and
-globalopt seems to calls it on initializers of globals.

Ah, thanks. Based on a few tests, it appears I can use more powerful
constant expressions in LLVM assembly when I use opt -globalopt. For
example, nested GEPs do not produce assertion failures now.

I wonder if that assertion failure could be turned into an error message
that suggests using "opt -globalopt".