How useful is static analysis based stack usage?

Go To Last Post
10 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Another one of those "would this be useful" questions :)

I whipped up a quick gcc plugin last night that basically prints

main : 2 : 2
  baz : 4 : 6
  bar : 42 : 44
    barinner : 4 : 48
  foo : 12 : 14
    fooinner : 3 : 17

(read as function name : function stack usage : total stack usage on code path).

for a program with two files, like this

a.c
----
struct test { int x; int y; };

extern int baz(int);
volatile int z;

void barinner(struct test* tests)
{
    tests[1].x = tests[1].y;
    z = 4;
}
void bar()
{
    struct test tests[10];
    barinner(tests);
    z = 3;
}

void fooinner(char c) { }
void foo() { char x[10]; fooinner(x[0]); z = 2; }

int main() 
{ 
    foo();
    bar();
    baz(100);

    return 0;
}

b.c
---
int baz(int x) { return x + 1; }

Stack usage for a function is calculated during prologue generation - I just combine that with callgraph information to find out stack usage along function call paths. When used with -flto, you get this information for the whole program, rather than just the translation unit.

This of course, will not be able to look into pre-compiled code (avr-libc, libgcc etc..), or handle recursion.

Do you guys think this would be useful?

Regards

Senthil

 

blog | website

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

saaadhu wrote:
Do you guys think this would be useful?
Definitely!

(I have my own utility for this. But I would certainly welcome something that comes standard with the toolchain.)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Very useful.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
This of course, will not be able to look into pre-compiled code (avr-libc, libgcc etc..), or handle recursion.

How about explicit calls to alloca()?

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah, those don't get accounted either.

Also, the function call overhead (return address on stack) is not accounted right now, as linker relaxation can later change calls to jumps. Perhaps I should take a pessimistic approach and show worst case stack usage?

Regards

Senthil

 

blog | website

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

saaadhu wrote:
Another one of those "would this be useful" questions :)

I whipped up a quick gcc plugin last night that basically prints

gcc has plugins?

"Demons after money.
Whatever happened to the still beating heart of a virgin?
No one has any standards anymore." -- Giles

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

gcc has plugins?

That's apparently how LTO works. I think the plugin mechanism was something added in binutils 2.23.

http://gcc.gnu.org/wiki/whopr/dr...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, gcc has had plugins atleast since 4.6.2, and probably before that. See http://gcc.gnu.org/onlinedocs/gc... .

I've put up the source code for my plugin at https://github.com/saaadhu/stack... - not a lot of code really.

Cliff, LTO is done by gcc and linker plugins are only needed if object files inside archives need to be available for LTO. Otherwise, the entire process is managed within the compiler - no binutils support is needed. See http://gcc.gnu.org/wiki/LinkTime... .

Regards

Senthil

 

blog | website

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Stack usage for a function is calculated during prologue generation

I'm not familiar with the mechanism(s) you are using for gathering the information. I'd assume automatics are taken into account, and register save?

Anyway, besides recursion I'd also have a disclaimer about nested interrupts.

Putting nested interrupts aside, the posted example doesn't show any ISRs. Practically, one would need to add the largest ISR stack hit to the deepest value from the static analysis, right?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
Quote:

Stack usage for a function is calculated during prologue generation

I'm not familiar with the mechanism(s) you are using for gathering the information. I'd assume automatics are taken into account, and register save?

Yes, whatever the compiler figures it needs space for during prologue generation. It just reads a value that is set by the actual compiler code that does prologue generation.

theusch wrote:

Anyway, besides recursion I'd also have a disclaimer about nested interrupts.

Agreed. I suppose it is possible to figure out worst case usage (assume every possible interrupt could get interrupted at the deepest point), but I don't know if it would be useful.

theusch wrote:

Putting nested interrupts aside, the posted example doesn't show any ISRs. Practically, one would need to add the largest ISR stack hit to the deepest value from the static analysis, right?

Yes, ISRs would show up as "root" nodes in the callgraph, just like main, and will show stack usage along with that ISR's function call path. Like you said, adding max ISR stack usage, with max non-ISR stack usage should work.

Code

void foo()
{
    char x[20];
}

void __attribute__((signal)) __vector_2()
{
    foo();
}

int main() 
{ 
    return 0;
}

Stack usage

 :  : 
--------------------
baz : 4 : 4
main : 2 : 2
  foo : 22 : 24
__vector_2 : 17 : 17
  foo : 22 : 39

Regards

Senthil

 

blog | website