Skip to content

cmd/compile: should not spill Duff-adjusted address #16515

@cherrymui

Description

@cherrymui

What version of Go are you using (go version)?
go version devel +c80e0d3 Wed Jul 27 05:43:36 2016 +0000 darwin/amd64

In SSA, Duff-adjusted address may be spilled and reused if there are more than one Duff calls within the same function. On AMD64, this address is currently spilled as a scalar, which means if the stack is moved, the spilled value is no longer useful. This is demonstrated with the first call of f in the following code:

package main

import "runtime"

type T [62]int

var sink interface{}

//go:noinline
func f(x *T) {
    // Two DUFFZEROs on the same address with a function call in between.
    // Duff-adjusted address will be spilled and loaded

    *x = T{} // DUFFZERO
    runtime.GC()
    (*x)[0] = 1
    g()      // call a function with large frame, trigger a stack move
    *x = T{} // DUFFZERO again
}

//go:noinline
func g() {
    var x [1000]int
    _ = x
}

//go:noinline
func h() {
    var d1 [1152]byte
    sink = &d1 // force heap allocation
    var d2 [1152]byte
    sink = &d2 // force heap allocation
    var d3 [1152]byte
    sink = &d3 // force heap allocation
    var d4 [1152]byte
    sink = &d4 // force heap allocation
    var d5 [1152]byte
    sink = &d5 // force heap allocation
    var d6 [1152]byte
    sink = &d6 // force heap allocation
    var d7 [1152]byte
    sink = &d7 // force heap allocation
    var d8 [1152]byte
    sink = &d8 // force heap allocation
}

func main() {
    var a T // on stack
    a[0] = 2
    f(&a)
    if a[0] != 0 {
        println("a[0] = ", a[0])
        panic("zeroing failed")
    }

    h() // allocate many objects to clear spans

    var s struct { a T; b [1986]int } // allocate 16K, hopefully it's in a new span and a few bytes before it is garbage
    sink = &s // force heap allocation
    s.a[0] = 2
    f(&s.a)
    if s.a[0] != 0 {
        println("s.a[0] =", s.a[0])
        panic("zeroing failed")
    }

}

where f is compiled to

    00000 (/tmp/duff.go:10) TEXT    "".f(SB), $0
    00001 (/tmp/duff.go:10) FUNCDATA    $0, "".gcargs·0(SB)
    00002 (/tmp/duff.go:10) FUNCDATA    $1, "".gclocals·1(SB)
    00003 (/tmp/duff.go:10) TYPE    "".x(FP)type.*"".T, $8
    00004 (/tmp/duff.go:10) TYPE    "".autotmp_9(SP)type.uint64, $8
v21 00005 (/tmp/duff.go:14) MOVQ    "".x(FP), AX
v6  00006 (/tmp/duff.go:14) TESTB   AX, (AX)
v24 00007 (/tmp/duff.go:14) LEAQ    -16(AX), DI
v22 00008 (/tmp/duff.go:14) MOVQ    DI, "".autotmp_9(SP)
v13 00009 (/tmp/duff.go:14) XORPS   X0, X0
v8  00010 (/tmp/duff.go:14) DUFFZERO    $155
v9  00011 (/tmp/duff.go:15) CALL    runtime.GC(SB)
v12 00012 (/tmp/duff.go:15) VARLIVE "".x(FP)
v17 00013 (/tmp/duff.go:16) MOVQ    "".x(FP), AX
v19 00014 (/tmp/duff.go:16) MOVQ    $1, (AX)
v20 00015 (/tmp/duff.go:17) CALL    "".g(SB)
v23 00016 (/tmp/duff.go:17) VARLIVE "".x(FP)
v25 00017 (/tmp/duff.go:18) MOVQ    "".autotmp_9(SP), DI
v27 00018 (/tmp/duff.go:14) XORPS   X0, X0
v26 00019 (/tmp/duff.go:18) DUFFZERO    $155
v28 00020 (/tmp/duff.go:19) VARLIVE "".x(FP)
b6  00021 (/tmp/duff.go:19) RET

Run with SSA on:

a[0] =  1
panic: zeroing failed

goroutine 1 [running]:
panic(0x568e0, 0xc42000a070)
    /Users/cherryyz/src/go-tip/src/runtime/panic.go:500 +0x1a1
main.main()
    /tmp/duff.go:53 +0x25b
exit status 2

(ok running with SSA off)

Note that it does not work if we change the type of the spill to pointer, since Duff-adjusted address (in this case x-16) may not point to valid object, causing stack scanning code to panic (may not always happen):

runtime: pointer 0xc420073ff0 to unused region of spanidx=0x39 span.base()=0xc420072000 span.limit=0xc420073f80 span.state=0
runtime: found in object at *(0xc420033cd8+0x0)
object=0xc420033cd8 k=0x6210019 s.base()=0xc420032000 s.limit=0x0 s.sizeclass=0 s.elemsize=0
fatal error: found bad pointer in Go heap (incorrect use of unsafe or cgo?)

One solution is to do Duff-adjustment in genValue, so reg allocator cannot see the intermediate value.

Found when doing ARM64 port.
/cc @aclements

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions