Discussion:
z/OS enum size pitfall
Daniel Richard G.
2017-08-22 20:13:54 UTC
Permalink
Hello list,

I'm writing in to report a bizarre issue with the IBM z/OS XLC compiler
that is currently causing one gnulib test to fail (test-timespec), and
may present an issue for application code simply because no other
compiler does things this way. My hope is to have gnulib integrate a
workaround so that this won't bite anyone else.

I have been in contact with IBM about this, originally reporting the
issue as a compiler bug. However, they responded that the compiler
behavior is conformant to the C standard and that they are less
concerned with matching the behavior of other systems than keeping
things as-is for the benefit of existing customer application code.

The problem has to do with the implicit integer type that is used for
enum symbols. Here is a sample program that illustrates the issue:

--------8<--------
#include <stdio.h>

enum { BILLION = 1000000000 };

static const unsigned int BILLION2 = 1000000000;

int main(void) {
int x = -999999999;
printf("BILLION = %d\n", (int)BILLION);
printf("x = %d\n", x);
printf("x / BILLION = %d\n", (int)(x / BILLION));
return 0;
}
-------->8--------

On GNU/Linux and AIX, with a minimal compiler invocation, this
program prints

BILLION = 1000000000
x = -999999999
x / BILLION = 0

However, on z/OS, it prints

BILLION = 1000000000
x = -999999999
x / BILLION = 3

What happens is that BILLION is implicitly typed as an unsigned int,
rather than an int. If you edit the code above to use BILLION2 instead
of BILLION, you'll see the same result on GNU/Linux.

test-timespec fails not because of the time function being tested, but
because of how TIMESPEC_RESOLUTION is defined in timespec.h.

IBM, in their response, suggested specifying the flag -qenumsize=4 .
With this flag, the output on z/OS matches that of other systems,
test-timespec passes, and the rest of the gnulib test suite is
unaffected. I think it may be worth considering having gnulib add this
flag by default on z/OS, to get the expected behavior.


--Daniel
--
Daniel Richard G. || ***@iSKUNK.ORG
My ASCII-art .sig got a bad case of Times New Roman.
Ben Pfaff
2017-08-22 20:25:55 UTC
Permalink
Post by Daniel Richard G.
Hello list,
I'm writing in to report a bizarre issue with the IBM z/OS XLC compiler
that is currently causing one gnulib test to fail (test-timespec), and
may present an issue for application code simply because no other
compiler does things this way. My hope is to have gnulib integrate a
workaround so that this won't bite anyone else.
I have been in contact with IBM about this, originally reporting the
issue as a compiler bug. However, they responded that the compiler
behavior is conformant to the C standard and that they are less
concerned with matching the behavior of other systems than keeping
things as-is for the benefit of existing customer application code.
The problem has to do with the implicit integer type that is used for
--------8<--------
#include <stdio.h>
enum { BILLION = 1000000000 };
static const unsigned int BILLION2 = 1000000000;
int main(void) {
int x = -999999999;
printf("BILLION = %d\n", (int)BILLION);
printf("x = %d\n", x);
printf("x / BILLION = %d\n", (int)(x / BILLION));
return 0;
}
-------->8--------
On GNU/Linux and AIX, with a minimal compiler invocation, this
program prints
BILLION = 1000000000
x = -999999999
x / BILLION = 0
However, on z/OS, it prints
BILLION = 1000000000
x = -999999999
x / BILLION = 3
What happens is that BILLION is implicitly typed as an unsigned int,
rather than an int. If you edit the code above to use BILLION2 instead
of BILLION, you'll see the same result on GNU/Linux.
It's odd that they claim that this conforms to the C standard. C11
says, in section 6.4.4.3 "Enumeration constants":

An identifier declared as an enumeration constant has type int.

It also says in section 6.7.2.2 "Enumeration specifiers":

The identifiers in an enumerator list are declared as constants that
have type int and may appear wherever such are permitted.

This seems pretty clear to me, so I wonder how this interpretation
arises.
Tim Rühsen
2017-08-22 20:43:07 UTC
Permalink
Post by Ben Pfaff
Post by Daniel Richard G.
Hello list,
I'm writing in to report a bizarre issue with the IBM z/OS XLC compiler
that is currently causing one gnulib test to fail (test-timespec), and
may present an issue for application code simply because no other
compiler does things this way. My hope is to have gnulib integrate a
workaround so that this won't bite anyone else.
I have been in contact with IBM about this, originally reporting the
issue as a compiler bug. However, they responded that the compiler
behavior is conformant to the C standard and that they are less
concerned with matching the behavior of other systems than keeping
things as-is for the benefit of existing customer application code.
The problem has to do with the implicit integer type that is used for
--------8<--------
#include <stdio.h>
enum { BILLION = 1000000000 };
static const unsigned int BILLION2 = 1000000000;
int main(void) {
int x = -999999999;
printf("BILLION = %d\n", (int)BILLION);
printf("x = %d\n", x);
printf("x / BILLION = %d\n", (int)(x / BILLION));
return 0;
}
-------->8--------
On GNU/Linux and AIX, with a minimal compiler invocation, this
program prints
BILLION = 1000000000
x = -999999999
x / BILLION = 0
However, on z/OS, it prints
BILLION = 1000000000
x = -999999999
x / BILLION = 3
What happens is that BILLION is implicitly typed as an unsigned int,
rather than an int. If you edit the code above to use BILLION2 instead
of BILLION, you'll see the same result on GNU/Linux.
It's odd that they claim that this conforms to the C standard. C11
An identifier declared as an enumeration constant has type int.
The identifiers in an enumerator list are declared as constants that
have type int and may appear wherever such are permitted.
This seems pretty clear to me, so I wonder how this interpretation
arises.
Do you know to which C standard the XLC compiler complies to ?

C99, 6.7.2.2p4 says

Each enumerated type shall be compatible with char, a signed integer type,
or an unsigned integer type. The choice of type is implementation-defined,108)
but shall be capable of representing the values of all the members of the
enumeration.

Regards, Tim
Ben Pfaff
2017-08-22 22:01:24 UTC
Permalink
Post by Tim Rühsen
Post by Ben Pfaff
Post by Daniel Richard G.
What happens is that BILLION is implicitly typed as an unsigned int,
rather than an int. If you edit the code above to use BILLION2 instead
of BILLION, you'll see the same result on GNU/Linux.
It's odd that they claim that this conforms to the C standard. C11
An identifier declared as an enumeration constant has type int.
The identifiers in an enumerator list are declared as constants that
have type int and may appear wherever such are permitted.
This seems pretty clear to me, so I wonder how this interpretation
arises.
Do you know to which C standard the XLC compiler complies to ?
C99, 6.7.2.2p4 says
Each enumerated type shall be compatible with char, a signed integer type,
or an unsigned integer type. The choice of type is implementation-defined,108)
but shall be capable of representing the values of all the members of the
enumeration.
I don't know what XLC conforms to.

C11 has the same text in 6.7.2.2p4. The specification for enums has not
changed significantly since C89.

Paul Eggert already explained the distinction between enumeration
constants and enumeration types, so I won't repeat it.
Daniel Richard G.
2017-08-23 02:09:22 UTC
Permalink
Post by Ben Pfaff
I don't know what XLC conforms to.
C11 has the same text in 6.7.2.2p4. The specification for enums has
not changed significantly since C89.
Paul Eggert already explained the distinction between enumeration
constants and enumeration types, so I won't repeat it.
All the commentary here is greatly appreciated; please excuse my delay
in replying.

Here is what IBM said relating to standards:

The compiler behavior is correct. The C standard says that the
enumerated type can be char, or any signed or unsigned integer type
(6.7.2.2#4).

The test case uses a default enumsize which is small and for the
range of values in the test case it reserves an unsigned int to hold
the enumeration values (this is documented in the User's Guide).

The conversion rules make the signed into unsigned int (6.3.1.8#1)
meaning that expression becomes (unsigned int)x/BILLION because
BILLION is an unsigned int;

The conversion of -999999999 produces 3294967297 (UINT_MAX +1
-999999999) which when divided by 1000000000 produces 3 as the
result.

There was no further qualification of "C standard" in the first
paragraph. However, the test program still returns 3 when XLC is
in C99 mode:

$ xlc -qlanglvl=stdc99 -o enumsize enumsize.c
$ ./enumsize
BILLION = 1000000000
x = -999999999
x / BILLION = 3

Now, from what I'm hearing here, it sounds like IBM may be in the wrong---
and this would be quite convenient, because while they may not be too
bothered when their interpretation of POSIX et al. is different from the
rest of the world's, they _will_ care about a red-letter violation of a
spec they claim to support.

I can't standards-lawyer my way out of a paper bag, but if someone here
could provide a line of argument that IBM's enum shenanigans are
categorically unsupported by the standard, I'll gladly send it on in the
hope that it'll get the beast to move.

One minor addendum: In reviewing the support ticket, I noticed a
suggestion to use

#pragma enum(int)

as a workaround, which would probably be more convenient than a compiler
flag (this could go right into config.h). But even though it is supposed
to be equivalent to the ENUMSIZE option, it does not affect the output
of the test program. Seems like another PMR is in order...


--Daniel
--
Daniel Richard G. || ***@iSKUNK.ORG
My ASCII-art .sig got a bad case of Times New Roman.
Ben Pfaff
2017-08-23 03:38:09 UTC
Permalink
Post by Daniel Richard G.
Post by Ben Pfaff
I don't know what XLC conforms to.
C11 has the same text in 6.7.2.2p4. The specification for enums has
not changed significantly since C89.
Paul Eggert already explained the distinction between enumeration
constants and enumeration types, so I won't repeat it.
All the commentary here is greatly appreciated; please excuse my delay
in replying.
The compiler behavior is correct. The C standard says that the
enumerated type can be char, or any signed or unsigned integer type
(6.7.2.2#4).
The test case uses a default enumsize which is small and for the
range of values in the test case it reserves an unsigned int to hold
the enumeration values (this is documented in the User's Guide).
The conversion rules make the signed into unsigned int (6.3.1.8#1)
meaning that expression becomes (unsigned int)x/BILLION because
BILLION is an unsigned int;
The conversion of -999999999 produces 3294967297 (UINT_MAX +1
-999999999) which when divided by 1000000000 produces 3 as the
result.
There was no further qualification of "C standard" in the first
paragraph. However, the test program still returns 3 when XLC is
$ xlc -qlanglvl=stdc99 -o enumsize enumsize.c
$ ./enumsize
BILLION = 1000000000
x = -999999999
x / BILLION = 3
Now, from what I'm hearing here, it sounds like IBM may be in the wrong---
and this would be quite convenient, because while they may not be too
bothered when their interpretation of POSIX et al. is different from the
rest of the world's, they _will_ care about a red-letter violation of a
spec they claim to support.
I can't standards-lawyer my way out of a paper bag, but if someone here
could provide a line of argument that IBM's enum shenanigans are
categorically unsupported by the standard, I'll gladly send it on in the
hope that it'll get the beast to move.
The C99 rationale is even clearer than the text, in section 6.4.4.3
"Enumeration constants":

Whereas an enumeration variable may have any integer type that
correctly represents all its values when widened to int, an
enumeration constant is only usable as the value of an
expression. Hence its type is simply int.

Paul Eggert
2017-08-22 21:43:23 UTC
Permalink
Post by Daniel Richard G.
I have been in contact with IBM about this, originally reporting the
issue as a compiler bug. However, they responded that the compiler
behavior is conformant to the C standard and that they are less
concerned with matching the behavior of other systems than keeping
things as-is for the benefit of existing customer application code.
There seems to be some miscommunication here. The enum type might be
either signed or unsigned, and I expect this is what IBM is talking
about. However, the enum constants that are declared all must be of type
'int'. This requirement has been in the standard for ages. For example,
given:

enum { a, b, c } v = a;

The expression "a < -1" must return 0, because a is zero and is of type
int. However, the expression "v < -1" might return 0 (if v is signed)
and it might return 1 (if v is unsigned). This is the case even though v
is zero, just as a is. Since the code in question is using the enum
constants, not the enum type, it must treat the values as signed
integers in any conforming compiler.
Loading...