]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: add go:notinheap type pragma
authorAustin Clements <austin@google.com>
Wed, 12 Oct 2016 02:53:27 +0000 (22:53 -0400)
committerAustin Clements <austin@google.com>
Sat, 15 Oct 2016 17:58:14 +0000 (17:58 +0000)
This adds a //go:notinheap pragma for declarations of types that must
not be heap allocated. We ensure these rules by disallowing new(T),
make([]T), append([]T), or implicit allocation of T, by disallowing
conversions to notinheap types, and by propagating notinheap to any
struct or array that contains notinheap elements.

The utility of this pragma is that we can eliminate write barriers for
writes to pointers to go:notinheap types, since the write barrier is
guaranteed to be a no-op. This will let us mark several scheduler and
memory allocator structures as go:notinheap, which will let us
disallow write barriers in the scheduler and memory allocator much
more thoroughly and also eliminate some problematic hybrid write
barriers.

This also makes go:nowritebarrierrec and go:yeswritebarrierrec much
more powerful. Currently we use go:nowritebarrier all over the place,
but it's almost never what you actually want: when write barriers are
illegal, they're typically illegal for a whole dynamic scope. Partly
this is because go:nowritebarrier has been around longer, but it's
also because go:nowritebarrierrec couldn't be used in situations that
had no-op write barriers or where some nested scope did allow write
barriers. go:notinheap eliminates many no-op write barriers and
go:yeswritebarrierrec makes it possible to opt back in to write
barriers, so these two changes will let us use go:nowritebarrierrec
far more liberally.

This updates #13386, which is about controlling pointers from non-GC'd
memory to GC'd memory. That would require some additional pragma (or
pragmas), but could build on this pragma.

Change-Id: I6314f8f4181535dd166887c9ec239977b54940bd
Reviewed-on: https://go-review.googlesource.com/30939
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
14 files changed:
src/cmd/compile/internal/gc/lex.go
src/cmd/compile/internal/gc/noder.go
src/cmd/compile/internal/gc/parser.go
src/cmd/compile/internal/gc/subr.go
src/cmd/compile/internal/gc/syntax.go
src/cmd/compile/internal/gc/type.go
src/cmd/compile/internal/gc/typecheck.go
src/cmd/compile/internal/gc/walk.go
src/cmd/compile/internal/syntax/nodes.go
src/cmd/compile/internal/syntax/parser.go
src/cmd/compile/internal/syntax/syntax.go
src/runtime/HACKING.md
test/notinheap.go [new file with mode: 0644]
test/notinheap2.go [new file with mode: 0644]

index b3c7a63a027f0c038ac2bb8d77db3196a392249a..df9790955f77900e8ba2ef66b1c2e48df7d60990 100644 (file)
@@ -64,6 +64,7 @@ func plan9quote(s string) string {
 type Pragma syntax.Pragma
 
 const (
+       // Func pragmas.
        Nointerface    Pragma = 1 << iota
        Noescape              // func parameters don't escape
        Norace                // func must not have race detector annotations
@@ -72,13 +73,15 @@ const (
        CgoUnsafeArgs         // treat a pointer to one arg as a pointer to them all
        UintptrEscapes        // pointers converted to uintptr escape
 
-       // Runtime-only pragmas.
+       // Runtime-only func pragmas.
        // See ../../../../runtime/README.md for detailed descriptions.
-
        Systemstack        // func must run on system stack
        Nowritebarrier     // emit compiler error instead of write barrier
        Nowritebarrierrec  // error on write barrier in this or recursive callees
        Yeswritebarrierrec // cancels Nowritebarrierrec in this function and callees
+
+       // Runtime-only type pragmas
+       NotInHeap // values of this type must not be heap allocated
 )
 
 func pragmaValue(verb string) Pragma {
@@ -130,6 +133,8 @@ func pragmaValue(verb string) Pragma {
                // in the argument list.
                // Used in syscall/dll_windows.go.
                return UintptrEscapes
+       case "go:notinheap":
+               return NotInHeap
        }
        return 0
 }
index 4d97b48bce68a64a04f8104ae44c7c485c13a327..65f39b3506377ccb9c54d9a485cf4670ef7d32c8 100644 (file)
@@ -188,6 +188,7 @@ func (p *noder) constDecl(decl *syntax.ConstDecl) []*Node {
 
 func (p *noder) typeDecl(decl *syntax.TypeDecl) *Node {
        name := typedcl0(p.name(decl.Name))
+       name.Name.Param.Pragma = Pragma(decl.Pragma)
 
        var typ *Node
        if decl.Type != nil {
index 5051767999e57ed440cab2eaa02fff38b5c3885c..b81724daee30515d5ec43e68c6bf8175e758e85b 100644 (file)
@@ -479,6 +479,7 @@ func (p *parser) typedcl() []*Node {
        }
 
        name := typedcl0(p.sym())
+       name.Name.Param.Pragma = p.pragma
 
        typ := p.try_ntype()
        // handle case where type is missing
index acd2b299c987b9902dda78b8945b8dcecb1983db..2f2c134d74c367dfac183c647d74a50c878f3f42 100644 (file)
@@ -863,6 +863,16 @@ func convertop(src *Type, dst *Type, why *string) Op {
                return 0
        }
 
+       // Conversions from regular to go:notinheap are not allowed
+       // (unless it's unsafe.Pointer). This is a runtime-specific
+       // rule.
+       if src.IsPtr() && dst.IsPtr() && dst.Elem().NotInHeap && !src.Elem().NotInHeap {
+               if why != nil {
+                       *why = fmt.Sprintf(":\n\t%v is go:notinheap, but %v is not", dst.Elem(), src.Elem())
+               }
+               return 0
+       }
+
        // 1. src can be assigned to dst.
        op := assignop(src, dst, why)
        if op != 0 {
index b0c5204ee343c8a477cb5b22c4307038fd36ec38..ec47eb08280e52f32e74d8aea589c0100a5ccd8e 100644 (file)
@@ -266,6 +266,11 @@ type Param struct {
        // and x.Innermost/Outer means x.Name.Param.Innermost/Outer.
        Innermost *Node
        Outer     *Node
+
+       // OTYPE pragmas
+       //
+       // TODO: Should Func pragmas also be stored on the Name?
+       Pragma Pragma
 }
 
 // Func holds Node fields used only with function-like nodes.
index 2dd1184fffd3a4df4b1e9f98f4989f7111fba4cb..29048f1a19c9de052c8a95497b9070bd88fb17c4 100644 (file)
@@ -160,6 +160,7 @@ type Type struct {
        Deferwidth bool
        Broke      bool  // broken type definition.
        Align      uint8 // the required alignment of this type, in bytes
+       NotInHeap  bool  // type cannot be heap allocated
 }
 
 // MapType contains Type fields specific to maps.
@@ -414,6 +415,7 @@ func typArray(elem *Type, bound int64) *Type {
        }
        t := typ(TARRAY)
        t.Extra = &ArrayType{Elem: elem, Bound: bound}
+       t.NotInHeap = elem.NotInHeap
        return t
 }
 
@@ -436,6 +438,7 @@ func typSlice(elem *Type) *Type {
 func typDDDArray(elem *Type) *Type {
        t := typ(TARRAY)
        t.Extra = &ArrayType{Elem: elem, Bound: -1}
+       t.NotInHeap = elem.NotInHeap
        return t
 }
 
@@ -822,6 +825,17 @@ func (t *Type) FieldSlice() []*Field {
 
 // SetFields sets struct/interface type t's fields/methods to fields.
 func (t *Type) SetFields(fields []*Field) {
+       for _, f := range fields {
+               // If type T contains a field F with a go:notinheap
+               // type, then T must also be go:notinheap. Otherwise,
+               // you could heap allocate T and then get a pointer F,
+               // which would be a heap pointer to a go:notinheap
+               // type.
+               if f.Type != nil && f.Type.NotInHeap {
+                       t.NotInHeap = true
+                       break
+               }
+       }
        t.Fields().Set(fields)
 }
 
index eaadb40c8a76675c67de9bc005d3a264ee941b08..33ed7fd9a72a8a530aefcf8f5b1020a55cfa177b 100644 (file)
@@ -403,6 +403,12 @@ OpSwitch:
                        n.Type = nil
                        return n
                }
+               if l.Type.NotInHeap {
+                       yyerror("go:notinheap map key not allowed")
+               }
+               if r.Type.NotInHeap {
+                       yyerror("go:notinheap map value not allowed")
+               }
                n.Op = OTYPE
                n.Type = typMap(l.Type, r.Type)
 
@@ -428,6 +434,9 @@ OpSwitch:
                        n.Type = nil
                        return n
                }
+               if l.Type.NotInHeap {
+                       yyerror("chan of go:notinheap type not allowed")
+               }
                t := typChan(l.Type, ChanDir(n.Etype)) // TODO(marvin): Fix Node.EType type union.
                n.Op = OTYPE
                n.Type = t
@@ -2087,6 +2096,12 @@ OpSwitch:
                ok |= Etop
                n.Left = typecheck(n.Left, Etype)
                checkwidth(n.Left.Type)
+               if n.Left.Type != nil && n.Left.Type.NotInHeap && n.Left.Name.Param.Pragma&NotInHeap == 0 {
+                       // The type contains go:notinheap types, so it
+                       // must be marked as such (alternatively, we
+                       // could silently propagate go:notinheap).
+                       yyerror("type %v must be go:notinheap", n.Left.Type)
+               }
                break OpSwitch
        }
 
@@ -3516,6 +3531,11 @@ func copytype(n *Node, t *Type) {
        t.ptrTo = ptrTo
        t.sliceOf = sliceOf
 
+       // Propagate go:notinheap pragma from the Name to the Type.
+       if n.Name != nil && n.Name.Param != nil && n.Name.Param.Pragma&NotInHeap != 0 {
+               t.NotInHeap = true
+       }
+
        // Update nodes waiting on this type.
        for _, n := range l {
                copytype(n, t)
index 9a03f1c95963ade98927fb29bb9726a49b90bd3f..e2699d1f0a98027a29d5bc09d2e1caf1763b98d6 100644 (file)
@@ -773,6 +773,9 @@ opswitch:
                case OAPPEND:
                        // x = append(...)
                        r := n.Right
+                       if r.Type.Elem().NotInHeap {
+                               yyerror("%v is go:notinheap; heap allocation disallowed", r.Type.Elem())
+                       }
                        if r.Isddd {
                                r = appendslice(r, init) // also works for append(slice, string).
                        } else {
@@ -1546,6 +1549,10 @@ 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"
@@ -2146,6 +2153,9 @@ func walkprint(nn *Node, init *Nodes) *Node {
 }
 
 func callnew(t *Type) *Node {
+       if t.NotInHeap {
+               yyerror("%v is go:notinheap; heap allocation disallowed", t)
+       }
        dowidth(t)
        fn := syslook("newobject")
        fn = substArgTypes(fn, t)
@@ -2217,6 +2227,12 @@ func needwritebarrier(l *Node, r *Node) bool {
                return false
        }
 
+       // No write barrier if this is a pointer to a go:notinheap
+       // type, since the write barrier's inheap(ptr) check will fail.
+       if l.Type.IsPtr() && l.Type.Elem().NotInHeap {
+               return false
+       }
+
        // Ignore no-op conversions when making decision.
        // Ensures that xp = unsafe.Pointer(&x) is treated
        // the same as xp = &x.
index 9555a4b9a8dde6c5c08e543735ee8e9fe81008c1..792b207ef1dabe1e9f66dd5a6cea6350c4e8198f 100644 (file)
@@ -87,10 +87,11 @@ type (
 
        // Name Type
        TypeDecl struct {
-               Name  *Name
-               Type  Expr
-               Alias bool
-               Group *Group // nil means not part of a group
+               Name   *Name
+               Type   Expr
+               Alias  bool
+               Group  *Group // nil means not part of a group
+               Pragma Pragma
                decl
        }
 
index 6cf899dd9147f97be859df17449b45384da05bf5..1eb85fb7eebe77c2348eaf68071934e074640a1f 100644 (file)
@@ -381,6 +381,7 @@ func (p *parser) typeDecl(group *Group) Decl {
                p.advance(_Semi, _Rparen)
        }
        d.Group = group
+       d.Pragma = p.pragma
 
        return d
 }
index 6c0abd118df12cf1d24468e5b7ecc82a0a501954..49831d0fbd03efeff2072cd00ff1b722a227b737 100644 (file)
@@ -12,8 +12,8 @@ import (
 
 type Mode uint
 
-// A Pragma value is a set of flags that augment a function
-// declaration. Callers may assign meaning to the flags as
+// A Pragma value is a set of flags that augment a function or
+// type declaration. Callers may assign meaning to the flags as
 // appropriate.
 type Pragma uint16
 
index c80e81a193e07bca99edd2710e7a90b68e3bdaef..d2f7b522b3dbcf9bf3a378875d62d44517ac3415 100644 (file)
@@ -47,3 +47,36 @@ functions that release the P or may run without a P and
 `go:yeswritebarrierrec` is used when code re-acquires an active P.
 Since these are function-level annotations, code that releases or
 acquires a P may need to be split across two functions.
+
+go:notinheap
+------------
+
+`go:notinheap` applies to type declarations. It indicates that a type
+must never be heap allocated. Specifically, pointers to this type must
+always fail the `runtime.inheap` check. The type may be used for
+global variables, for stack variables, or for objects in unmanaged
+memory (e.g., allocated with `sysAlloc`, `persistentalloc`, or
+`fixalloc`). Specifically:
+
+1. `new(T)`, `make([]T)`, `append([]T, ...)` and implicit heap
+   allocation of T are disallowed. (Though implicit allocations are
+   disallowed in the runtime anyway.)
+
+2. A pointer to a regular type (other than `unsafe.Pointer`) cannot be
+   converted to a pointer to a `go:notinheap` type, even if they have
+   the same underlying type.
+
+3. Any type that contains a `go:notinheap` type is itself
+   `go:notinheap`. Structs and arrays are `go:notinheap` if their
+   elements are. Maps and channels of `go:notinheap` types are
+   disallowed. To keep things explicit, any type declaration where the
+   type is implicitly `go:notinheap` must be explicitly marked
+   `go:notinheap` as well.
+
+4. Write barriers on pointers to `go:notinheap` types can be omitted.
+
+The last point is the real benefit of `go:notinheap`. The runtime uses
+it for low-level internal structures to avoid memory barriers in the
+scheduler and the memory allocator where they are illegal or simply
+inefficient. This mechanism is reasonably safe and does not compromise
+the readability of the runtime.
diff --git a/test/notinheap.go b/test/notinheap.go
new file mode 100644 (file)
index 0000000..c3fdfd6
--- /dev/null
@@ -0,0 +1,55 @@
+// errorcheck -+
+
+// Copyright 2016 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.
+
+// Test type-checking errors for go:notinheap.
+
+package p
+
+//go:notinheap
+type nih struct{}
+
+// Types embedding notinheap types must be notinheap.
+
+type embed1 struct {
+       x nih
+} // ERROR "must be go:notinheap"
+
+type embed2 [1]nih // ERROR "must be go:notinheap"
+
+type embed3 struct {
+       x [1]nih
+} // ERROR "must be go:notinheap"
+
+type embed4 map[nih]int // ERROR "go:notinheap map key not allowed"
+
+type embed5 map[int]nih // ERROR "go:notinheap map value not allowed"
+
+type emebd6 chan nih // ERROR "chan of go:notinheap type not allowed"
+
+type okay1 *nih
+
+type okay2 []nih
+
+type okay3 func(x nih) nih
+
+type okay4 interface {
+       f(x nih) nih
+}
+
+// Type conversions don't let you sneak past notinheap.
+
+type t1 struct{ x int }
+
+//go:notinheap
+type t2 t1
+
+var sink interface{}
+
+func i() {
+       sink = new(t1)                     // no error
+       sink = (*t2)(new(t1))              // ERROR "cannot convert(.|\n)*t2 is go:notinheap"
+       sink = (*t2)(new(struct{ x int })) // ERROR "cannot convert(.|\n)*t2 is go:notinheap"
+}
diff --git a/test/notinheap2.go b/test/notinheap2.go
new file mode 100644 (file)
index 0000000..944f299
--- /dev/null
@@ -0,0 +1,43 @@
+// errorcheck -+
+
+// Copyright 2016 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.
+
+// Test walk errors for go:notinheap.
+
+package p
+
+//go:notinheap
+type nih struct {
+       next *nih
+}
+
+// Globals and stack variables are okay.
+
+var x nih
+
+func f() {
+       var y nih
+       x = y
+}
+
+// Heap allocation is not okay.
+
+var y *nih
+var z []nih
+
+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"
+}
+
+// Writes don't produce write barriers.
+
+var p *nih
+
+//go:nowritebarrier
+func h() {
+       y.next = p.next
+}