EmbeddedRelated.com
Forums
Memfault Beyond the Launch

whose 8051 cc overlays static inline stack frames

Started by PPAATT January 10, 2004
I wonder if any of the 8051 cc folk I see here are working to solve such
troubles as:

Once upon a time, a C compiler targeting the 8051 blew more space and time than
I could afford in its translation of:

extern void x(char chx);
static /* inline */ void c(char chc) { ...; x(chc); ... }
static /* inline */ void b(char chb) { ...; c(chb); ... }
void a(char cha) { ...; b(cha); ... }

1) The compiler did substitute static ram for stack, by trusting my promise
that none of this code ever reentered itself.

2) But this compiler did not support the inline keyword, so I lost two bytes of
stack per call/ return, pointlessly.

3) And this compiler blew three bytes of static ram for the ch1 ch2 ch3 args,
even though C's pass-by-value means they all could coexist in the same space.

I did engage the compiler vendor at the time in an e-mail conversation.  I
failed to persuade my contact that my suffering was worth logging, much less
resolving.  Eventually the code we shipped manually expanded the work inline,
using ASCII graphics to mark the inline subroutine boundaries, and stored
everything in globals rather than locals, a safe practice only if our manual
analysis of control flow vs. data flow holds true over time.

Pat LaVarre
http://members.aol.com/ppaatt/losslessc/
http://groups.google.com/groups?q=8051+static+inline
PPAATT <ppaatt@aol.com> wrote:

> 1) The compiler did substitute static ram for stack, by trusting my > promise that none of this code ever reentered itself.
Of course it did. On a '51, that's about the only chance you have. Putting arguments or automatic variables on the stack is a non-option if there's no more than 128 Bytes of stack to begin with. Without static call-tree analysis and static allocation/overlaying of variables, C would be close to be impossible to implement on a '51.
> 2) But this compiler did not support the inline keyword, so I lost > two bytes of stack per call/ return, pointlessly.
Supporting the inline keyword is not the real issue --- anyway, "inline" is an official C keyword only as of the C99 revision of the standard, which I'm quite certain your C compiler never claimed to be compliant to. The real issue is actually inlining code where possible, which a compiler is allowed regardless of whether you used the inline keyword or not.
> 3) And this compiler blew three bytes of static ram for the ch1 ch2 > ch3 args, even though C's pass-by-value means they all could coexist > in the same space.
Whatever made you believe this? It's wrong. Memory re-use can only be decided by analysis of the actual code, and how it uses the value. Pass-by-value has quite exactly nothing to do with that. -- Hans-Bernhard Broeker (broeker@physik.rwth-aachen.de) Even if all the snow were burnt, ashes would remain.
> > 3) And this compiler blew three bytes of static ram for the ch1 ch2 > > ch3 args, even though C's pass-by-value means they all could coexist > > in the same space. > > Whatever made you believe this? It's wrong. Memory re-use can only > be decided by analysis of the actual code, and how it uses the value. > Pass-by-value has quite exactly nothing to do with that.
Thanks for helping me learn I should have said more, sorry we disagree over how elastic the meaning of jargon can be. Again my C89 fragment was: extern void x(char chx); static /* inline */ void c(char chc) { ...; x(chc); ... } static /* inline */ void b(char chb) { ...; c(chb); ... } void a(char cha) { ...; b(cha); ... } Ouch now I see I neglected to mention: I also know that in the actual code here shown as "..." ellipses, there were no mentions of cha chb chc. That's the observation that tells me we can store cha chb chc all in the same static byte. Is there no 8051 cc compiler available that can make that same observation and act on it?
> "inline" is an official C keyword only as of the C99 revision of the > standard, which I'm quite certain your C compiler never claimed to be > compliant to.
Yes the compiler in question was a 1989 C not a 1999 C compiler.
> > But this compiler did not support the inline keyword, so I lost > > two bytes of stack per call/ return, pointlessly. > > Supporting the inline keyword is not the real issue ... > The real issue is actually inlining code where > possible, which a compiler is allowed regardless of whether you used > the inline keyword or not.
I don't mind having to tell the compiler I do want code inlined whenever it saves me the time/space of call/return. I can even survive having to tell the compiler with explicit options or a pragma or a C99 keyword. What killed me was having bought a compiler supposedly targetted at 64 KiB rom images that didn't have any way to inline, and technical support that didn't even understand what I wanted. Any 8051 cc customers out there have a better experience to share? Pat LaVarre
In article <2695edf1.0401101309.409da92@posting.google.com>, Pat LaVarre
<ppaatt@aol.com> writes
>Again my C89 fragment was:
>> "inline" is an official C keyword only as of the C99 revision of the >> standard, which I'm quite certain your C compiler never claimed to be >> compliant to. >Yes the compiler in question was a 1989 C not a 1999 C compiler.
AFAIK there is only one C99 compiler and that is the Tasking Tricore compiler. Most are C90 or C95. This is C90 with A1 and the TC's
>What killed me was having bought a compiler supposedly targetted at 64 >KiB rom images that didn't have any way to inline, and technical >support that didn't even understand what I wanted.
AFAIK none of them do inline. the good ones optimise very well anyway and will effectivly inline where they can. Which compiler was it? /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ \/\/\/\/\ Chris Hills Staffs England /\/\/\/\/\ /\/\/ chris@phaedsys.org www.phaedsys.org \/\/ \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
Pat LaVarre <ppaatt@aol.com> wrote:
[...]

> Thanks for helping me learn I should have said more, sorry we disagree > over how elastic the meaning of jargon can be.
IMHO, sloppy use of jargon has no place in what really was a rather thinly veiled public accusation of the entire community of '51 C compiler makers of being lazy. If you want to voice a complaint, you should give a complete and accurate record of the facts.
> Again my C89 fragment was:
> extern void x(char chx); > static /* inline */ void c(char chc) { ...; x(chc); ... } > static /* inline */ void b(char chb) { ...; c(chb); ... } > void a(char cha) { ...; b(cha); ... }
> Ouch now I see I neglected to mention: I also know that in the actual > code here shown as "..." ellipses, there were no mentions of cha chb > chc. That's the observation that tells me we can store cha chb chc > all in the same static byte.
You may be overlooking some of the pickier details of C, most notably the "aliasing problem". If there's even a single pointer being used to access any char object hidden in those "..."s, the compiler has to assume it no longer knows whether that, e.g. cha is still needed even after the return of function b(). What appears obvious to you isn't necessarily obvious to the compiler, too. It may generally be impossible for it to find out.
> Is there no 8051 cc compiler available that can make that same > observation and act on it?
I see no reason why they shouldn't --- figuring this out should be no unsurmountable obstacle for the kind of static analysis these compilers have to run. But, as they say, the proof of the pudding is in the eating. Post a complete, compilable example, and people might even feed it to their compilers of choice and report on results. Ah, heck, I'll give it a shot myself... -- Hans-Bernhard Broeker (broeker@physik.rwth-aachen.de) Even if all the snow were burnt, ashes would remain.
> Which compiler was it?
I'll answer that offline. Here I consciously named no names to leave me free to point out the technical support folk appeared clueless, seemingly knowing even less of C language law than I do. Pat LaVarre
> But, as they say, the proof of the pudding is > in the eating. Post a complete, compilable example, and people might > even feed it to their compilers of choice and report on results. > > Ah, heck, I'll give it a shot myself...
Thank you. I suggest we try: ---- #define tbd /* ... */ extern void x(char chx); static /* inline */ void c(char chc) { tbd; x(chc); tbd; } static /* inline */ void b(char chb) { tbd; c(chb); tbd; } void a(char cha) { tbd; b(cha); tbd; } --- I will post again when I'm next local to a C compiler, so that I can confirm that indeed one or more C compilers accept this code.
> a rather > thinly veiled public accusation of the entire community of '51 C > compiler makers of being lazy.
I deny this. I mean to say the C compilers I have tried for the 8051 do not deliver space efficiency comparable to what I'm used to seeing in proprietary 8051 asm source code. My compiler folk have chosen by design to produce unreasonable machine code. I imagine they had good reasons. Shipping the compiler soon enough to let people use it, among them. I see no evidence of laziness. But offline I couldn't persuade people to answer my question.
> If you want to voice a complaint, you > should give a complete and accurate record of the facts.
Sorry I lack the authority to disclose all the facts and all my own work. Certainly I prefer to work openly e.g. my http://udfko.blog-city.com/ work to make Linux more often read back what I write thru it. But in real life often the paid work I find comes entangled with NDA, preassignment of IP, etc.
> > Again my C89 fragment was: > > > extern void x(char chx); > > static /* inline */ void c(char chc) { ...; x(chc); ... } > > static /* inline */ void b(char chb) { ...; c(chb); ... } > > void a(char cha) { ...; b(cha); ... } > > > Ouch now I see I neglected to mention: I also know that in the actual > > code here shown as "..." ellipses, there were no mentions of cha chb > > chc. That's the observation that tells me we can store cha chb chc > > all in the same static byte. > > You may be overlooking some of the pickier details of C, most notably > the "aliasing problem".
I had hoped to find such an education here, thank you.
> If there's even a single pointer being used > to access any char object hidden in those "..."s, the compiler has to > assume it no longer knows whether that, e.g. cha is still needed even > after the return of function b().
Yes.
> What appears obvious to you isn't > necessarily obvious to the compiler, too.
Yes. That's where I like to see more or less vendor-specific ways of helping me emphasise the obvious e.g. the C99 inline keyword.
> It may generally be > impossible for it to find out.
Lost me help?
> > Is there no 8051 cc compiler available that can make that same > > observation and act on it? > > I see no reason why they shouldn't --- figuring this out should be no > unsurmountable obstacle for the kind of static analysis these > compilers have to run.
Aye. Seeing the compiler complete the proof of no recursion gave me a false hope of seeing that compiler reasonably translate this "local procedure" idiom. Pat LaVarre
Pat LaVarre wrote:
> > > But, as they say, the proof of the pudding is > > in the eating. Post a complete, compilable example, and people might > > even feed it to their compilers of choice and report on results. > > > > Ah, heck, I'll give it a shot myself... > > Thank you. I suggest we try: > > ---- > > #define tbd /* ... */ > extern void x(char chx); > static /* inline */ void c(char chc) { tbd; x(chc); tbd; } > static /* inline */ void b(char chb) { tbd; c(chb); tbd; } > void a(char cha) { tbd; b(cha); tbd; } > > ---
Surprising it is valid code, and gcc can be made to restrict its actions to the normal integer promotions (I think): c:\c\junk>gcc -c -fomit-frame-pointer -O3 junk.c c:\c\junk>objdump -dS junk.o junk.o: file format coff-go32 Disassembly of section .text: 00000000 <_a>: #define tbd /* ... */ extern void x(char chx); static /* inline */ void c(char chc) { tbd; x(chc); tbd; } 0: 0f be 54 24 04 movsbl 0x4(%esp,1),%edx 5: 89 54 24 04 mov %edx,0x4(%esp,1) 9: e9 f2 ff ff ff jmp 0 <_a> e: 90 nop f: 90 nop It also passes splint. However splint -strict is outraged :-) c:\c\junk>splint junk.c Splint 3.0.1.6 --- 11 Feb 2002 Finished checking --- no warnings c:\c\junk>splint -strict junk.c Splint 3.0.1.6 --- 11 Feb 2002 junk.c(2,20): Declaration parameter has name: chx A parameter in a function prototype has a name. This is dangerous, since a macro definition could be visible here. (Use either -protoparamname or -namechecks to inhibit warning) junk.c: (in function c) junk.c(3,43): Undetected modification possible from call to unconstrained function x: x An unconstrained function is called in a function body where modifications are checked. Since the unconstrained function may modify anything, there may be undetected modifications in the checked function. (Use -modunconnomods to inhibit warning) junk.c(3,43): Statement has no effect (possible undected modification through call to unconstrained function x): x(chc) Statement has no visible effect --- no values are modified. It may modify something through a call to an unconstrained function. (Use -noeffectuncon to inhibit warning) junk.c: (in function b) junk.c(4,43): Undetected modification possible from call to unconstrained function c: c junk.c(4,43): Statement has no effect (possible undected modification through call to unconstrained function c): c(chb) junk.c: (in function a) junk.c(5,23): Undetected modification possible from call to unconstrained function b: b junk.c(5,23): Statement has no effect (possible undected modification through call to unconstrained function b): b(cha) junk.c(2,13): Function x declared but not defined A function or variable is declared, but not defined in any source code file. (Use -declundef to inhibit warning) junk.c(5,6): Function a declared but not used A function is declared but not used. Use /*@unused@*/ in front of function header to suppress message. (Use -fcnuse to inhibit warning) junk.c(5,34): Definition of a junk.c(2,13): Function x exported but not declared in header file A declaration is exported, but does not appear in a header file. (Use -exportheader to inhibit warning) junk.c(5,6): Function a exported but not declared in header file junk.c(5,34): Definition of a Finished checking --- 11 code warnings -- Chuck F (cbfalconer@yahoo.com) (cbfalconer@worldnet.att.net) Available for consulting/temporary embedded and systems. <http://cbfalconer.home.att.net> USE worldnet address!
> AFAIK none of them do inline.
Ouch.
> the good ones optimise very well anyway > and will effectivly inline where they can.
Good to hear. I think I remember: I wrote a dis/assembler pair with some flow analysis to review the machine code actually produced by the C development environment. The image produced contained dozens of addresses reached by only one call instruction, whoops. As for causes: 1) I remember the specific example I gave here. People had written some subroutines merely to structure code and to make variables local, not to be called twice or more. 2) Also I remember the C compiler working to allow future separate compilation that never actually did occur. The C compiler would write machine code as if anything extern (i.e. called from another source file) could be called twice. And the linker didn't know how to distinguish a subroutine called once. Also I remember my own quick-and-dirty assembler reassembled the image dramatically faster than the compiler could compile & link it. I think I remember I could even change the assembly code and reassemble faster than the C environment could `make`. Pat LaVarre
Pat LaVarre <ppaatt@aol.com> wrote:

> Thank you. I suggest we try:
> ---- > #define tbd /* ... */ > extern void x(char chx); > static /* inline */ void c(char chc) { tbd; x(chc); tbd; } > static /* inline */ void b(char chb) { tbd; c(chb); tbd; } > void a(char cha) { tbd; b(cha); tbd; }
> ---
OK. I compiled that. And the compiler I chose *did* find your requested optimization. It allocated all of cha, chb and chc to register R7, and the calls to b, c and x all became plain JMP operations. I.e. the code looks like this: ; FUNCTION _c: JMP _x ; FUNCTION _b: JMP _c ; FUNCTION _a: JMP _a I didn't test that, but I guess the "linker code packing" feature will reduce that even further, to make _a consist of just JMP _x. It does have an optimization that is supposed to "follow through" on chained jumps like these and retarget directly to the final one. Get your own Keil eval copy and see for yourself.
> I mean to say the C compilers I have tried for the 8051 do not deliver > space efficiency comparable to what I'm used to seeing in proprietary > 8051 asm source code.
Well, as the saying goes, if it's asm code you want, I trust it you know where to find it. C compilers for '51 can do some rather impressive tricks these days, but there still *is* a limit to what they can do. C99, if anyone actually decides to do it, will help a bit. Not that much through the 'inline' keyword as through the new 'restrict' which lets you help the compiler get across the performance limitations caused by aliasing. -- Hans-Bernhard Broeker (broeker@physik.rwth-aachen.de) Even if all the snow were burnt, ashes would remain.

Memfault Beyond the Launch