Author Topic: Disabling Compiler Optimization vs Using volatile  (Read 27016 times)

0 Members and 17 Guests are viewing this topic.

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: Disabling Compiler Optimization vs Using volatile
« Reply #25 on: June 26, 2025, 10:39:28 am »
C is a very complicated language
No. It is as simple as it gets (besides assembler and machine code). It allows you to tell the computer what you want it to do in very simple terms, and it does precisely that. It allows the programmer to understand what he is doing, but it also requires the programmer to understand what he is doing. C is almost perfect, but only for those who can express their thoughts and goals clearly and coherently. Not all people can do it, so other languages have become a necessity.

I don't think you do know as much as you think you know.

Here's one document illustrating one aspect of the inconsistent "definitions" in C.
N.B. MacLaren really is/was an expert: his job was to help highly intelligent people get the maximum performance from their C code. Hence he knows where the skeletons are buried, since he kept on uncovering them.
N.B. where this was presented and discussed: the standards committee, who can be expected to know more than average or good practitioners.
N.B. this is a far more fundamental question than mere modifiers

https://www.open-std.org/jtc1/sc22/wg14/9350
What is an Object in C Terms?
This is asking the question "What is an object when it is at home?"  We
need to start with an explanation of why a precise answer to the
question is so important.
...
The vast majority of commercial implementors and important users are not
members of SC22/WG14 or even any national body affiliated with it, and
work solely from the standard.  As they should, because ISO rules (and
all normal commercial practice) specify that the standard is the
official document, and records of its development should be used for at
most clarification.
...
I can witness that this area has caused major headaches ever since C90
was developed, and that many [compiler] implementors are very seriously unhappy
about C99.  They simply do not know what most of it implies and, as is
generally agreed, the subtleties in this area are critical for both
optimisation and the development of robust applications.
  Most of the
ones that I have contacts with had severe problems deciding exactly
what optimisations were allowed in C90, and often went through several
iterations before and industry consensus emerged.
<many examples omitted>
...
Nick Maclaren,
University of Cambridge Computing Service,
New Museums Site, Pembroke Street, Cambridge CB2 3QG, England.

Perhaps things were completely unambiguously clarified in later standards, but given the starting point I strongly doubt it.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 
The following users thanked this post: hans

Offline eutectique

  • Frequent Contributor
  • **
  • Posts: 638
  • Country: be
Re: Disabling Compiler Optimization vs Using volatile
« Reply #26 on: June 26, 2025, 11:32:58 am »
Isn't FreeRTOS written in C?

Yes, with the kernel functions implemented as __asm volatile() or #pragma inline_asm patches.

https://github.com/FreeRTOS/FreeRTOS-Kernel/tree/main/portable
 
The following users thanked this post: SteveThackery

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: Disabling Compiler Optimization vs Using volatile
« Reply #27 on: June 26, 2025, 12:29:24 pm »
Isn't FreeRTOS written in C?

Yes, with the kernel functions implemented as __asm volatile() or #pragma inline_asm patches.

https://github.com/FreeRTOS/FreeRTOS-Kernel/tree/main/portable

Surely that means it isn't written in C.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 
The following users thanked this post: hans

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Disabling Compiler Optimization vs Using volatile
« Reply #28 on: June 26, 2025, 12:36:43 pm »
Isn't FreeRTOS written in C?

Yes, with the kernel functions implemented as __asm volatile() or #pragma inline_asm patches.

https://github.com/FreeRTOS/FreeRTOS-Kernel/tree/main/portable

Surely that means it isn't written in C.

Only 99%.
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: Disabling Compiler Optimization vs Using volatile
« Reply #29 on: June 26, 2025, 01:20:26 pm »
Isn't FreeRTOS written in C?

Yes, with the kernel functions implemented as __asm volatile() or #pragma inline_asm patches.

https://github.com/FreeRTOS/FreeRTOS-Kernel/tree/main/portable

Surely that means it isn't written in C.

Only 99%.

Precisely :)

You could achieve that with many other languages.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Online ejeffrey

  • Super Contributor
  • ***
  • Posts: 4849
  • Country: us
Re: Disabling Compiler Optimization vs Using volatile
« Reply #30 on: June 26, 2025, 02:30:57 pm »
Quote
[multi-threading] relied on code libraries that were compiler and machine specific, and could not be expressed in C.
So what?
Why is it such a big deal to require a small core of machine-specific assembly language to implement some feature?

Back in the old days, we took it for granted that there were things that could not be done within the limits of the "preferred" language.  Sometimes your fortran program had to have code written in assembly language, or your BASIC program had to POKE some hex code into memory and then CALL it.  It was no big deal...

That is the opposite of the point.

The point of the article is that you can't just write a few assembly routines to create threads and implement locks, at least not if you want it to be guaranteed correct.  You need the compiler to be thread aware when generating any code that might be run in a multi threaded environment. And you need your language or platform to specify what operations are allowed.

Just implementing threads as an assembly library almost always works but it had some uncommon but severe bugs in the 90s that were the result of compilers not being thread aware.  By the early 2000s compilers had mostly fixed these bugs, implicitly defining a memory model but it took to 2011 for the standard to catch up.

And the benefit of a proper memory model in the language was that we could go beyond locks and use atomic operations and lock free algorithms.  Because while the library approach mostly works for locks it really falls down for lock free algorithms.
 

Offline hans

  • Super Contributor
  • ***
  • Posts: 1985
  • Country: 00
Re: Disabling Compiler Optimization vs Using volatile
« Reply #31 on: June 26, 2025, 02:45:00 pm »
C is a very complicated language
No. It is as simple as it gets (besides assembler and machine code). It allows you to tell the computer what you want it to do in very simple terms, and it does precisely that. It allows the programmer to understand what he is doing, but it also requires the programmer to understand what he is doing. C is almost perfect, but only for those who can express their thoughts and goals clearly and coherently. Not all people can do it, so other languages have become a necessity.

edit: crossed out a logically incorrect conclusion.
Thats exactly the point why its so complicated. Easy to learn, hard to master. There are so many traps to fall into (implicit type casts, undefined behaviour, etc.). Things like pragmas or __attribute__((naked, used)) is typically a clear indicator we're doing something that is not part of the vanilla C spec. These definitions are different per compiler, and their implementation may differ per architecture too. A library such as FreeRTOS has some bookkeeping functions that are mostly C, but are sprinkled with in-line assembler constructs (such as critical sections), and the context switch routine is (almost?) always pure assembler.

C code is dragged into situations where it was not "designed" for.  However, stretching C up this thin is a necessary evil in the embedded field to achieve the things we want to (e.g. work directly with hardware). On a MCU, there is literally not much of a choice to serve an IRQ: its either served in time with correct behaviour, or the system may just as well not even exist.

If that then means certain specifications must be met (such as making functions reentrant), then we must resolve race conditions, inform the compiler that a variable may have writers that are out of scope, etc. And as often is the case with the 'lean abstraction' approach in C, it means that many programmers must reinvent their own wheels. I don't mean this as a critique like "C programmers are stubborn, use C++/Rust/etc. its so much fancier", its a necessary evil to have low overhead to run code on smallest of microcontrollers.

Languages like C# or Java have much easier to write code because they can incur the high overhead penalty on desktop computers. These languages knew their added value was in this aspect, and thus first focused for decades on a complete language specification and memory model, and are incrementally trying to improve performance (e.g. with a better JIT). Its funny because C# has the volatile keyword as well, but when I searched for it on StackExchange, I came across a typical desktop software engineer answer as: "don't use it, you must be doing something crazy -- use ConcurrentQueue instead"
Only now their language specification has become mature, are they finally focusing on overhead and performance. C# has received major speeds up to their LINQ library for example, where up to 50x speed ups for some functions is possible.
« Last Edit: June 26, 2025, 02:47:10 pm by hans »
 

Offline shapirus

  • Super Contributor
  • ***
  • Posts: 2254
  • Country: ua
Re: Disabling Compiler Optimization vs Using volatile
« Reply #32 on: June 26, 2025, 02:53:57 pm »
C is a very complicated language
No. It is as simple as it gets (besides assembler and machine code). It allows you to tell the computer what you want it to do in very simple terms, and it does precisely that. It allows the programmer to understand what he is doing, but it also requires the programmer to understand what he is doing. C is almost perfect, but only for those who can express their thoughts and goals clearly and coherently. Not all people can do it, so other languages have become a necessity.

edit: crossed out a logically incorrect conclusion.
Thats exactly the point why its so complicated. Easy to learn, hard to master. There are so many traps to fall into (implicit type casts, undefined behaviour, etc.). Things like pragmas or __attribute__((naked, used)) is typically a clear indicator we're doing something that is not part of the vanilla C spec. These definitions are different per compiler, and their implementation may differ per architecture too. A library such as FreeRTOS has some bookkeeping functions that are mostly C, but are sprinkled with in-line assembler constructs (such as critical sections), and the context switch routine is (almost?) always pure assembler.
Correct. That is why I remarked about knowing what you're doing. As a tool, C is still simple, but situations you can end up in while solving problems with it can be complicated indeed.
Of course it's not a universal tool. Yes you can implement anything in C, but sometimes there are better tools that can save time and/or prevent human errors (typically at the expense of something, of course).
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 11290
  • Country: fi
Re: Disabling Compiler Optimization vs Using volatile
« Reply #33 on: June 26, 2025, 03:15:00 pm »
This discussion always runs in circles.

> "Systems programming in C is ugly!"
< "OK, suggest something else"
> "Blah blah blah" (not capable of suggesting anything)
or
> Suggests a language completely incapable of any systems programming and only for that reason, lacking those ugly constructs
or
> Suggest an even more complex language with 10 new drawbacks for each 1 fixed thing.

C is used because it does the job, is not too ugly for them who actually use it, and due to failure for anyone to really suggest something better without new sacrifices. It appears that with any new alternative (e.g. Rust), comes some disaster (Pythonesque library version dependency hell for example)
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: Disabling Compiler Optimization vs Using volatile
« Reply #34 on: June 26, 2025, 04:05:03 pm »
I habitually compile my own C code with -Wall -O2, sometimes using -Wall -Wextra -O2.  I never, ever need to "disable optimizations".

I sometimes do use unsafe optimizations that let the compiler generate faster code but no longer follow the C standards exactly, but the -O2 (or -Os or -Og) options never enable such by themselves.

I want to set up a timer interrupt on the 8051, using keil ,I’m trying to understand the proper use of the volatile keyword in embedded C.
As explained above, volatile tells the compiler it cannot infer the value of the volatile object, and therefore must do the actual access.

Another similar issue wrt. optimization is the precision and range of numerical expressions.  If you want to calculate x * y, but limit it to the range and precision of unsigned char, all you need to do is a cast: (unsigned char)(x * y).  You do not need a temporary variable at all.

The third issue, common on 8-bit architectures, is non-atomic accesses.  As detailed above by explained above by HwAoRrDk, it occurs because the object is read from or written to in smaller parts, and the interrupt can occur mid-way during the write.  The solution is to temporarily disable interrupts, do the access, and then re-enable interrupts.  Typical code implements that as saving the interrupt state, then restoring it, so that if you already have interrupts disabled, you don't enable them too early.  On 8051, you should have a function that saves the Interrupt Enable register, then disables interrupts globally, and another that restores interrupts.

I don't use Keil, so I don't know what they actually are for OP.  If we assume they are, for simplicity,
    unsigned char  save_interrupts_disable(void);  // Returns current Interrupt Enable register, disabling interrupts globally
    void restore_interrupts(unsigned char);  // Set Interrupt Enable register to a previously saved value
then it would be best to use accessor functions to manipulate the variables (larger than 8 bits) used in both the interrupt and other code:

    static volatile TYPE shared_variable;

    static inline TYPE get_shared_variable(void) {
        unsigned char  state;
        TYPE value;
        state = save_interrupts_disable();
        value = shared_variable;
        restore_interrupts(state);
        return value;
    }

    static inline void set_shared_variable(const TYPE value) {
        unsigned char  state;
        state = save_interrupts_disable();
        shared_variable = value;
        restore_interrupts(state);
    }

    static inline TYPE replace_shared_variable(const TYPE newvalue) {
        unsigned char  state;
        TYPE oldvalue;
        state = save_interrupts_disable();
        oldvalue = shared_variable;
        shared_variable = newvalue;
        restore_interrupts(state);
        return oldvalue;
    }

The variable is declared volatile exactly because it can be changed from an interrupt context at any time, so the compiler must not infer its value at any point, and must do explicit accesses to it.

The save_interrupts_disable() function is used to stop interrupts from occurring during the access, so that even if the variable is copied piecewise, it will not be interrupted, and will thus be effectively atomic.

The restore_interrupts() restores the interrupt enable register, so that these functions can be used from both normal code, as well as from within an interrupt handler.

You could open-code the interrupt blocking for the access duration, but that is error-prone.

Finally, the static inline designation tells the compiler that this function will only be used in the current compilation unit (.c source file), and that the compiler can inline the code (copying it) instead of generating a function and calls to that function.  Because the accessor functions are only available in the current compilation unit, it makes sense to limit the shared variable to the current compilation unit, therefore the shared variable is declared static.

C11 and later did introduce atomic types via #include <stdatomic.h>, but I do not know if Keil supports those, implements them internally via implicit interrupt disable/restore like above, or does something else.
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: Disabling Compiler Optimization vs Using volatile
« Reply #35 on: June 26, 2025, 04:32:57 pm »
C code is dragged into situations where it was not "designed" for.  However, stretching C up this thin is a necessary evil in the embedded field to achieve the things we want to (e.g. work directly with hardware). On a MCU, there is literally not much of a choice to serve an IRQ: its either served in time with correct behaviour, or the system may just as well not even exist.

In the late 80s and early 90s C came to a fork in the road: to be a high-level language suitable for general purpose applications or to be a low-level language close to the silicon. Either choice was possible and valid, and a good language could have been created from either objective.

Unfortunately the C fraternity had a limited background (to exaggerate, operating systems on PDP11) and were blind to experience and knowledge gained in other domains. Consequently they naively presumed C was The Answer, and decided they didn't need to decide. The language thus mutated into something that was unnecessarily poor for both for both high and low level systems. In trying to be all things to all people, it ended up as being substandard in all domains.

Quote
Languages like C# or Java have much easier to write code because they can incur the high overhead penalty on desktop computers. These languages knew their added value was in this aspect, and thus first focused for decades on a complete language specification and memory model, and are incrementally trying to improve performance (e.g. with a better JIT).

For modern applications on modern computers, C compilers have to be pessimising compilers. That's a consequence of its roots in single-thread single-core systems, a perfectly reasonable assumption half a century ago. In order to try to get around those inhibitions, all sorts of subtle nasal daemons have had to be introduced, essentially of the form "I know the compiler can't determine X, but since I and the people who use and will maintain my code are all wizards like me, you can trust us all when we say we haven't violated X".

As for performance, it isn't so clear cut, as was demonstrated by HP's Dynamo compiler project, and is visible in the various "profile guided optimisations" added to some compilers. Java has such profile guided optimisations built in; they are called "HotSpot".

Quote
Its funny because C# has the volatile keyword as well, but when I searched for it on StackExchange, I came across a typical desktop software engineer answer as: "don't use it, you must be doing something crazy -- use ConcurrentQueue instead"

Precisely.

Exactly the same is true for Java.

Quote
Only now their language specification has become mature, are they finally focusing on overhead and performance. C# has received major speeds up to their LINQ library for example, where up to 50x speed ups for some functions is possible.

C# (like C) can only base optimisations on what can be proved from the source code. Java's HotSpot, OTOH, optimises on what the code is actually doing at run time, and hence can make many more optimisations. I wonder whether the performance optimisations you indicate are derived from HotSpot-style profile guided optimisations.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: Disabling Compiler Optimization vs Using volatile
« Reply #36 on: June 26, 2025, 04:40:30 pm »
C is used because it does the job, is not too ugly for them who actually use it, and due to failure for anyone to really suggest something better without new sacrifices. It appears that with any new alternative (e.g. Rust), comes some disaster (Pythonesque library version dependency hell for example)

C is like COBOL. It continues to be used because teachers (think they are) familiar with it and compilers are ubiquitous - not because it is inherently the best language.

Rust isn't perfect by any means, but it is a much better foundation for computing on modern multicore machines.

C will continue to underlay many systems, just as COBOL continues to be the basis of many systems. But the future belongs to other languages. Time to stop mangling undergrads minds with C's IB, UB etc, and let them concentrate their minds on more interesting and important problems.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 11290
  • Country: fi
Re: Disabling Compiler Optimization vs Using volatile
« Reply #37 on: June 26, 2025, 06:22:49 pm »
C is like COBOL. It continues to be used because teachers (think they are) familiar with it

This is a ridiculously detached-from-reality argument. Maybe in 1980's this was true. C was nearly totally abandoned by the academy already in late 1990's, and actually one of the biggest problems of C is that it is not taught properly anywhere, so most academic literature about it and code examples are seriously outdated.

In 2005, we were taught C++; many other universities used Java as their primary teaching language. Today I'm sure Python is very popular also in teaching.

Quote
Time to stop mangling undergrads minds with C's IB, UB etc, and let them concentrate their minds on more interesting and important problems.

Did you ever realize that the fact that "modern replacements" do not have IB and UB in the standards is because either there is no standard at all (Rust), or standard is so poorly thought out that such concerns are just blatantly ignored? Compilers just do whatever they want; even Java was very well infamous for "write once, debug everywhere". In C at least you can blame yourself for doing IB/UB.

IB and UB are not nice things and ideal, perfect language would have none, but at least they are formally documented. Along with the hated preprocessor, having a decent standard is one of the technically strongest points of C.
« Last Edit: June 26, 2025, 06:28:52 pm by Siwastaja »
 
The following users thanked this post: tooki, JPortici, uer166, SiliconWizard

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: Disabling Compiler Optimization vs Using volatile
« Reply #38 on: June 26, 2025, 06:49:06 pm »
C is like COBOL. It continues to be used because teachers (think they are) familiar with it

This is a ridiculously detached-from-reality argument. Maybe in 1980's this was true. C was nearly totally abandoned by the academy already in late 1990's, and actually one of the biggest problems of C is that it is not taught properly anywhere, so most academic literature about it and code examples are seriously outdated.

In 2005, we were taught C++; many other universities used Java as their primary teaching language. Today I'm sure Python is very popular also in teaching.

Come to think of it, I suspect you are right. I haven't had contact with university output since then.

Nonetheless, the rest of that sentence stands and is supported by that observation, viz: C is like COBOL. It continues to be used because teachers (think they are) familiar with it and compilers are ubiquitous - not because it is inherently the best language.

I ought to remind people of the old aphorism "science advances one funeral at a time", and apply it to computer "science".

Quote
Quote
Time to stop mangling undergrads minds with C's IB, UB etc, and let them concentrate their minds on more interesting and important problems.

Did you ever realize that the fact that "modern replacements" do not have IB and UB in the standards is because either there is no standard at all (Rust), or standard is so poorly thought out that such concerns are just blatantly ignored? Compilers just do whatever they want; even Java was very well infamous for "write once, debug everywhere". In C at least you can blame yourself for doing IB/UB.

Rust is indeed imperfect, and IMHO it is yet to be proven that it will succeed in the long term.

Nonetheless, I bet that it will succeed in the long term, because it has significant advantages in current and future computing environments, and there is significant momentum behind it and improvements are being made.

If I had my time again, I would learn Rust in preference to C.

Quote
IB and UB are not nice things and ideal, perfect language would have none, but at least they are formally documented. Along with the hated preprocessor, having a decent standard is one of the technically strongest points of C.

They are also subtle and pervasive that "defining them" amounts to little more than painting "keep out" on a door in a poorly maintained industrial estate.

C has an indecent standard; standards should be sufficiently good that they can be comprehended by mere mortals. Everything I've seen (e.g. see ref above) indicates that experts have great difficulty understanding what they mean, and even difficulty understanding that they don't have a deep understanding.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: Disabling Compiler Optimization vs Using volatile
« Reply #39 on: June 26, 2025, 08:21:21 pm »
Look for LL/SC, if wanting more.
8051 architecture does not have that; it is much closer to compare-exchange than load-linked store-conditional.

For single bytes in internal data memory (0x00..0xFF) loads and stores (MOV), increments (INC), decrements (DEC), and exchange/swap (XCH) between data memory byte and the accumulator are atomic.

For external memory (0x0100..0xFFFF), only single byte loads and stores are atomic.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17813
  • Country: fr
Re: Disabling Compiler Optimization vs Using volatile
« Reply #40 on: June 27, 2025, 02:49:27 am »
On the simplest, single-core architectures, the usual way of implementing atomic operations when there's lack of specific instructions is just to disable interrupts / instructions that must execute "atomically" / enable interrupts (if they were enabled before), as the only way of interrupting instruction flow is precisely from interrupts. Sounds barbarian, but that works and that's pretty much how atomics are usually implemented, for instance, on the Cortex-M0.

Fun fact: this approach is not necessarily less efficient than using LL/SC approaches (when available), sometimes it's just the opposite. It all depends, but something to be aware of.

 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Disabling Compiler Optimization vs Using volatile
« Reply #41 on: June 27, 2025, 05:49:11 am »
On the simplest, single-core architectures, the usual way of implementing atomic operations when there's lack of specific instructions is just to disable interrupts / instructions that must execute "atomically" / enable interrupts (if they were enabled before), as the only way of interrupting instruction flow is precisely from interrupts. Sounds barbarian, but that works and that's pretty much how atomics are usually implemented, for instance, on the Cortex-M0.

Fun fact: this approach is not necessarily less efficient than using LL/SC approaches (when available), sometimes it's just the opposite. It all depends, but something to be aware of.

It's not really comparable.

LL/SC is specifically for multiple cores and in fact will not provide exclusion on a single core unless the task-switching code takes steps to invalidate any reservation that is currently held by doing an SC to a scratch location.

On the other hand, if you have two 6502s or z80s or something on the same memory bus then doing CLI on one will not prevent the other CPU from interfering in an atomic operation. You'd need some other external mechanism perhaps detecting RMW instuctions (which both CPUs have e.g. INC/DEC) or a mempry-mapped latch that each CPU could write to to pause the other CPU's, or pause if it tries to write, or write to a specific address.
 

Online cv007

  • Super Contributor
  • ***
  • Posts: 1061
Re: Disabling Compiler Optimization vs Using volatile
« Reply #42 on: June 27, 2025, 06:55:59 am »
Quote
and that's pretty much how atomics are usually implemented, for instance, on the Cortex-M0.
Works fine for others also, whether an M33 or an M0 or an avr. I would guess most here are not touching multiple core mcu's, and those that are most likely are using standard methods provided to them to deal with those type of problems.

The following are a couple simple examples of what that looks like using C++, and becomes quite easy to use. The interrupt status is saved and disabled at the point of object creation, and is restored at the end of the scope which could be a function, or braces within a function. I your function is on the long side, you can restrict the time interrupts are off by using curly braces around just the code needing protection, or if the function is short just protect the whole function.

This also points out where 32bit in a way gets harder-you no longer have a single interrupt level, so 'outside the ISR' thinking no longer applies. If you want to be able to adjust interrupt priorities at any time throughout a project you will not be able to assume you know when interrupt protection is needed so will have to assume it is always needed. Maybe with 4 run levels you could keep track of things up to a point, but move to 16 and you are not going to want to figure it out every time you make an adjustment to priorities.

The systick example has a u64 for systemMS_ which updates every second. Now, if some higher priority isr wants to get a time value (via another function in the Systick class) its irq could have fired between the u64 write and the time function could be using an incorrect systemMS_ value. In this case its only a once in ~49 day possible problem but no matter- you simply protect things that need protection and are not making probability calculations with a cutoff point.

A different kind of thing exists in the second example for an stm32. Even though writes to gpio registers are 32 bit (or 8,16) you still have shared registers. If you want to set a pin to analog for example, or turn on/off a pullup, etc., and do not want to worry about where in your code this is done (various run levels/priorities) you will want to protect those shared registers otherwise you have the possibility some higher priority code 'losing' the changes it made to the lower priorty code which was interrupted in the middle of also changing a pin on the same port,

In both cases what needed protection was protected, and you have eliminated potential bugs whatever the probabilities were.

Code: [Select]
class Systick   {

                ...

static inline u64 systemMS_; //no overflow possible in anyone's lifetime
                //get 0-999ms from counter

                static void
isr             () //irq rate 1/sec
                {
                InterruptLock lock;
                systemMS_ += 1000; //is a u64, so irq protect the write
                }

static u64
now             (){ /* return systemMS_ + computed 0-999ms from counter */ }
                ...

                };


class GpioPin   {

                ...

                auto
analog          ()
                {
                CPU::InterruptLock lock;
                deinit(); //default state, which is analog
                return *this;
                }

                ...

                };

Note- the Systick now() function is dealing with a running systick counter so interrupt protection will not help, but is easy enough to check the counter before and after the systemMS_ read to see if a rollover occurred. One can also check if systick irq is pending first (could be called with irq's disabled) and manually call the isr first if so.
« Last Edit: June 27, 2025, 07:00:58 am by cv007 »
 

Online peter-h

  • Super Contributor
  • ***
  • Posts: 6075
  • Country: gb
  • Doing electronics since the 1960s...
Re: Disabling Compiler Optimization vs Using volatile
« Reply #43 on: June 27, 2025, 09:06:32 am »
FreeRTOS (I use it) is written in C but has a few bits of asm where stuff is not possible in C e.g. direct stack manipulation.

I wrote a simple RTOS in the 1980s and did the same thing there.

We have some long threads on optimisation. I use -Og because one can still debug quite well and the result is not significantly worse than -O3. And developing in say -Og and releasing with -O3 is bad risk management because you lose all the regression testing that was naturally done during development.
Z80 Z180 Z280 Z8 S8 8031 8051 H8/300 H8/500 80x86 90S1200 32F417
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 11290
  • Country: fi
Re: Disabling Compiler Optimization vs Using volatile
« Reply #44 on: June 27, 2025, 09:09:38 am »
Rust is indeed imperfect,... I bet that it will succeed in the long term,...
If I had my time again, I would learn Rust in preference to C.

We hear you: C is horrible and no-go, because the standard has a few special edge cases which are undefined, meaning that compiler writers can do whatever they want with a few select constructs, and programmers should avoid those.

In Rust, on the other hand, every construct is undefined or implementation-defined behavior, meaning that compiler writers can do and will do whatever they want, changing with every version, and programmers should, by the same definition, avoid the whole language, but for you that only means it's imperfect, but something you could consider learning.

I enjoy double standards.
 
The following users thanked this post: JPortici, Nominal Animal, gerbay

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 11290
  • Country: fi
Re: Disabling Compiler Optimization vs Using volatile
« Reply #45 on: June 27, 2025, 09:21:19 am »
And developing in say -Og and releasing with -O3 is bad risk management because you lose all the regression testing that was naturally done during development.

Absolutely. You should be optimally delivering the same binary that was used in testing; regardless if it's automated or manual testing.

Which is also why I dislike the test-with-debugger idea. As the name suggest, debuggers are designed for... debugging. Finding bugs that are otherwise very hard to locate.

I strongly suggest building in self-verification, self-testing (asserts or similar) and logging features in the firmware itself. Those work the same way both in "development", and then later on the field. After you used the same concepts, same tooling during development, and problems still sometimes arise on the field, it is then super easy to go into your mental "developing mode" - extract logs, maybe add more logging - and it's an advantage if you are not limited to the physical debugger interface like SWD/JTAG, but can do that even remotely.

There is some size overhead from checking and logging, but so there is size overhead from -Og. But code that checks and logs offers way more, than just -Og. Of course, you can have both.
 

Online cv007

  • Super Contributor
  • ***
  • Posts: 1061
Re: Disabling Compiler Optimization vs Using volatile
« Reply #46 on: June 27, 2025, 10:45:56 am »
I start out at -Os, stay at -Os, and never move out of -Os. If there is ever a need to understand generated asm code that optimization makes difficult to read, I will make the function temporarily noinline so will be able read it easier and understand if it is doing what I think it should be doing. Using a single optimization from start to finish means you are dealing with a compiler that does not change the way it thinks so you get a consistency you don't get by moving around in optimization. When reading generated asm for example, you are looking at a compiler with one personality instead of three or four.

Simple example -Os vs -Og-
https://godbolt.org/z/G4bq6WfGv
Whether that is a great example or not, the -Og version will take a long time to get through, and as soon as you went to -Os the whole loop basically disappeared. Which points out at minimum you are introducing changes in timing. When using -Os from the start, you will notice when optimization has unintended results (optimized away) at the point this code is fresh in your mind, and not a month later when you decide you need -Os and have already accumulated a dozen such problems.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: Disabling Compiler Optimization vs Using volatile
« Reply #47 on: June 27, 2025, 11:43:11 am »
On the simplest, single-core architectures, the usual way of implementing atomic operations when there's lack of specific instructions is just to disable interrupts / instructions that must execute "atomically" / enable interrupts (if they were enabled before), as the only way of interrupting instruction flow is precisely from interrupts. Sounds barbarian, but that works and that's pretty much how atomics are usually implemented, for instance, on the Cortex-M0.
You mean, like the example I showed for OP in reply #34? ;D
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: Disabling Compiler Optimization vs Using volatile
« Reply #48 on: June 27, 2025, 12:05:34 pm »
Simple example -Os vs -Og-
https://godbolt.org/z/G4bq6WfGv
Whether that is a great example or not, the -Og version will take a long time to get through, and as soon as you went to -Os the whole loop basically disappeared. Which points out at minimum you are introducing changes in timing. When using -Os from the start, you will notice when optimization has unintended results

Why unintended?  You wrote code that does nothing except set a to 0. The optimised code has precisely the same effect, but faster. That's what you WANT.

If you actually wanted a to be stepped through all 4 billion values then you'd slap a "volatile" on it.

To be honest with a non-extern variable I'm surprised it didn't just delete the useless variable entirely.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 11290
  • Country: fi
Re: Disabling Compiler Optimization vs Using volatile
« Reply #49 on: June 27, 2025, 12:35:52 pm »
I start out at -Os, stay at -Os, and never move out of -Os. If there is ever a need to understand generated asm code that optimization makes difficult to read, I will make the function temporarily noinline so will be able read it easier and understand if it i

This is a sane advice in my experience for at least AVR and ARM Cortex-M<anything> targets. Sometimes I test attributing some specific speed-critical functions to -O2 or even -O3, but probably half of the time I get back to -Os even with them when I measure that the time difference is negligible, and size difference significant. Sometimes -O3 gives worse performance but almost always a very significant size increase.

Most important is to stay away from -O0, and avoid the thinking pattern that adjusting optimization level is useful for any other purposes (e.g. debugging) than finetuning size - speed relationships when performance matters, and sometimes it does.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

-->