Propsal for new names of integer types:
Although the C standard presently defines integer types whose size is unaffected by the size of int (e.g. uint16_t), the behavior of such types is very much dependent upon the size of int. For example, in the function
int32_t comp(uint32_t x, int16_t y) { return x > y; }
machines where int is larger than 32 bits will define uint32_t as a type which will get promoted to a larger signed int, while machines where int is 32 bits or smaller will define uint32_t as a type which will not get promoted except when necessary to balance something of a larger type.
I would propose naming new types wrapN_t and unumN_t with the semantics that if type wrapN_t is defined, its behavior must be essentially equivalent to how a uintN_t would behave on a system where int was no larger than N bits, and if type unumN_t is defined, its behavior must be essentially equivalent to how a uintN_t would behave on a system where int is 2N bits or larger. Systems which presently define various uintN_t types could define the larger uwrapN_t and the smaller unumN_t types as aliases for uintN_t. Having code which relies upon promotion behavior use such types in preference to uintN_t would make the intention of the code clearer and would also make it easier to migrate such code to machines with different size integers. Had the above function used type uwrap32_t or unum32_t instead of uint32_t, the intended meaning would have been unambiguous.
To facilitate migration of code using uwrapN_t to platforms with larger integer types, I would propose the following rules for platforms making them available for types smaller than int:
1. When promoting values to int, the system must keep track of what type they would have been if the size of "int" were equal to that of the smallest supported uwrapN_t type.
2. If one operand of an operator requiring balancing is a uwrapN_t type and the other would have been a type that would have been no larger if int were N bits, then the other operand will be coerced to uwrapN_t, as will be the result of such expression, and behavior shall be equivalent to performing the computation with the uwrapN_t [a system may promote uwrapN_t to int, perform the computation, and then cast the result back, if and only if its behavior in case of any possible overflows would be consistent with having performed the operation directly on the uwrapN_t].
3. Types uintN_t and uwrapN_t must have identical representations, and uwrapN_t and uintN_t shall be considered equivalent in function declarations; it shall be legal for a function declaration to specify any type equivalent to uintN_t and its definition to specify uwrapN_t, or vice versa, even if they are different types.
4. For purposes of the Strict Aliasing Rule, a uwrapN_t shall be equivalent to a uintN_t.
To facilitate migration of code using unumN_t to platforms having smaller integer types, I would propose the following rules for platforms making them available for types whose values are not all representable as "int":
1. When Usual Arithmetic Promotions would apply, a variable of type unumN_t must be promoted to a signed integer type which is capable of representing any value it might hold.
2. If a variable or directly-cast expression of type unumN_t is passed to a variadic function, it shall be equivalent to `uintN_t`.
3. If an expression containing `uNum_t` is passed to a variadic function, and if the promotion required by #1 would cause the result to be a type incompatible from what would have resulted without the promotion, a compiler must reject the code. A compiler may accept the code if the type is changed but it can ensure compatible behavior.
4. Types uintN_t and unumN_t must have identical representations,
and unumN_t and uintN_t shall be considered equivalent in function
declarations; it shall be legal for a function declaration to specify
any type equivalent to uintN_t and its definition to specify unumN_t,
even if they are different types.
5. For purposes of the Strict Aliasing Rule, a unumN_t shall be equivalent to a uintN_t.
Additional proposed types to facilitate optimization
At present, operations on signed integer types smaller than int are subject to different rules from those on larger types, rules which presently impede optimizations. Further, the guaranteed semantics of unsigned types often limit optimization opportunities even in cases where programmers have no need of those guarantees. I would therefore suggest that for types intN_t and uintN_t there be additional defined types xuintN_t, ointN_t, and ouintN_t, whose defined behaviors would be consistent with those of intN_t and uintN_t, but subject to the following constraints:
The extensible types:
1. Values and variables of type xuintN_t would be similar to `uintN_t` except that a compiler would be allowed to retain an arbitrary number of additional bits. If a value of X is stored to e.g. an xuint16_t, each read of that value could yield X & (65535 | arbitrary_value), with a potentially different arbitrary value used for each read.
2. Writing to any of the constituent char or unsigned char values of an extensible-type variable would clear any "extra" bits associated therewith.
3. It shall be legal for a function declaration to specify any type equivalent to uintN_t and its definition to specify xuintN_t, even if they are different types, but the reverse is not true; compilers may flag an error if a declaration specifies xuintN_t and the definition specifies anything other than xuintN_t or ouintN_t, even if the types are otherwise compatible, and must flag an error if it cannot ensure that the function will not receive a value outside the range of its defined type. Note that systems which declare `xuintN_t` as a synonym for `uintN_t` will implicitly meet this requirement.
4. For purposes of the Strict Aliasing Rule, types uintN_t and xuintN_t would generally be equivalent, with the proviso that writing as a non-x (and non-o) type must cause any "extra bits" to be cleared, and reading as a non-x (and non-o) type must cause any extra bits to be ignored. Note that systems which declare ointN_t and ouintN_t as a synonyms for
intN_t and uintN_t will implicitly meet this requirement.
The optimizer-friendly types:
1. Variables and values of type ointN_t or ouintN_t behave like intN_t and uintN_t with values that are within the range of the latter types, but coercion of out-of-range values to `ointN_t` or `ouintN_t` would be exempt from the normal rules applicable to out-of-range coercions to `intN_t` and `uintN_t`.
2. It is recommended that coercion of out-of-range value to an ointN_t or ouintN_t yield a Partially-Indeterminate Value as described below; such semantics should allow nearly all of the useful optimization opportunities that would be afforded by Undefined Behavior, without requiring that code expend effort to avoid such behavior in cases where a Partially-Indeterminate Value would satisfy program requirements.
3. It shall be legal for a function declaration to specify any type
equivalent to uintN_t or xuintN_t and its definition to specify ouintN_t, even if
they are different types, but the reverse is not true; compilers may
flag an error if a declaration specifies ouintN_t and the definition
specifies anything other than ouintN_t, even if the types
are otherwise compatible, and must flag an error if it cannot ensure that the function will not receive a value outside the range of its defined type. Note that systems which declare ointN_t and ouintN_t as a synonyms for intN_t and uintN_t will implicitly meet this requirement.
4. For purposes of the Strict Aliasing Rule, types uintN_t and
xuintN_t would generally be equivalent, with the proviso that writing as
a non-o (and non-x) type must cause any "extra bits" to be cleared, and
reading as a non-o (and non-x) type must cause any extra bits to be
ignored. Note that systems which declare ointN_t and ouintN_t as a synonyms for
intN_t and uintN_t will implicitly meet this requirement.
Partially-Indeterminate Value:
A compiler shall be allowed to behave as though each value has a "hidden" bit indicating whether it represents a Partially-Indeterminate Value. If a value of an N-bit type holds a partially-indeterminate value, then each rvalue conversion may independently behave as any positive or negative mathematical integer which is congruent to the original mod 2^n, without regard for whether that number would be representable in any data type, and without regard for whether the type is signed or unsigned. If a partially-indeterminate value of a particular type is coerced to a longer type, the value of any additional bits in the result shall be Unspecified; if the longer type can accommodate Partially-Indeterminate Values, the result will be a Partially-Indeterminate Value of that longer type.
For example, consider "ouint8_t x=258; ouint16_t y=x; uint32_t z1=y,z2=y;"
x will be a Partially-Indeterminate Value congruent to 2 mod 256.
y will be a Partially-Indeterminate Value congruent to K*256+2 mod 65536, for some value K
z1 will be equal L1*65536+K*256+2 mod 4294967296, for the above value K and some arbitrary value L1.
z2 will be equal L2*65536+K*256+2 mod 4294967296, for the above value K and some arbitrary value L2not necessarily equal to L1.
Note that under these rules, Partially-Indeterminate Values are highly "contagious", thus facilitating many kinds of optimization, but their behavior is sufficiently constrained that code need not waste time on checks to avoid operations that would yield Partially-Indeterminate Value any possible result from such operations would be acceptable. As a simple example, if a platform specified that integer overflow would yield Partially-Indeterminate Value, compilers would be able to assume that any integer i will be less than i+1, but code like "uint16_t x,y,z; ... x=y*z;" would have fully-defined behavior for all values of y and z even on a 32-bit system [the computation y*z might yield Partially-Indeterminate Value, but the assignment to x would throw away all indeterminate bits].
TEST DIV
No comments:
Post a Comment