There is a new 8051 C compiler in beta test that beats Keil's compiler by 35% code size, and 14% speed, on the Dhrystone benchmark. And, there is no need to select a memory model or use special keywords to control data placement.
More details here: www.htsoft.com/.../silabs8051beta
1) No reference to "best choice" of optimizing flags for the two compilers.
2) Too small application to show difference in code size - remember that size of RTL affects small projects more.
3) How much of the code optimization results in speed changes for other applications? The Dhrystone isn't exactly relevant for an 8-bit microcontroller with 1-bit instructions...
They really have to produce more information before making any claims in one direction or the other. Compiling an application that makes use of a lot of one-bit variables and compare between two compilers, and then compila a program using a lot of 16-bit orh 32-bit variables and you will see that the comparisons will vary a lot. Code size and speed can only be deduced from a significantly large code base of very varying - but applicable - code.
I am sure, anyone with enough experience can make a compiler that will make faster and more compact code. who gives a hoot if the result is not 'uniquely breakpointable'. Th result from Keil can be much better if you use a higher optimization level, buy who in his/her right mind would use code the emulator can not 'uniquely' breakpoin on. Sorry, I have now offended a lot of people, but debuggability is far more important than those last few percent efficiency. What really offend me is that nobody (yet) has made a compiler/linker/optimizer that fully maintain program flow and is optimized in all other respects.
Erik
PS Clyde, do you really think it is apprpiate to promote your stuff on a website run by a competitor?
I am a Keil user, have been for many years and still believe that it is the best tool for the '51 developments that I have been involved it. so do I. But is there anything wrong with wanting 'the best' better. I have no problems whatsoever with the optimizations provided by Keil (they are optional and I do not use them) I just want another level where some are implemented and the "debuggability killers" are not.
Someone above talked about "debugging by printf". That will work in many cases, but in "my world" where everything hangs on nanoseconds, inserting a time consuming call can make the "debug aid" a bug.
Erik, I've been following the various threads within this thread with some interest. I wasn't entirely sure what you meant by "uniquely breakpointable" at first, but having done some homework, I now believe that what you are referring to is the problem with some compilers that setting a breakpoint on one line of code may cause the debugger to stop at that breakpoint even though the execution sequence is within a different function or at least section of code.
The major reason why this would happen is that the compiler or linker has performed what is known as "procedural abstraction" or "reverse inlining", i.e. a section of code common to two or more places in the program has been moved aside and replaced with calls to the common code section.
Is that an accurate summary of your concerns?
If I may answer for erik until he does so himself, "Yes." You are on the right track, as that is the most common break-point-able snafu when debugging. Others include an obfuscated method of data store handling (especially passing and returning to and from unctions) is also a concern.
The compiler that you advocate with project-wide "Omniscient Code Generation" is a great and welcomed addition to the optimization processes. But my (our) worry is that the code becomes difficult to track or trace the flow for "debugging" purposes. Much of this thread has to do with the importance of this ability versus the gained benefits of the "tightest-code ever" trade off. Sometimes we are in control of the *need* for it, and sometimes we are not.
My concern is equally shared between development debugging phases, and code validation and qualification phases. The ability to break before a function, load the test-case passed parameters into the appropriate data-stores, and then execute the code unit (function), and finally extract the results is a typical [automated] validation process. This allows the test benches to stress the ranges and conditions of the unit under test and extract the output for post-analysis.
When the break-points and data-stores are not cleanly delineated, the the unit-level testing (or "debug") becomes a bit hectic... and possibly impossible. Even the test-benches need qualification, so having to go through hoops in order to ensure a valid test takes not only time but also extensive and highly tailored documentation; not to mention the explanation to those who are in QA and might not be as savvy to the complexities of the system.
Even if the embedded engineer doesn't do such formal qualification testing methods, the "debugging" process is basically the same as the FQT. You break at a known point (in "C") and track/trace the flow and data through the unit under test (the function or suspected area of error), and figure out why your logic/implementation is wrong.
Typically this is done at the assembly level and not simply at the "C" level since the 'competent' can easily see the "C" level errors quickly. It is our 'advanced' errors in thought that we spend all of our time "debugging" the assembly.
Being able to use a compiler optimization switch to enable/disable such delineation would ease some of these issues. (e.g. Keil's optimization level is selectable, but I'm sure most of the more serious users would prefer the pick-n-choose which types of optimizations take place and not just "Level-9 and all of the levels before it." But we also realize the interdependencies and the overall difficulty in picking and choosing the types of optimizations). Having the OGC do its thing with the caveat that unique entry/exit nodes are to be kept in order to allow break-points would be a good start.
I like the OCG method, but I am concerned over the sacrifice needed in the ease of development in order to eek out a few percentage points of margin on a design.
My personal opinion is that the OCG method will eventually become the new standard for embedded system compilers, and the leading products will have paid a special effort to address the debug, development, and validation issues.
--Cpt. Vince Foster 2nd Cannon Place Fort Marcy Park, VA
I'm not sure I have understood this. You appear to be saying that given an error in a 'C' program that is caused by:
a) Faulty logic or b) Faulty implementation of correct logic
you might find yourself debugging at assembly level to spot the error?
If you use #define expressions you may get into troubles with multiple increments/decrements that isn't visible when you single-step the C source. You either have to run the pre-processor to get the code expansion, or single-step the assembler code.
C++ also have a bit of magic that can require assembler debugging unless you already "know" where you should put a breakpoint to catch the next step.
Yes. that is what I am saying. Most errors are either faulty logic or the faulty implementation of correct logic... and of course the faulty implementation of fautly logic.
At the "C" level, these can be found 'easily' since your emulator/simulator can show you the logical flow as you single-step through the high-level "C" code, and you watch the data-stores change accordingly. But since the 'hard' problems take a much larger percent of our debugging time, we usually are single-stepping through the underlying assembly code that supports each of the "C" statements in order to find our mistakes. Per Westermark just made a clear and common 'error' that usually is found at the assembly level.
The reason I elaborate on this distinction has to do with the highly-optimized code that causes the underlying assembly language to be seemingly scattered and dis-jointed due to the use of shared code segments and other "odd looking" (but valid) code that the compiler may generate.
Hi Vince, thanks for the reply. I can tell you (hopefully without pushing anyone's buttons :-) that the results I mentioned in the post that started this thread do not depend on any inlining or reverse inlining - i.e. the code IS uniquely breakpointable.
Those optimizations are on the agenda, but they're not dependent on OCG, which is the really new technology that has delivered the improved performance.
The "obfuscated data store" question depends on the capabilities of the debugger (and debug format). The compiler does provide full information as to where variables are stored (which can change between memory and registers) at any given point in the program, but many debuggers and debug formats do not have the capability to make use of this. Elf/Dwarf is AFAIK the most capable format in this regard.
I also appreciate your comments on selectability of optimizations. This is precisely the kind of feedback I was looking for in starting this thread. OCG is a powerful technology, but the final objective is to deliver to engineers what they need - and this thread clearly illustrates that different people have different needs!
Clyde
Hi Clyde,
remember me from the beta days of tha XA compiler?
Those optimizations are on the agenda, but they're not dependent on OCG, which is the really new technology that has delivered the improved performance to repeat my point ANY optimizations is desirable to SOME, the issue is an extremely flexible 'menu' of which you want to suit YOUR environment.
Erik, I remember the XA, but not you, I'm afraid. But then I think I've forgotten more than I know, so don't take it personally :-)
I do not think that it is ok to put a brochure in a competitors display rack - I find it unethical. Switching over to the electronic world doesn't make a difference.
The reason that HT has posted their advertisement here simply has to do with traffic and volume. The Keil forum has it and the HT forum does not. Even so, I'm not sure that it makes much sense to remove it.
Every few years, another Keil competitor has a "new" compiler release that generates smaller and faster code. At Keil, we welcome this kind of innovation and embrace how it helps to expand and grow the 8051 microcontroller marketplace.
Jon
Per Westermark just made a clear and common 'error' that usually is found at the assembly level.
I'm not sure that I see that as an example of something that might need to be debugged at assembly level. It's a straightforward 'C' level error caused by a failure to understand either side-effects, macro expansion rules or both. As such I wouldn't expect a competent 'C' programmer to make the error, never mind be unable to spot it at the 'C' level.
I would be interested to see a concrete example of an error that a competent 'C' programmer might make that would not be more easily spotted by reviewing the 'C' code rather than stepping through compiler generated assembly code.
I have to agree with Jack, although I think that making mistakes has little to do with what he considers "competence". When I have trouble with macros I usually look at the preprocessor output, I don't bother to debug them in assembly.
Here's one. Taken from real life, slightly simplified.
unsigned int i; unsigned int some_array[12]; ... for(i = 8; i < 12; i++) { some_array[i] = 0xFF; }
After the loop, some_array[9...11] were found to be unmodified. No other tasks or ISRs are access some_array at the same time. Did you find the error in the C code ?
I got a bit of code written by another developer, and containing a library.
What wasn't obvious whas that the nice guy had decided to create a function-looking #define without the very common curtesy to select all capitals.
Would you suspect the following code to step the pointer twice?
while (*msg) put_data(*msg++);
By your implication, I was incompetent for assuming that the documented "function" actually was a function. Sumething documented as a function should really behave as a function, don't you think?
Since I assumed it to be a function (as the documentation claimed), I saw no need to look at any preprocessor output. However, single-stepping through the code with mixxed assembler/C made it obvious that the function call did not do what I expected, and why the extra increment managed to step past the termination character. If msg had had multiple characters, I might have noticed that only characters at even positions was emitted, but in this case my only character was emitted (as expected), but then followed by a very large number of random junk.
Life is a lot easier when you have written every single line of the code - as soon as someone else have been involved, you have to assume that they have followed the traditional best-practices or you will never manage to get a final product.
By your implication, I was incompetent for assuming that the documented "function" actually was a function.
Not at all.
Sumething documented as a function should really behave as a function, don't you think?
Absolutely.
Indeed.
What I was after an example of was something to illustrate the premise I was querying, which was:
You appear to be saying that given an error in a 'C' program that is caused by:
To that end I asked for:
In other words, an algorithmic or logical error rather than one introduced by someone else's mistake.
I'm interested because if there are situations like this, I certainly haven't come across them. If I find a bug when testing I know that the chances that the problem is in my code are high, so I check my code. I can't imagine why I might find it easier in this situation to look at compiler generated assembly rather than the source code I actually wrote.
A lot of debuggers allows you to watch _both_ your C code and assembler, so it does not represent a big disadvantage to look at the assembler.
In my case, it showed where I was guilty of an incorrect assumption. I assumed that the library was written by a competent developer.
But there could also be a situation where I am blind to my own errors, because parts of my brain have already decided that a specific piece of code _must_ be correct. Seeing both assembly + C could then kick my brain out of its incorrect track and have it starting to see what is really there, instead what it assumes is there.
Have you ever looked at a table for your keys, and failed to see them just because your mind have already decided that they can't be there, or that they have to be on the right side of the desk, or that the bright red key ring that is sticking out under a paper just can't be your keys since you know that you haven't touched that paper since the last time you had your keys?
Our brain is a marvel at pattern matching, which is the reason it is hopeless to try to write an application with any real intelligence. But an engine with too good pattern matching has a tendancy to sometimes find patterns where no patterns exists.
I'm a lot beter to stay focused when looking at really advanced algorithms. Most overlooked errors are likely to be in the trival parts of the code - or maybe the debug printout that is left inside the algorithm. If you see 50 lines of non-trivial code, and three debug printouts, you are likely to skip over the debug lines and put all your focus on the "real" code. Such irrational - but not too uncommon - decisions can easily make you miss that little = instead of == in one of the printouts. Or maybe someone have been "optimizing" a bit and added a ++ in the printout, since that saves a line of code - until I come along and decides that the prinouts should be conditionally included...
In the old days, we needed the assembler output since we couldn't trust the compilers. Todays compilers are so reliable that we can limit ourselves to take a peek at the code output for extremely time-critical code, look at compiler output to learn how to use the assembler instructions of a new processor, or now and then just to get our brains to switch track and start to process data again, instead of living on old assumptions.
Mr. Sprat, those logic/implementation errors are found in the "C" construct in most of the occurrences... not necessarily most of the 'time.'
You can find ten of those errors in ten minutes but the one that which takes two hours is the type I'm talking about. Hence, most of your 'time' is spent debugging something that is not glaringly obvious that the faulty "C" implementation of faulty logic is in error. Usually the problems that take the most time are those that are a result of 'undocumented features' in the data-sheets, somebody else's code, or self generated. And for those long duration bugs, you'll need to delve into the assembly.
Your request for a logic/implementation flaw that is more easily found at the assembly level was fulfilled by Mr. Westermark's #define type errors. Yes it is possible to deduce it from within the "C" platform, but a quicker approach is to validate the post-processor result of the #define at the assembly level. It was a valid example of your request.
But an example of a 'bug' that is more easily found in the hand tracing level of assembly code is this one (yes, it is an 'undocumented feature' of Keil' optimization settings, but it could be argued that I created it myself too)...
extern u16 Get_ADC_Level( void ); #define MIN_VOLTAGE (2458) // 2457.6 = ((FULL_SCALE)/2)+(FULL_SCALE*0.10)) void Intruder_isr( void ) interrupt 0 using 2 { u16 val; do { Charge_Pump( ); // should take 10mS/kV val = Get_ADC_Vector( ); // lsb = 0.043 kV } while( val < MIN_VOLTAGE ); // removed time-out and hi-rel code // for 'example' clarity }
The "Get_ADC_Vector( )" function is external and in another module--as you would expect. The compiler (Keil) compiles and links with zero errors/warnings.
By having "Global Register Coloring" enabled in Keil's "Code Optimization" options, the Keil compiler did/does not account for the register bank switch from the "using 2" directive, so the parameter passed back from the function is in error: the compiler generates code that accessed the registers absolutely and tries to place them into the function-return registers.
Keil should have either identified that you must use "Don't use absolute register access" when you use "Global Register Coloring" and you are using the "using" directive in your code, -OR- Keil's optimizer should have properly handled it.
This example of an assembly traced bug didn't take too long because I realized that the 'using' directive does modify the reg-bank and I knew it was a risk point.
But initially I, like any typical user, was relying on Keil to handle that deviation properly: especially since the pre-optimization proved valid. (An OCG would 'see' this cross module error and avoid it)
FYI: My cure was to eliminate the "using" directive since it was clearly not a need. This is because I take the time/overhead to call two functions, from within the ISR, I could obviously afford the time delays of not switching banks. I also un-checked the "Global Register Coloring" since Keil proved that you cannot trust it. Keil's own documentation does not clarify the conflict.
Sprat, the real point I was making is that most of a "competent" embedded engineer's TIME is spent in dealing with "bugs" at the assembly level. The bugs that are cured at the "C" level are the easy "Doh! what a stupid mistake" type while the gotcha's are not as easily found and do require assembly level tracing.
Hopefully the underlying assembly code is not so mangled as to make it hard to trace. (such as register optimization where some needed data-store is held in R3 since the optimizer knows that it can stay there until it is needed, and doesn't have to write it out to the data space just to keep the data-element current. So then you can find the [traced] code comparing against a 'mysterous' R3 that was loaded a long time ago instead of comparing against 'Critical_Threshold' which is what you expected to see).
"find patterns where no patterns exists" == "code hallucinations"
You can find ten of those errors in ten minutes but the one that which takes two hours is the type I'm talking about. Hence, most of your 'time' is spent debugging something that is not glaringly obvious that the faulty "C" implementation of faulty logic is in error. Usually the problems that take the most time are those that are a result of 'undocumented features' in the data-sheets, somebody else's code, or self generated. And for those long duration bugs, you'll need to delve into the assembly
The issue here is that debugging is not pantyhose (one size does NOT fit all). There are individual methods to the sequence ( look at source, check it in the ICE, do a, most likely hopeless, try with a simulator, insert some printf ...) but to find all bugs in a timely manner the most dangerous attitude is "THIS is THE way".
Yes, I do, occasionally resort to looking at the assembler, that is, indeed, a tool in my toolchest and it has, at times helped immensely. Does that make it "the right debugging method", of course not, but neither does it make it "the wrong debugging method".
View all questions in Keil forum