I have a pretty big program which shows some very weird behavior during compiling.
Depending on the order in which I included two files, the program either compiled fine or gave a bunch of "L15 Multiple Call to Function" warnings. Each warning had some random function called from main as "NAME" and always "INTERRUPTVECTORS_INT0/MAIN" (see below) as "CALLER1" and "?C_C51STARTUP" as "CALLER2".
The two included files only defined a bunch of functions, none of which did anything noteworthy. Their respective declarations are all in a single file included much earlier. And those functions don't refer to each other.
I merged the two files together and started moving functions around. I narrowed it down to two functions which, when placed in one order, compiled fine, but produced the same warnings when placed in the opposite order. This is one of those two functions:
bit GetSomeBitOfSomeState(unsigned int State, unsigned char BitIndex) { // return (State & (1 << BitIndex)) != 0; return 1 << State; }
Through some trial and error I found that the code, as above, compiled fine (in one order), but replacing "State" with "BitIndex" (which seems like a very mundane thing to do) caused the same warnings to show up again.
The INTERRUPTVECTORS_INT0 mentioned above is this:
void InterruptVectors_Int0() interrupt 0 { if (IsRunning) { ZeroPassCount++; } }
IsRunning is defined in a much earlier file as
bit IsRunning = false;
(I might have forgotten a
volatile
here, but its presence does not have any effect on the problem) and ZeroPassCount is defined as
volatile unsigned int ZeroPassCount = 0;
in that same file. If I comment out just the
ZeroPassCount++;
, the program compiles fine (assuming the return statement from above still contains "BitIndex"), but commenting out the if-statement (i.e.
executes always) or having nothing commented out at all, causes the warnings to show up again.
In the "OVERLAY MAP OF MODULE: ..." section of the .MAP file there is indeed a link from INTERRUPTVECTORS_INT0/MAIN to ?PR?MAIN:
FUNCTION/MODULE BIT_GROUP DATA_GROUP XDATA_GROUP --> CALLED FUNCTION/MODULE START STOP START STOP START STOP =================================================================================== INTERRUPTVECTORS_INT0/MAIN ----- ----- ----- ----- ----- ----- +--> ?PR?MAIN MAIN ----- ----- ----- ----- ----- ----- +--> ?CO?MAIN ?CO?MAIN ----- ----- ----- ----- ----- ----- +--> ...
But I don't understand how this is possible, because InterruptVectors_Int0 does not call any functions. It merely updates a global variable.
Any insights into how I can systematically find out what's causing this and how I can fix it? Since some changes I made were completely unrelated to any of the warnings, any change I try might randomly make the problem disappear or reappear, so systematic trial and error is very difficult to do.
It's actually "L15 Multiple Call to Segment" - isn't it?
http://www.keil.com/support/man/docs/bl51/bl51_l15.htm
http://www.keil.com/support/man/docs/lx51/lx51_l15.htm
Googling "L15 Multiple Call to Segment" gives a few previous threads, and a couple of knowledgebase articles - do any of them help?
Nope, it's "WARNING L15: MULTIPLE CALL TO FUNCTION".
I did find quite a few articles about this same warning (and I was surprised to see it with different wording, too), but all of the ones I found actually do deal with a function that is called by multiple roots. My ISR doesn't call anything. (Through one of the links you provided I found a forum thread with a similar problem - namely http://www.keil.com/forum/4221/ - but the probem didn't get resolved in that thread.)
I suspect that the linker is accidentally overlaying two things which shouldn't be overlaid, and then detecting side-effect of that. But I don't know how to check that and even if I would, I wouldn't know how to reliably fix it. I could maybe simply exclude all ISRs from overlaying analysis, but then I won't know whether it's actually fixed or whether it just randomly happens to work.
Oh, really? *sigh* I "fixed" it.
I am using function pointers in my program. I have a big table as such:
typedef void (code * CallbackHandler)(); typedef struct { CallbackHandler HandlerForSomething; CallbackHandler HandlerForSomethingElse; CallbackHandler AnotherHandlerForSomething; //... } FooCallbacks; FooCallbacks code Foos[] = { { Thing1_HandlerForSomething, Thing1_HandlerForSomethingElse, Thing1_AnotherHandlerForSomething, //... }, { Thing2_HandlerForSomething, Thing2_HandlerForSomethingElse, Thing2_AnotherHandlerForSomething, //... }, { /* ... */ }, //... }
In the past, this table was not declared as "code". This caused some weird behavior where local variables in main got overwritten. (I do understand, why that happened.) I found http://www.keil.com/support/docs/210.htm, declared the table as "code" and that fixed the problem. However, a lot of L13 warnings showed up, to the point where the linker refused to link the program (L232). I did not see how a recursion could possibly happen in the functions mentioned in the warnings, so I decided to take the simple route and suppress the warnings. Today I tried to fix those warnings. The .MAP file helped quite a lot. Example:
PROGRAM RECURSIONS REMOVED FROM CALL TREE ========================================= +--> ?CO?MAIN | | THING1_HANDLERFORSOMETHING/MAIN | | _SOMEFUNCTION1/MAIN <--+ SOMEFUNCTION2/MAIN
SomeFunction2 was using a table like "unsigned char code SomeTable[][] = {...};" If my understanding is correct, links from ?CO?MAIN to each of those functions in the big table are added because that table is declared as "code". The functions call some other functions. Those other functions use global constants which are placed in ?CO?MAIN. I am not entirely sure why this is considered a recursion, since SomeFunction2 only uses constant data. (And it's very confusing that the warnings say "Recursive Call to Function", while "Called" is not actually a function. I assume the wording was changed in future versions of the linker.) Through some poking around I stumbled upon the solution (or at least what I believe to be a solution): I created a linker command file with the following directives:
OVERLAY(* ! Thing1_HandlerForSomething) OVERLAY(* ! Thing1_HandlerForSomethingElse) OVERLAY(* ! Thing1_AnotherHandlerForSomething) OVERLAY(* ! Thing2_HandlerForSomething) OVERLAY(* ! Thing2_HandlerForSomethingElse) OVERLAY(* ! Thing2_AnotherHandlerForSomething) //... OVERLAY(?PR?MAIN?MAIN ! Thing1_HandlerForSomething) OVERLAY(?PR?MAIN?MAIN ! Thing1_HandlerForSomethingElse) OVERLAY(?PR?MAIN?MAIN ! Thing1_AnotherHandlerForSomething) OVERLAY(?PR?MAIN?MAIN ! Thing2_HandlerForSomething) OVERLAY(?PR?MAIN?MAIN ! Thing2_HandlerForSomethingElse) OVERLAY(?PR?MAIN?MAIN ! Thing2_AnotherHandlerForSomething) //...
The first half removes all links to the functions in the big table. The second half adds links back, but they come from the actual "void main()" function, instead of the constants segment.
Now the L13 warnings are gone (I made sure to remove the suppression) and with them, the L15 warnings are gone as well.
Now I have 2 follow-up questions:
1. Is this a "good" solution? I don't really know why the link from ?PR?MAIN?MAIN is allowed but the link from ?CO?MAIN isn't.
1.5 If it is, how do I handle conditional compilation in the big table?
FooCallbacks code Foos[] = { #if Bar {Thing1_HandlerForSomething, Thing1_HandlerForSomethingElse, Thing1_AnotherHandlerForSomething, /* ... */ }, #else {Thing2_HandlerForSomething, Thing2_HandlerForSomethingElse, Thing2_AnotherHandlerForSomething, /* ... */ }, #endif { /* ... */ }, //... }
Keeping directives for both Things in the linker command file produces L11 warnings (which makes sense). Ideally I would rather not have to comment out lines in the linker command file every time I change Bar.
2. How can I know whether this has actually fixed the problem or whether it's coincidence? The program seems to run fine, but testing is unreliable here since symptoms (if there even are any) might be difficult to spot.
LOL - on reading your 1st reply, I was just about to say, "you're not using function pointers, are you?" !!
:D
Yes, that would do it!
There's a couple of App Notes that describe why function pointers are problematic for Keil C51, and the steps needed to get it to work.
Whether it's easiest to just avoid them is debatable ...
Whether it's easiest to just avoid them is debatable ... I'll enter the debate -- avoid them
specifically on an 8051, that is.
It's probably one of those, "Use an 8051 or functions pointers - choose one" questions ... ?
I mean... once you know how the linker relates different functions when creating function pointers, it doesn't seem all too crazy. But getting there is the hard part. And I don't just mean understanding the rules of the linker. The problem is when you don't know that you are doing something dangerous.
The original symptom (before the L13 warnings) was that a single character on the display wasn't updated from an underscore to a hyphen when power was turned on. Because the function which draws to the display still got data to print an underscore. Because the function which changes that backing data was never called. Because the initialization function calling that function was never called. Because the loop in another function, which calls a bunch of initialization functions by function pointer, only ever ran one iteration. Because the loop counter got overwritten with a high value after the first function pointer call. Because the first called initialization function trashed the local variables of that calling function. Because the linker thought those two functions could be overlapped. Because the link to the initialization function came from whichever code was generated to initialize the table.
It all makes sense when you know it. But getting from the symptom to the root cause is a mind-bending journey of "WTF?", "WHY?" and "HOW EVEN?".
But when everything is working smoothly, doing low-level programming is usually a lot of fun :D
Indeed.
And there is the ongoing maintenance worry that you might change something in the code, but forget one of the details to keep the linker happy ...
But when everything is working smoothly, doing low-level programming is usually a lot of fun
you may not be familiar with the common standard non-maintainable code is worthless
I have, on occasion, when tasked with maintenance ended up rewriting the code to a maintainable shape