What does a dead register mean?


My understanding of a “dead” register is a def that is never used. However,
when I dump the MI after reg alloc on a simple program I see the following sequence:

ADJCALLSTACKDOWN64 0, 0, 0, implicit-def dead %rsp, implicit-def dead %eflags, implicit-def dead %ssp, implicit %rsp, implicit %ssp
CALL64pcrel32 @foo, <regmask %bh %bl %bp %bpl %bx %ebp %ebx %rbp %rbx %r12 %r13 %r14 %r15 %r12b %r13b %r14b %r15b %r12d %r13d %r14d %r15d %r12w %r13w %r14w %r15w>, implicit %rsp, implicit %ssp, implicit-def %rsp, implicit-def %ssp
ADJCALLSTACKUP64 0, 0, implicit-def dead %rsp, implicit-def dead %eflags, implicit-def dead %ssp, implicit %rsp, implicit %ssp

The ADJCALLSTACKDOWN64 has implicit-def dead %rsp. However the next instruction,
CALL64pcrel32 has an implicit use of %rsp. This would be a use of %rsp as defined
in ADJCALLSTACKDOWN64 making that non-dead.

So I guess my understanding of dead is incorrect. Could you please explain what dead means?

For reference:
Source file(a.c):
void foo(void);
void boo(){ foo(); }

clang -S -emit-llvm -Xclang -disable-O0-optnone a.c
llc -print-after=“stack-slot-coloring” a.ll

A better explanation than I would have given is here: https://groups.google.com/forum/#!topic/llvm-dev/NlGopW6_QxE

The optimization pass that would have eliminated that was turned off by -O0 so you see that behavior. At -O2 you get a tail call

TCRETURNdi64 ga:foo, 0, <regmask %BH %BL %BP %BPL %BX %DI %DIL %EBP %EBX %EDI %ESI %RBP %RBX %RDI %RSI %SI %SIL %R12 %R13 %R14 %R15 %XMM6 %XMM7 %XMM8 %XMM9 %XMM10 %XMM11 %XMM12 %XMM13 %XMM14 %XMM15 %R12B %R13B %R14B %R15B %R12D %R13D %R14D %R15D %R12W %R13W %R14W %R15W>, %RSP

O0 didn’t optimize away the usage with no side effects, so it produces the assembly:

boo: # @boo


call foo
add rsp, 40

O2 recognizes that the ret and rsp adjustment can move straight into foo() which then does all of the things boo would have done if it had locals that were used or parameters. The

boo: # @boo


jmp foo # TAILCALL

It’s also related to why you see both sp and rsp being marked as modified. They technically are, but since one is a subregister it doesn’t need to be explicitly marked. Here’s an O0 example of how bad O0 generated code can end up. Note that there aren’t any uses of the return from foo, and it doesn’t have side effects:

C file:

int foo(long x, long y, long z)
int retVal = x * y + z;
int* unused = &retVal;
return retVal;

void boo()
int x = 1;
int* y = &x;
int z = *y;
foo(x, *y, z);

-O0 version

You are right about your interpretation of "dead". The case here is that RSP is a reserved register and so its liveness isn't really tracked. The "implicit-def dead" is an idiom used to mean that the register (reserved or not) is clobbered. The other implicit uses/defs can come from instruction definitions to indicate that this instruction uses and/or modifies a given register (regardless of its explicit operands), but for reserved registers there doesn't need to be any continuity between the defs and the uses.


Thank you for the explanation!