1
0
mirror of https://github.com/golang/go synced 2024-09-29 20:24:34 -06:00

cmd/compile/internal/noder: pointer shaping for unified IR

This CL implements pointer shaping in unified IR, corresponding to the
existing pointer shaping implemented in the non-unified frontend.

For example, if `func F[T any]` is instantiated as both `F[*int]` and
`F[*string]`, we'll now generate a single `F[go.shape.*uint8]` shaped
function that can be used by both.

Fixes #54513.

Change-Id: I2cef5ae411919e6dc5bcb3cac912abecb4cd5218
Reviewed-on: https://go-review.googlesource.com/c/go/+/424734
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
Matthew Dempsky 2022-08-18 03:14:23 -07:00
parent 330cffb869
commit b23d469e85
3 changed files with 59 additions and 28 deletions

View File

@ -795,18 +795,31 @@ func (dict *readerDict) mangle(sym *types.Sym) *types.Sym {
}
// shapify returns the shape type for targ.
func shapify(targ *types.Type) *types.Type {
if targ.IsShape() {
return targ
}
//
// If basic is true, then the type argument is used to instantiate a
// type parameter whose constraint is a basic interface.
func shapify(targ *types.Type, basic bool) *types.Type {
base.Assertf(targ.Kind() != types.TFORW, "%v is missing its underlying type", targ)
// TODO(go.dev/issue/54513): Better shaping than merely converting
// to underlying type. E.g., shape pointer types to unsafe.Pointer
// when we know the element type doesn't matter, and then enable
// cmd/compile/internal/test.TestInst.
// When a pointer type is used to instantiate a type parameter
// constrained by a basic interface, we know the pointer's element
// type can't matter to the generated code. In this case, we can use
// an arbitrary pointer type as the shape type. (To match the
// non-unified frontend, we use `*byte`.)
//
// Otherwise, we simply use the type's underlying type as its shape.
//
// TODO(mdempsky): It should be possible to do much more aggressive
// shaping still; e.g., collapsing all pointer-shaped types into a
// common type, collapsing scalars of the same size/alignment into a
// common type, recursively shaping the element types of composite
// types, and discarding struct field names and tags. However, we'll
// need to start tracking how type parameters are actually used to
// implement some of these optimizations.
under := targ.Underlying()
if basic && targ.IsPtr() && !targ.Elem().NotInHeap() {
under = types.NewPtr(types.Types[types.TUINT8])
}
sym := types.ShapePkg.Lookup(under.LinkString())
if sym.Def == nil {
@ -839,6 +852,20 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex
dict.targs = append(implicits[:nimplicits:nimplicits], explicits...)
dict.implicits = nimplicits
// Within the compiler, we can just skip over the type parameters.
for range dict.targs[dict.implicits:] {
// Skip past bounds without actually evaluating them.
r.typInfo()
}
dict.derived = make([]derivedInfo, r.Len())
dict.derivedTypes = make([]*types.Type, len(dict.derived))
for i := range dict.derived {
dict.derived[i] = derivedInfo{r.Reloc(pkgbits.RelocType), r.Bool()}
}
// Runtime dictionary information; private to the compiler.
// If any type argument is already shaped, then we're constructing a
// shaped object, even if not explicitly requested (i.e., calling
// objIdx with shaped==true). This can happen with instantiating
@ -852,26 +879,15 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex
// And if we're constructing a shaped object, then shapify all type
// arguments.
if dict.shaped {
for i, targ := range dict.targs {
dict.targs[i] = shapify(targ)
for i, targ := range dict.targs {
basic := r.Bool()
if dict.shaped {
dict.targs[i] = shapify(targ, basic)
}
}
dict.baseSym = dict.mangle(sym)
// For stenciling, we can just skip over the type parameters.
for range dict.targs[dict.implicits:] {
// Skip past bounds without actually evaluating them.
r.typInfo()
}
dict.derived = make([]derivedInfo, r.Len())
dict.derivedTypes = make([]*types.Type, len(dict.derived))
for i := range dict.derived {
dict.derived[i] = derivedInfo{r.Reloc(pkgbits.RelocType), r.Bool()}
}
dict.typeParamMethodExprs = make([]readerMethodExprInfo, r.Len())
for i := range dict.typeParamMethodExprs {
typeParamIdx := r.Len()

View File

@ -846,6 +846,25 @@ func (w *writer) objDict(obj types2.Object, dict *writerDict) {
// N.B., the go/types importer reads up to the section, but doesn't
// read any further, so it's safe to change. (See TODO above.)
// For each type parameter, write out whether the constraint is a
// basic interface. This is used to determine how aggressively we
// can shape corresponding type arguments.
//
// This is somewhat redundant with writing out the full type
// parameter constraints above, but the compiler currently skips
// over those. Also, we don't care about the *declared* constraints,
// but how the type parameters are actually *used*. E.g., if a type
// parameter is constrained to `int | uint` but then never used in
// arithmetic/conversions/etc, we could shape those together.
for _, implicit := range dict.implicits {
tparam := implicit.Type().(*types2.TypeParam)
w.Bool(tparam.Underlying().(*types2.Interface).IsMethodSet())
}
for i := 0; i < ntparams; i++ {
tparam := tparams.At(i)
w.Bool(tparam.Underlying().(*types2.Interface).IsMethodSet())
}
w.Len(len(dict.typeParamMethodExprs))
for _, info := range dict.typeParamMethodExprs {
w.Len(info.typeParamIdx)

View File

@ -5,7 +5,6 @@
package test
import (
"internal/goexperiment"
"internal/testenv"
"io/ioutil"
"os"
@ -18,9 +17,6 @@ import (
// TestInst tests that only one instantiation of Sort is created, even though generic
// Sort is used for multiple pointer types across two packages.
func TestInst(t *testing.T) {
if goexperiment.Unified {
t.Skip("unified currently does stenciling, not dictionaries")
}
testenv.MustHaveGoBuild(t)
testenv.MustHaveGoRun(t)