Why there are eight bytes pad in the stack frame of main function?

已查看 195 次
跳至第一个未读帖子

tarorual

未读,
2022年12月19日 14:31:472022/12/19
收件人 golang-nuts
package main

func main() {
  var x int32 = 1
  nop(x)
}

//go:noinline
func nop(x int32) {}

Go version: go1.16.15 windows/amd64

I wrote above code and compiled it into assembly with `go1.16.15 tool compile -S -N -l main.go` for truly understanding Go functions call.

The following is the assembly code of nop function:

"".nop STEXT nosplit size=1 args=0x8 locals=0x0 funcid=0x0
        0x0000 00000 (main.go:9)        TEXT    "".nop(SB), NOSPLIT|ABIInternal, $0-8
        0x0000 00000 (main.go:9)        FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:9)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0000 00000 (main.go:9)        RET


Obviously, the nop function returns directly without doing anything. It only has a `int32` type parameter, but we can see the `argsize` is 8 bytes in the assembly:

TEXT    "".nop(SB), NOSPLIT|ABIInternal, $0-8

Question1: Why the `argsize` is 8 bytes not 4 bytes?

The following is the assembly code of main function:

"".main STEXT size=73 args=0x0 locals=0x18 funcid=0x0
        0x0000 00000 (main.go:3)        TEXT    "".main(SB), ABIInternal, $24-0
        0x0000 00000 (main.go:3)        MOVQ    TLS, CX
        0x0009 00009 (main.go:3)        PCDATA  $0, $-2
        0x0009 00009 (main.go:3)        MOVQ    (CX)(TLS*2), CX
        0x0010 00016 (main.go:3)        PCDATA  $0, $-1
        0x0010 00016 (main.go:3)        CMPQ    SP, 16(CX)
        0x0014 00020 (main.go:3)        PCDATA  $0, $-2
        0x0014 00020 (main.go:3)        JLS     66
        0x0016 00022 (main.go:3)        PCDATA  $0, $-1
        0x0016 00022 (main.go:3)        SUBQ    $24, SP
        0x001a 00026 (main.go:3)        MOVQ    BP, 16(SP)
        0x001f 00031 (main.go:3)        LEAQ    16(SP), BP
        0x0024 00036 (main.go:3)        FUNCDATA        $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0024 00036 (main.go:3)        FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x0024 00036 (main.go:4)        MOVL    $1, "".x+12(SP)
        0x002c 00044 (main.go:5)        MOVL    $1, (SP)
        0x0033 00051 (main.go:5)        PCDATA  $1, $0
        0x0033 00051 (main.go:5)        CALL    "".nop(SB)
        0x0038 00056 (main.go:6)        MOVQ    16(SP), BP
        0x003d 00061 (main.go:6)        ADDQ    $24, SP
        0x0041 00065 (main.go:6)        RET


I drawn a picture of stack according to the assembly code:
https://i.stack.imgur.com/AHN1b.png

Question2: I find there are confusing 8 bytes in the stack, why do these 8 bytes exist?

Question3:  Why MOVL    $1, "".x+12(SP)but not  MOVL    $1, "".x-12(SP)?

I think memory alignment causes the above phenomenon, but I'm not sure.

Keith Randall

未读,
2022年12月19日 18:46:282022/12/19
收件人 golang-nuts
Q1: stack frames are rounded to a multiple of 8 bytes, to keep the stack pointer 8-byte aligned. Part of that is rounding argsize to 8 bytes. (It's not strictly necessary, and we could report 4, I think, if there are no return values.)
Q2: I think 4 are from rounding argsize up to 8 bytes, and 4 are padding to keep the total frame a multiple of 8 bytes.
Q3: offsets are from the updated stack pointer (the one generated by the SUBQ $24, SP instruction). So they will be positive.

tarorual

未读,
2022年12月21日 04:33:082022/12/21
收件人 golang-nuts
Q2: I agree with the statement "4 are from rounding argsize up to 8 bytes". According to the Go internal ABI specification (although it is published for Go1.17 and later version, I think it has some reference value), there is a pointer-sized (8 bytes in amd64) alignments after stack-assigned arguments, so there are 4 bytes pad after the `int32` argument.
I don't understand why other 4 bytes are padded before x, not after x: a possible padding.png. It is more reasonable to pad after x, I think :)

Q3: According to the official doc A Quick Guide to Go's Assembler, I think the SP in  MOVL    $1, "".x+12(SP) is "pseudo SP":

The SP pseudo-register is a virtual stack pointer used to refer to frame-local variables and the arguments being prepared for function calls. It points to the highest address within the local stack frame, so references should use negative offsets in the range [−framesize, 0): x-8(SP), y-4(SP), and so on.

On architectures with a hardware register named SP, the name prefix distinguishes references to the virtual stack pointer from references to the architectural SP register. That is, x-8(SP) and -8(SP) are different memory locations: the first refers to the virtual stack pointer pseudo-register, while the second refers to the hardware's SP register.

回复全部
回复作者
转发
0 个新帖子