sllw where sll expected

56 views
Skip to first unread message

Richard Herveille

unread,
Mar 28, 2018, 6:08:24 AM3/28/18
to sw-...@groups.riscv.org, Richard Herveille

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

 

 

 

cid:image001.png@01D348FE.8B6D1030

 

Richard Herveille

Managing Director

Phone +31 (45) 405 5681

Cell +31 (6) 5207 2230

richard....@roalogic.com

 

 

Andreas Schwab

unread,
Mar 28, 2018, 6:12:34 AM3/28/18
to Richard Herveille, sw-...@groups.riscv.org
On Mär 28 2018, Richard Herveille <richard....@roalogic.com> 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.

> Is this a bug, or intended? Any suggestions?

This is how C works.

Andreas.

--
Andreas Schwab, SUSE Labs, sch...@suse.de
GPG Key fingerprint = 0196 BAD8 1CE9 1970 F4BE 1748 E4D4 88E3 0EEA B9D7
"And now for something completely different."

Richard Herveille

unread,
Mar 28, 2018, 7:55:29 AM3/28/18
to Andreas Schwab, sw-...@groups.riscv.org, Richard Herveille

 

 

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

 

 

 

 

Andreas Schwab

unread,
Mar 28, 2018, 8:38:45 AM3/28/18
to Richard Herveille, sw-...@groups.riscv.org
On Mär 28 2018, Richard Herveille <richard....@roalogic.com> wrote:

> ‘val’ and ‘offset” are of type size_t, which is “unsigned long long”.

Irrelevant.

> The C integer conversion rules state:

Irrelevant.

> ‘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.

Bruce Hoult

unread,
Mar 28, 2018, 8:52:15 AM3/28/18
to Richard Herveille, Andreas Schwab, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 2:55 PM, Richard Herveille <richard....@roalogic.com> wrote:

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.


"int" was the register size for most computers up until the late 90s -- except for 8 bit computers where it was 16 bits.

There were quite a few portability problems moving software from computers with 16 bit int to 32 bit int, but there were relatively few computers and programs at the time.

32 bit computers ruled the world for a couple of decades. The assumption that int is 32 bits got baked into software pretty hard. The first 64 bit workstations made int be 64 bits (this is known as the "ILP64" model). This caused massive software portability problems, and at least the Unix community pretty quickly decided to standardise on "LP64" with long and pointer being 64 bits, but int remaining as 32.

You can read about the reasons here:


Richard Herveille

unread,
Mar 28, 2018, 1:53:56 PM3/28/18
to Andreas Schwab, sw-...@groups.riscv.org, Richard Herveille

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

 

 

Richard Herveille

unread,
Mar 28, 2018, 1:56:22 PM3/28/18
to Bruce Hoult, Andreas Schwab, sw-...@groups.riscv.org, Richard Herveille

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,

Richard

 

 

cid:image001.png@01D348FE.8B6D1030

 

Richard Herveille

Managing Director

Phone +31 (45) 405 5681

Cell +31 (6) 5207 2230

richard....@roalogic.com

 

 

Jim Wilson

unread,
Mar 28, 2018, 2:09:58 PM3/28/18
to Richard Herveille, Bruce Hoult, Andreas Schwab, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 10:56 AM, Richard Herveille <richard....@roalogic.com> wrote:

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?


Note that section 6.5.6 Additive Operators says "If both operands have arithmetic type, the usual arithmetic conversions are performed on them.".  This is a reference to section 6.3.1.8 Usual Arithmetic Conversions which says that if one operand is type int and one operand is type long, then the int is converted to long.

But note that section 6.5.7 Bitwise Shift Operators says "The integer promotions are performed on each of the operands.".  This is something different.  This is a reference to section 6.3.1.1 Boolean, Characters, and Integer, which in paragraph two mentions that char/short get converted to int in an expression.  See in particular note 58 which mentions that integer promotions apply to unary operators and shift operators.  So what you have is an int shift, not a size_t shift.

Jim

Samuel Falvo II

unread,
Mar 28, 2018, 2:14:18 PM3/28/18
to Richard Herveille, Andreas Schwab, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 10:53 AM, Richard Herveille
<richard....@roalogic.com> wrote:
> 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’?

I'm not sure if this is the rationale, or even if it is intended to
comply with the specifications, but the right-hand operand to << is
known to fit within an 8-bit quantity. Therefore, it's possible that
(int) << (char) means that the result will be (int), *even* if the RHS
of << is a larger data type. Consider, if you use 0xFF <<
0x8000000000000000, this pretty much guarantees the result will be
zero, regardless of the final integer width.

I think this is the rationale that the C compiler is taking: since it
doesn't make any sense for the RHS to fall outside 0..63 (or even
0..127 for RV128), it implicitly considers it as a small integer, so
the integer LHS is taken to be the larger rank. This might explain
why you need to explicitly cast the LHS.

--
Samuel A. Falvo II

Jim Wilson

unread,
Mar 28, 2018, 2:18:22 PM3/28/18
to Richard Herveille, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 3:08 AM, Richard Herveille <richard....@roalogic.com> wrote:

 

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


The intermediate language (IL) emitted by the RISC-V backend for the sllw instruction does not indicate that the result is sign-extended to 64-bits, and hence the optimizer is not able to optimize away the unnecessary sign extend instruction.  We do have a second pattern that allows us to combine a sllw and a sign-extend, except it doesn't match in this case because the not operation is in the way.  This is probably fixable with some work on the RISC-V backend.  You could try submitting a bug report into the riscv-gcc github project, or the FSF GCC bugzilla, so that this can be tracked.

I can get the right result if I force the sign-extend before the not.
  val &= ~(size_t)(0xff << offset);
but of course fixing the RISC-V backend would be better if possible.

Jim

Richard Herveille

unread,
Mar 28, 2018, 2:21:56 PM3/28/18
to Jim Wilson, sw-...@groups.riscv.org
Hi Jim,

Sent from my iPad
Does this emit ‘sllw’ or ‘sll’?

Richard 

Jim Wilson

unread,
Mar 28, 2018, 2:23:53 PM3/28/18
to Richard Herveille, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 11:21 AM, Richard Herveille
<richard....@roalogic.com> wrote:
> I can get the right result if I force the sign-extend before the not.
> val &= ~(size_t)(0xff << offset);
>
>
> Does this emit ‘sllw’ or ‘sll’?

I get
sub:
li a5,255
sllw a1,a5,a1
not a1,a1
and a0,a1,a0
ret

I'm just putting the sign-extend before the not so that it can be
combined with the shift.

Jim

Richard Herveille

unread,
Mar 28, 2018, 2:31:48 PM3/28/18
to Jim Wilson, sw-...@groups.riscv.org
Hi Jim,

Sent from my iPad

Ok, so that explains, fixes the sext.w.

Richard

>
> Jim

Palmer Dabbelt

unread,
Mar 28, 2018, 2:42:45 PM3/28/18
to richard....@roalogic.com, br...@hoult.org, sch...@suse.de, sw-...@groups.riscv.org, richard....@roalogic.com
This is just how C works, it isn't a RISC-V thing. "(int)0xff <<
(size_t)offset" is of size int, which is then promoted to a size_t when anded
with val. All the LP64 compilers I have around generate code this way:

$ cat test.c
typedef long size_t;

size_t func_int(size_t add, size_t val, size_t offset)
{
val &= (int)0xff << offset;
return val;
}

size_t func_long(size_t add, size_t val, size_t offset)
{
val &= (long)0xff << offset;
return val;
}
$ riscv64-unknown-linux-gnu-gcc test.c -S -o- -O3
func_int:
li a5,255
sllw a1,a5,a1
and a0,a1,a0
ret
func_long:
li a5,255
sll a1,a5,a1
and a0,a1,a0
ret
$ x86_64-pc-linux-gnu-gcc test.c -S -o- -O3
func_int:
movl %esi, %ecx
movl $255, %edx
sall %cl, %edx
movslq %edx, %rax
andq %rdi, %rax
ret
func_long:
movl %esi, %ecx
movl $255, %edx
salq %cl, %rdx
movq %rdx, %rax
andq %rdi, %rax
ret
$ .local/opt/gcc-4.9.1/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc test.c -S -o- -O3
func_int:
mov w2, 255
lsl w1, w2, w1
sxtw x1, w1
and x0, x1, x0
ret
func_long:
mov x2, 255
lsl x1, x2, x1
and x0, x1, x0
ret
$ .local/opt/gcc-7.3.0-nolibc/sparc64-linux/bin/sparc64-linux-gcc test.c -S -o- -O3
func_int:
mov 255, %g1
sll %g1, %o1, %o1
sra %o1, 0, %o1
jmp %o7+8
and %o1, %o0, %o0
func_long:
mov 255, %g1
sllx %g1, %o1, %o1
jmp %o7+8
and %o1, %o0, %o0
$ .local/opt/gcc-7.3.0-nolibc/mips64-linux/bin/mips64-linux-gcc test.c -S -o- -O3 -march=mips64 -mabi=64
func_int:
sll $5,$5,0
li $2,255 # 0xff
sll $2,$2,$5
jr $31
and $2,$2,$4
func_long:
sll $5,$5,0
li $2,255 # 0xff
dsll $2,$2,$5
jr $31
and $2,$2,$4

On Wed, 28 Mar 2018 10:56:16 PDT (-0700), richard....@roalogic.com wrote:
> 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,
>
> Richard
>
>
>
>
>
>
>

Jim Wilson

unread,
Mar 28, 2018, 3:02:42 PM3/28/18
to Richard Herveille, Bruce Hoult, Andreas Schwab, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 11:35 AM, Richard Herveille
<richard....@roalogic.com> wrote:
> How then would I specify the shift such that it performs an sllw for rv32
> and sll for rv64?

If sll and sllw produce the same result, why does it matter?

As Palmer and Andreas have mentioned, this is just how the C language
works. But you could use macros to determine which int type is the
same as the word size, and then cast the left hand size of a shift to
that type. It wouldn't be convenient, but it would work.

Jim

Richard Herveille

unread,
Mar 28, 2018, 3:32:39 PM3/28/18
to Jim Wilson, Bruce Hoult, Andreas Schwab, sw-...@groups.riscv.org
Hi Jim,

Sent from my iPad

> On 28 Mar 2018, at 21:02, Jim Wilson <ji...@sifive.com> wrote:
>
> On Wed, Mar 28, 2018 at 11:35 AM, Richard Herveille
> <richard....@roalogic.com> wrote:
>> How then would I specify the shift such that it performs an sllw for rv32
>> and sll for rv64?
>
> If sll and sllw produce the same result, why does it matter?

That’s the whole issue; sllw and sll do NOT produce the same result.
I use << to generate bit masks and set bits
sll provides access to bits 63:0, but sllw can only access bits 32:0.

Richard

Richard Herveille

unread,
Mar 28, 2018, 3:34:53 PM3/28/18
to Palmer Dabbelt, br...@hoult.org, sch...@suse.de, sw-...@groups.riscv.org
Hi Palmer,

I understand that now.
But how then should I write the C code to set/clear bits for a 64bit register, in case of rv64, and use the same code for a 32bit register, in case of rv32.
Do all constants and variables have to be type cast?

Sent from my iPad
> --
> You received this message because you are subscribed to the Google Groups "RISC-V SW Dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to sw-dev+un...@groups.riscv.org.
> To post to this group, send email to sw-...@groups.riscv.org.
> Visit this group at https://groups.google.com/a/groups.riscv.org/group/sw-dev/.
> To view this discussion on the web visit https://groups.google.com/a/groups.riscv.org/d/msgid/sw-dev/mhng-534f5513-caae-4fc1-a824-bcbbd5d0689b%40palmer-si-x1c4.

Richard Herveille

unread,
Mar 28, 2018, 3:37:40 PM3/28/18
to Jim Wilson, Bruce Hoult, Andreas Schwab, sw-...@groups.riscv.org


Sent from my iPad

> On 28 Mar 2018, at 21:02, Jim Wilson <ji...@sifive.com> wrote:
>
> As Palmer and Andreas have mentioned, this is just how the C language
> works. But you could use macros to determine which int type is the
> same as the word size, and then cast the left hand size of a shift to
> that type. It wouldn't be convenient, but it would work.

Isn’t that what size_t is for? That’s what I am doing now. But does that really mean that any constant or small isnt must be typecast to size_t before it can be used in a shift?

Richard


>
> Jim

Richard Herveille

unread,
Mar 28, 2018, 3:38:30 PM3/28/18
to Jim Wilson, Bruce Hoult, Andreas Schwab, sw-...@groups.riscv.org


Sent from my iPad
int, not isnt



>
> Richard
>
>
>>
>> Jim

Palmer Dabbelt

unread,
Mar 28, 2018, 3:39:09 PM3/28/18
to richard....@roalogic.com, br...@hoult.org, sch...@suse.de, sw-...@groups.riscv.org
"long" is generally the size of a register, but to be on the safe side I
usually abstract this behind something like "reg_t" as it's possible to have
ILP32 ABIs or 64-bit targets.
> To view this discussion on the web visit https://groups.google.com/a/groups.riscv.org/d/msgid/sw-dev/738199EA-88C6-4461-B1E8-D1C2A166684E%40roalogic.com.

Jim Wilson

unread,
Mar 28, 2018, 4:03:44 PM3/28/18
to Bruce Hoult, Richard Herveille, Andreas Schwab, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 12:32 PM, Bruce Hoult <br...@hoult.org> wrote:
> Is there any machine (that anyone cares about now) where "long" is not the
> register size?

The x32 ABI on x86_64 systems. The n32 ABI on MIPS64 systems. Etc.

Jim

Jim Wilson

unread,
Mar 28, 2018, 4:12:54 PM3/28/18
to Richard Herveille, Bruce Hoult, Andreas Schwab, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 12:37 PM, Richard Herveille
<richard....@roalogic.com> wrote:
> Isn’t that what size_t is for? That’s what I am doing now. But does that really mean that any constant or small isnt must be typecast to size_t before it can be used in a shift?

No, size_t is the type of the result of sizeof, and is an integer type
large enough to represent the size of the largest representable
object, which is effectively the size of an address. Addresses are
not necessarily the same size as the register size.

The type you use in a shift depends on the result you want. If you
want a 64-bit result, then you must use a 64-bit type. If you don't
need a 64-bit result, then you do not need to use a 64-bit type.

Jim

Samuel Falvo II

unread,
Mar 28, 2018, 4:36:38 PM3/28/18
to Jim Wilson, Richard Herveille, Bruce Hoult, Andreas Schwab, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 1:12 PM, Jim Wilson <ji...@sifive.com> wrote:
> No, size_t is the type of the result of sizeof, and is an integer type
> large enough to represent the size of the largest representable
> object, which is effectively the size of an address. Addresses are
> not necessarily the same size as the register size.

I think Richard might have to make his own typedef union:

typedef union {
int i;
void *p;
} reg_t;

I would have recommended the use of (u)intptr_t for this situation,
but apparently, even *that* isn't guaranteed to meet Richard's needs,
per https://stackoverflow.com/questions/9492798/using-intptr-t-instead-of-void#9492910
. :(

> The type you use in a shift depends on the result you want. If you
> want a 64-bit result, then you must use a 64-bit type. If you don't
> need a 64-bit result, then you do not need to use a 64-bit type.

I think he wants the same source code to be size-adaptive, depending
on what target it's compiled for, without the need for preprocessor
macros.

Jim Wilson

unread,
Mar 28, 2018, 5:13:28 PM3/28/18
to Samuel Falvo II, Richard Herveille, Bruce Hoult, Andreas Schwab, sw-...@groups.riscv.org
On Wed, Mar 28, 2018 at 1:36 PM, Samuel Falvo II <sam....@gmail.com> wrote:
> I think he wants the same source code to be size-adaptive, depending
> on what target it's compiled for, without the need for preprocessor
> macros.

There is no direct relationship between type sizes and register/word
sizes. The C language does not provide a type which is the same size
as the register/word size. There is also no direct relationship
between register sizes and word sizes. There are 32-bit machines with
64-bit registers, for instance a PPC e500v1, where SIMD values are
held in the integer registers, and hence the integer registers are
64-bits even though there are no non-SIMD arithmetic instructions that
can access all 64-bit bits of the registers. There are 64-bit
machines with 32-bit registers. Etc.

You just have to write code depending on what you are doing. If you
need a shift that is 32-bits on a 32-bit machine (or 32-bit type
system), and a shift that is 64-bits on a 64-bit machine (or a 64-bit
type system), then you can do that by using macros to define an
appropriate type for the shift. For instance, for a riscv target you
could use

#include <stdint.h>

#if __riscv_xlen == 64
typedef int64_t wordsize;
#elif __riscv_xlen == 32
typedef int32_t wordsize;
#else
#error "unsupported __riscv_xlen size"
#endif

int i = sizeof (wordsize);

If it is the type system you care about, and not the actual register
size, then you can probably just use long, which will be 64-bits with
a 64-bit type system and 32-bits with a 32-bit type system. Though
beware of P64 (aka LLP64) systems, which have 64-bit pointers and
32-bit longs. 64-bit windows is in this category.

There is really no easy solution for this problem. There are far too
many different architectures, implementations, type systems, etc, that
are all supported by C compilers like GCC. You just have to figure
out how to write code that does what you want it to do.

Jim

Andreas Schwab

unread,
Mar 29, 2018, 4:08:03 AM3/29/18
to Richard Herveille, sw-...@groups.riscv.org
There is no size_t in the equation. You have int << whatever, which is
int. See above.

Andreas Schwab

unread,
Mar 29, 2018, 4:14:14 AM3/29/18
to Samuel Falvo II, Richard Herveille, sw-...@groups.riscv.org
On Mär 28 2018, Samuel Falvo II <sam....@gmail.com> wrote:

> of << is a larger data type. Consider, if you use 0xFF <<
> 0x8000000000000000,

This is undefined.

Samuel Falvo II

unread,
Mar 29, 2018, 10:38:19 AM3/29/18
to Andreas Schwab, Richard Herveille, RISC-V SW Dev
It's hypothetical.  I was trying to illustrate a point, it wasn't intended to be taken literally.
Reply all
Reply to author
Forward
0 new messages