Skip to content

Commit

Permalink
cmd/compile: don't allow go:notinheap on the heap or stack
Browse files Browse the repository at this point in the history
Right now we just prevent such types from being on the heap. This CL
makes it so they cannot appear on the stack either. The distinction
between heap and stack is pretty vague at the language level (e.g. it
is affected by -N), and we don't need the flexibility anyway.

Once go:notinheap types cannot be in either place, we don't need to
consider pointers to such types to be pointers, at least according to
the garbage collector and stack copying. (This is the big win of this
CL, in my opinion.)

The distinction between HasPointers and HasHeapPointer no longer
exists. There is only HasPointers.

This CL is cleanup before possible use of go:notinheap to fix #40954.

Update #13386

Change-Id: Ibd895aadf001c0385078a6d4809c3f374991231a
Reviewed-on: https://go-review.googlesource.com/c/go/+/249917
Run-TryBot: Keith Randall <[email protected]>
TryBot-Result: Gobot Gobot <[email protected]>
Reviewed-by: Matthew Dempsky <[email protected]>
Reviewed-by: Cuong Manh Le <[email protected]>
  • Loading branch information
randall77 committed Aug 25, 2020
1 parent 95df156 commit d9a6bdf
Show file tree
Hide file tree
Showing 16 changed files with 54 additions and 68 deletions.
3 changes: 3 additions & 0 deletions src/cmd/compile/internal/gc/escape.go
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,9 @@ func (e *Escape) newLoc(n *Node, transient bool) *EscLocation {
if e.curfn == nil {
Fatalf("e.curfn isn't set")
}
if n != nil && n.Type != nil && n.Type.NotInHeap() {
yyerrorl(n.Pos, "%v is go:notinheap; stack allocation disallowed", n.Type)
}

n = canonicalNode(n)
loc := &EscLocation{
Expand Down
10 changes: 1 addition & 9 deletions src/cmd/compile/internal/gc/pgen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func typeWithoutPointers() *types.Type {

func typeWithPointers() *types.Type {
t := types.New(TSTRUCT)
f := &types.Field{Type: types.New(TPTR)}
f := &types.Field{Type: types.NewPtr(types.New(TINT))}
t.SetFields([]*types.Field{f})
return t
}
Expand Down Expand Up @@ -181,14 +181,6 @@ func TestStackvarSort(t *testing.T) {
nodeWithClass(Node{Type: &types.Type{}, Sym: &types.Sym{Name: "xyz"}}, PAUTO),
nodeWithClass(Node{Type: typeWithoutPointers(), Sym: &types.Sym{}}, PAUTO),
}
// haspointers updates Type.Haspointers as a side effect, so
// exercise this function on all inputs so that reflect.DeepEqual
// doesn't produce false positives.
for i := range want {
want[i].Type.HasPointers()
inp[i].Type.HasPointers()
}

sort.Sort(byStackVar(inp))
if !reflect.DeepEqual(want, inp) {
t.Error("sort failed")
Expand Down
14 changes: 7 additions & 7 deletions src/cmd/compile/internal/gc/plive.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ func (lv *Liveness) regEffects(v *ssa.Value) (uevar, kill liveRegMask) {
case ssa.LocalSlot:
return mask
case *ssa.Register:
if ptrOnly && !v.Type.HasHeapPointer() {
if ptrOnly && !v.Type.HasPointers() {
return mask
}
regs[0] = loc
Expand All @@ -451,7 +451,7 @@ func (lv *Liveness) regEffects(v *ssa.Value) (uevar, kill liveRegMask) {
if loc1 == nil {
continue
}
if ptrOnly && !v.Type.FieldType(i).HasHeapPointer() {
if ptrOnly && !v.Type.FieldType(i).HasPointers() {
continue
}
regs[nreg] = loc1.(*ssa.Register)
Expand Down Expand Up @@ -568,13 +568,13 @@ func onebitwalktype1(t *types.Type, off int64, bv bvec) {
if t.Align > 0 && off&int64(t.Align-1) != 0 {
Fatalf("onebitwalktype1: invalid initial alignment: type %v has alignment %d, but offset is %v", t, t.Align, off)
}
if !t.HasPointers() {
// Note: this case ensures that pointers to go:notinheap types
// are not considered pointers by garbage collection and stack copying.
return
}

switch t.Etype {
case TINT8, TUINT8, TINT16, TUINT16,
TINT32, TUINT32, TINT64, TUINT64,
TINT, TUINT, TUINTPTR, TBOOL,
TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128:

case TPTR, TUNSAFEPTR, TFUNC, TCHAN, TMAP:
if off&int64(Widthptr-1) != 0 {
Fatalf("onebitwalktype1: invalid alignment, %v", t)
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/gc/range.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ func arrayClear(n, v1, v2, a *Node) bool {
n.Nbody.Append(nod(OAS, hn, tmp))

var fn *Node
if a.Type.Elem().HasHeapPointer() {
if a.Type.Elem().HasPointers() {
// memclrHasPointers(hp, hn)
Curfn.Func.setWBPos(stmt.Pos)
fn = mkcall("memclrHasPointers", nil, nil, hp, hn)
Expand Down
23 changes: 11 additions & 12 deletions src/cmd/compile/internal/gc/walk.go
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,9 @@ opswitch:
}

case ONEW:
if n.Type.Elem().NotInHeap() {
yyerror("%v is go:notinheap; heap allocation disallowed", n.Type.Elem())
}
if n.Esc == EscNone {
if n.Type.Elem().Width >= maxImplicitStackVarSize {
Fatalf("large ONEW with EscNone: %v", n)
Expand Down Expand Up @@ -1324,6 +1327,9 @@ opswitch:
l = r
}
t := n.Type
if t.Elem().NotInHeap() {
yyerror("%v is go:notinheap; heap allocation disallowed", t.Elem())
}
if n.Esc == EscNone {
if !isSmallMakeSlice(n) {
Fatalf("non-small OMAKESLICE with EscNone: %v", n)
Expand Down Expand Up @@ -1365,10 +1371,6 @@ opswitch:
// When len and cap can fit into int, use makeslice instead of
// makeslice64, which is faster and shorter on 32 bit platforms.

if t.Elem().NotInHeap() {
yyerror("%v is go:notinheap; heap allocation disallowed", t.Elem())
}

len, cap := l, r

fnname := "makeslice64"
Expand Down Expand Up @@ -1403,7 +1405,7 @@ opswitch:

t := n.Type
if t.Elem().NotInHeap() {
Fatalf("%v is go:notinheap; heap allocation disallowed", t.Elem())
yyerror("%v is go:notinheap; heap allocation disallowed", t.Elem())
}

length := conv(n.Left, types.Types[TINT])
Expand Down Expand Up @@ -2012,9 +2014,6 @@ func walkprint(nn *Node, init *Nodes) *Node {
}

func callnew(t *types.Type) *Node {
if t.NotInHeap() {
yyerror("%v is go:notinheap; heap allocation disallowed", t)
}
dowidth(t)
n := nod(ONEWOBJ, typename(t), nil)
n.Type = types.NewPtr(t)
Expand Down Expand Up @@ -2589,15 +2588,15 @@ func mapfast(t *types.Type) int {
}
switch algtype(t.Key()) {
case AMEM32:
if !t.Key().HasHeapPointer() {
if !t.Key().HasPointers() {
return mapfast32
}
if Widthptr == 4 {
return mapfast32ptr
}
Fatalf("small pointer %v", t.Key())
case AMEM64:
if !t.Key().HasHeapPointer() {
if !t.Key().HasPointers() {
return mapfast64
}
if Widthptr == 8 {
Expand Down Expand Up @@ -2744,7 +2743,7 @@ func appendslice(n *Node, init *Nodes) *Node {
nodes.Append(nod(OAS, s, nt))

var ncopy *Node
if elemtype.HasHeapPointer() {
if elemtype.HasPointers() {
// copy(s[len(l1):], l2)
nptr1 := nod(OSLICE, s, nil)
nptr1.Type = s.Type
Expand Down Expand Up @@ -3082,7 +3081,7 @@ func walkappend(n *Node, init *Nodes, dst *Node) *Node {
// Also works if b is a string.
//
func copyany(n *Node, init *Nodes, runtimecall bool) *Node {
if n.Left.Type.Elem().HasHeapPointer() {
if n.Left.Type.Elem().HasPointers() {
Curfn.Func.setWBPos(n.Pos)
fn := writebarrierfn("typedslicecopy", n.Left.Type.Elem(), n.Right.Type.Elem())
n.Left = cheapexpr(n.Left, init)
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/ssa/decompose.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func decomposeStringPhi(v *Value) {

func decomposeSlicePhi(v *Value) {
types := &v.Block.Func.Config.Types
ptrType := types.BytePtr
ptrType := v.Type.Elem().PtrTo()
lenType := types.Int

ptr := v.Block.NewValue0(v.Pos, OpPhi, ptrType)
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/ssa/gen/dec.rules
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@
(Load <typ.Int>
(OffPtr <typ.IntPtr> [2*config.PtrSize] ptr)
mem))
(Store dst (SliceMake ptr len cap) mem) =>
(Store {t} dst (SliceMake ptr len cap) mem) =>
(Store {typ.Int}
(OffPtr <typ.IntPtr> [2*config.PtrSize] dst)
cap
(Store {typ.Int}
(OffPtr <typ.IntPtr> [config.PtrSize] dst)
len
(Store {typ.BytePtr} dst ptr mem)))
(Store {t.Elem().PtrTo()} dst ptr mem)))

// interface ops
(ITab (IMake itab _)) => itab
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/ssa/nilcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ func nilcheckelim2(f *Func) {
continue
}
if v.Type.IsMemory() || v.Type.IsTuple() && v.Type.FieldType(1).IsMemory() {
if v.Op == OpVarKill || v.Op == OpVarLive || (v.Op == OpVarDef && !v.Aux.(GCNode).Typ().HasHeapPointer()) {
if v.Op == OpVarKill || v.Op == OpVarLive || (v.Op == OpVarDef && !v.Aux.(GCNode).Typ().HasPointers()) {
// These ops don't really change memory.
continue
// Note: OpVarDef requires that the defined variable not have pointers.
Expand Down
7 changes: 4 additions & 3 deletions src/cmd/compile/internal/ssa/rewritedec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/cmd/compile/internal/ssa/writebarrier.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func needwb(v *Value, zeroes map[ID]ZeroRegion) bool {
if !ok {
v.Fatalf("store aux is not a type: %s", v.LongString())
}
if !t.HasHeapPointer() {
if !t.HasPointers() {
return false
}
if IsStackAddr(v.Args[0]) {
Expand Down
24 changes: 6 additions & 18 deletions src/cmd/compile/internal/types/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -1398,14 +1398,9 @@ func (t *Type) IsUntyped() bool {
return false
}

// TODO(austin): We probably only need HasHeapPointer. See
// golang.org/cl/73412 for discussion.

// HasPointers reports whether t contains a heap pointer.
// Note that this function ignores pointers to go:notinheap types.
func (t *Type) HasPointers() bool {
return t.hasPointers1(false)
}

func (t *Type) hasPointers1(ignoreNotInHeap bool) bool {
switch t.Etype {
case TINT, TUINT, TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64,
TUINT64, TUINTPTR, TFLOAT32, TFLOAT64, TCOMPLEX64, TCOMPLEX128, TBOOL, TSSA:
Expand All @@ -1415,34 +1410,27 @@ func (t *Type) hasPointers1(ignoreNotInHeap bool) bool {
if t.NumElem() == 0 { // empty array has no pointers
return false
}
return t.Elem().hasPointers1(ignoreNotInHeap)
return t.Elem().HasPointers()

case TSTRUCT:
for _, t1 := range t.Fields().Slice() {
if t1.Type.hasPointers1(ignoreNotInHeap) {
if t1.Type.HasPointers() {
return true
}
}
return false

case TPTR, TSLICE:
return !(ignoreNotInHeap && t.Elem().NotInHeap())
return !t.Elem().NotInHeap()

case TTUPLE:
ttup := t.Extra.(*Tuple)
return ttup.first.hasPointers1(ignoreNotInHeap) || ttup.second.hasPointers1(ignoreNotInHeap)
return ttup.first.HasPointers() || ttup.second.HasPointers()
}

return true
}

// HasHeapPointer reports whether t contains a heap pointer.
// This is used for write barrier insertion, so it ignores
// pointers to go:notinheap types.
func (t *Type) HasHeapPointer() bool {
return t.hasPointers1(true)
}

func (t *Type) Symbol() *obj.LSym {
return TypeLinkSym(t)
}
Expand Down
7 changes: 3 additions & 4 deletions src/runtime/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -981,9 +981,8 @@ func MapHashCheck(m interface{}, k interface{}) (uintptr, uintptr) {
}

func MSpanCountAlloc(bits []byte) int {
s := mspan{
nelems: uintptr(len(bits) * 8),
gcmarkBits: (*gcBits)(unsafe.Pointer(&bits[0])),
}
s := (*mspan)(mheap_.spanalloc.alloc())
s.nelems = uintptr(len(bits) * 8)
s.gcmarkBits = (*gcBits)(unsafe.Pointer(&bits[0]))
return s.countAlloc()
}
3 changes: 2 additions & 1 deletion src/runtime/mgcmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,8 @@ func scanstack(gp *g, gcw *gcWork) {
x := state.head
state.head = x.next
if stackTraceDebug {
for _, obj := range x.obj[:x.nobj] {
for i := 0; i < x.nobj; i++ {
obj := &x.obj[i]
if obj.typ == nil { // reachable
continue
}
Expand Down
2 changes: 0 additions & 2 deletions src/runtime/mgcstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,6 @@ func (obj *stackObject) setType(typ *_type) {

// A stackScanState keeps track of the state used during the GC walk
// of a goroutine.
//
//go:notinheap
type stackScanState struct {
cache pcvalueCache

Expand Down
5 changes: 1 addition & 4 deletions src/runtime/runtime2.go
Original file line number Diff line number Diff line change
Expand Up @@ -909,15 +909,12 @@ type _defer struct {

// A _panic holds information about an active panic.
//
// This is marked go:notinheap because _panic values must only ever
// live on the stack.
// A _panic value must only ever live on the stack.
//
// The argp and link fields are stack pointers, but don't need special
// handling during stack growth: because they are pointer-typed and
// _panic values only live on the stack, regular stack pointer
// adjustment takes care of them.
//
//go:notinheap
type _panic struct {
argp unsafe.Pointer // pointer to arguments of deferred call run during panic; cannot move - known to liblink
arg interface{} // argument to panic
Expand Down
12 changes: 10 additions & 2 deletions test/notinheap2.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,32 @@ type nih struct {
next *nih
}

// Globals and stack variables are okay.
// Global variables are okay.

var x nih

// Stack variables are not okay.

func f() {
var y nih
var y nih // ERROR "nih is go:notinheap; stack allocation disallowed"
x = y
}

// Heap allocation is not okay.

var y *nih
var z []nih
var w []nih
var n int

func g() {
y = new(nih) // ERROR "heap allocation disallowed"
z = make([]nih, 1) // ERROR "heap allocation disallowed"
z = append(z, x) // ERROR "heap allocation disallowed"
// Test for special case of OMAKESLICECOPY
x := make([]nih, n) // ERROR "heap allocation disallowed"
copy(x, z)
z = x
}

// Writes don't produce write barriers.
Expand Down

0 comments on commit d9a6bdf

Please sign in to comment.