What does an opaque function call mean in the compiler optimization?I'v found it in Why do global variables cause trouble for compiler optimizations in function calls?, and 'opaque function call' really confuses me.
It seems that an opaque function call is the function call that the compiler has no information about it. But what does it mean?
As you mention in the question, an opaque function call is a call to a function that the compiler has no prior information about. This implies that the compiler can make no assumptions about the side effects of this call except what is guaranteed by the language definition. For example, since the compiler has no other information it must assume that the function call can modify any global variable and must ensure that any local changes are stored before the call, it must also reload global variables used after the call. Further, the compiler can never skip making a call to this function even when calling it looks useless since there is no way for the compiler to know this for certain.
Related
I would like to know what could happen in a situation like this:
int foo()
{
return 1;
}
void bar()
{
void(*fPtr)();
fPtr = (void(*)())foo;
fPtr();
}
Address of function returning int is assigned to pointer of void(*)() type and the function pointed is called.
What does the standard say about it?
Regardless of answer to 1st question: Are we safe to call the function like this? In practise shouldnt the outcome be just that callee (foo) will put something in EAX / RAX and caller (bar) will just ignore the rax content and go on with the program? I'm interested in Windows calling convention x86 and x64.
Thanks a lot for your time
1)
From the C11 standard - 6.5.2.2 - 9
If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined
It is clearly stated that if a function is called using a pointer of type that does not match the type it is defined with, it leads to Undefined Behavior.
But the cast is okay.
2)
Regarding your second question - In case of a well defined Calling convention XXX and implementation YYYY -
You might have disassembled a sample program (even this one) and figured out that it "works". But there are slight complications. You see, the compilers these days are very smart. There are some compilers which are capable of performing precise inter procedural analysis. Some compiler might figure out that you have behavior that is not defined and it might make some assumption that might break the behavior.
A simple example -
Since the compiler sees that this function is being called with type void(*)(), it will assume that it is not supposed to return anything, and it might remove the instructions required to return the correct value.
In this case other functions calling this functions (in a right way) will get a bad value and thus it would have visible bad effects.
PS: As pointed out by #PeterCordes any modern, sane and useful compiler won't have such an optimization and probably it is always safe to use such calls. But the intent of the answer and the example (probably too simplistic) is to remind that one must tread very carefully when dealing with UBs.
What happens in practice depends a lot on how the compiler implements this. You're assuming C is just a thin ("obvious") layer over asm, but it isn't.
In this case, a compiler can see that you're calling a function through a pointer with the wrong type (which has undefined behavior1), so it could theoretically compile bar() to:
bar:
ret
A compiler can assume undefined behavior never happens during the execution of a program. Calling bar() always results in undefined behavior. Therefore the compiler can assume bar is never called and optimize the rest of the program based on that.
1 C99, 6.3.2.3/8:
If a converted
pointer is used to call a function whose type is not compatible with the pointed-to type,
the behavior is undefined.
About sub-question 2:
Nearly all x86 calling conventions I know (cdecl, stdcall, syscall, fastcall, pascal, 64-bit Windows and 64-bit Linux) will allow void functions to modify the ax/eax/rax register and the difference between an int function and a void function is only that the returned value is passed in the eax register.
The same is true for the "default" calling convention on most other CPUs I have already worked with (MIPS, Sparc, ARM, V850/RH850, PowerPC, TriCore). The register name is not eax but different, of course.
So when using these calling convention you can safely call the int function using a void pointer.
There are however calling conventions where this is not the case: I've read about a calling convention that implicitly use an additional argument for non-void functions...
At the asm level only, this is safe in all normal x86 calling conventions for integer types: eax/rax is call-clobbered, and the caller doesn't have to do anything differently to call a void function vs. an int function and ignoring the return value.
For non-integer return types, this is a problem even in asm. Struct returns are done via a hidden pointer arg that displaces the other args, and the caller is going to store through it so it better not hold garbage. (Assuming the case is more complex than the one shown here, so the function doesn't just inline when optimization is enabled.) See the Godbolt link below for an example of calling through a casted function pointer that results in a store through a garbage "pointer" in rdi.
For legacy 32-bit code, FP return values are in st(0) on the x87 stack, and it's the caller's responsibility to not leave the x87 stack unbalanced. float / double / __m128 return values are safe to ignore in 64-bit ABIs, or in 32-bit code using a calling convention that returns FP values in xmm0 (SSE/SSE2).
In C, this is UB (see other answers for quotes from the standard). When possible / convenient, prefer a workaround (see below).
It's possible that future aggressive optimizations based on a no-UB assumption could break code like this. For example, a compiler might assume any path that leads to UB is never taken, so an if() condition that leads to this code running must always be false.
Note that merely compiling bar() can't break foo() or other functions that don't call bar(). There's only UB if bar() ever runs, so emitting a broken externally-visible definition for foo() (like #Ajay suggests) is not a possible consequence. (Except maybe if you use whole-program optimization and the compiler proves that bar() is always called at least once.) The compiler can break functions that call bar(), though, at least the parts of them that lead to the UB.
However, it is allowed (by accident or on purpose) by many current compilers for x86. Some users expect this to work, and this kind of thing is present in some real codebases, so compiler devs may support this usage even if they implement aggressive optimizations that would otherwise assume this function (and thus all paths that lead to it in any callers) never run. Or maybe not!
An implementation is free to define the behaviour in cases where the ISO C standard leaves the behaviour undefined. However, I don't think gcc/clang or any other compiler explicitly guarantees that this is safe. Compiler devs might or might not consider it a compiler bug if this code stopped working.
I definitely can't recommend doing this, because it may well not continue to be safe. Hopefully if compiler devs decide to break it with aggressive no-UB-assuming optimizations, there will be options to control which kinds of UB are assumed not to happen. And/or there will be warnings. As discussed in comments, whether to take a risk of possible future breakage for short-term performance / convenience benefits depends on external factors (like will lives be at risk, and how carefully you plan to maintain in the future, e.g. checking compiler warnings with future compiler versions.)
Anyway, if it works, it's because of the generosity of your compiler, not because of any kind of standards guarantee. This compiler generosity may be intentional and semi-maintained, though.
See also discussion on another answer: the compilers people actually use aim to be useful, not just standards compliant. The C standard allows enough freedom to make a compliant but not very useful implementation. (Many would argue that compilers that assume no signed overflow even on machines where it has well-defined semantics have already gone past this point, though. See also What Every C Programmer Should Know About Undefined Behavior (an LLVM blog post).)
If the compiler can't prove that it would be UB (e.g. if it can't statically determine which function a function-pointer is pointing to), there's pretty much no way it can break (if the functions are ABI-compatible). Clang's runtime UB-sanitizer would still find it, but a compiler doesn't have much choice in code-gen for calling through an unknown function pointer. It just has to call the way the ABI / calling convention says it should. It can't tell the difference between casting a function pointer to the "wrong" type and casting it back to the correct type (unless you dereference the same function pointer with two different types, which means one or the other must be UB. But the compiler would have a hard time proving it, because the first call might not return. noreturn functions don't have to be marked noreturn.)
But remember that link-time optimization / inlining / constant-propagation could let the compiler see which function is pointed to even in a function that gets a function pointer as an arg or from a global variable.
Workarounds (for a function before you take its address):
If the function won't be part of Link-Time-Optimization, you could lie to the compiler and give it a prototype that matches how you want to call it (as long as you're sure you got the asm-level calling convention is compatible).
You could write a wrapper function. It's potentially less efficient (an extra jmp if it just tail-calls the original), but if it inlines then you're cloning the function to make a version that doesn't do any of the work of creating a return value. This might still be a loss if that was cheap compared to the extra I-cache / uop cache pressure of a 2nd definition, if the version that does return a value is used too.
You could also define an alternate name for a function, using linker stuff so both symbols have the same address. That way you can have two prototypes for the same block of compiler-generated machine code.
Using the GNU toolchain, you can use an attribute on a prototype to make it a weak alias (at the asm / linker level). This doesn't work for all targets; it works for ELF object files, but IDK about Windows.
// in GNU C:
int foo(void) { return 4; }
// include this line in a header if you want; weakref is per translation unit
// a definition (or prototype) for foo doesn't have to be visible.
static void foo_void(void) __attribute((weakref("foo"))); // in C++, use the mangled name
int bar_safe(void) {
void (*goo)(void) = (void(*)())foo_void;
goo();
return 1;
}
example on Godbolt for gcc7.2 and clang5.0.
gcc7.2 inlines foo through the weak alias call to foo_void! clang doesn't, though. I think that means that this is safe, and so is function-pointer casting, in gcc. Alternatively it means that this is potentially dangerous, too. >.<
clang's undefined-behaviour sanitizer does runtime function typeinfo checking (in C++ mode only) for calls through function pointers. int () is different from void (), so it will detect and report this UB on x86. (See the asm on Godbolt). It probably doesn't mean it's actually unsafe at the moment, though, because it doesn't yet detect / warn about it at compile time.
Use the above workarounds in the code that takes the address of the function, not in the code that receives a function pointer.
You want to let the compiler see a real function with the signature that it will eventually be called with, regardless of the function pointer type you pass it through. Make an alias / wrapper with a signature that matches what the function pointer will eventually be cast to. If that means you have to cast the function pointer to pass it in the first place, so be it.
(I think it's safe to create a pointer to the wrong type as long as it's not dereferenced. It's UB to even create an unaligned pointer, even if you don't dereference, but that's different.)
If you have code that needs to deref the same function pointer as int foo(args) in one place and void foo(args) in another place, you're screwed as far as avoiding UB.
C11 ยง6.3.2.3 paragraph 8:
A pointer to a function of one type may be converted to a pointer to a
function of another type and back again; the result shall compare
equal to the original pointer. If a converted pointer is used to call
a function whose type is not compatible with the referenced type, the
behavior is undefined.
Do gcc's function attributes extensions have any effect when used on the type of function pointers?
The function attributes can be used when declaring the type of function pointers but at least some of them seem to have no effect.
https://stackoverflow.com/a/28740576/1128289
The gcc documentation itself does not address this issue.
Very generally, the C standard basically says that handling an object over a pointer whose type is not aligned with the type of the object itself generates undefined behaviour - There are many exceptions to this general rule, but, apparently, none of them seems to apply to your case.
That means we're moving on very unsafe grounds here, in the first place.
First, you need to distinguish function attributes into two classes:
Function attributes that actually change something in the behavior or location of the function itself like aligned or interrupt- A function that is not attributed that way will not change its inner code once you declare a pointer to it interrupt, for example (the code that is generated for the function would need to dynamically change - for example, a "return from interrupt" instruction replaced by a "return normally" one - depending on along what type of pointer it would have been called). This is obviously not possible.
Function attributes that tell the calling code something about the behaviour that can be expected from the function - like noreturn or malloc, for example. Those attributes sometimes might not modify the function code itself (you'll never know, however...), but rather tell the calling code something about assumptions it can make in order to optimise. These assumptions will affect the calling function only and thus can be triggered by tweaking a pointer (you don't even need a pointer to do that, a modified function prototype should suffice - for the language, that would actually turn out to have the same effect). If you tell the compiler it can make such assumptions and those turn out not to be true, this will, however, lead to all sorts of things go wrong. After all, C makes the programmer responsible for making sure a pointer points to the right type of thing.
There are, however, function attributes that actually restrict the amount of assumptions a calling function would be allowed to make (the most obvious would be malloc that tells calling code it returns yet untyped and uninitialized memory). Those should be relatively safe to use (I do, however, fail to come up with a use case atm)
Without very detailed knowledge on what belongs into (1) or (2) above, and what exactly a function attribute might affect in both called and calling code (which would be very difficult to achieve, because I don't recall to ever have seen that documented in detail), you thus will not be able to decide whether this pointer tweaking actually is possible and what side effects it might generate.
Also, in my opinion, there is not much of a difference between tweaking a pointer to a function and calling an external function with a (deliberately) wrong prototype. This might give you some insight on what you are actually trying to do...
In this specific example, the code would be down_interruptible(&semaphore). down_interruptible returns an integer. Should the function still run appropriately despite no assignment for the return value, or does the lack of assignment cause the statement to be skipped altogether?
You are free to ignore a returned value in C (although it is considered bad practice in some cases, e.g. when an error code is returned), the function call will still be made.
Generally speaking, compiler are not permitted to remove code having observable side effects. (So technically speaking, if your function does nothing, the compiler could omit the call)
Yes, unless it doesn't make any difference and the compiler finds it handy to optimize the program and cancel the function's call.
Can anyone comment on the point of the following function, which appears to do not very much:
// Returns stored values
int getDetails(const int param1[],
int* param2,
int* param3,
int* param4)
{
(void)param1;
(void)param2;
(void)param3;
(void)param4;
return 0;
}
The comment is actually there with the code. I'm thinking it must be some kind of odd stub but it is being called and I'm racking my brains to try to imagine what I'm missing.
My best hunch so far is that the function has been deprecated but not removed and the (void)param is to avoid compiler warnings about unused variables.
Statements like (void)param1; are typically used to suppress warnings about unused function parameters. (As an aside, in C++ you could also comment out or remove the parameter names.)
You're correct that the function does nothing. If other code doesn't create a pointer to it, you could safely remove it.
It's an empty function. Casts to void suppress warnings about unused parameters.
Such functions are often used when a function must be called unconditionally or a valid function pointer must be provided, but really the function has nothing to do.
I have a few such functions in my compiler's code generator. There are two code generators actually, one for x86 and the other for MIPS. I compile and link one or the other, never both at the same time.
The code generators are different internally but have the same external API. So, some functions specific to one CPU have some work to do while the same functions for the other have nothing to do. Logically, some of them are empty since there's nothing to do.
My guess (opinion - sorry!) that it could be a stub as you say. If you have a function that takes one or more function pointers to achieve something and it does not allow for a NULL (don't bother with this) then you have to provide something for it to call.
The casts are probably to avoid the "unused parameter" warning.
You're right, it has no point.
All it does is explicitly ignore the arguments by evaluating them and casting the result to (void), and return 0.
Is the return value being used in the context of the call? The best approach is of course to remove the call and replace it with a 0 if the return value is being used, and test the program.
Some Compilers shows error/warning when you are not using the arguments passed to it , to avoid that mention that like it in your code . If the function is not called any where or not assigned to any function pointers , you can remove it as it is not doing anything specific
Just wanted to verify that in VC++, unused member functions which are never called are by default considered as inlined functions by the compiler? If so why it is so, why not completely discard (since it will never be called) this function instead of in-lining it?
What is the advantage?
Update
The question is why even inline it when it will never be called? Why not simply discard it forever, just like some unused variables are discarded.
Member functions are considered inline without use of the inline keyword if they are defined in the body of the class definition. Whether they are called or not has nothing to do with it.
Unused member functions can't generally be discarded because their names have external linkage -- that is to say, some other translation unit or executable might call them, that hasn't even been written at the time this translation unit is compiled or this executable is linked.
Once you get to link-time, if the implementation somehow knows that this cannot happen then it could discard the code for the function. For example because the OS has no means to look up symbols in an executable, or because you've told the linker to strip them out using some implementation-defined option.
Relating this to VC++ in particular: on Windows you can look up symbols in executables if they're dllexport. So those functions won't generally be discarded even at link time, and other unused functions can't be discarded at compile time just because this TU doesn't use it. For most classes defined in the usual way, with a header file that declares the member functions and a source file that defines them, the functions are unused in that source file. So if the compiler discarded them because they were unused in that TU, nothing would ever work.
I think (I'm not sure) that whether the function is inline or not is relevant to whether it can be discarded, but might not mean that it can be entirely discarded. It's true that if it's inline, and someone calls it, then that someone must have the definition of the function in their TU. So in some sense the function is "not needed". However, any static local variables must be shared no matter what TU it's called from, and the address of the function itself must be the same no matter what TU it's taken in. So there may still have to be "something" there even if it's not the full code for the function.
But as I said -- even if inline functions can be discarded when unused, not all unused functions are inline.
Inline it where? It's never called, so it's impossible to inline it into any call site.
The Standard mandates whether a function is or is not considered inline. Whether or not it is called is irrelevant.