Skip to content

Commit

Permalink
go/ssa: add MultiConvert instruction
Browse files Browse the repository at this point in the history
Adds a new MultiConvert instruction. MultiConvert instructions
are a catch all for conversions involving a typeparameter that
would result in multiple different types of conversion
instruction [sequences].

Updates golang/go#56849

Change-Id: I6c2f53ccef1b933406096d6ca2867f1007a13bd3
Reviewed-on: https://go-review.googlesource.com/c/tools/+/457436
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Tim King <taking@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
  • Loading branch information
timothy-king committed Feb 6, 2023
1 parent f124b50 commit a762c82
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 112 deletions.
68 changes: 68 additions & 0 deletions go/callgraph/vta/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"golang.org/x/tools/go/callgraph"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typeparams"
)

// node interface for VTA nodes.
Expand Down Expand Up @@ -375,6 +376,8 @@ func (b *builder) instr(instr ssa.Instruction) {
// SliceToArrayPointer: t1 = slice to array pointer *[4]T <- []T (t0)
// No interesting flow as sliceArrayElem(t1) == sliceArrayElem(t0).
return
case *ssa.MultiConvert:
b.multiconvert(i)
default:
panic(fmt.Sprintf("unsupported instruction %v\n", instr))
}
Expand Down Expand Up @@ -655,6 +658,71 @@ func addReturnFlows(b *builder, r *ssa.Return, site ssa.Value) {
}
}

func (b *builder) multiconvert(c *ssa.MultiConvert) {
// TODO(zpavlinovic): decide what to do on MultiConvert long term.
// TODO(zpavlinovic): add unit tests.
typeSetOf := func(typ types.Type) []*typeparams.Term {
// This is a adaptation of x/exp/typeparams.NormalTerms which x/tools cannot depend on.
var terms []*typeparams.Term
var err error
switch typ := typ.(type) {
case *typeparams.TypeParam:
terms, err = typeparams.StructuralTerms(typ)
case *typeparams.Union:
terms, err = typeparams.UnionTermSet(typ)
case *types.Interface:
terms, err = typeparams.InterfaceTermSet(typ)
default:
// Common case.
// Specializing the len=1 case to avoid a slice
// had no measurable space/time benefit.
terms = []*typeparams.Term{typeparams.NewTerm(false, typ)}
}

if err != nil {
return nil
}
return terms
}
// isValuePreserving returns true if a conversion from ut_src to
// ut_dst is value-preserving, i.e. just a change of type.
// Precondition: neither argument is a named type.
isValuePreserving := func(ut_src, ut_dst types.Type) bool {
// Identical underlying types?
if types.IdenticalIgnoreTags(ut_dst, ut_src) {
return true
}

switch ut_dst.(type) {
case *types.Chan:
// Conversion between channel types?
_, ok := ut_src.(*types.Chan)
return ok

case *types.Pointer:
// Conversion between pointers with identical base types?
_, ok := ut_src.(*types.Pointer)
return ok
}
return false
}
dst_terms := typeSetOf(c.Type())
src_terms := typeSetOf(c.X.Type())
for _, s := range src_terms {
us := s.Type().Underlying()
for _, d := range dst_terms {
ud := d.Type().Underlying()
if isValuePreserving(us, ud) {
// This is equivalent to a ChangeType.
b.addInFlowAliasEdges(b.nodeFromVal(c), b.nodeFromVal(c.X))
return
}
// This is equivalent to either: SliceToArrayPointer,,
// SliceToArrayPointer+Deref, Size 0 Array constant, or a Convert.
}
}
}

// addInFlowEdge adds s -> d to g if d is node that can have an inflow, i.e., a node
// that represents an interface or an unresolved function value. Otherwise, there
// is no interesting type flow so the edge is omitted.
Expand Down
52 changes: 51 additions & 1 deletion go/ssa/builder_go120_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,55 @@ func TestBuildPackageGo120(t *testing.T) {
{"slice to zero length array type parameter", "package p; var s []byte; func f[T ~[0]byte]() { tmp := (T)(s); var z T; _ = tmp == z}", nil},
{"slice to non-zero length array type parameter", "package p; var s []byte; func h[T ~[1]byte | [4]byte]() { tmp := T(s); var z T; _ = tmp == z}", nil},
{"slice to maybe-zero length array type parameter", "package p; var s []byte; func g[T ~[0]byte | [4]byte]() { tmp := T(s); var z T; _ = tmp == z}", nil},
{
"rune sequence to sequence cast patterns", `
package p
// Each of fXX functions describes a 1.20 legal cast between sequences of runes
// as []rune, pointers to rune arrays, rune arrays, or strings.
//
// Comments listed given the current emitted instructions [approximately].
// If multiple conversions are needed, these are seperated by |.
// rune was selected as it leads to string casts (byte is similar).
// The length 2 is not significant.
// Multiple array lengths may occur in a cast in practice (including 0).
func f00[S string, D string](s S) { _ = D(s) } // ChangeType
func f01[S string, D []rune](s S) { _ = D(s) } // Convert
func f02[S string, D []rune | string](s S) { _ = D(s) } // ChangeType | Convert
func f03[S [2]rune, D [2]rune](s S) { _ = D(s) } // ChangeType
func f04[S *[2]rune, D *[2]rune](s S) { _ = D(s) } // ChangeType
func f05[S []rune, D string](s S) { _ = D(s) } // Convert
func f06[S []rune, D [2]rune](s S) { _ = D(s) } // SliceToArrayPointer; Deref
func f07[S []rune, D [2]rune | string](s S) { _ = D(s) } // SliceToArrayPointer; Deref | Convert
func f08[S []rune, D *[2]rune](s S) { _ = D(s) } // SliceToArrayPointer
func f09[S []rune, D *[2]rune | string](s S) { _ = D(s) } // SliceToArrayPointer; Deref | Convert
func f10[S []rune, D *[2]rune | [2]rune](s S) { _ = D(s) } // SliceToArrayPointer | SliceToArrayPointer; Deref
func f11[S []rune, D *[2]rune | [2]rune | string](s S) { _ = D(s) } // SliceToArrayPointer | SliceToArrayPointer; Deref | Convert
func f12[S []rune, D []rune](s S) { _ = D(s) } // ChangeType
func f13[S []rune, D []rune | string](s S) { _ = D(s) } // Convert | ChangeType
func f14[S []rune, D []rune | [2]rune](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer; Deref
func f15[S []rune, D []rune | [2]rune | string](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer; Deref | Convert
func f16[S []rune, D []rune | *[2]rune](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer
func f17[S []rune, D []rune | *[2]rune | string](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer | Convert
func f18[S []rune, D []rune | *[2]rune | [2]rune](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer | SliceToArrayPointer; Deref
func f19[S []rune, D []rune | *[2]rune | [2]rune | string](s S) { _ = D(s) } // ChangeType | SliceToArrayPointer | SliceToArrayPointer; Deref | Convert
func f20[S []rune | string, D string](s S) { _ = D(s) } // Convert | ChangeType
func f21[S []rune | string, D []rune](s S) { _ = D(s) } // Convert | ChangeType
func f22[S []rune | string, D []rune | string](s S) { _ = D(s) } // ChangeType | Convert | Convert | ChangeType
func f23[S []rune | [2]rune, D [2]rune](s S) { _ = D(s) } // SliceToArrayPointer; Deref | ChangeType
func f24[S []rune | *[2]rune, D *[2]rune](s S) { _ = D(s) } // SliceToArrayPointer | ChangeType
`, nil,
},
{
"matching named and underlying types", `
package p
type a string
type b string
func g0[S []rune | a | b, D []rune | a | b](s S) { _ = D(s) }
func g1[S []rune | ~string, D []rune | a | b](s S) { _ = D(s) }
func g2[S []rune | a | b, D []rune | ~string](s S) { _ = D(s) }
func g3[S []rune | ~string, D []rune |~string](s S) { _ = D(s) }
`, nil,
},
}

for _, tc := range tests {
Expand All @@ -44,7 +93,8 @@ func TestBuildPackageGo120(t *testing.T) {

pkg := types.NewPackage("p", "")
conf := &types.Config{Importer: tc.importer}
if _, _, err := ssautil.BuildPackage(conf, fset, pkg, files, ssa.SanityCheckFunctions); err != nil {
_, _, err = ssautil.BuildPackage(conf, fset, pkg, files, ssa.SanityCheckFunctions)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
Expand Down
1 change: 1 addition & 0 deletions go/ssa/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
// *FieldAddr ✔ ✔
// *FreeVar ✔
// *Function ✔ ✔ (func)
// *GenericConvert ✔ ✔
// *Global ✔ ✔ (var)
// *Go ✔
// *If ✔
Expand Down
Loading

0 comments on commit a762c82

Please sign in to comment.