Author Topic: Processor design: What is with x86 shift operations greater than 32?  (Read 2860 times)

0 Members and 4 Guests are viewing this topic.

Offline trossinTopic starter

  • Contributor
  • Posts: 24
  • Country: us
    • Demoboy
So, I'm coding/hacking a C compiler for my little hobby processor and generating random equations to compare the results against gcc and I found that on my laptop that shifts by more that 32 just ignore the upper bits.  So a shift by 0x23 results in a shift of 3.  I tried the same code on a Raspberry pi and got the same answer as my hobby processor which is either 0 or all ones if it is a signed right shift.

On Raspberry Pi (ARM) I get a result of 0x00000000 while on my laptop I get a result of 0x2468acf0.  Fun stuff.  I prefer the answer of zero even though it takes a little more hardware. 

Any thoughts on this?

   Thanks

#include <stdio.h>
#include <stdint.h>

int main(int argc,char *argv[])
{
   uint32_t A,Z;

   A = 0x21;
   Z = 0x12345678 << A;
   printf("Z=0x%08x\n",Z);
}

 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17780
  • Country: fr
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #1 on: February 12, 2026, 12:09:22 am »
You didn't say what your "laptop" is and what OS it runs or GCC version.

But try recompiling it with at least -O1 optimization and re-run. Have fun.
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 4833
  • Country: us
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #2 on: February 12, 2026, 12:17:14 am »
Yes, in the C standard negative shifts or shifts by greater than the left operand width are undefined.  So the compiler is allowed to do anything.  Generally that means that compilers use whatever shift operator the platform provides with no extra coercion or checks, because that is the fastest thing to do.

I'm not sure how big of a deal it is these days, but in older CPUs, the barrel shifter was often the critical path in the ALU.  Any additional logic delay could require lowering the clock frequency.

According to documentation I found online, 32-bit ARM considers the bottom 8 bits of the shift amount, so shifting left by 0x101 would be equivalent to shifting by 0x01.  AARCH64 uses only the bottom 5 or 6 bits, just like x86_64.

In either case, there is no _guarantee_ that the compiler will do anything in particular for undefined behavior.  So knowing the behavior of the shift operation on x86 or arm doesn't mean you can rely on that in C code written on those platforms.  Unexpected behavior is especially likely when the values are compile time constants.  If you care, you need to test the value yourself.
 
The following users thanked this post: I wanted a rude username

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17780
  • Country: fr
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #3 on: February 12, 2026, 12:40:28 am »
Well, yes, it is undefined behavior. They have improved the wording of the semantics of bitwise shift in C23, but this still applies:
Quote
If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

So, to the op: welcome to the wonderful world of C undefined behaviors, and have fun with your C compiler. It is allowed to implement it any way it sees fit, and as you noticed, it will usually take the most efficient path for a given target architecture.

I don't think you should bother "saturating" the shift amount operand (as that's what I understand you meant here) on the processor level, unless you have a good reason to do so (ie. if you think it provides any "safety" benefit). But again in C, it's undefined behavior, so anything goes. If you implemented bit shift with one cycle per bit shift, then adding saturation on the shift amount operand is not too costly, but if you implemented a full barrel shifter able to shift by any mount up to the bit width in one cycle, then saturating the shift amount may be way more costly and likely not worth it. I haven't seen many processors that actually implemented that, indeed they usually just take the lowest n bits of the operand.

The reason there's a difference with optimizations on or not is simple: with optimizations on (even at a low level), the operation in your code can be computed at compile time and so becomes "correct" (as in: what you'd expect to see), but without optimization, it compiles the operation entirely as code and so you effectively  run into the UB.

And the reason there's also a difference, even with the same compiler, on a different target is most likely because the 'int' type has a different width on either. Looks like 'int' is 32-bit on your laptop and 64-bit on the RPi, explaining that on the RPi, your code is not UB. The left-hand operand 0x12345678 is promoted to 'int' in both cases, but the width of 'int' depends on the target. The fact that the result is assigned to a uint32_t variable does not change that.

 
The following users thanked this post: Someone

Offline trossinTopic starter

  • Contributor
  • Posts: 24
  • Country: us
    • Demoboy
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #4 on: February 12, 2026, 02:29:23 am »
I used standard int size of 32 (uint32_t) so the difference is not based on integer size. I get the same results with gcc and Visual Studio and a 2024 and 2013 vintage Intel procs so I figure it is a processor behavior and not a compiler behavior.  I first noticed this with 16 bit variables.

The answer seems to be that x86 uses 5 bits while ARM is using 8 and I’m using 16. It is a parallel timing path to use more bits as all that is needed is a little OR tree to see if the upper bits are non-zero and if so make the result 0 or all 1’s for a negative source and signed right shift.

I get that the C allows for undefined behavior but  to me it seems odd that at least a fault is not thrown in the overflow case by the architecture design. That could be done without hurting timing and add protection against malicious code or at least add protection for mission critical code.

Thanks for all the answers. You all are the best!
 

Online 5U4GB

  • Super Contributor
  • ***
  • Posts: 1727
  • Country: au
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #5 on: February 12, 2026, 02:30:41 am »
 
The following users thanked this post: edavid, I wanted a rude username

Offline 44kgk1lkf6u

  • Regular Contributor
  • *
  • Posts: 151
  • Country: 00
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #6 on: February 12, 2026, 03:37:30 am »
The programmer may wish for other behaviors when the shift amount is not right, such as saturating or constraint error.  Even when the programmer needs the number to become 0, he needs to use a programming language where the shift operation is defined as such for the compiler to take advantage of the architecture.  The benefit of making the hardware handle out-of-range shift amounts this way is tiny.  So I suggest designing the instruction set architecture to make it easy to make the hardware.  The 8086 likely used the entire 8 bits because it was the easiest behavior for the implementation, namely looping.
 

Online Berni

  • Super Contributor
  • ***
  • Posts: 5370
  • Country: si
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #7 on: February 12, 2026, 06:43:35 am »
Undefined behaviors in C are there for a reason.

The C language is meant to not only run on a wide range of CPU architectures, but it is meant to also run fast on them. However the way CPUs do very basic operations can differ slightly, so if we forced the C compiler to resolve all undefined behaviors then the compiler would be forced to insert extra instructions around the offending CPU instruction to handle all the edge cases. This could mean that something that normally takes 1 clock cycle might now take x2 to x10 more cycles.

In most cases a program would never hit any of these edge cases, so it could waste a lot of processing power to constantly check for them at runtime. Instead the C compiler developers define what behaviors are undefined and just let the CPU do what it wants to do. That way the user can implement edge case safeguards only where they are relevant, slowing down only parts of the code that actually need to handle the edge cases.

When code is being written specifically for an architecture these undefined behaviors can actually even be very useful. One of the most popular ones is casting pointers into different forms. It is an insanely fast method to serialize any variable by simply casting it to a uint8_t, however what you get out of that is highly architecture dependent and hence undefined. Good luck trying to build a C compiler that makes this a well defined behavior across all architectures (especially without absolutely tanking the performance of the entire program).
 
The following users thanked this post: I wanted a rude username

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 2435
  • Country: pl
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #8 on: February 12, 2026, 08:45:35 pm »
trossin: it’s always best to use the simplest and most readable code to analyze behavior. Layers of indirection are making it harder. The code uses the uint32_t alias. For each single expression in this code, we need to make an additional step. We have to consider what types are actually involved, what is their signedness and size. For uint32_t this is not a complicated step, but it’s still additional place to check.(1) For no good reason: it only brings obscurity. But e.g. uint16_t it would be much worse.

Fortunately uint32_t has no influence on this code.

Since the type used in these calculations is on most platforms(2) exactly 32 bits wide, for argument of 33 (0x21) this code is meaningless. It’s meaningless in exactly the same sense as 0-1, or as √-1 in ℝ, or arcsin(10) is undefined in common maths. And this image is the only thing I can offer to you.

… before you’re being sold the story of C having some special feature called “undefined behavior” and the rest of the folklore: that it has been “introduced,” that it has a purpose, that it’s specific to C or C++, that compiler vendors intentionally abuse it, or — in borderline conspiracy theory style — that they want to break your code to make it faster.

Since there is no requirement to detect meaningless code, compilers do assume that a programmer knows what they write and that they wrote meaningful code. And under that assumption they generate machine code. Not guessing, what the programmer might have hallucinated the code does. Depending on how the compiler is implemented, the final outcome may vary. But it’s generally falling into two categories. The machine code being a kind of blind replacement, acting as if the source made sense. Or the machine code having a completely unrelated behavior, possibly acting even as if the invalid fragment didn’t exist. Not because the compiler is malicious, detects invalid code and tries to make your life harder, but because under the meaningfulness assumption that fragment would not affect the behavior. The former is harder to detect, because it may show expected outcome by sheer luck: this is common with signed overflows. The latter is easier to detect, because the outcomes do not match expectations, but it may introduce serious bugs if left undetected.


(1) It added a few minutes of rethinking, if I’m not missing something. It delayed my reply by over half an hour. Just to make sure I’m giving correct information.
(2) On all I can think of. But my knowledge is limited and there may exist a platform for which the following statement is false.
« Last Edit: February 12, 2026, 08:50:16 pm by golden_labels »
Why 📎 | We live in times when half of people have IQ below 100.
 

Online Berni

  • Super Contributor
  • ***
  • Posts: 5370
  • Country: si
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #9 on: February 13, 2026, 07:08:13 am »
Bitshifting a 32bit int by 33 or more bits in either direction does have a valid answer, it is 0x00000000 (since any new bits are defined to be 0 in a normal bitshift)

There are cases to be made for the more fancy types of shifting like the arthritic bitshift, circular bitshift, saturating bitshift. However you could extrapolate what sensible answer for a too long shift would be by doing the shift using that many 1 bit shift operations in a row and see where the result ends up. But most people don't even know about these special bitshifts because most programming languages don't even offer an operator for doing them, yet a lot of CPUs do have instructions for them. The compiler might still emit one of these special bitshift instructions if the optimization step finds a shortcut where they might be useful, or you just directly ask for one using some builtin primitive define whatever.

When C compilers pre-calculate the operation at compile time (such as shifting a constant by a constant) they will generally make sure to be consistent with the architecture specifics like that. That way undefined behavior is still consistent on the same architecture. That way it won't intentionally break it, but still using undefined behavior in your program is at your own risk.
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 2435
  • Country: pl
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #10 on: February 13, 2026, 08:27:00 am »
Bitshifting a 32bit int by 33 or more bits in either direction does have a valid answer, it is 0x00000000 (since any new bits are defined to be 0 in a normal bitshift)
It does not — it is not in the domain.

Why 📎 | We live in times when half of people have IQ below 100.
 

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 6411
  • Country: nz
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #11 on: February 13, 2026, 09:03:10 am »
Bitshifting a 32bit int by 33 or more bits in either direction does have a valid answer, it is 0x00000000 (since any new bits are defined to be 0 in a normal bitshift)
It does not — it is not in the domain.

How about n << 16 << 17 ?

That has the same well-defined answer on every ISA, if "filled with 0s" is that you want.

You can force the semantics you want on any CPU with code such as:

Code: [Select]
#define SHIFTZERO(n, sz) (((n) << ((sz)>>1)) << ((sz)-((sz)>>1)))
#define SHIFTMOD(n, sz) ((n) << ((sz) % 32));

int foo(int n, int sz) {
    return SHIFTZERO(n, sz);
}

int fooc(int n) {
    return SHIFTZERO(n, 0x23);
}

int bar(int n, int sz) {
    return SHIFTMOD(n, sz);
}

int barc(int n) {
    return SHIFTMOD(n, 0x23);
}

https://godbolt.org/z/v98b3WWzj

I don't know if there is a way to reformulate SHIFTZERO to get good code for foo(), but the other three cases work great on all platforms!

At least it's only 4 instructions on the non-x86 ISAs. And 4 non-mov instructions on x86 too.

The price you pay to get the exact semantics you want....
« Last Edit: February 13, 2026, 09:04:43 am by brucehoult »
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 2435
  • Country: pl
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #12 on: February 13, 2026, 09:35:04 am »
Bitshifting a 32bit int by 33 or more bits in either direction does have a valid answer, it is 0x00000000 (since any new bits are defined to be 0 in a normal bitshift)
It does not — it is not in the domain.
How about n << 16 << 17 ?
For n being of the type discussed — 32-bit wide `unsigned int` — both shifts seem perfectly valid to me.

We may as well cast to `unsigned long long` too, then shift by 33 there, then cast back to `unsigned int`. I believe any reasonable compiler will do this in a single operation.
Why 📎 | We live in times when half of people have IQ below 100.
 

Online Berni

  • Super Contributor
  • ***
  • Posts: 5370
  • Country: si
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #13 on: February 13, 2026, 10:37:41 am »
Yes but using a unsigned long long also requires a 32bit CPU to do extra work to set up registers and the larger bitshift instruction might even be slower.

Either way making the shift act a certain way with such nonsense inputs makes the operation many times slower as simply using whatever the CPUs bitshift instruction does.

There is another undefined behavior for bitshifting by a negative number. The C compiler will compile it, but it is again undefined behavior and what actually happens when executing it depends on the architecture.

This is just one of many many undefined behaviors, even basic things like overflow behavior when adding numbers or casting pointers to a different size are undefined behaviors. It is unreasonable to expect the C compiler to fix those automatically on architectures that do something different with them.
Have a look at how long the list of undefined behaviors is: https://port70.net/~nsz/c/c99/n1256.html#J.2
 

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 6411
  • Country: nz
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #14 on: February 13, 2026, 10:46:22 am »
Yes but using a unsigned long long also requires a 32bit CPU to do extra work to set up registers and the larger bitshift instruction might even be slower.

No, because the compiler can see you're not using the hi bits and avoid actually doing a double-width shift.

If the correct operation of your algorithm depends on  a particular behaviour for shifts bigger than the word size then being a little slower on machines with different shift definition is just something you have to accept, there is no way around it. The main things are to get the answer you want everywhere, and hopefully to get the optimal instruction on machines that do natively have the semantics you want.
 

Online Berni

  • Super Contributor
  • ***
  • Posts: 5370
  • Country: si
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #15 on: February 13, 2026, 12:18:04 pm »
I just meant that the CPU always has to do extra work to change this undefined behavior (such as having the x86 bitshift work like that ARM bitshft). So it doesn't make sense for the compiler to try handling the currently undefined behavior every time you do a bitshift, so the way the compiler works in OPs case is sensible in picking whatever is convenient (ie using the simple bitshift instruction as is with no extra guardrails).

It is the responsibility of the programmer to avoid the undefined behaviors when that matters to how the program works.

But in a higher level language that is more focused on portability such as say Java, Python..etc and is slower anyway, then it might make sense to have the compiler fix architecture quirks like this.Some languages even check for overflows during math operations.
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 2435
  • Country: pl
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #16 on: February 13, 2026, 12:21:21 pm »
Yes but using a unsigned long long also requires a 32bit CPU to do extra work to set up registers and the larger bitshift instruction might even be slower.
Don’t underestimate compilers! :D They really went a long way since simple high-level assemblers:

Code: [Select]
uint32_t reverse(uint32_t s[static 1]) {
    uint32_t d;
    unsigned char* sp = (void*)s;
    unsigned char* dp = (void*)&d;
   
    for (int i = 0; i < 16; i += 5) {
        dp[i % 4] = sp[3 - (i % 4)];
    }
   
    return d;
}
x86-64 (gcc, clang):
Code: [Select]
   0: 8b 07                mov    (%rdi),%eax
   2: 0f c8                bswap  %eax
   4: c3                    ret
AVR8 (gcc):
Code: [Select]
   0: fc 01        movw r30, r24
   2: 72 81        ldd r23, Z+2 ; 0x02
   4: 63 81        ldd r22, Z+3 ; 0x03
   6: 81 81        ldd r24, Z+1 ; 0x01
   8: 90 81        ld r25, Z
   a: 08 95        ret
aarch64 (gcc):
Code: [Select]
   0: b9400000 ldr w0, [x0]
   4: 5ac00800 rev w0, w0
   8: d65f03c0 ret
Either way making the shift act a certain way with such nonsense inputs makes the operation many times slower as simply using whatever the CPUs bitshift instruction does.
But does it really?
Code: [Select]
unsigned int shift(unsigned int x) {
    x <<= 16;
    return x << 17;
}

static int shiftby(unsigned int x, unsigned int n) {
    x <<= n / 2;
    return x << (n - n / 2);
}

unsigned int invoke_33(unsigned int x) {
    return shiftby(x, 33);
}

unsigned int invoke_8(unsigned int x) {
    return shiftby(x, 8);
}

unsigned int invoke_23(unsigned int x) {
    return shiftby(x, 23);
}

unsigned int invoke_fixed33(void) {
    return shiftby(0xDEADBEEF, 33);
}

unsigned int invoke_fixed20(void) {
    return shiftby(0xDEADBEEF, 20);
}
x86-64 (gcc, clang):
Code: [Select]
0000000000000000 <shift>:
   0: 31 c0                xor    %eax,%eax
   2: c3                    ret
   3: 66 90                xchg   %ax,%ax
   5: 66 66 2e 0f 1f 84 00 data16 cs nopw 0x0(%rax,%rax,1)
   c: 00 00 00 00

0000000000000010 <invoke_33>:
  10: 31 c0                xor    %eax,%eax
  12: c3                    ret
  13: 66 90                xchg   %ax,%ax
  15: 66 66 2e 0f 1f 84 00 data16 cs nopw 0x0(%rax,%rax,1)
  1c: 00 00 00 00

0000000000000020 <invoke_8>:
  20: 89 f8                mov    %edi,%eax
  22: c1 e0 08              shl    $0x8,%eax
  25: c3                    ret
  26: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
  2d: 00 00 00

0000000000000030 <invoke_23>:
  30: 89 f8                mov    %edi,%eax
  32: c1 e0 17              shl    $0x17,%eax
  35: c3                    ret
  36: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
  3d: 00 00 00

0000000000000040 <invoke_fixed33>:
  40: 31 c0                xor    %eax,%eax
  42: c3                    ret
  43: 66 90                xchg   %ax,%ax
  45: 66 66 2e 0f 1f 84 00 data16 cs nopw 0x0(%rax,%rax,1)
  4c: 00 00 00 00

0000000000000050 <invoke_fixed20>:
  50: b8 00 00 f0 ee        mov    $0xeef00000,%eax
  55: c3                    ret

There is another undefined behavior for bitshifting by a negative number. The C compiler will compile it, but it is again undefined behavior and what actually happens when executing it depends on the architecture.
Architecture-dependence suggests it’s in some way reliable. The thing is: it is not. It can even give different results inside a single binary on the same machine, depending on the execution path.

This is just one of many many undefined behaviors, even basic things like overflow behavior when adding numbers or casting pointers to a different size are undefined behaviors. It is unreasonable to expect the C compiler to fix those automatically on architectures that do something different with them.
But there is nothing to fix. It’s not broken. It’s outside of the domain. The same way as there is nothing to fix in arctan(10) or 0-1 in maths.
Why 📎 | We live in times when half of people have IQ below 100.
 

Online Berni

  • Super Contributor
  • ***
  • Posts: 5370
  • Country: si
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #17 on: February 13, 2026, 02:01:06 pm »
Yes when the compiler optimizations are on and it can determine the shift by value at compile time then it will just optimize the whole thing out. They create whatever assembly gives the same result that best satisfies the optimization settings.

I am saying the same thing. Undefined behavior in the C language is there for a good reason and one shouldn't trust it to always work like that. My point is that it is unreasonable for the C compiler to massage undefined behavior into anything strictly consistent.

But still some undefined behavior can be useful. It is a very common practice to pack/unpack data by wrongly casting it as a array of a different type(especially in embedded where the code only runs on one particular MCU anyway). This is undefined too, but since it is usually very painful for CPUs to work with an endianess opposite of their own means that the compiler will always pick the same way of handling it. (Also there are often defines provided to tell you things like endianess)
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 2435
  • Country: pl
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #18 on: February 13, 2026, 07:03:04 pm »
Yes when the compiler optimizations are on and it can determine the shift by value at compile time then it will just optimize the whole thing out. They create whatever assembly gives the same result that best satisfies the optimization settings.
Please re-check the code snippets I posted. I believe you misunderstood them. I did show a case of “optimizing out” constants, but most of it is not optimizing out anything.

This is a response to claims, that the proposed methods are more expensive. They’re not: any decent compiler nowadays generates equivalent behavior, not line-by-line search-and-replace-with-opcodes as it was in the past. And the equivalent behavior is generated equally well regardless of how it was described in source. Of course as long as the compiler can “understand it.” As depicted with bytes swapping, they can go far further than one would expect in that matter.

But still some undefined behavior can be useful. It is a very common practice to pack/unpack data by wrongly casting it as a array of a different type(especially in embedded where the code only runs on one particular MCU anyway). This is undefined too, but since it is usually very painful for CPUs to work with an endianess opposite of their own means that the compiler will always pick the same way of handling it. (Also there are often defines provided to tell you things like endianess)
This is not useful. It’s outright invalid and did already lead to many security vulnerabilities and other serious bugs.

It’s a good example of of a programmer guessing the meaning of the code, while the actual meaning is completely different. And compilers follow the latter, not programmer’s hallucinations. In some simple compilers, which work as a kind of high-level assemblers (not far from replacing patterns), this may give an illusion of working consistently. But in any modern compiler that “understands” the source and only generates corresponding behavior this is not the case.

There are system libraries, which may take advantage of what would otherwise be undefined, but:
  • They are very tightly bound to the compiler, tracing any changes, sometimes even mutually.
  • They often benefit from not being compiled as C, but as an extension of C with modified semantics.
  • Some rely on very precisely set of compilation options.
None of this is true for “normal” programs.
« Last Edit: February 13, 2026, 07:04:53 pm by golden_labels »
Why 📎 | We live in times when half of people have IQ below 100.
 

Offline trossinTopic starter

  • Contributor
  • Posts: 24
  • Country: us
    • Demoboy
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #19 on: February 14, 2026, 03:48:11 am »
Sorry to cause a compiler philosophy war. I was more interested in processor design. I feel if possible that doing math correctly makes for a more fault tolerant machine.

I still believe/know that a N bit value shifted left by a value greater than or equal to N is zero. A shift right by that amount is zero for an unsigned shift and either all 1s or zero for a signed shift based on the sign of the input.

I noticed that using constants gives the correct results with gcc and Visual studio on x86. I only found non-ideal behavior when the shift amount was a variable.

I’m fine with the compiler not dealing with it. I think the processor should. With all the crazy out of order execution fun and branch prediction magic it is trivial to micro fault the instruction (detecting over range shift while letting the ALU do its thing) then replace the ALU result the next cycle if timing is poop.

I worked on a machine that had backup registers for doing speculative execution and would only commit the results if the “guess” was correct. I’m floored that this shift behavior is acceptable for commercial products.
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 4833
  • Country: us
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #20 on: February 14, 2026, 04:19:32 am »
I’m fine with the compiler not dealing with it. I think the processor should. With all the crazy out of order execution fun and branch prediction magic it is trivial to micro fault the instruction (detecting over range shift while letting the ALU do its thing) then replace the ALU result the next cycle if timing is poop.

I think this ends up being circular reasoning.

Processors do different things.  So the C standard says it's undefined behavior.  So conforming C programs aren't allowed to do it.  So compiler vendors don't have to work around it at a performance cost.  Processor designers largely target C, and it gives no performance improvement since it's not required behavior.  So processor manufacturers don't care to change it.

In the end it just stays the way it's always been because it's always been that way.  For sure at least modern OoO professors could easily implement any behavior desired if it was important.

If it were a security issue there might be some motivation for someone (compiler writers, processor designers, or standards committee) to unilaterally fix it.  We've finally got some movement from the C++ standards committee to try to reduce security vulnerability from undefined behavior with e.g., the new handling of uninitialized variables.  But I doubt there has ever been a security vulnerability caused by undefined variable shift overflows.
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 4833
  • Country: us
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #21 on: February 14, 2026, 04:37:35 am »
]But there is nothing to fix. It’s not broken. It’s outside of the domain. The same way as there is nothing to fix in arctan(10) or 0-1 in maths.

This is a vacuous statement. It's outside the domain because it's defined to be outside the domain.  I That's like if I defined "ADD" to be an "summation operation on integers other than 7" and then people got upset when my processor evaluates "5 + 7" as five.  I would be entirely correct, yet horribly wrong on a much deeper level.

If you increase the domain, there is a single well defined answer for what it would be.  One option that is consistent with all of the normal arithmetic rules you expect of shift operations -- for instance that "x >> 33 === (x >> 17) >> 16".   One option that is transparently obvious to essentially everyone as "the correct answer."

In this way it's totally unlike your example of 0-1.  In that case there is no single value you can choose that meets all the algebraic consistency requirements.  There are circumstances where it's useful to add a value to represent that, but nothing that's generally algebraically consistent.
 

Offline golden_labels

  • Super Contributor
  • ***
  • Posts: 2435
  • Country: pl
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #22 on: February 14, 2026, 04:13:43 pm »
Sorry to cause a compiler philosophy war. I was more interested in processor design. I feel if possible that doing math correctly makes for a more fault tolerant machine.
Everything is fine. You didn’t cause any war! :) Look, our fingers are not bleeding on the keyboards yet.

I still believe/know that a N bit value shifted left by a value greater than or equal to N is zero. A shift right by that amount is zero for an unsigned shift and either all 1s or zero for a signed shift based on the sign of the input.
There is nothing to know or believe here, though. That’s because there is no universal knowledge in this topic. It is something we, humans, arbitrarily define. There is no Flying Spaghetti Monster to send us messages, ultimately defining left shifts.

Some, including you above, may decide that in expression `a << b`, b above width of a is within the domain and that the result is 0. Some others may agree that it’s within the domain, but it should reserve a special value to indicate an overflow. Or may keep the uppermost bit. Some others may require some runtime error condition. Others may consider crashing the machine. And some may define that such b is simply not in the domain. C arbitrarily selects this last option.

For the record, for the original C the question was non-existent from hardware standpoint. PDP-11 had no hardware acceleration for n-bit shifts. It had to be done in software, similar to modern AVR8 family. The underlying hardware instructions were doing signed multiplication by 2 with marking an overflow, on a 16-bit word (ASL) or two 8-bit bytes (ASLB).

This is a vacuous statement. It's outside the domain because it's defined to be outside the domain.  I That's like if I defined "ADD" to be an "summation operation on integers other than 7" and then people got upset when my processor evaluates "5 + 7" as five.  I would be entirely correct, yet horribly wrong on a much deeper level.

If you increase the domain, there is a single well defined answer for what it would be.  One option that is consistent with all of the normal arithmetic rules you expect of shift operations -- for instance that "x >> 33 === (x >> 17) >> 16".   One option that is transparently obvious to essentially everyone as "the correct answer."

In this way it's totally unlike your example of 0-1.  In that case there is no single value you can choose that meets all the algebraic consistency requirements.  There are circumstances where it's useful to add a value to represent that, but nothing that's generally algebraically consistent.
And on what basis do you assume that 5 + 7 is not 5? It’s written in a scripture of your religion? Do numbers 5 and 7, and operator + orbit Earth, and we examined them with telescopes to act one way and not another? No, we did not. We defined them to act this way. We arbitrarily choose this behavior, because it happened to be more useful that other options. Except for the cases, where we decided it’s useful to choose otherwise: 5 + 7 = 5 in modular arithmetic.

Similarly, there is no natural rules of shift arithmetic. Just because one may be accustomed to something doesn’t mean it’s universal. It’s just our brain being wired that way and not another, because of what it was exposed before. In many cases to such an extreme, that it’s impossible for us to see other interpretations or accept them as equally valid.

The 0-1 mention was about the language having expressions that are outside of its domain, in general, not whether we could find some specific expression that could potentially have some useful interpretation in some circumstances. But that 0-1 is outside of the domain is, once again, an arbitrary choice, a definition, and there is nothing magical or universally true about it being that way. It has a perfectly well-defined, well-behaving, and consistent value in some algebras. What an great joke it is… one example is C. :D
« Last Edit: February 14, 2026, 04:39:45 pm by golden_labels »
Why 📎 | We live in times when half of people have IQ below 100.
 

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 6411
  • Country: nz
Re: Processor design: What is with x86 shift operations greater than 32?
« Reply #23 on: February 15, 2026, 02:16:02 am »
For the record, for the original C the question was non-existent from hardware standpoint. PDP-11 had no hardware acceleration for n-bit shifts. It had to be done in software, similar to modern AVR8 family. The underlying hardware instructions were doing signed multiplication by 2 with marking an overflow, on a 16-bit word (ASL) or two 8-bit bytes (ASLB).

Correct.

If your hardware only has a "shift by 1" instruction (which for left shift is just an add) then getting 0 after >word_size shifts seems natural.

But if your initial hardware has a barrel shifter then just taking the log(word_size) LSBs as inputs into each stage of the barrel shifter and ignoring all the higher bits is what makes sense.

If your algorithm simply demands one interpretation or the other then it's a matter of as few as one or two extra instructions to get exactly what you want, which is not going to affect the performance on anything practical.
 
The following users thanked this post: Someone


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf