The allocation of register parameter spill space is weird.

291 views
Skip to first unread message

Fannie Zhang

unread,
Jul 19, 2021, 6:59:36 AM7/19/21
to golan...@googlegroups.com

Hi all,

 

Refer to the Go internal ABI specification https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md,  the stack frame is shown in the diagram below.

 

The allocation of register parameter spill space looks strange.  In the below example,  before bar() calls add(), bar() needs to spill register-assigned arguments ‘a1’ and ‘a3’ to the spill space on frame stack. According to my understanding, the spill space for ‘a1’ and ‘a2’ should be allocated on bar()’s frame stack. But the current implementation spills the first register-assigned argument ‘a1’ to bar()’s frame stack and spills the second register-assigned argument ‘a3’ to main()’s frame stack. Please see the below assembly codes.

 

// source code.

func main() {

        a := [1]int{40}

        b := [2]int{10, 20}

        c := 30

        d := bar(a, b, c)

        fmt.Println(d)

}

 

//go:noinline

func bar(a1 [1]int, a2 [2]int, a3 int) int {

        var t int

        t += a2[0]

        t += a2[1]

        r := add(t, a3)     // spill register-assigned arguments to stack.

        r += a1[0] + a3

        return r

}

 

//go:noinline

func add(a, b int) int {

        return a+b

}

 

// assembly codes.

The size of bar()’s stack frame is 32 bytes. 

spill ‘a1’ to bar()’s frame stack.spill ‘a3’ to main()’s frame stack.

 

The size of bar()’s stack frame is 32 bytes, I think 16 bytes of it should be allocated for spill space. So my question is why the argument ‘a3’ is spilled to main()’s frame stack not bar()’s frame stack?  Thank you.

 

Best regards,

Fannie Zhang

image008.emz
image010.emz
image012.emz

cherry

unread,
Jul 19, 2021, 11:21:52 AM7/19/21
to Fannie Zhang, golan...@googlegroups.com
Hello Fannie,

In the current implementation, in theory, if an in-register argument needs to be spilled, it spills to the caller's frame, next to the on-stack arguments. So in your example both a1 and a3 _should_ spill to bar's argument area, which is in the caller of bar, i.e. main's frame. However, there is a known bug that it sometimes spills to the wrong place, to its own frame (in your case, bar's frame). It does not affect the correctness of code execution, but it is still good to be fixed (not in Go 1.17).

On Mon, Jul 19, 2021 at 6:59 AM Fannie Zhang <Fannie...@arm.com> wrote:

Hi all,

 

Refer to the Go internal ABI specification https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md,  the stack frame is shown in the diagram below.


By the way, the dev.regabi branch is not active anymore. Please refer to the main branch or the dev.typeparams branch. Thanks.

Cherry

Fannie...@arm.com

unread,
Jul 19, 2021, 10:20:00 PM7/19/21
to golang-dev
On Monday, July 19, 2021 at 11:21:52 PM UTC+8 cherry wrote:
Hello Fannie,

In the current implementation, in theory, if an in-register argument needs to be spilled, it spills to the caller's frame, next to the on-stack arguments. So in your example both a1 and a3 _should_ spill to bar's argument area, which is in the caller of bar, i.e. main's frame. However, there is a known bug that it sometimes spills to the wrong place, to its own frame (in your case, bar's frame). It does not affect the correctness of code execution, but it is still good to be fixed (not in Go 1.17).

Ok, I got it. Thank you for the explanation.
And I am not familiar with the related implementation, but I am happy to look into it and try to fix it. But if you get ahead of me, feel free to send a CL. Thanks. 🙂
 

By the way, the dev.regabi branch is not active anymore. Please refer to the main branch or the dev.typeparams branch. Thanks.
 
The above results are built by Go of main branch. Thank you.

Best regards,
Fannie

David Chase

unread,
Jul 20, 2021, 9:44:58 AM7/20/21
to Fannie...@arm.com, golang-dev
It is not the most annoying register allocation/spill bug we have.

--
You received this message because you are subscribed to the Google Groups "golang-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-dev+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-dev/cb8599b5-3d59-4c22-b857-249e999c0653n%40googlegroups.com.

Fannie...@arm.com

unread,
Jul 20, 2021, 10:07:35 PM7/20/21
to golang-dev
When the above case was changed as below,  the register allocator becomes smarter. The register-assigned arguments 'a1' and 'a3' are spilled to caller's frame. So this bug is related with the value's type? Does register allocator handle different types of values differently? Thank you.
// func bar(a1 [1]int, a2 [2]int, a3 int) {...}
func bar(a1 int, a2 [2]int, a3 int) {
   t := a2[0] + a2[1]
   r := add(t, a3)
   r += a1 a3
}

Best regards,
Fannie

Reply all
Reply to author
Forward
0 new messages