Author Topic: GCC ARM32 comparison question  (Read 2543 times)

0 Members and 1 Guest are viewing this topic.

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3694
  • Country: gb
  • Doing electronics since the 1960s...
GCC ARM32 comparison question
« on: June 17, 2022, 03:50:27 pm »
Code: [Select]
volatile uint8_t a=0xaa;
volatile uint8_t b=0x55;
volatile bool fred=false;

if (a != ~b)
{
  fred=true;
}

// now fred=true

b=~b;
fred=false;
if (a != b)
{
  fred=true;
}

// now fred=false

How does this work? Does inverting a byte produce some larger variable? Even this doesn't work:

Code: [Select]
if (a != (~b))
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8167
  • Country: fi
Re: GCC ARM32 comparison question
« Reply #1 on: June 17, 2022, 04:10:03 pm »
This is basic C, you can Google it all, or read any tutorial - and of course, the standard itself (any version really). Instead of direct answer, let me help you with keywords:

C operators:
~ operator
!= operator
https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B

C integer promotion

BTW, from the strange way of using volatile I'm assuming you are flashing this on an MCU target and looking at it in debugger, am I right? But IMHO, for simple "how does that language work" tests, I would prefer just having gcc installed on your PC, write a small program and use printf(). This easily extents to be a larger unit test where you can loop through possible values and check the correctness.
« Last Edit: June 17, 2022, 04:14:20 pm by Siwastaja »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: GCC ARM32 comparison question
« Reply #2 on: June 17, 2022, 04:11:39 pm »
Does inverting a byte produce some larger variable?
You forgot the automatic integer promotions C compilers (have to) do.  Everything smaller than int that can be described by an int, gets converted to an int, and so on.

The correct expression here is
if (a != (uint8_t)(~b))
 
The following users thanked this post: Siwastaja, newbrain, SiliconWizard

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3694
  • Country: gb
  • Doing electronics since the 1960s...
Re: GCC ARM32 comparison question
« Reply #3 on: June 17, 2022, 04:15:27 pm »
Yes; embedded target. I really should set up Borland C v3.1 on my PC :)

I did suspect the uint8_t was being converted to an int, so

~0

becomes

0xffffffff

for the comparison (or some such).

The reason this has not caught me out before is because most of my 32F417 coding has been uint32_t, flipping bits destined for registers, etc.
« Last Edit: June 17, 2022, 04:17:02 pm by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14445
  • Country: fr
Re: GCC ARM32 comparison question
« Reply #4 on: June 17, 2022, 05:59:52 pm »
Yep, integer promotion is the key to integer arithmetics in C. And yep, it can be a bit confusing and a bit annoying. As shown above, you'll need explicit conversions.
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3694
  • Country: gb
  • Doing electronics since the 1960s...
Re: GCC ARM32 comparison question
« Reply #5 on: June 17, 2022, 07:18:22 pm »
Why is the cast needed?

It is surely obvious what the intention is? Comparing a byte with (say) 32 bits is meaningless, unless one "knows" the int value will always fit within a byte.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: GCC ARM32 comparison question
« Reply #6 on: June 17, 2022, 07:49:42 pm »
Why is the cast needed?
Because the C standard says that before an expression is evaluated, integer promotions are performed.

We cannot avoid that, but we can fix the comparison logic by casting the promoted value back to uint8_t range and precision.  (Since C99, a cast suffices – regardless of optimization level – to limit the cast expression to the range and precision of the cast type.)
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14445
  • Country: fr
Re: GCC ARM32 comparison question
« Reply #7 on: June 17, 2022, 07:53:42 pm »
I set the '-Wconversion' flag in compilers to catch potential implicit conversion issues, and I highly recommend it.
Unfortunately, it doesn't catch anything in this particular comparison case.

As Nominal said, integer promotion is a basic rule in C, and it's just the way it works. It *does* confuse a lot of people.
With your example, what seems the most confusing to many is that a unary operator will trigger integer promotion, while you'd usually expect it to preserve the original type of the operand. I do not think that not preserving the original type is a good idea here, but I didn't design C nor was ever part of the std commitee, so my opinion probably doesn't matter much, and doing otherwise in any new std revision would probably break too much code to be even considered.

To completely avoid this kind of "issues", you'd need a strongly-typed language, such as Ada, which wouldn't even allow comparing objects of a different type (unless you provided the corresponding operator.)

Implicit conversions are, IMHO, the "root of all evil", but in practice, there seems to be way more people hating too strongly-typed languages than people hating implicit conversions (which look convenient at first sight), so... there you have it. Pick your poison.
« Last Edit: June 17, 2022, 08:00:53 pm by SiliconWizard »
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3694
  • Country: gb
  • Doing electronics since the 1960s...
Re: GCC ARM32 comparison question
« Reply #8 on: June 17, 2022, 09:26:15 pm »
Quote
Because the C standard says that before an expression is evaluated, integer promotions are performed.

Why would that be a good idea?

I know cases like sscanf which always read integers and there is no way to get them to read directly into a uint8_t for example.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6239
  • Country: fi
    • My home page and email address
Re: GCC ARM32 comparison question
« Reply #9 on: June 17, 2022, 10:22:57 pm »
Quote
Because the C standard says that before an expression is evaluated, integer promotions are performed.
Why would that be a good idea?
:-//

C is an imperfect tool; that's just how it works.
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1719
  • Country: se
Re: GCC ARM32 comparison question
« Reply #10 on: June 17, 2022, 11:20:01 pm »
Yes; embedded target. I really should set up Borland C v3.1 on my PC :)
I hope you are joking. I fear you aren't.

Why would that be a good idea?
Mostly, hysterical raisins.
You can read more in an old, but very good document:
"Rationale for International Standard — Programming Languages — C"
Specifically, in chapter 6.3 Conversions.

The whole doc is worth reading though, as a companion to the standard - it was written originally for C89, but was updated to reflect C99.
« Last Edit: June 17, 2022, 11:30:54 pm by newbrain »
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline oPossum

  • Super Contributor
  • ***
  • Posts: 1415
  • Country: us
  • Very dangerous - may attack at any time
Re: GCC ARM32 comparison question
« Reply #11 on: June 18, 2022, 04:52:20 am »
Code: [Select]

if((a^b) == 0xFF) { }  // Lower 8 bits are compliment

 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3694
  • Country: gb
  • Doing electronics since the 1960s...
Re: GCC ARM32 comparison question
« Reply #12 on: June 18, 2022, 05:27:51 am »
Quote
I hope you are joking. I fear you aren't.

I was joking :)

But I still have the manuals on the shelf.

I can very easily test C code on my 32F417 target. I have an RTOS task called APP and just stick it in there.

I did read that C document on "integer promotion" and still don't get why this is needed. They could just treat uint8_t and int8_t as genuine standalone variable types. Currently, it seems, they get promoted to uint and int respectively for any operations, so their only advantage is less storage space.
« Last Edit: June 18, 2022, 05:32:53 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11236
  • Country: us
    • Personal site
Re: GCC ARM32 comparison question
« Reply #13 on: June 18, 2022, 05:59:39 am »
C is old and its promotion rules are generally considered bad. Modern languages handle this much better. And "uint8_t" and "int8_t" were added to the standard decades after the initial release, and the original type system is much less strict and poorly (platform and implementation) defined.

Also, if there were no promotion, you will be equally surprised by arithmetic operations. I bet you rely on that a lot in your code.
Alex
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3694
  • Country: gb
  • Doing electronics since the 1960s...
Re: GCC ARM32 comparison question
« Reply #14 on: June 18, 2022, 06:18:39 am »
Quote
you will be equally surprised by arithmetic operations

Can you give me an example?

Actually I don't do arithmetic on byte variables, other than ++ or --. Also I never use int8_t. On the ARM32 the perf is so high there is no point. I don't use pointers, and use int or uint32_t as an array index. Even float mult is 1 clock. It's a different world.

But in the old days people wrote whole programs in byte variables for speed. For example Honeywell did an autopilot (KFC225) which appears to do its algorithms using signed bytes (with occassional severe underflow problems as it happens). On the other hand, and AFAIK they used C, it would not have gained them much because bytes would be promoted to int which on the CPU they used would be a 16 bit reg so unless one uses asm code (e.g. 8x8=16 multiply) you gain little. In those days, 1980-1995, I used only asm, on all the CPUs listed below except arm32.
« Last Edit: June 18, 2022, 06:31:37 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Online ataradov

  • Super Contributor
  • ***
  • Posts: 11236
  • Country: us
    • Personal site
Re: GCC ARM32 comparison question
« Reply #15 on: June 18, 2022, 06:41:28 am »
I don't know what you man by "in byte variables for speed", but "int" is guaranteed to be faster, since on 16- and 32- bit CPUs compilers have to issue extra instructions to comply with 8-bit semantics. Some people think they are optimizing (like using uint8_t in short "for" loops), but in reality they are just making things worse.

As for the example - any reasonably complicated expression with data in byte arrays. For example, you have an array of 5 bytes and you want to calculate average:

Code: [Select]
uint8_t a[5] = {1, 123, 27, 13, 222 };
uint8_t avg = (a[0] + a[1]+ a[2]+ a[3]+ a[4]) / 5; // Wrong without promotion
uint8_t avg = ((int)a[0] + (int)a[1]+ (int)a[2]+ (int)a[3]+ (int)a[4]) / 5; // You would have to do something like this.
Depending on the new rules, only the first manual promotion may be necessary, but that is error-prone when making modifications.

Stuff like this happens all the time.

And again, C promotion rules are not good, but it is what it is, and if you want to use C, you just need to learn them.
« Last Edit: June 18, 2022, 06:44:10 am by ataradov »
Alex
 
The following users thanked this post: newbrain

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3694
  • Country: gb
  • Doing electronics since the 1960s...
Re: GCC ARM32 comparison question
« Reply #16 on: June 18, 2022, 08:04:51 am »
Quote
I don't know what you man by "in byte variables for speed", but "int" is guaranteed to be faster, since on 16- and 32- bit CPUs compilers have to issue extra instructions to comply with 8-bit semantics. Some people think they are optimizing (like using uint8_t in short "for" loops), but in reality they are just making things worse.

I am sure you know all this, so I am surprised by your question.

Take the Z80. HL holds a uint16_t x and you are doing x << 4.

 ld b, 4
loop: add hl, hl
 djnz loop

The loop counter is a byte.

If you were forced to an "int", which on an IAR C compiler was 16 bits, you would need something like

 ld bc, 4
loop: add hl, hl
 dec bc
 ld a, b
 or c
 jr nz, loop

There is a million cases where 8 bit values is far faster, on those micros. One also had 8x8=16 multiply, 16/8=8 divide, etc. Converting every int8 into 16 bit arithmetic bloats the code 5x to 10x. In fact I remember rewriting a lot of IAR code in asm and wondering why they are doing everything in 16 bits, with the top byte set to 0. 8x8=16 is probably 10x faster than 16x16=32 and then discarding the top 16 bits. No wonder C got a crap name back then for generating crap code :)

In later years, CPUs became "genuinely 16 bit" even though they still had the crippling 64k address space. H8/500 was one such.

On the arm32 I use int or uint32_t for loop counters; it merely wastes RAM :)

Re your example, that is wrong code :) because anybody adding up even two bytes should know the result can be > 255. Classic integer maths overflow. If using integers (rather than floats) one needs to be intimately familiar with the actual range of values i.e. the actual data. This was well known since for ever. The Apollo guidance stuff used int32 heavily and had to deal with this. So avg should be a uint16_t and possibly a uint32_t if adding up lots of them. Auto promoting avg to uint16_t only saves your skin partially, and hides the real problem.

People who can't be bothered with actual data ranges have to use floats - and pay a heavy price it... in the old days, of x100 to x1000 less speed. Not today, with a 168 megaflop 32F417.

AIUI, uint32_t avg = (a[0] + a[1]+ a[2]+ a[3]+ a[4]) would promote each of the five items to a uint32_t before doing the addition. That's probably wrong too :)
« Last Edit: June 18, 2022, 08:19:09 am by peter-h »
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: GCC ARM32 comparison question
« Reply #17 on: June 18, 2022, 08:09:37 am »
I don't use pointers, and use int or uint32_t as an array index.

Both of those pessimise speed on different machines. The best choice if you're indexing from the actual name of the array is size_t. If you're indexing from a pointer that may be into the middle of an array then ptrdiff_t is better than int.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: GCC ARM32 comparison question
« Reply #18 on: June 18, 2022, 08:30:51 am »
Quote
I don't know what you man by "in byte variables for speed", but "int" is guaranteed to be faster, since on 16- and 32- bit CPUs compilers have to issue extra instructions to comply with 8-bit semantics. Some people think they are optimizing (like using uint8_t in short "for" loops), but in reality they are just making things worse.

I am sure you know all this, so I am surprised by your question.

Take the Z80. HL holds a uint16_t x and you are doing x << 4.

 ld b, 4
loop: add hl, hl
 djnz loop

The loop counter is a byte.

If you were forced to an "int", which on an IAR C compiler was 16 bits, you would need something like

 ld bc, 4
loop: add hl, hl
 dec bc
 ld a, b
 or c
 jr nz, loop

That's kind of dumb because it's no bigger, and much faster, to do as sdcc does:

Code: [Select]
#include <stdint.h>

uint16_t shl4(uint16_t n){
  return n<<4;
}

Using "sdcc -mz80 --opt-code-size -S dumb_loop.c":

Code: [Select]
.area _CODE
;dumb_loop.c:3: uint16_t shl4(uint16_t n){
; ---------------------------------
; Function shl4
; ---------------------------------
_shl4::
;dumb_loop.c:4: return n<<4;
add hl, hl
add hl, hl
add hl, hl
add hl, hl
ex de, hl
;dumb_loop.c:5: }
ret

Ok, let's make it a variable shift:

Code: [Select]
uint16_t shl4(uint16_t n, int sh){
  return n<<sh;
}

Code: [Select]
.area _CODE
;dumb_loop.c:3: uint16_t shl4(uint16_t n, int sh){
; ---------------------------------
; Function shl4
; ---------------------------------
_shl4::
;dumb_loop.c:4: return n<<sh;
ld b, e
inc b
jr 00104$
00103$:
add hl, hl
00104$:
djnz 00103$
ex de, hl
;dumb_loop.c:5: }
ret

Hmm. I guess you forgot that any shift amount over 16 is going to give the same result -- 0 -- so you don't have to worry about counts over 255. But the compiler didn't forget!
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1719
  • Country: se
Re: GCC ARM32 comparison question
« Reply #19 on: June 18, 2022, 08:48:45 am »
AIUI, uint32_t avg = (a[0] + a[1]+ a[2]+ a[3]+ a[4]) would promote each of the five items to a uint32_t before doing the addition. That's probably wrong too :)
The result of the expression that is the right operand of the '=' operator will be converted to uint32_t.
But the operands of the four '+' operators sub-expressions will be converted to signed int, as discussed above.
The final effect is the same, in this specific case.

I don't use pointers, and use int or uint32_t as an array index.
Liar, liar, pants on fire.
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8167
  • Country: fi
Re: GCC ARM32 comparison question
« Reply #20 on: June 18, 2022, 10:30:42 am »
Implicit conversions are, IMHO, the "root of all evil", but in practice, there seems to be way more people hating too strongly-typed languages than people hating implicit conversions (which look convenient at first sight), so... there you have it. Pick your poison.

I totally agree, yet most people seem to have gripes with C being too strongly typed, i.e., they want even weaker typing, to the point of automatic implicit conversions from anything to anything, with little or no control over types at all. It seems easy on surface, but such lack of control is a recipe for disaster. C is at least halfway there to strong typing.
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3694
  • Country: gb
  • Doing electronics since the 1960s...
Re: GCC ARM32 comparison question
« Reply #21 on: June 18, 2022, 10:35:26 am »
This has been a huge learning experience for me.

So, AIUI, C makes any variable size shorter than int pointless, unless you are trying to save storage space.

In particular (unless very short of stack space) using a variable smaller than int inside a function is pointless because any storage used is chucked away upon exit. And a lot of variables are optimised anyway so never stored in RAM.

This is why I have not been bitten by this (until yesterday) because the bulk of my C programming has been on the arm32 and there I know the arm32 is natively 32 bit so uint8_t int8_t uint16_t int16_t are pointless. The only time I have used int16 was for peripherals registers which are 16 bit, like DMA transfer counters. uint8_t I have used for buffers, obviously, because practically all "data" is byte sized. My previous C coding was a little bit of hacking somebody else's C code about 20 years previously.

I still wonder why C doesn't internally represent bytes as bytes. They are quite common in embedded systems :) Expanding every byte retrieved from a buffer to 32 bits doesn't generate more code, or lose speed, on an arm32, but on most older stuff it does one or the other or both. Of course few people care about old CPUs today...

I do know djnz, btw - I posted it above :)

Strong typing is a PITA. I did tons of Pascal and even implemented a Pascal compiler on an embedded customer-programmable product. The language was near-useless without a lot of extensions.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8167
  • Country: fi
Re: GCC ARM32 comparison question
« Reply #22 on: June 18, 2022, 10:41:26 am »
I did read that C document on "integer promotion" and still don't get why this is needed.

You are missing the fact that a language which actually runs and enables a big part of our computerized world, the language needs to be properly standardized and specified. Compilers just can't work they way which would seem logical or good, because then everyone would disagree about what it exactly is.

In the process of standardization, poor choices happen, it is inevitable; people make mistakes, have bad ideas, or are thinking about some different use case which ends up being irrelevant.

C is a product of such process, it's far from perfect.

C's strengths are:
* It is standardized, proper language, which changes slowly. It's stable.
* It is good enough,
* While it has a dozen of stupid ideas or footguns, it doesn't have hundreds of them. A normal human being with say 110IQ, with programmer mentality, can actually learn the language in a few years!

Languages that fundamentally change all the time, or specification is all over the place, become toy languages like Python or PHP. They are usable in their own domain, of course. But there is a difference: if you ask why something is brain-dead in C or in PHP, for C, the reason is: "it was considered a good idea in 1980's. it is standardized like this, all greybeards know it, and it has to stay that way for compatibility". For PHP, the answer is: "no one knows. It's just a bug. It may or may not randomly change."
 
The following users thanked this post: SiliconWizard

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: GCC ARM32 comparison question
« Reply #23 on: June 18, 2022, 11:09:43 am »
So, AIUI, C makes any variable size shorter than int pointless, unless you are trying to save storage space.

No, not at all.

The C language always specifies things "as if". Integer calculations on values shorter than int must act "as if" they were promoted to int and the calculation done in int. But if the ISA in question has the ability to do operations directly on bytes or shorts AND the compiler can prove that the result will be the same, then it is allowed to use them.

Code: [Select]
char addb(char *b, char *c){
  return *b + *c;
}

Z80 (using sdcc):

Code: [Select]
_addb::
ld c, (hl)
ld a, (de)
add a, c
ret

x86_64: (gcc)

Code: [Select]
addb(char*, char*):
        movzx   eax, BYTE PTR [rsi]
        add     al, BYTE PTR [rdi]
        ret

MSP430:

Code: [Select]
addb(char*, char*):
        MOV.B   @R12, R12
        ADD.B   @R13, R12
        RET

x86: (msvc)

Code: [Select]
char addb(char *,char *) PROC                             ; addb, COMDAT
        mov     eax, DWORD PTR _b$[esp-4]
        mov     ecx, DWORD PTR _c$[esp-4]
        mov     al, BYTE PTR [eax]
        add     al, BYTE PTR [ecx]
        ret     0

All of those are compiling C code to use byte ALU operations.

It's not at all pointless to use char and short.

If the result of the function was int instead of char then the code would be different, with a full 16 bit (or more) add -- or at least with the next byte getting the carry flag (if char is unsigned).
« Last Edit: June 18, 2022, 11:12:42 am by brucehoult »
 

Offline peter-hTopic starter

  • Super Contributor
  • ***
  • Posts: 3694
  • Country: gb
  • Doing electronics since the 1960s...
Re: GCC ARM32 comparison question
« Reply #24 on: June 18, 2022, 11:29:59 am »
OK, sure, but nobody answered my question :)

Except in terms of "it is the C standard, so accept it".

ISTM that this "promotion to int of almost everything shorter" is an attempt to prevent bad coding leading to what has since for ever been known in the embedded world (whether manually blowing fusible link PROMs one byte at a time, or asm, or C or whatever) as "integer overflow". The cost of doing this is

- on CPUs which are mostly not natively int sized (most of the 8/16 bit stuff like the Z80 etc) it bloats a lot of stuff, and slows it down, a lot
- if there is enough oveflow to make MSB=1 then you will likely get real problems with any comparisons, because the promotion is to signed int
- it conceals most integer overflow, which is nearly always loss of real data - unless doing a checksum ;)
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf