golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
124k stars 17.67k forks source link

cmd/compile: Inaccurate AuxInt value for newobject call #68631

Open And-ZJ opened 3 months ago

And-ZJ commented 3 months ago

Go version

go version go1.23rc2 linux/arm64

Output of go env in your module/workspace:

Pasted part of content:

GO111MODULE='auto'
GOARCH='arm64'
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='arm64'
GOHOSTOS='linux'
GOOS='linux'
GOROOT='/usr1/GoRelease/go1.23rc2'
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr1/GoRelease/go1.23rc2/pkg/tool/linux_arm64'
GOVERSION='go1.23rc2'
GODEBUG=''
GOTELEMETRY='local'
GOARM64='v8.0'

What did you do?

When I look at the call to runtime.newobject in SSA, it looks like the AuxInt value is inaccurate.

I think It is larger than expected. In addition, the value is larger in ARM64 than in AMD64.

My test code temp.go:

package main

var g1 *int64

//go:noinline
func test1(a *int64) *int64 {
    g1 = a
    return a
}

func main() {
    a := new(int64)
    test1(a)
}

Build, dump SSA and view assembly code:

GOSSAFUNC=main.main go build -a -trimpath temp.go
objdump -rd --disassemble=main.main temp

ARM64(SSA dump in before insert phis phase):

b1:
v1 (?) = InitMem <mem>
v2 (?) = SP <uintptr>
v3 (?) = SB <uintptr>
v4 (?) = Addr <*uint8> {type:int64} v3
v5 (12) = StaticLECall <*int64,mem> {AuxCall{runtime.newobject}} [24] v4 v1
v6 (12) = SelectN <mem> [1] v5
v7 (12) = SelectN <*int64> [0] v5 (a[*int64])
v8 (13) = StaticLECall <*int64,mem> {AuxCall{main.test1}} [8] v7 v6
v9 (13) = SelectN <mem> [1] v8
v10 (13) = SelectN <*int64> [0] v8
v11 (14) = MakeResult <mem> v9
Ret v11 (14)
name a[*int64]: v7

ARM64(assembly code):

0000000000076f70 <main.main>:
   76f70:   f9400b90    ldr x16, [x28, #16]
   76f74:   eb3063ff    cmp sp, x16
   76f78:   54000169    b.ls    76fa4 <main.main+0x34>  // b.plast
   76f7c:   f81d0ffe    str x30, [sp, #-48]!
   76f80:   f81f83fd    stur    x29, [sp, #-8]
   76f84:   d10023fd    sub x29, sp, #0x8
   76f88:   f0000060    adrp    x0, 85000 <type:*+0x5000>
   76f8c:   910e0000    add x0, x0, #0x380
   76f90:   97fe8da8    bl  1a630 <runtime.newobject>
   76f94:   97ffffdf    bl  76f10 <main.test1>
   76f98:   a97ffbfd    ldp x29, x30, [sp, #-8]
   76f9c:   9100c3ff    add sp, sp, #0x30
   76fa0:   d65f03c0    ret
   76fa4:   aa1e03e3    mov x3, x30
   76fa8:   97ffed62    bl  72530 <runtime.morestack_noctxt.abi0>
   76fac:   17fffff1    b   76f70 <main.main>

AMD64(SSA dump in before insert phis phase):

b1:
v1 (?) = InitMem <mem>
v2 (?) = SP <uintptr>
v3 (?) = SB <uintptr>
v4 (?) = Addr <*uint8> {type:int64} v3
v5 (12) = StaticLECall <*int64,mem> {AuxCall{runtime.newobject}} [16] v4 v1
v6 (12) = SelectN <mem> [1] v5
v7 (12) = SelectN <*int64> [0] v5 (a[*int64])
v8 (13) = StaticLECall <*int64,mem> {AuxCall{main.test1}} [8] v7 v6
v9 (13) = SelectN <mem> [1] v8
v10 (13) = SelectN <*int64> [0] v8
v11 (14) = MakeResult <mem> v9
Ret v11 (14)
name a[*int64]: v7

AMD64(assembly code):

0000000000466820 <main.main>:
  466820:   49 3b 66 10             cmp    0x10(%r14),%rsp
  466824:   76 1f                   jbe    466845 <main.main+0x25>
  466826:   55                      push   %rbp
  466827:   48 89 e5                mov    %rsp,%rbp
  46682a:   48 83 ec 10             sub    $0x10,%rsp
  46682e:   48 8d 05 4b 5b 00 00    lea    0x5b4b(%rip),%rax        # 46c380 <type:*+0x5380>
  466835:   e8 a6 4a fa ff          callq  40b2e0 <runtime.newobject>
  46683a:   e8 81 ff ff ff          callq  4667c0 <main.test1>
  46683f:   48 83 c4 10             add    $0x10,%rsp
  466843:   5d                      pop    %rbp
  466844:   c3                      retq   
  466845:   e8 b6 b0 ff ff          callq  461900 <runtime.morestack_noctxt.abi0>
  46684a:   eb d4                   jmp    466820 <main.main>

In ARM64: The newobject's AuxInt is 24. The test1's AuxInt is 8. Note the test1 function that has only one input parameter and one return value. The stack space of the main function uses 48 bytes.

In AMD64: The newobject's AuxInt is 16. The test1's AuxInt is 8. The stack space of the main function uses 24 bytes (0x10 + 8).

The StaticLECall in https://github.com/golang/go/blob/30b6fd60a63c738c2736e83b6a6886a032e6f269/src/cmd/compile/internal/ssa/_gen/genericOps.go#L436

Here, AuxInt indicates the size of the input parameter, but it is not specified whether the mem parameter is included. It is suspected that the mem parameter is not included.

The auxCallOff in https://github.com/golang/go/blob/30b6fd60a63c738c2736e83b6a6886a032e6f269/src/cmd/compile/internal/ssa/op.go#L365

Here, AuxInt indicates includes the size of the input and output parameters?

The runtime call newobject may emit at (*state).newObject at https://github.com/golang/go/blob/30b6fd60a63c738c2736e83b6a6886a032e6f269/src/cmd/compile/internal/ssagen/ssa.go#L713

In this way, it call (*state).rtcall at https://github.com/golang/go/blob/30b6fd60a63c738c2736e83b6a6886a032e6f269/src/cmd/compile/internal/ssagen/ssa.go#L5941

AuxInt appears to be the sum of the size of the input and output parameters and FixedFrameSize.

The common call test1 may emit at (*state).call at https://github.com/golang/go/blob/30b6fd60a63c738c2736e83b6a6886a032e6f269/src/cmd/compile/internal/ssagen/ssa.go#L5353

In this way, AuxInt mostly equal to (*ABIParamResultInfo).ArgWidth(), This should be the size of the stack space to be used when calling. The space includes the spill space of the register transfer parameter, and the space on the stack of the input and output parameters of the stack transfer parameter.

So, I've got a lot of explanations for AuxInt of this situation and I'm confused.

From the main function assembly, I think the space used on the stack is larger than what is actually needed.

image

The gray areas that are marked here are what I consider to be unused space.

For other functions that use the (*state).rtcall, the situation may be similar.

This should have happened in previous versions of go.

I haven't been able to find out if anyone else has had the same problem.

Thank you for your reading.

My understanding may be incorrect, please point it out, thanks.

What did you see happen?

The caller's stack space used by newobject is larger than my expected. It's AuxInt is inaccurate?

What did you expect to see?

More precise AuxInt of newobject and other runtime calls?

gabyhelp commented 3 months ago

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

cherrymui commented 3 months ago

It seems the auxint is computed pre-register-ABI in ssagen/ssa.go. For the stack ABI (ABI0), this would be correct. With register ABI, it can be smaller.

cc @dr2chase

mknyszek commented 3 months ago

Is this an issue that's new to the current release? (Is it observable in older releases?) For now, putting in Backlog under the assumption that it's from an older release.

cherrymui commented 3 months ago

I don't think it's new.

And-ZJ commented 3 months ago

Hello, Thanks for the reply. This problem should be existed in previous versions of go. I checked 1.22 and 1.20 under ARM64, AuxInt for newobject is 24, AuxInt for test1 is 8.