types in load/store

Hi,

I have a confusion about types used in load/store,
(The Often Misunderstood GEP Instruction — LLVM 16.0.0git documentation) says that [...]
Furthermore, loads and stores don't have to use the same types as the
type of the underlying object. Types in this context serve only to
specify memory size and alignment. Beyond that there are merely a hint
to the optimizer indicating how the value will likely be used. [...]

I was wondering that in which cases we may have undefined behaviors if
types in load/store don't match the objects'. In C, they need to be
compatible, which means the size of types should be same, otherwise
undefined. Do we follow the same convention in LLVM IR? Does GEP also
follow similar rules when types mismatch?

The other question is about 'when loading a value of a type like i20
with a size that is not an integral number of bytes, the result is
undefined if the value was not originally written using a store of the
same type'. At this case, can we make an assumption that typically we
only load the 20 bits, but ignore extra bits, like what store does at
such a case. Is there any problem that stops such an assumption?

Thanks.

Hi Jianzhou,

The other question is about 'when loading a value of a type like i20
with a size that is not an integral number of bytes, the result is
undefined if the value was not originally written using a store of the
same type'. At this case, can we make an assumption that typically we
only load the 20 bits, but ignore extra bits, like what store does at
such a case. Is there any problem that stops such an assumption?

the way the code generators currently work is that when you store an
i20, three bytes are written: your 20 bits followed by 4 zero bits.
When an i20 is loaded, which means loading three bytes, the code
generators "know" that the extra 4 bits must be zero, and may perform
some simplifications based on this knowledge. If you store three bytes,
and bits 20 to 23 are not zero, and you try to load it as an i20, you
may therefore get strange results due to such simplifications.

Ciao,

Duncan.

PS: The fact that the extra stored bits are zero shouldn't be relied
upon, since it may change in the future.

Interesting -- sounds like a place where signed and unsigned types
would be helpful, since they could be stored zero- or sign-extended as
appropriate.

~ Scott

Hi,

I have a confusion about types used in load/store,
(The Often Misunderstood GEP Instruction — LLVM 16.0.0git documentation) says that [...]
Furthermore, loads and stores don't have to use the same types as the
type of the underlying object. Types in this context serve only to
specify memory size and alignment. Beyond that there are merely a hint
to the optimizer indicating how the value will likely be used. [...]

I was wondering that in which cases we may have undefined behaviors if
types in load/store don't match the objects'. In C, they need to be
compatible, which means the size of types should be same, otherwise
undefined. Do we follow the same convention in LLVM IR? Does GEP also
follow similar rules when types mismatch?

I misunderstood C99 ISO, such behaviors are defined not when types
have the same sizes, but when they are same (compatible) types with
signed or qualified extension (this is much stronger than being of
same sizes), or reading char by char:

7 An object shall have its stored value accessed only by an lvalue
expression that has one of
the following types:
— a type compatible with the effective type of the object,
[...]
— a type that is the signed or unsigned type corresponding to the
effective type of the
object,
[...]
— an aggregate or union type that includes one of the aforementioned
types among its
members (including, recursively, a member of a subaggregate or
contained union), or
— a character type.
(sec 6.5, items 6 and 7, page 67-68,
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf)

If LLVM IR is weaker than these C restrictions, then I have the
following questions about when GEP is undefined:
1) Can I load a value partially or overlapped with other stored
values? For example, if the stored values are of type [10*i32], and we
cast i32* to {i8, i4, float} *, can we successfully load each fields
via the addresses from GEPs? Since IR allows to define data layout of
targets (size and alignment for types), does whether such GEPs
undefined depend on its data layout?

2) C allows characters as the least granularity when loading. Does
LLVM have the same assumption?

Hi Jianzhou,

I misunderstood C99 ISO, such behaviors are defined not when types
have the same sizes, but when they are same (compatible) types with
signed or qualified extension (this is much stronger than being of
same sizes), or reading char by char:

7 An object shall have its stored value accessed only by an lvalue
expression that has one of
the following types:
— a type compatible with the effective type of the object,
[...]
— a type that is the signed or unsigned type corresponding to the
effective type of the
object,
[...]
— an aggregate or union type that includes one of the aforementioned
types among its
members (including, recursively, a member of a subaggregate or
contained union), or
— a character type.
(sec 6.5, items 6 and 7, page 67-68,
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf)

LLVM does not have any such restrictions.

If LLVM IR is weaker than these C restrictions, then I have the
following questions about when GEP is undefined:

In your examples, it is not GEP that would be undefined, but a load or
store from the GEP. GEP just offsets the memory address. In C too it
is not invalid to offset or cast a pointer; it is loading from or storing
to the cast or offset pointer that may be invalid.

1) Can I load a value partially or overlapped with other stored
values? For example, if the stored values are of type [10*i32], and we
cast i32* to {i8, i4, float} *, can we successfully load each fields
via the addresses from GEPs?

Yes, except that as previously mentioned this is invalid for the i4 if
the original value was not set by performing an i4 store.

Since IR allows to define data layout of

targets (size and alignment for types), does whether such GEPs
undefined depend on its data layout?

As I mentioned, there is no problem with GEPs being undefined.

2) C allows characters as the least granularity when loading. Does
LLVM have the same assumption?

LLVM doesn't have a notion of "character". Currently all processors that LLVM
targets are capable of addressing an octet (8 bits), but nothing smaller. This
means that the smallest granularity is currently i8.

Ciao,

Duncan.

Hi Jianzhou,

I misunderstood C99 ISO, such behaviors are defined not when types
have the same sizes, but when they are same (compatible) types with
signed or qualified extension (this is much stronger than being of
same sizes), or reading char by char:

7 An object shall have its stored value accessed only by an lvalue
expression that has one of
the following types:
— a type compatible with the effective type of the object,
[...]
— a type that is the signed or unsigned type corresponding to the
effective type of the
object,
[...]
— an aggregate or union type that includes one of the aforementioned
types among its
members (including, recursively, a member of a subaggregate or
contained union), or
— a character type.
(sec 6.5, items 6 and 7, page 67-68,
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf)

LLVM does not have any such restrictions.

If LLVM IR is weaker than these C restrictions, then I have the
following questions about when GEP is undefined:

In your examples, it is not GEP that would be undefined, but a load or
store from the GEP. GEP just offsets the memory address. In C too it
is not invalid to offset or cast a pointer; it is loading from or storing
to the cast or offset pointer that may be invalid.

1) Can I load a value partially or overlapped with other stored
values? For example, if the stored values are of type [10*i32], and we
cast i32* to {i8, i4, float} *, can we successfully load each fields
via the addresses from GEPs?

Yes, except that as previously mentioned this is invalid for the i4 if
the original value was not set by performing an i4 store.

Since IR allows to define data layout of

targets (size and alignment for types), does whether such GEPs
undefined depend on its data layout?

As I mentioned, there is no problem with GEPs being undefined.

2) C allows characters as the least granularity when loading. Does
LLVM have the same assumption?

LLVM doesn't have a notion of "character". Currently all processors that LLVM
targets are capable of addressing an octet (8 bits), but nothing smaller. This
means that the smallest granularity is currently i8.

Thanks. To understand how load/store work in LLVM, I looked into the
interpreter, where I found the target information is defined in
TargetData class, and aggregate types (like struct and array) compute
the correct padding and alignment from TargetData first before memory
access.

But I still run into one question, according to the code, the
visitLoad/visitStore functions used by the interpreter does not allow
accessing aggregate types, only simple types are legal. On the other
hand, the GenericValue used by interpreter to store values in memory
only considers simple types (int, float, typ*) too, and each
'getOperandValue' also takes aggregate constants illegal when
converting them into GenericValue.

Actually my input *.ll files have load/store instructions on struct
(used in functions), which are taken as return values to ensure not to
be deleted. Interpreters (lli -force-interpreter ) can still
interpreter the code without any error/warning about meeting aggregate
values for memory accessing. I was wondering if lli (2.7) does any
transformation to flatten aggregate types into simple types, and split
such loads/stores into a sequence on primitive values. Thanks.

Hi Jianzhou,

I misunderstood C99 ISO, such behaviors are defined not when types
have the same sizes, but when they are same (compatible) types with
signed or qualified extension (this is much stronger than being of
same sizes), or reading char by char:

7 An object shall have its stored value accessed only by an lvalue
expression that has one of
the following types:
— a type compatible with the effective type of the object,
[...]
— a type that is the signed or unsigned type corresponding to the
effective type of the
object,
[...]
— an aggregate or union type that includes one of the aforementioned
types among its
members (including, recursively, a member of a subaggregate or
contained union), or
— a character type.
(sec 6.5, items 6 and 7, page 67-68,
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf)

LLVM does not have any such restrictions.

If LLVM IR is weaker than these C restrictions, then I have the
following questions about when GEP is undefined:

In your examples, it is not GEP that would be undefined, but a load or
store from the GEP. GEP just offsets the memory address. In C too it
is not invalid to offset or cast a pointer; it is loading from or storing
to the cast or offset pointer that may be invalid.

1) Can I load a value partially or overlapped with other stored
values? For example, if the stored values are of type [10*i32], and we
cast i32* to {i8, i4, float} *, can we successfully load each fields
via the addresses from GEPs?

Yes, except that as previously mentioned this is invalid for the i4 if
the original value was not set by performing an i4 store.

Since IR allows to define data layout of

targets (size and alignment for types), does whether such GEPs
undefined depend on its data layout?

As I mentioned, there is no problem with GEPs being undefined.

2) C allows characters as the least granularity when loading. Does
LLVM have the same assumption?

LLVM doesn't have a notion of "character". Currently all processors that LLVM
targets are capable of addressing an octet (8 bits), but nothing smaller. This
means that the smallest granularity is currently i8.

Thanks. To understand how load/store work in LLVM, I looked into the
interpreter, where I found the target information is defined in
TargetData class, and aggregate types (like struct and array) compute
the correct padding and alignment from TargetData first before memory
access.

But I still run into one question, according to the code, the
visitLoad/visitStore functions used by the interpreter does not allow
accessing aggregate types, only simple types are legal. On the other
hand, the GenericValue used by interpreter to store values in memory
only considers simple types (int, float, typ*) too, and each
'getOperandValue' also takes aggregate constants illegal when
converting them into GenericValue.

Actually my input *.ll files have load/store instructions on struct
(used in functions), which are taken as return values to ensure not to
be deleted. Interpreters (lli -force-interpreter ) can still
interpreter the code without any error/warning about meeting aggregate
values for memory accessing. I was wondering if lli (2.7) does any
transformation to flatten aggregate types into simple types, and split
such loads/stores into a sequence on primitive values. Thanks.

Sorry. lli doesn't allow such loads/stores with -force-interpreter. (I
had just gave a wrong option...) ---
  LLVM ERROR: Cannot load value of type { i32, float }!