Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chapter1: Frame pointer #2

Closed
aarzilli opened this issue Mar 5, 2018 · 9 comments
Closed

chapter1: Frame pointer #2

aarzilli opened this issue Mar 5, 2018 · 9 comments
Assignees

Comments

@aarzilli
Copy link
Contributor

aarzilli commented Mar 5, 2018

Like most recent compilers, the Go tool suite always references argument and locals using offsets from the stack-pointer directly in the code it generates. This allows for the frame-pointer to be used as an extra general-purpose register on platform with fewer registers (e.g. x86).

But it doesn't, starting with 1.8 (or was it 1.7 or 1.9?) the compiler emits instructions to use the frame pointer for its intended purpose, to help dtrace-like debugging tools. Just like you say later in the chapter. Maybe I'm misunderstanding.

@teh-cmc
Copy link
Owner

teh-cmc commented Mar 5, 2018

Like most recent compilers, the Go tool suite always references argument and locals using offsets from the stack-pointer directly in the code it generates. This allows for the frame-pointer to be used as an extra general-purpose register on platform with fewer registers (e.g. x86).

But it doesn't, starting with 1.8 (or was it 1.7 or 1.9?) the compiler emits instructions to use the frame pointer for its intended purpose, to help dtrace-like debugging tools. Just like you say later in the chapter. Maybe I'm misunderstanding.

To be honest, I was quite confused about this whole affair myself, and was secretly hoping for somebody to hop along and say something that would make me tick.. and you did! Thank you.

What really drove me mad here is this excerpt from the documentation that I quoted in the first chapter and which goes like this:

The FP pseudo-register is a virtual frame pointer used to refer to function arguments. The compilers maintain a virtual frame pointer and refer to the arguments on the stack as offsets from that pseudo-register.

Now unless I'm missing something blaringly obvious here, something doesn't add up. Frame-pointer support for amd64 had not landed until Go 1.7, i.e. ~2 years ago. This excerpt from the documentation, however, has been there for more than 4 years according to git blame.
And I've yet to see any assembly generated by the Go compiler that relies on the frame-pointer to reference anything. Have you?

In any case, how could the compiler refer to a register that wasn't even maintained back then? And which is only supported for very few platforms anyway, even to this day?

So here's what I think is happening here:
Back in the good old days of Go pre-1.7, the language had zero support for frame-pointers, and so the generated assembly naturally used to reference arguments and locals relative to the stack-pointer. Somehow the documentation didn't properly reflect that, either that or I'm misreading it entirely.
Then support for frame-pointers (amd64 only) shipped in Go 1.7 to help with debugging tools for server-side development.
As this point though, there was no good reason to change the behaviour of the existing compiler (quite the contrary); so the compiler continues to use the stack-pointer exclusively to this day.

What we end up with is a situation where the compiler does insert a function-preamble in order to properly maintain the frame-pointer, on some platforms, but still doesn't rely on it for anything. It's only there as a gift for external tooling.
Why the documentation says otherwise is still a mystery that I cannot wrap my head around.

This story is kinda driving me mad tbh, so I'm not sure that I'm still making much sense at this point.
Do you think this makes sense? If it does, I should update the chapter to reflect this.

@teh-cmc teh-cmc self-assigned this Mar 5, 2018
@dgryski
Copy link

dgryski commented Mar 5, 2018

The /doc/asm page could probably use updating if that's the case. Maybe file a bug?

@aarzilli
Copy link
Contributor Author

aarzilli commented Mar 5, 2018

Yes, there's two different "frame pointer registers" going on here. The one output by the compiler's pseudoassembly, called FP, is a fake register, like TLS and SB, it doesn't exist and the assembler translates it into whatever it wants (specifically, the stack pointer with a different offset).

The other "frame pointer" is the actual architecture register, for example BP in amd64. I think they have the same value, but I've never checked.

I don't know why the pseudoassembly uses FP instead of using SP directly, it could be a holdover from plan9 or it could be to leave an opening to support alloca(3) type allocations in the future, I'm guessing only people from the original go team know.

@aarzilli
Copy link
Contributor Author

aarzilli commented Mar 5, 2018

PS. technically the documentation isn't wrong just confusing.

@teh-cmc
Copy link
Owner

teh-cmc commented Mar 5, 2018

I don't know why the pseudoassembly uses FP instead of using SP directly

But that's the thing: AFAICT, the compiler uses neither the virtual one nor the hardware one (although the documentation states differently). :/

I think @dgryski is right; I'll file an issue when I get a minute or two, and hopefully we'll get some valuable insights straight from the source.

@teh-cmc
Copy link
Owner

teh-cmc commented Apr 14, 2018

I've added a link to this discussion in the relevant part of chapter 1; and will be closing this now.
Don't hesitate to add more comments even if it's closed!

Hopefully I'll take the time to update chapter 1 with all these learnings some day.

@teh-cmc
Copy link
Owner

teh-cmc commented May 1, 2018

This has finally been elucidated, as far as I'm concerned; see #21 (comment) for a detailed explanation.

TL;DR: All of this confusion (at least on my part) stemmed from the fact that I was looking for something that simply doesn't exist, except that the documentation says that it does, except that no, really, it doesn't.

There is no such thing as the virtual SP. There is no such thing as the virtual FP. Those don't exist anymore. They are relics of the past and are only mentioned in this documentation because it's outdated.
This holds true for amd64, at least. See the linked comment if you'd like details.

There is indeed support for maintaining the hardware frame-pointer on some architectures in recent versions of the toolchain, to help external debuggers do their job, as you'd expect.
This has nothing to do with the virtual frame-pointer though. Nothing. It doesn't exist.
Also, as expected, the compiler doesn't rely on the hardware FP for anything. It merely maintains it out of politeness for external tools. Everything relies on the hardware stack-pointer.

I'm pretty sure @aarzilli had it right all along; except I couldn't wrap my head around his explanations as I was desperately looking for this virtual FP that keeps being mentioned but actually doesn't exist anywhere outside of the outdated documentation.

@teh-cmc
Copy link
Owner

teh-cmc commented May 1, 2018

Also, I really, really need to update that chapter.

@teh-cmc
Copy link
Owner

teh-cmc commented May 1, 2018

My earlier comment (#2 (comment)) is misleading: what I mean when I say that these virtual registers don't exist anymore is that they won't ever be shown to you in assembly outputs generated by the standard go tools (compile -S, objdump...), even when it very much look like they are! The only place you'll see them is in hand-written go-asm (assuming amd64).

Once again, I'd suggest having a look at the discussion I've had with @cch123 in #21 for the nitty gritty details.

icarus-sparry pushed a commit to icarus-sparry/go-internals that referenced this issue Aug 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants