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:
parent
4cf7f5f694
commit
de61465156
@ -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.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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()
|
||||||
|
@ -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))
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user