0

I have a multi-processor system with a "shared" memory region used for communication. In some cases one of the processors needs to process some considerably large data in the shared memory and place the result back. Normally, I would access the shared memory using volatile pointers to ensure the data is written and read from it, when instructed and the correct data is visible to all the processors (consider it is not cached memory), but unfortunately the processing needs to be done using library functions, while the library isn't written with any special considerations for shared memory (i.e. not taking volatile pointers fore one). Here is an artificial example for illustration:

struct request {
    size_t    data_size;
    uint8_t   data[MAX_REQ];
}

struct response {
    size_t    data_size;
    uint8_t   some_other_data[MAX_RESP];
}

volatile struct request req __attribute__((section(".shared_mem")));
volatile struct request resp __attribute__((section(".shared_mem")));

// The function that is provided by a library I have no control over
extern void library_function(uint8_t* data_in, size_t size_in, uint8_t* data_out, size_t size_out);


// The required usage
library_function(req.data_size, req.data, resp.data_size, req.some_other_data);
...... 

This will obviously and rightfully give a warning similar to

expected 'uint8_t *' {aka 'unsigned char *'} but argument is of type 'volatile uint8_t *' {aka 'volatile unsigned char *'}
   20 | extern void library_function(uint8_t* data_in, size_t size_in, uint8_t* data_out, size_t size_out);

and rightfully so.

First of all I am not completely getting the picture of what could go wrong with the code. Since the function is in external library, the compiler has no way of knowing what it does with the passed buffers, so likely won't ever optimize the call out. The optimization on the library side is not a concern I guess. Is LTO the only potential troublemaker here?
Second, if there is indeed a potential issue, but my question is whether there is any way around it (not only the warning, but potential issues of the memory not being properly accessed as expected) besides copying the data back and forth to/from a local buffer before and after processing, and letting the function to work with the local memory instead (disabling optimizations globally isn't a good solution too, besides I am not sure it gives any guarantee).

Note, the question is not about synchronization between the processors or potential race-conditions when both processors might be accessing the same memory at a time of the processing, but purely about what the compiler might do to "screw up" the expected logic.

11
  • 1
    volatile accesses do not really have anything to do with cache coherency. Commented Jul 26, 2024 at 14:48
  • @IanAbbott I am not talking about cache at all. I want to make sure that a compiler, when it sees that I write to this buffer, will actually generate an instruction to write to it. Same for reading. Commented Jul 26, 2024 at 14:50
  • What do you expect the library function to do, if not read from the input buffer and write to the output buffer? Are other processes monitoring the buffers during the call to the library function? What inter-process communication mechanisms do you have available? Commented Jul 26, 2024 at 14:58
  • So long as you are passing in pointers, the library routines will, by the time they return, have updated memory. So the library routine does not need to be volatile. HOWEVER, be aware that the library routine will not necessarily update memory in a predictable or intuitive way. You can have points in time when your shared memory image will be in an inconsistent state, before the library routine returns. Having the library routine do its work directly in the shared memory is risky if other threads might be accessing that memory concurrently. Commented Jul 26, 2024 at 15:06
  • @IanAbbott Here is some example: godbolt.org/z/fsWEKeYvs As you can see the memory in neither buff nor vbuff is ever read/written, because memset is discarding volatile, and the compiler is assuming it is already set to zero. But memset is a special case as it is a standard function that the compiler might be making special assumptions about it. Commented Jul 26, 2024 at 15:11

2 Answers 2

0

Why do you think the problem has to do with linking or optimization at all? The warning is unrelated to those steps, and is telling you that there's a qualifier missing on the type. Put another way, you're providing the wrong type to the function.

In this instance, the compiler is certainly doing the right thing by warning you. When a variable is volatile, this means it could be changed anywhere - we regularly define things as volatile when they could be changed by hardware events completely outside the environment your program is operating in, say by a pin change which triggers an interrupt which writes this variable.

You should ensure that any such variable is always accessed by code which knows it is volatile. The library function you are calling, however does not know this. It will be optimised by assuming the variable is not volatile, and may end up using stale values of the variable. This may not even a result of the optimization of the library during its building - the library code may itself be written to miminize reads of that variable, or the authors may have subconsciously made design choices under the assumption the variable is not volatile. Volatile is the special case - the dominant assumption is that variables are well defined to the extent that your code can explain everything that happens to that variable.

To avoid the warning and the issue, you should copy the volatile value into a regular variable and use that to call the function. This way, you know with certainty what value the function used. In case the value changed during the function's execution, you can in principle detect it and take corrective action if your application requires it.

Sign up to request clarification or add additional context in comments.

17 Comments

The warning is unrelated to those steps, and is telling you that there's a qualifier missing on the type. - it is very related, because volatile qualified is only relevant for optimization steps. I know all this, and my question is not why this warning is happening or what volatile is, but how I can work around the issue, and what will happen if I forcibly cast/ignore the warning.
Re “Why do you think the problem has to do with linking or optimization at all?”: Regarding optimization, the only reason volatile exists is because of optimization. The C standard specifies C semantics using an abstract machine in which each access to an object is a read or a write in the abstract machine. Then the C standard permits C implementations to use any actual implementation of the code provide that certain defined observable behavior is the same for the actual implementation as it is for the abstract machine…
… This license allows optimization: Each access to an object in the code does not have to be an access in the actual implementation. If a C implementation implemented the abstract machine directly, with no optimization, then volatile would have no effect; the C implementation would already be implementing an actual access for each semantic abstract machine access. It is only when optimization is performed that volatile is needed. And that is why the problem has to do with optimization.
… If the library is given a pointer to a buffer and puts data in that buffer, there is no mechanism by which it can cache that data in registers instead of writing it to the buffer. When the routine returns, the data must actually be in the buffer. OP’s question is getting at this effect: In such a C implementation, is it necessarily true that, when the library routine returns, the data will be in memory, even if the library routine did not use volatile? It is a reasonable question, and linking is a crucial part of it.
… In this situation (some of it implied and should be clarified in the question), some other process writes data to the buffer, and then this process uses the data and owns it for a period of time. The data will not change from other sources. Then the library routine is called, and it updates the data, and, again, the data is not changed or used by other processes or hardware. Then the library routines, and the data is changed in memory and is consistent. That is what this question is about.
|
0

From C11 6.7.3 paragraph 7:

[…] If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.135)

135)This applies to those objects that behave as if they were defined with qualified types, even if they are never actually defined as objects in the program (such as an object at a memory-mapped input/output address).

OP's question is asking about the potential issues of removing the volatile qualifier when passing a pointer to an object defined with a volatile-qualified type to a library function, i.e. when the type of the library function's formal parameter is not a pointer to a volatile-qualified type.

First, OP asks what could go wrong with the code. Using the parameter to refer to the volatile-qualified object results in undefined behavior, so anything could go wrong. I think it is OK if the function converts the parameter value to a pointer to a volatile-qualified type and only uses the converted pointer to access the object. There are no library functions in the C standard that are defined to behave like that.

Second, OP asks whether there is any way around it. The only way round it is to declare the object without a volatile-qualified type, and use some other mechanism to determine which parts of the object have a stable value at any given time and which parts have an unstable value. The stable part could be processed by a library function as long as the library function does not assume that it has any particular value, and as long as the function call is not optimized out.

One way to prevent a call to a library function being optimized out is to call it via a volatile function pointer. For example:

{
    void *(* volatile fp_memcpy)(void * restrict, const void * restrict, size_t) = memcpy;
    fp_memcpy(dest, src, n); // function call will not be optimized out
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.