-
Notifications
You must be signed in to change notification settings - Fork 353
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: about SP register #21
Comments
You are definitely right that the virtual stack-pointer has nothing to do with stack-growth, it just wouldn't make any sense. On another note, your comment about go not generating code that refer to the virtual SP brought back some dark demons (see #2) of mine, and so I've decided to elucidate this mystery once and for all. Right here, right now. I think I'm starting to have a somewhat clear picture of all the intricacies surrounding the virtual stack-pointer (as well as the other virtual registers, really), so I'll try to relay them as best as I can.
For starters, the official documentation states the following:
It goes on to add this:
Now consider this simple code: func add(a, b int) int { return a + b } which compiles down to this for ;; GOOS=linux GOARCH=amd64 go tool compile -S main.go
0x0000 00000 TEXT "".add(SB), NOSPLIT, $0-24
0x0000 00000 FUNCDATA $0, gclocals·54241e171da8af6ae173d69da0236748(SB)
0x0000 00000 FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 MOVQ "".b+16(SP), AX ;; ??
0x0005 00005 MOVQ "".a+8(SP), CX ;; ??
0x000a 00010 ADDQ CX, AX
0x000d 00013 MOVQ AX, "".~r2+24(SP) ;; ??
0x0012 00018 RET Particularly of interest are those three lines: 0x0000 00000 MOVQ "".b+16(SP), AX ;; ??
0x0005 00005 MOVQ "".a+8(SP), CX ;; ??
0x000d 00013 MOVQ AX, "".~r2+24(SP) ;; ?? These three references to the stack-pointer clearly use a named prefix, which should mean that they refer to the virtual stack-pointer, according to the documentation I've just quoted above. So which is it? What are we really looking at here? The hardware SP or the virtual SP? 000000000044c150 <main.add>:
44c150: 48 8b 44 24 10 mov 0x10(%rsp),%rax
44c155: 48 8b 4c 24 08 mov 0x8(%rsp),%rcx
44c15a: 48 01 c8 add %rcx,%rax
44c15d: 48 89 44 24 18 mov %rax,0x18(%rsp)
44c162: c3 retq We can see that all of these three lines were clearly referencing the hardware SP, not the virtual one (i.e. there offsets are not readjusted in any way). This whole ordeal is in the same vein as #2; and I'm starting to understand the pattern here. Why do I specify "at least"? Because it looks like virtual registers are handled widely differently on each platform (try compiling for
|
I've linked to this discussion in the relevant part of chapter 1. I fell like quite a few low-level details of this chapter are gonna need a complete rewrite one of these days, though. |
TL;DR; The asm doc is not outdated. The rules in golang.org/doc/asm are only for hand-written asm code, not for generated code. I found this discussion: Although they were talking about some other things, but as Rob Pike said, the rules in golang.org/doc/asm are made for hand-written asm code, not for the compiler generated(go tool compile -S or go tool objdump) asm code. The asm code pieces in your project are generated by the compiler, so the SP register is always hardware one(at least for go 1.10). After the code being compiled, the use of virtual registers will be translated to platform-dependent hardware registers and offsets. If you use virtual SP/FP, it will be translated to hardware SP(with offset adjusted). Sometimes compiler may insert some data(caller BP) to your stack frame, it also needs to adjust the framesize to the correct size. And this insertion behavior also affects the offset of function input param from the hardware SP. (This can be proved by my experiment code in the end of this issue.) I also did some experiments, and found out that the virtual SP and hardware SP point to the same location when your stack frame's size is 0( when 1. no local variables; 2.no arguments and returns when calling other functions). They points to different locations when stack frame size is bigger than 0, that is
this is the situation for the function in chapter 1 example: func add(a, b int) int { return a + b }
Whether
func Framepointer_enabled(goos, goarch string) bool {
return framepointer_enabled != 0 && goarch == "amd64" && goos != "nacl"
} Also notice that, the name framepointer in golang.org/doc/asm refers to the FP(virtual register), and the framepointer in this These conclusions are not explained in go's documents or src code clearly, so it's easy to misunderstand... ps: my experiment code: t.go package main
import (
"fmt"
)
func output(int) (int, int, int)
func output2(int) (int, int, int)
func main() {
a, b, c := output(987654321)
fmt.Println(a, b, c)
a, b, c = output2(98765)
fmt.Println(a, b, c)
} t.s: // just experiments
// should not use positive offset to virtual SP in your production code
#include "textflag.h"
// func output(int) (int, int)
// frame size is 0 or 8 will affect the result
TEXT ·output(SB), $8-32
MOVQ 24(SP), DX // hardware SP + 24(framesize+caller BP+ret address) should points to the first input argument
MOVQ DX, ret3+24(FP)
MOVQ argx+16(SP), BX // virtual SP + 16(caller BP+ret address) should points to the first input argument
MOVQ BX, ret2+16(FP)
MOVQ arg0+0(FP), AX // first argument
MOVQ AX, ret1+8(FP)
RET
TEXT ·output2(SB), $0-32
MOVQ 8(SP), DX // hardware SP+8(ret address) points to the first input argument
MOVQ DX, ret3+24(FP)
MOVQ argx+8(SP), BX // virtual SP+8(ret address) points to the first input argument
MOVQ BX, ret2+16(FP)
MOVQ arg0+0(FP), AX // first argument
MOVQ AX, ret1+8(FP)
RET Put them in same dir, |
Thanks for the extra digging @cch123, it helps a lot.
As far as I'm concerned, stating that the documentation isn't outdated, but rather that it's just that the standard tools don't quite follow it anymore is misleading at best... For example, the following code: func add(a, b int) int { return a + b } when compiled down to 0x0000 00000 TEXT "".add(SB), LEAF, $-8-24
0x0000 00000 MOVD 16(g), R1
0x0004 00004 MOVD RSP, R2
0x0008 00008 CMP R1, R2
0x000c 00012 BLS 36
0x0010 00016 FUNCDATA ZR, gclocals·54241e171da8af6ae173d69da0236748(SB)
0x0010 00016 FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0010 00016 MOVD "".a(FP), R0
0x0014 00020 MOVD "".b+8(FP), R1
0x0018 00024 ADD R1, R0, R0
0x001c 00028 MOVD R0, "".~r2+16(FP)
0x0020 00032 RET (R30)
0x0024 00036 NOP
0x0024 00036 PCDATA $0, $-1
0x0024 00036 MOVD R30, R3
0x0028 00040 CALL runtime.morestack_noctxt(SB)
0x002c 00044 JMP 0 still clearly uses the virtual frame-pointer in its assembly output; even though Keith Randall mentions that "The FP pseudoreg has been dropped from assembly output" in the discussion that you've linked. I get your point though, and you're right. And this discussion that you've linked to clearly sheds a lot of light on all this confusion. Particularly troubling is this excerpt from the documentation, which has been one of the heaviest point of contention for me:
Clearly the assembly output outright ignores this convention, as can be demonstrated in the following code: 0x0000 00000 TEXT "".add(SB), NOSPLIT, $16-24
0x0000 00000 SUBQ $16, SP
0x0004 00004 MOVQ BP, 8(SP)
0x0009 00009 LEAQ 8(SP), BP
0x000e 00014 FUNCDATA $0, gclocals·54241e171da8af6ae173d69da0236748(SB)
0x000e 00014 FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x000e 00014 MOVQ $10, ""..autotmp_4(SP)
0x0016 00022 MOVQ $10, "".y(SB)
0x0021 00033 MOVQ ""..autotmp_4(SP), AX
0x0025 00037 MOVQ AX, "".~r2+40(SP)
0x002a 00042 MOVQ 8(SP), BP
0x002f 00047 ADDQ $16, SP
0x0033 00051 RET Here 44c150: 48 83 ec 10 sub $0x10,%rsp
44c154: 48 89 6c 24 08 mov %rbp,0x8(%rsp)
44c159: 48 8d 6c 24 08 lea 0x8(%rsp),%rbp ;; 8(SP), no adjustments
44c15e: 48 c7 04 24 0a 00 00 movq $0xa,(%rsp)
44c165: 00
44c166: 48 c7 05 97 67 07 00 movq $0xa,0x76797(%rip)
44c16d: 0a 00 00 00
44c171: 48 8b 04 24 mov (%rsp),%rax
44c175: 48 89 44 24 28 mov %rax,0x28(%rsp) ;; "".~r2+40(SP), no adjustments
44c17a: 48 8b 6c 24 08 mov 0x8(%rsp),%rbp
44c17f: 48 83 c4 10 add $0x10,%rsp That's dangerously misleading, imho. Of course, now that we know that the standard tools never refer to the virtual Your experiments show without a doubt that those virtual registers are still supported by the compiler and work as expected, which I'm not surprised, otherwise we'd have a lot of broken code on our hands. When I said that these registers don't exist, that's what I meant: that they are never used by the standard tools ( It seems like the rules regarding which platforms use these virtual registers or not in their outputs are purely arbitrary and, AFAIK, undocumented. Anyway, I've got a much clearer picture of the whole situation now, thanks to you. Kudos. |
(I've edited my earlier comment.) |
Thank you!! This has been driving me nuts! I just posted about it in Gopher Slack https://gophers.slack.com/archives/C029RQSEE/p1644720012635449. I'm trying to get the diagrams for https://github.com/akutz/go-interface-values/blob/main/docs/02-interface-values/09-on-the-stack.md correct, and I've been stuck on this issue for days!! Thank you again! |
腾讯公司友情提醒:机主暂时奔出服务区、、、呵呵
|
The asm code in this chapter is generated by the compiler, I think Go compiler(1.10) will not generate code that refer to virtual register SP, it should be the hardware SP.
The text was updated successfully, but these errors were encountered: