Skip to content

Commit

Permalink
internal/abi: define EmptyInterface, TypeOf, and NoEscape
Browse files Browse the repository at this point in the history
This change defines two commonly-defined functions and a
commonly-defined type in internal/abi to try and deduplicate some
definitions. This is motivated by a follow-up CL which will want access
to TypeOf in yet another package.

There still exist duplicate definitions of all three of these things in
the runtime, and this CL doesn't try to handle that yet. There are far
too many uses in the runtime to handle manually in a way that feels
comfortable; automated refactoring will help.

For #62483.

Change-Id: I02fc64a28f11af618f6071f94d27f45c135fa8ac
Reviewed-on: https://go-review.googlesource.com/c/go/+/573955
Auto-Submit: Michael Knyszek <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: David Chase <[email protected]>
  • Loading branch information
mknyszek authored and gopherbot committed Apr 17, 2024
1 parent 2073b35 commit 4742c52
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 65 deletions.
22 changes: 22 additions & 0 deletions src/internal/abi/escape.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package abi

import "unsafe"

// NoEscape hides the pointer p from escape analysis, preventing it
// from escaping to the heap. It compiles down to nothing.
//
// WARNING: This is very subtle to use correctly. The caller must
// ensure that it's truly safe for p to not escape to the heap by
// maintaining runtime pointer invariants (for example, that globals
// and the heap may not generally point into a stack).
//
//go:nosplit
//go:nocheckptr
func NoEscape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}
10 changes: 10 additions & 0 deletions src/internal/abi/iface.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

package abi

import "unsafe"

// The first word of every non-empty interface type contains an *ITab.
// It records the underlying concrete type (Type), the interface type it
// is implementing (Inter), and some ancillary information.
Expand All @@ -15,3 +17,11 @@ type ITab struct {
Hash uint32 // copy of Type.Hash. Used for type switches.
Fun [1]uintptr // variable sized. fun[0]==0 means Type does not implement Inter.
}

// EmptyInterface describes the layout of a "interface{}" or a "any."
// These are represented differently than non-empty interface, as the first
// word always points to an abi.Type.
type EmptyInterface struct {
Type *Type
Data unsafe.Pointer
}
11 changes: 11 additions & 0 deletions src/internal/abi/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,17 @@ var kindNames = []string{
UnsafePointer: "unsafe.Pointer",
}

// TypeOf returns the abi.Type of some value.
func TypeOf(a any) *Type {
eface := *(*EmptyInterface)(unsafe.Pointer(&a))
// Types are either static (for compiler-created types) or
// heap-allocated but always reachable (for reflection-created
// types, held in the central map). So there is no need to
// escape types. noescape here help avoid unnecessary escape
// of v.
return (*Type)(NoEscape(unsafe.Pointer(eface.Type)))
}

func (t *Type) Kind() Kind { return t.Kind_ & KindMask }

func (t *Type) HasName() bool {
Expand Down
5 changes: 1 addition & 4 deletions src/internal/reflectlite/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,7 @@ func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
// Noescape so this doesn't make i to escape. See the comment
// at Value.typ for why this is safe.
return toType((*abi.Type)(noescape(unsafe.Pointer(eface.typ))))
return toType(abi.TypeOf(i))
}

func (t rtype) Implements(u Type) bool {
Expand Down
30 changes: 9 additions & 21 deletions src/internal/reflectlite/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (v Value) typ() *abi.Type {
// types, held in the central map). So there is no need to
// escape types. noescape here help avoid unnecessary escape
// of v.
return (*abi.Type)(noescape(unsafe.Pointer(v.typ_)))
return (*abi.Type)(abi.NoEscape(unsafe.Pointer(v.typ_)))
}

// pointer returns the underlying pointer represented by v.
Expand All @@ -113,7 +113,7 @@ func (v Value) pointer() unsafe.Pointer {
func packEface(v Value) any {
t := v.typ()
var i any
e := (*emptyInterface)(unsafe.Pointer(&i))
e := (*abi.EmptyInterface)(unsafe.Pointer(&i))
// First, fill in the data portion of the interface.
switch {
case ifaceIndir(t):
Expand All @@ -127,36 +127,36 @@ func packEface(v Value) any {
typedmemmove(t, c, ptr)
ptr = c
}
e.word = ptr
e.Data = ptr
case v.flag&flagIndir != 0:
// Value is indirect, but interface is direct. We need
// to load the data at v.ptr into the interface data word.
e.word = *(*unsafe.Pointer)(v.ptr)
e.Data = *(*unsafe.Pointer)(v.ptr)
default:
// Value is direct, and so is the interface.
e.word = v.ptr
e.Data = v.ptr
}
// Now, fill in the type portion. We're very careful here not
// to have any operation between the e.word and e.typ assignments
// that would let the garbage collector observe the partially-built
// interface value.
e.typ = t
e.Type = t
return i
}

// unpackEface converts the empty interface i to a Value.
func unpackEface(i any) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
e := (*abi.EmptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
t := e.Type
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
return Value{t, e.Data, f}
}

// A ValueError occurs when a Value method is invoked on
Expand Down Expand Up @@ -185,12 +185,6 @@ func methodName() string {
return f.Name()
}

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *abi.Type
word unsafe.Pointer
}

// mustBeExported panics if f records that the value was obtained using
// an unexported field.
func (f flag) mustBeExported() {
Expand Down Expand Up @@ -482,9 +476,3 @@ var dummy struct {
b bool
x any
}

//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}
8 changes: 2 additions & 6 deletions src/reflect/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -1212,16 +1212,12 @@ func (t *structType) FieldByName(name string) (f StructField, present bool) {
// TypeOf returns the reflection [Type] that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
// Noescape so this doesn't make i to escape. See the comment
// at Value.typ for why this is safe.
return toType((*abi.Type)(noescape(unsafe.Pointer(eface.typ))))
return toType(abi.TypeOf(i))
}

// rtypeOf directly extracts the *rtype of the provided value.
func rtypeOf(i any) *abi.Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return eface.typ
return abi.TypeOf(i)
}

// ptrMap is the cache for PointerTo.
Expand Down
38 changes: 18 additions & 20 deletions src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (v Value) typ() *abi.Type {
// types, held in the central map). So there is no need to
// escape types. noescape here help avoid unnecessary escape
// of v.
return (*abi.Type)(noescape(unsafe.Pointer(v.typ_)))
return (*abi.Type)(abi.NoEscape(unsafe.Pointer(v.typ_)))
}

// pointer returns the underlying pointer represented by v.
Expand All @@ -119,7 +119,7 @@ func (v Value) pointer() unsafe.Pointer {
func packEface(v Value) any {
t := v.typ()
var i any
e := (*emptyInterface)(unsafe.Pointer(&i))
e := (*abi.EmptyInterface)(unsafe.Pointer(&i))
// First, fill in the data portion of the interface.
switch {
case t.IfaceIndir():
Expand All @@ -133,36 +133,36 @@ func packEface(v Value) any {
typedmemmove(t, c, ptr)
ptr = c
}
e.word = ptr
e.Data = ptr
case v.flag&flagIndir != 0:
// Value is indirect, but interface is direct. We need
// to load the data at v.ptr into the interface data word.
e.word = *(*unsafe.Pointer)(v.ptr)
e.Data = *(*unsafe.Pointer)(v.ptr)
default:
// Value is direct, and so is the interface.
e.word = v.ptr
e.Data = v.ptr
}
// Now, fill in the type portion. We're very careful here not
// to have any operation between the e.word and e.typ assignments
// that would let the garbage collector observe the partially-built
// interface value.
e.typ = t
e.Type = t
return i
}

// unpackEface converts the empty interface i to a Value.
func unpackEface(i any) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
e := (*abi.EmptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
t := e.Type
if t == nil {
return Value{}
}
f := flag(t.Kind())
if t.IfaceIndir() {
f |= flagIndir
}
return Value{t, e.word, f}
return Value{t, e.Data, f}
}

// A ValueError occurs when a Value method is invoked on
Expand Down Expand Up @@ -200,12 +200,6 @@ func valueMethodName() string {
return "unknown method"
}

// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *abi.Type
word unsafe.Pointer
}

// nonEmptyInterface is the header for an interface value with methods.
type nonEmptyInterface struct {
itab *abi.ITab
Expand Down Expand Up @@ -1597,7 +1591,7 @@ func (v Value) IsZero() bool {
// v.ptr doesn't escape, as Equal functions are compiler generated
// and never escape. The escape analysis doesn't know, as it is a
// function pointer call.
return typ.Equal(noescape(v.ptr), unsafe.Pointer(&zeroVal[0]))
return typ.Equal(abi.NoEscape(v.ptr), unsafe.Pointer(&zeroVal[0]))
}
if typ.TFlag&abi.TFlagRegularMemory != 0 {
// For some types where the zero value is a value where all bits of this type are 0
Expand All @@ -1623,7 +1617,7 @@ func (v Value) IsZero() bool {
// If the type is comparable, then compare directly with zero.
if typ.Equal != nil && typ.Size() <= abi.ZeroValSize {
// See noescape justification above.
return typ.Equal(noescape(v.ptr), unsafe.Pointer(&zeroVal[0]))
return typ.Equal(abi.NoEscape(v.ptr), unsafe.Pointer(&zeroVal[0]))
}
if typ.TFlag&abi.TFlagRegularMemory != 0 {
// For some types where the zero value is a value where all bits of this type are 0
Expand Down Expand Up @@ -1736,7 +1730,7 @@ func (v Value) SetZero() {
case Slice:
*(*unsafeheader.Slice)(v.ptr) = unsafeheader.Slice{}
case Interface:
*(*emptyInterface)(v.ptr) = emptyInterface{}
*(*abi.EmptyInterface)(v.ptr) = abi.EmptyInterface{}
case Chan, Func, Map, Pointer, UnsafePointer:
*(*unsafe.Pointer)(v.ptr) = nil
case Array, Struct:
Expand Down Expand Up @@ -4015,8 +4009,12 @@ func contentEscapes(x unsafe.Pointer) {
}
}

// This is just a wrapper around abi.NoEscape. The inlining heuristics are
// finnicky and for whatever reason treat the local call to noescape as much
// lower cost with respect to the inliner budget. (That is, replacing calls to
// noescape with abi.NoEscape will cause inlining tests to fail.)
//
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
return abi.NoEscape(p)
}
16 changes: 2 additions & 14 deletions src/strings/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package strings

import (
"internal/abi"
"internal/bytealg"
"unicode/utf8"
"unsafe"
Expand All @@ -22,27 +23,14 @@ type Builder struct {
buf []byte
}

// noescape hides a pointer from escape analysis. It 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.
// USE CAREFULLY!
// This was copied from the runtime; see issues 23382 and 7921.
//
//go:nosplit
//go:nocheckptr
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}

func (b *Builder) copyCheck() {
if b.addr == nil {
// This hack works around a failing of Go's escape analysis
// that was causing b to escape and be heap allocated.
// See issue 23382.
// TODO: once issue 7921 is fixed, this should be reverted to
// just "b.addr = b".
b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
b.addr = (*Builder)(abi.NoEscape(unsafe.Pointer(b)))
} else if b.addr != b {
panic("strings: illegal use of non-zero Builder copied by value")
}
Expand Down

0 comments on commit 4742c52

Please sign in to comment.