Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add gohacks.Slice/StringHeader.
See golang/go#19367 for rationale. Note that the
upstream decision arrived at in that thread, while useful for some of our use
cases, doesn't account for all of our SliceHeader use cases (we often use
SliceHeader to extract pointers from slices in a way that avoids bounds
checking and/or handles nil slices correctly) and also doesn't exist yet.

PiperOrigin-RevId: 350259679
  • Loading branch information
nixprime authored and gvisor-bot committed Feb 17, 2021
1 parent 2f35fa1 commit 2a070c8
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 62 deletions.
7 changes: 6 additions & 1 deletion pkg/fdchannel/BUILD
Expand Up @@ -6,12 +6,17 @@ go_library(
name = "fdchannel",
srcs = ["fdchannel_unsafe.go"],
visibility = ["//visibility:public"],
deps = [
"//pkg/gohacks",
],
)

go_test(
name = "fdchannel_test",
size = "small",
srcs = ["fdchannel_test.go"],
library = ":fdchannel",
deps = ["//pkg/sync"],
deps = [
"//pkg/sync",
],
)
9 changes: 5 additions & 4 deletions pkg/fdchannel/fdchannel_unsafe.go
Expand Up @@ -20,9 +20,10 @@ package fdchannel

import (
"fmt"
"reflect"
"syscall"
"unsafe"

"gvisor.dev/gvisor/pkg/gohacks"
)

// int32 is the real type of a file descriptor.
Expand Down Expand Up @@ -53,10 +54,10 @@ func (ep *Endpoint) Init(sockfd int) {
// sendmsg+recvmsg for a zero-length datagram is slightly faster than
// sendmsg+recvmsg for a single byte over a stream socket.
cmsgSlice := make([]byte, syscall.CmsgSpace(sizeofInt32))
cmsgReflect := (*reflect.SliceHeader)(unsafe.Pointer(&cmsgSlice))
cmsgSliceHdr := (*gohacks.SliceHeader)(unsafe.Pointer(&cmsgSlice))
ep.sockfd = int32(sockfd)
ep.msghdr.Control = (*byte)(unsafe.Pointer(cmsgReflect.Data))
ep.cmsg = (*syscall.Cmsghdr)(unsafe.Pointer(cmsgReflect.Data))
ep.msghdr.Control = (*byte)(cmsgSliceHdr.Data)
ep.cmsg = (*syscall.Cmsghdr)(cmsgSliceHdr.Data)
// ep.msghdr.Controllen and ep.cmsg.* are mutated by recvmsg(2), so they're
// set before calling sendmsg/recvmsg.
}
Expand Down
13 changes: 6 additions & 7 deletions pkg/flipcall/flipcall_unsafe.go
Expand Up @@ -61,13 +61,12 @@ func (ep *Endpoint) dataLen() *uint32 {
// - Writers must not assume that they will read back the same data that they
// have written. In other words, writers should avoid reading from Data() at
// all.
func (ep *Endpoint) Data() []byte {
var bs []byte
bsReflect := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
bsReflect.Data = ep.packet + PacketHeaderBytes
bsReflect.Len = int(ep.dataCap)
bsReflect.Cap = int(ep.dataCap)
return bs
func (ep *Endpoint) Data() (bs []byte) {
bshdr := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
bshdr.Data = ep.packet + PacketHeaderBytes
bshdr.Len = int(ep.dataCap)
bshdr.Cap = int(ep.dataCap)
return
}

// ioSync is a dummy variable used to indicate synchronization to the Go race
Expand Down
38 changes: 29 additions & 9 deletions pkg/gohacks/gohacks_unsafe.go
Expand Up @@ -12,14 +12,35 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// +build go1.13
// +build !go1.17

// Check type signatures when updating Go version.

// Package gohacks contains utilities for subverting the Go compiler.
package gohacks

import (
"reflect"
"unsafe"
)

// SliceHeader is equivalent to reflect.SliceHeader, but represents the pointer
// to the underlying array as unsafe.Pointer rather than uintptr, allowing
// SliceHeaders to be directly converted to slice objects.
type SliceHeader struct {
Data unsafe.Pointer
Len int
Cap int
}

// StringHeader is equivalent to reflect.StringHeader, but represents the
// pointer to the underlying array as unsafe.Pointer rather than uintptr,
// allowing StringHeaders to be directly converted to strings.
type StringHeader struct {
Data unsafe.Pointer
Len int
}

// Noescape hides a pointer from escape analysis. Noescape is the identity
// function but escape analysis doesn't think the output depends on the input.
// Noescape is inlined and currently compiles down to zero instructions.
Expand All @@ -36,22 +57,21 @@ func Noescape(p unsafe.Pointer) unsafe.Pointer {
// ImmutableBytesFromString is equivalent to []byte(s), except that it uses the
// same memory backing s instead of making a heap-allocated copy. This is only
// valid if the returned slice is never mutated.
func ImmutableBytesFromString(s string) []byte {
shdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
var bs []byte
bshdr := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
func ImmutableBytesFromString(s string) (bs []byte) {
shdr := (*StringHeader)(unsafe.Pointer(&s))
bshdr := (*SliceHeader)(unsafe.Pointer(&bs))
bshdr.Data = shdr.Data
bshdr.Len = shdr.Len
bshdr.Cap = shdr.Len
return bs
return
}

// StringFromImmutableBytes is equivalent to string(bs), except that it uses
// the same memory backing bs instead of making a heap-allocated copy. This is
// only valid if bs is never mutated after StringFromImmutableBytes returns.
func StringFromImmutableBytes(bs []byte) string {
// This is cheaper than messing with reflect.StringHeader and
// reflect.SliceHeader, which as of this writing produces many dead stores
// of zeroes. Compare strings.Builder.String().
// This is cheaper than messing with StringHeader and SliceHeader, which as
// of this writing produces many dead stores of zeroes. Compare
// strings.Builder.String().
return *(*string)(unsafe.Pointer(&bs))
}
5 changes: 4 additions & 1 deletion pkg/safemem/BUILD
Expand Up @@ -11,7 +11,10 @@ go_library(
"seq_unsafe.go",
],
visibility = ["//:sandbox"],
deps = ["//pkg/safecopy"],
deps = [
"//pkg/gohacks",
"//pkg/safecopy",
],
)

go_test(
Expand Down
13 changes: 6 additions & 7 deletions pkg/safemem/block_unsafe.go
Expand Up @@ -16,9 +16,9 @@ package safemem

import (
"fmt"
"reflect"
"unsafe"

"gvisor.dev/gvisor/pkg/gohacks"
"gvisor.dev/gvisor/pkg/safecopy"
)

Expand Down Expand Up @@ -148,12 +148,11 @@ func (b Block) TakeFirst64(n uint64) Block {

// ToSlice returns a []byte equivalent to b.
func (b Block) ToSlice() []byte {
var bs []byte
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&bs))
hdr.Data = uintptr(b.start)
hdr.Len = b.length
hdr.Cap = b.length
return bs
return *(*[]byte)(unsafe.Pointer(&gohacks.SliceHeader{
Data: b.start,
Len: b.length,
Cap: b.length,
}))
}

// Addr returns b's start address as a uintptr. It returns uintptr instead of
Expand Down
7 changes: 4 additions & 3 deletions pkg/safemem/seq_unsafe.go
Expand Up @@ -17,9 +17,10 @@ package safemem
import (
"bytes"
"fmt"
"reflect"
"syscall"
"unsafe"

"gvisor.dev/gvisor/pkg/gohacks"
)

// A BlockSeq represents a sequence of Blocks, each of which has non-zero
Expand Down Expand Up @@ -184,8 +185,8 @@ func (bs BlockSeq) Tail() BlockSeq {
return BlockSeq{}
}
var extSlice []Block
extSliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(&extSlice))
extSliceHdr.Data = uintptr(bs.data)
extSliceHdr := (*gohacks.SliceHeader)(unsafe.Pointer(&extSlice))
extSliceHdr.Data = bs.data
extSliceHdr.Len = bs.length
extSliceHdr.Cap = bs.length
tailSlice := skipEmpty(extSlice[1:])
Expand Down
23 changes: 4 additions & 19 deletions pkg/sentry/arch/stack_unsafe.go
Expand Up @@ -15,8 +15,6 @@
package arch

import (
"reflect"
"runtime"
"unsafe"

"gvisor.dev/gvisor/pkg/marshal/primitive"
Expand All @@ -33,35 +31,22 @@ import (
// On error, the contents of the stack and the bottom cursor are undefined.
func (s *Stack) pushAddrSliceAndTerminator(src []usermem.Addr) (int, error) {
// Note: Stack grows upwards, so push the terminator first.
srcHdr := (*reflect.SliceHeader)(unsafe.Pointer(&src))
switch s.Arch.Width() {
case 8:
nNull, err := primitive.CopyUint64Out(s, StackBottomMagic, 0)
if err != nil {
return 0, err
}
var dst []uint64
dstHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dst))
dstHdr.Data = srcHdr.Data
dstHdr.Len = srcHdr.Len
dstHdr.Cap = srcHdr.Cap
n, err := primitive.CopyUint64SliceOut(s, StackBottomMagic, dst)
// Ensures src doesn't get GCed until we're done using it through dst.
runtime.KeepAlive(src)
srcAsUint64 := *(*[]uint64)(unsafe.Pointer(&src))
n, err := primitive.CopyUint64SliceOut(s, StackBottomMagic, srcAsUint64)
return n + nNull, err
case 4:
nNull, err := primitive.CopyUint32Out(s, StackBottomMagic, 0)
if err != nil {
return 0, err
}
var dst []uint32
dstHdr := (*reflect.SliceHeader)(unsafe.Pointer(&dst))
dstHdr.Data = srcHdr.Data
dstHdr.Len = srcHdr.Len
dstHdr.Cap = srcHdr.Cap
n, err := primitive.CopyUint32SliceOut(s, StackBottomMagic, dst)
// Ensure src doesn't get GCed until we're done using it through dst.
runtime.KeepAlive(src)
srcAsUint32 := *(*[]uint32)(unsafe.Pointer(&src))
n, err := primitive.CopyUint32SliceOut(s, StackBottomMagic, srcAsUint32)
return n + nNull, err
default:
panic("Unsupported arch width")
Expand Down
5 changes: 2 additions & 3 deletions pkg/sentry/vfs/mount_unsafe.go
Expand Up @@ -17,7 +17,6 @@ package vfs
import (
"fmt"
"math/bits"
"reflect"
"sync/atomic"
"unsafe"

Expand Down Expand Up @@ -153,8 +152,8 @@ func (mt *mountTable) Init() {

func newMountTableSlots(cap uintptr) unsafe.Pointer {
slice := make([]mountSlot, cap, cap)
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&slice))
return unsafe.Pointer(hdr.Data)
hdr := (*gohacks.SliceHeader)(unsafe.Pointer(&slice))
return hdr.Data
}

// Lookup returns the Mount with the given parent, mounted at the given point.
Expand Down
7 changes: 2 additions & 5 deletions pkg/sync/generic_atomicptrmap_unsafe.go
Expand Up @@ -17,8 +17,6 @@
package atomicptrmap

import (
"reflect"
"runtime"
"sync/atomic"
"unsafe"

Expand Down Expand Up @@ -372,9 +370,8 @@ func (shard *apmShard) rehash(oldSlots unsafe.Pointer) {

// Allocate the new table.
newSlotsSlice := make([]apmSlot, newSize)
newSlotsReflect := (*reflect.SliceHeader)(unsafe.Pointer(&newSlotsSlice))
newSlots := unsafe.Pointer(newSlotsReflect.Data)
runtime.KeepAlive(newSlotsSlice)
newSlotsHeader := (*gohacks.SliceHeader)(unsafe.Pointer(&newSlotsSlice))
newSlots := newSlotsHeader.Data
newMask := newSize - 1

// Start a writer critical section now so that racing users of the old
Expand Down
7 changes: 4 additions & 3 deletions pkg/usermem/addr_range_seq_unsafe.go
Expand Up @@ -17,8 +17,9 @@ package usermem
import (
"bytes"
"fmt"
"reflect"
"unsafe"

"gvisor.dev/gvisor/pkg/gohacks"
)

// An AddrRangeSeq represents a sequence of AddrRanges.
Expand Down Expand Up @@ -163,8 +164,8 @@ func (ars AddrRangeSeq) externalTail() AddrRangeSeq {
tailLimit = int64(ars.limit - headLen)
}
var extSlice []AddrRange
extSliceHdr := (*reflect.SliceHeader)(unsafe.Pointer(&extSlice))
extSliceHdr.Data = uintptr(ars.data)
extSliceHdr := (*gohacks.SliceHeader)(unsafe.Pointer(&extSlice))
extSliceHdr.Data = ars.data
extSliceHdr.Len = ars.length
extSliceHdr.Cap = ars.length
return addrRangeSeqFromSliceLimited(extSlice[1:], tailLimit)
Expand Down

0 comments on commit 2a070c8

Please sign in to comment.