davidgiven / ack

The Amsterdam Compiler Kit
http://tack.sf.net
Other
420 stars 59 forks source link

Used local functions are inlined, but unused local functions are not removed? #230

Closed tkchia closed 3 years ago

tkchia commented 3 years ago

Running

ack -mlinux386 -O6 -c.s test.c

on the code

extern int real_foo(int x, int y);

static int foo(int x, int y)
{
    return real_foo(x, y);
}

int main(void)
{
    return foo(1, 2);
}

yields

.sect .text; .sect .rom; .sect .data; .sect .bss
.extern _main
.sect .text
.align 4
_main:
push ebp
mov ebp,esp
push 2
push 1
call _real_foo
pop ecx
pop ecx
leave
ret

where the body of foo(, ) is effectively inlined into main(), with no out-of-line foo(, ) remaining.

However if I instead write

extern int real_foo(int x, int y);

static int foo(int x, int y)
{
    return real_foo(x, y);
}

int main(void)
{
    return 0;
}

then the output is

.sect .text; .sect .rom; .sect .data; .sect .bss
.sect .text
.align 4
__II0:
push ebp
mov ebp,esp
push 12(ebp)
push 8(ebp)
call _real_foo
pop ecx
pop ecx
leave
ret
.extern _main
.align 4
_main:
push ebp
mov ebp,esp
xor eax,eax
leave
ret

where for some reason the unused foo(, ) function is not removed.

Thank you!

davidgiven commented 3 years ago

Inlining is handled by ego, in the il module, which is unusually well documented. See page 24 here: http://tack.sourceforge.net/olddocs/ego.pdf

It turns out that this behaviour is intentional, as the called extern procedure may access the dynamic call chain via the lxl, lxa or dch EM instructions. These are used for handling nested functions. Inlining the function would change the call chain and render the code invalid. Of course, C doesn't support these and never calls them, but ego doesn't know this.

I'm not sure there's a way round this one...

tkchia commented 3 years ago

Hello @davidgiven,

Thanks!

I am not entirely sure that nested functions explain this behaviour. Apparently the optimizer thinks that foo( ) can be inlined and elided (despite calling to the external real_foo( )), if there are actual calls to foo( ). The optimizer somehow only fails to get rid of foo( ) when it is never actually called.

But thank you for the pointer to the ego documentation. I will read through it and also take a closer look at the source code to see what I might be able to do.

Thank you!

davidgiven commented 3 years ago

Oh --- I'm sorry, I misread your message! (I shouldn't do this late at night...) Yes, I agree, that looks weird and suggests a bug. My gut feeling is that foo() isn't showing up on the call graph and is therefore being ignored by the inliner, so it never gets elided. That ought to be a relatively easy fix, for anyone who understands ego...