Hi,
I got a piece of code, which does something like shown below (mask some bits in val);
size_t val;
size_t offset;
…
val &= ~(0xff << offset);
…
For an rv64 compile, the toolchain translates the above shift into (s0 contains ‘offset’):
li a5,255
sllw a5,a5,s0 <<should be sll
not a5,a5
sext.w a5,a5 <<???
and a0,a5,a0
I have no idea why it adds the sign extend. Also this seems superfluous given the previous instruction is “not a5,a5”.
But more disturbing is the ‘sllw’, because, obviously, that is not what is intended and results in incorrect code.
I have to type-cast 0xff to (size_t)0xff for this to work, “val &= ~((size_t)0xff << offset)”. Why would that be necessary? Can’t the compiler figure out that this should all be 64bit operations?
Is this a bug, or intended? Any suggestions?
Thanks,
Richard

Richard Herveille
Managing Director
Phone +31 (45) 405 5681
Cell +31 (6) 5207 2230
On 28/03/2018, 12:12, "Andreas Schwab" <sch...@suse.de> wrote:
I have to type-cast 0xff to (size_t)0xff for this to work, “val &= ~((size_t)0xff << offset)”. Why would that be necessary? Can’t the compiler figure out that this should all be 64bit operations?
0xff is of type int, as is (0xff << offset). The context doesn't matter.
That’s not true.
I always thought that integer was the ‘native’ size (for RV64 that would be 64bits). But that doesn’t always seem to be true. I already used size_t to cover that.
‘val’ and ‘offset” are of type size_t, which is “unsigned long long”.
0xff is of type integer.
The C integer conversion rules state:
· If both operands have the same type, then no further conversion is needed.
· Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
· Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
· Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
· Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type
· The rank of long long int shall be greater than the rank of long int, which shall be greater than the rank of int, which shall be greater than the rank of short int, which shall be greater than the rank of signed char.
· The rank of any unsigned integer type shall equal the rank of the corresponding signed integer type
‘size_t’ has a greater rank than ‘integer’. Therefore according to these rules the integer should be converted to size_t and the result should be of size_t. And as such the compiler behavior is incorrect.
Is this a bug, or intended? Any suggestions?
This is how C works.
The rules above state how the C compiler is supposed to work. Please don’t be condescending (at least I took it that way). The question is serious and valid. If I am missing something please tell me what I am missing.
Cheers,
Richard
I always thought that integer was the ‘native’ size (for RV64 that would be 64bits). But that doesn’t always seem to be true. I already used size_t to cover that.
Hi Andreas,
On 28/03/2018, 14:38, "Andreas Schwab" <sch...@suse.de> wrote:
‘val’ and ‘offset” are of type size_t, which is “unsigned long long”.
Irrelevant.
The C integer conversion rules state:
Irrelevant.
Really, why?
‘size_t’ has a greater rank than ‘integer’. Therefore according to these rules the integer should be converted to size_t and the result should be of size_t. And as such the compiler behavior is incorrect.
Nope. Please read 6.4.4.1 Integer constants. Then read 6.5.7 Bitwise
shift operators.
From 6.4.4.1
835 The type of an integer constant is the first of the corresponding list in which its value can be represented.
So 0xff would be of type int
‘int’ has a lower ranking than size_t, which is ‘unsigned long long’.
From 6.5.7
1184 The type of the result is that of the promoted left operand.
From 6.3.1.8
714 Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
So that means the (int)0xff must be promoted to (unsigned long long)0xff. Then why is the result of the operation an ‘int’ and not an ‘unsigned long long’?
Richard
Hi Bruce,
Thanks, that’s very informative. But it still doesn’t explain why the result of “(size_t)val &= (int)0xff << (size_t)offset” is of type ‘int’ (for rv64). Why isn’t it of type size_t as I would expect from the code description and the C-rules?
Thanks, that’s very informative. But it still doesn’t explain why the result of “(size_t)val &= (int)0xff << (size_t)offset” is of type ‘int’ (for rv64). Why isn’t it of type size_t as I would expect from the code description and the C-rules?
For an rv64 compile, the toolchain translates the above shift into (s0 contains ‘offset’):
li a5,255
sllw a5,a5,s0 <<should be sll
not a5,a5
sext.w a5,a5 <<???
and a0,a5,a0