1
0
mirror of https://github.com/golang/go synced 2024-11-22 23:50:03 -07:00

[dev.typeparams] cmd/compile: allow inlining in instantiated functions

Change markType to scan generic types and methods, so that inlineable
functions inside generic functions/methods will be properly marked for
export, which means inlining inside instantiated functions will work
correctly.

Also, fix handling of closures for instantiated functions. Some code
needs to be adjusted, since instantiated functions/methods are compiled
as if in the package of the source generic function/type, rather than in
the local package. When we create the closure struct, we want to make
sure that the .F field has the same package as the other fields for the
closure variables. Also, we need to disable a check in tcCompLit() when
being done for an instantiated function, since fields of the closure
struct will be from the source package, not the local package.

Re-enabled part of the orderedmapsimp test that was disabled because of
these issues.

Change-Id: Ic4dba8917da0a36b17c0bdb69d6d6edfdf14104a
Reviewed-on: https://go-review.googlesource.com/c/go/+/324331
Trust: Dan Scales <danscales@google.com>
Run-TryBot: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Dan Scales 2021-06-01 10:49:14 -07:00
parent 4cf7f5f694
commit de61465156
10 changed files with 103 additions and 69 deletions

View File

@ -94,15 +94,14 @@ func (p *exporter) markObject(n ir.Node) {
// markType recursively visits types reachable from t to identify // markType recursively visits types reachable from t to identify
// functions whose inline bodies may be needed. // functions whose inline bodies may be needed.
func (p *exporter) markType(t *types.Type) { func (p *exporter) markType(t *types.Type) {
if t.IsInstantiatedGeneric() {
// Re-instantiated types don't add anything new, so don't follow them.
return
}
if p.marked[t] { if p.marked[t] {
return return
} }
p.marked[t] = true p.marked[t] = true
if t.HasTParam() {
// Don't deal with any generic types or their methods, since we
// will only be inlining actual instantiations, not generic methods.
return
}
// If this is a named type, mark all of its associated // If this is a named type, mark all of its associated
// methods. Skip interface types because t.Methods contains // methods. Skip interface types because t.Methods contains
@ -159,5 +158,8 @@ func (p *exporter) markType(t *types.Type) {
p.markType(f.Type) p.markType(f.Type)
} }
} }
case types.TTYPEPARAM:
// No other type that needs to be followed.
} }
} }

View File

@ -109,6 +109,16 @@ func (g *irgen) funcDecl(out *ir.Nodes, decl *syntax.FuncDecl) {
} }
g.funcBody(fn, decl.Recv, decl.Type, decl.Body) g.funcBody(fn, decl.Recv, decl.Type, decl.Body)
if fn.Type().HasTParam() && fn.Body != nil {
// Set pointers to the dcls/body of a generic function/method in
// the Inl struct, so it is marked for export, is available for
// stenciling, and works with Inline_Flood().
fn.Inl = &ir.Inline{
Cost: 1,
Dcl: fn.Dcl,
Body: fn.Body,
}
}
out.Append(fn) out.Append(fn)
} }

View File

@ -17,8 +17,6 @@ import (
"go/constant" "go/constant"
) )
// For catching problems as we add more features
// TODO(danscales): remove assertions or replace with base.FatalfAt()
func assert(p bool) { func assert(p bool) {
if !p { if !p {
panic("assertion failed") panic("assertion failed")

View File

@ -949,7 +949,7 @@ func writeType(t *types.Type) *obj.LSym {
// in the local package, even if they may be marked as part of // in the local package, even if they may be marked as part of
// another package (the package of their base generic type). // another package (the package of their base generic type).
if tbase.Sym() != nil && tbase.Sym().Pkg != types.LocalPkg && if tbase.Sym() != nil && tbase.Sym().Pkg != types.LocalPkg &&
!tbase.IsInstantiated() { !tbase.IsFullyInstantiated() {
if i := typecheck.BaseTypeIndex(t); i >= 0 { if i := typecheck.BaseTypeIndex(t); i >= 0 {
lsym.Pkg = tbase.Sym().Pkg.Prefix lsym.Pkg = tbase.Sym().Pkg.Prefix
lsym.SymIdx = int32(i) lsym.SymIdx = int32(i)
@ -1795,7 +1795,7 @@ func methodWrapper(rcvr *types.Type, method *types.Field, forItab bool) *obj.LSy
// instantiated methods. // instantiated methods.
if rcvr.IsPtr() && rcvr.Elem() == method.Type.Recv().Type && if rcvr.IsPtr() && rcvr.Elem() == method.Type.Recv().Type &&
rcvr.Elem().Sym() != nil && rcvr.Elem().Sym().Pkg != types.LocalPkg && rcvr.Elem().Sym() != nil && rcvr.Elem().Sym().Pkg != types.LocalPkg &&
!rcvr.Elem().IsInstantiated() { !rcvr.Elem().IsFullyInstantiated() {
return lsym return lsym
} }

View File

@ -311,8 +311,19 @@ func tcCompLit(n *ir.CompLitExpr) (res ir.Node) {
f := t.Field(i) f := t.Field(i)
s := f.Sym s := f.Sym
if s != nil && !types.IsExported(s.Name) && s.Pkg != types.LocalPkg {
base.Errorf("implicit assignment of unexported field '%s' in %v literal", s.Name, t) // Do the test for assigning to unexported fields.
// But if this is an instantiated function, then
// the function has already been typechecked. In
// that case, don't do the test, since it can fail
// for the closure structs created in
// walkClosure(), because the instantiated
// function is compiled as if in the source
// package of the generic function.
if !(ir.CurFunc != nil && strings.Index(ir.CurFunc.Nname.Sym().Name, "[") >= 0) {
if s != nil && !types.IsExported(s.Name) && s.Pkg != types.LocalPkg {
base.Errorf("implicit assignment of unexported field '%s' in %v literal", s.Name, t)
}
} }
// No pushtype allowed here. Must name fields for that. // No pushtype allowed here. Must name fields for that.
n1 = AssignConv(n1, f.Type, "field value") n1 = AssignConv(n1, f.Type, "field value")

View File

@ -74,8 +74,25 @@ func ClosureType(clo *ir.ClosureExpr) *types.Type {
// The information appears in the binary in the form of type descriptors; // The information appears in the binary in the form of type descriptors;
// the struct is unnamed so that closures in multiple packages with the // the struct is unnamed so that closures in multiple packages with the
// same struct type can share the descriptor. // same struct type can share the descriptor.
// Make sure the .F field is in the same package as the rest of the
// fields. This deals with closures in instantiated functions, which are
// compiled as if from the source package of the generic function.
var pkg *types.Pkg
if len(clo.Func.ClosureVars) == 0 {
pkg = types.LocalPkg
} else {
for _, v := range clo.Func.ClosureVars {
if pkg == nil {
pkg = v.Sym().Pkg
} else if pkg != v.Sym().Pkg {
base.Fatalf("Closure variables from multiple packages")
}
}
}
fields := []*types.Field{ fields := []*types.Field{
types.NewField(base.Pos, Lookup(".F"), types.Types[types.TUINTPTR]), types.NewField(base.Pos, pkg.Lookup(".F"), types.Types[types.TUINTPTR]),
} }
for _, v := range clo.Func.ClosureVars { for _, v := range clo.Func.ClosureVars {
typ := v.Type() typ := v.Type()

View File

@ -1332,24 +1332,9 @@ func (w *exportWriter) funcExt(n *ir.Name) {
} }
} }
// Inline body. // Write out inline body or body of a generic function/method.
if n.Type().HasTParam() { if n.Type().HasTParam() && n.Func.Body != nil && n.Func.Inl == nil {
if n.Func.Inl != nil { base.FatalfAt(n.Pos(), "generic function is not marked inlineable")
// n.Func.Inl may already be set on a generic function if
// we imported it from another package, but shouldn't be
// set for a generic function in the local package.
if n.Sym().Pkg == types.LocalPkg {
base.FatalfAt(n.Pos(), "generic function is marked inlineable")
}
} else {
// Populate n.Func.Inl, so body of exported generic function will
// be written out.
n.Func.Inl = &ir.Inline{
Cost: 1,
Dcl: n.Func.Dcl,
Body: n.Func.Body,
}
}
} }
if n.Func.Inl != nil { if n.Func.Inl != nil {
w.uint64(1 + uint64(n.Func.Inl.Cost)) w.uint64(1 + uint64(n.Func.Inl.Cost))

View File

@ -8,6 +8,7 @@ import (
"cmd/compile/internal/base" "cmd/compile/internal/base"
"cmd/internal/src" "cmd/internal/src"
"fmt" "fmt"
"strings"
"sync" "sync"
) )
@ -279,10 +280,23 @@ func (t *Type) SetRParams(rparams []*Type) {
} }
} }
// IsInstantiated reports whether t is a fully instantiated generic type; i.e. an // IsBaseGeneric returns true if t is a generic type (not reinstantiated with
// another type params or fully instantiated.
func (t *Type) IsBaseGeneric() bool {
return len(t.RParams()) > 0 && strings.Index(t.Sym().Name, "[") < 0
}
// IsInstantiatedGeneric returns t if t ia generic type that has been
// reinstantiated with new typeparams (i.e. is not fully instantiated).
func (t *Type) IsInstantiatedGeneric() bool {
return len(t.RParams()) > 0 && strings.Index(t.Sym().Name, "[") >= 0 &&
t.HasTParam()
}
// IsFullyInstantiated reports whether t is a fully instantiated generic type; i.e. an
// instantiated generic type where all type arguments are non-generic or fully // instantiated generic type where all type arguments are non-generic or fully
// instantiated generic types. // instantiated generic types.
func (t *Type) IsInstantiated() bool { func (t *Type) IsFullyInstantiated() bool {
return len(t.RParams()) > 0 && !t.HasTParam() return len(t.RParams()) > 0 && !t.HasTParam()
} }

View File

@ -100,25 +100,25 @@ type keyValue[K, V any] struct {
} }
// iterate returns an iterator that traverses the map. // iterate returns an iterator that traverses the map.
// func (m *Map[K, V]) Iterate() *Iterator[K, V] { func (m *Map[K, V]) Iterate() *Iterator[K, V] {
// sender, receiver := Ranger[keyValue[K, V]]() sender, receiver := Ranger[keyValue[K, V]]()
// var f func(*node[K, V]) bool var f func(*node[K, V]) bool
// f = func(n *node[K, V]) bool { f = func(n *node[K, V]) bool {
// if n == nil { if n == nil {
// return true return true
// } }
// // Stop the traversal if Send fails, which means that // Stop the traversal if Send fails, which means that
// // nothing is listening to the receiver. // nothing is listening to the receiver.
// return f(n.left) && return f(n.left) &&
// sender.Send(context.Background(), keyValue[K, V]{n.key, n.val}) && sender.Send(context.Background(), keyValue[K, V]{n.key, n.val}) &&
// f(n.right) f(n.right)
// } }
// go func() { go func() {
// f(m.root) f(m.root)
// sender.Close() sender.Close()
// }() }()
// return &Iterator[K, V]{receiver} return &Iterator[K, V]{receiver}
// } }
// Iterator is used to iterate over the map. // Iterator is used to iterate over the map.
type Iterator[K, V any] struct { type Iterator[K, V any] struct {

View File

@ -41,24 +41,21 @@ func TestMap() {
panic(fmt.Sprintf("unexpectedly found %q", []byte("d"))) panic(fmt.Sprintf("unexpectedly found %q", []byte("d")))
} }
// TODO(danscales): Iterate() has some things to be fixed with inlining in gather := func(it *a.Iterator[[]byte, int]) []int {
// stenciled functions and using closures across packages. var r []int
for {
// gather := func(it *a.Iterator[[]byte, int]) []int { _, v, ok := it.Next()
// var r []int if !ok {
// for { return r
// _, v, ok := it.Next() }
// if !ok { r = append(r, v)
// return r }
// } }
// r = append(r, v) got := gather(m.Iterate())
// } want := []int{'a', 'b', 'x'}
// } if !a.SliceEqual(got, want) {
// got := gather(m.Iterate()) panic(fmt.Sprintf("Iterate returned %v, want %v", got, want))
// want := []int{'a', 'b', 'x'} }
// if !a.SliceEqual(got, want) {
// panic(fmt.Sprintf("Iterate returned %v, want %v", got, want))
// }
} }