diff --git a/src/cmd/compile/internal/gc/lex.go b/src/cmd/compile/internal/gc/lex.go index b3c7a63a02..df9790955f 100644 --- a/src/cmd/compile/internal/gc/lex.go +++ b/src/cmd/compile/internal/gc/lex.go @@ -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 } diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 4d97b48bce..65f39b3506 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -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 { diff --git a/src/cmd/compile/internal/gc/parser.go b/src/cmd/compile/internal/gc/parser.go index 5051767999..b81724daee 100644 --- a/src/cmd/compile/internal/gc/parser.go +++ b/src/cmd/compile/internal/gc/parser.go @@ -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 diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go index acd2b299c9..2f2c134d74 100644 --- a/src/cmd/compile/internal/gc/subr.go +++ b/src/cmd/compile/internal/gc/subr.go @@ -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 { diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index b0c5204ee3..ec47eb0828 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -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. diff --git a/src/cmd/compile/internal/gc/type.go b/src/cmd/compile/internal/gc/type.go index 2dd1184fff..29048f1a19 100644 --- a/src/cmd/compile/internal/gc/type.go +++ b/src/cmd/compile/internal/gc/type.go @@ -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) } diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index eaadb40c8a..33ed7fd9a7 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -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) diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index 9a03f1c959..e2699d1f0a 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -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. diff --git a/src/cmd/compile/internal/syntax/nodes.go b/src/cmd/compile/internal/syntax/nodes.go index 9555a4b9a8..792b207ef1 100644 --- a/src/cmd/compile/internal/syntax/nodes.go +++ b/src/cmd/compile/internal/syntax/nodes.go @@ -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 } diff --git a/src/cmd/compile/internal/syntax/parser.go b/src/cmd/compile/internal/syntax/parser.go index 6cf899dd91..1eb85fb7ee 100644 --- a/src/cmd/compile/internal/syntax/parser.go +++ b/src/cmd/compile/internal/syntax/parser.go @@ -381,6 +381,7 @@ func (p *parser) typeDecl(group *Group) Decl { p.advance(_Semi, _Rparen) } d.Group = group + d.Pragma = p.pragma return d } diff --git a/src/cmd/compile/internal/syntax/syntax.go b/src/cmd/compile/internal/syntax/syntax.go index 6c0abd118d..49831d0fbd 100644 --- a/src/cmd/compile/internal/syntax/syntax.go +++ b/src/cmd/compile/internal/syntax/syntax.go @@ -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 diff --git a/src/runtime/HACKING.md b/src/runtime/HACKING.md index c80e81a193..d2f7b522b3 100644 --- a/src/runtime/HACKING.md +++ b/src/runtime/HACKING.md @@ -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 index 0000000000..c3fdfd6daa --- /dev/null +++ b/test/notinheap.go @@ -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 index 0000000000..944f2993ab --- /dev/null +++ b/test/notinheap2.go @@ -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 +}