diff --git a/go/callgraph/vta/graph.go b/go/callgraph/vta/graph.go index ebb497149e1..2537123f4c4 100644 --- a/go/callgraph/vta/graph.go +++ b/go/callgraph/vta/graph.go @@ -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. @@ -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)) } @@ -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. diff --git a/go/ssa/builder_go120_test.go b/go/ssa/builder_go120_test.go index 04fb11a2d22..a691f938c04 100644 --- a/go/ssa/builder_go120_test.go +++ b/go/ssa/builder_go120_test.go @@ -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 { @@ -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) } }) diff --git a/go/ssa/doc.go b/go/ssa/doc.go index 13d02413a67..afda476b369 100644 --- a/go/ssa/doc.go +++ b/go/ssa/doc.go @@ -66,6 +66,7 @@ // *FieldAddr ✔ ✔ // *FreeVar ✔ // *Function ✔ ✔ (func) +// *GenericConvert ✔ ✔ // *Global ✔ ✔ (var) // *Go ✔ // *If ✔ diff --git a/go/ssa/emit.go b/go/ssa/emit.go index f52d87a16a1..1731c797506 100644 --- a/go/ssa/emit.go +++ b/go/ssa/emit.go @@ -168,32 +168,6 @@ func isValuePreserving(ut_src, ut_dst types.Type) bool { return false } -// isSliceToArrayPointer reports whether ut_src is a slice type -// that can be converted to a pointer to an array type ut_dst. -// Precondition: neither argument is a named type. -func isSliceToArrayPointer(ut_src, ut_dst types.Type) bool { - if slice, ok := ut_src.(*types.Slice); ok { - if ptr, ok := ut_dst.(*types.Pointer); ok { - if arr, ok := ptr.Elem().Underlying().(*types.Array); ok { - return types.Identical(slice.Elem(), arr.Elem()) - } - } - } - return false -} - -// isSliceToArray reports whether ut_src is a slice type -// that can be converted to an array type ut_dst. -// Precondition: neither argument is a named type. -func isSliceToArray(ut_src, ut_dst types.Type) bool { - if slice, ok := ut_src.(*types.Slice); ok { - if arr, ok := ut_dst.(*types.Array); ok { - return types.Identical(slice.Elem(), arr.Elem()) - } - } - return false -} - // emitConv emits to f code to convert Value val to exactly type typ, // and returns the converted value. Implicit conversions are required // by language assignability rules in assignments, parameter passing, @@ -208,23 +182,15 @@ func emitConv(f *Function, val Value, typ types.Type) Value { ut_dst := typ.Underlying() ut_src := t_src.Underlying() - dst_types := typeSetOf(ut_dst) - src_types := typeSetOf(ut_src) - - // Just a change of type, but not value or representation? - preserving := underIs(src_types, func(s types.Type) bool { - return underIs(dst_types, func(d types.Type) bool { - return s != nil && d != nil && isValuePreserving(s, d) // all (s -> d) are value preserving. - }) - }) - if preserving { - c := &ChangeType{X: val} - c.setType(typ) - return f.emit(c) - } - // Conversion to, or construction of a value of, an interface type? if isNonTypeParamInterface(typ) { + // Interface name change? + if isValuePreserving(ut_src, ut_dst) { + c := &ChangeType{X: val} + c.setType(typ) + return f.emit(c) + } + // Assignment from one interface type to another? if isNonTypeParamInterface(t_src) { c := &ChangeInterface{X: val} @@ -247,9 +213,83 @@ func emitConv(f *Function, val Value, typ types.Type) Value { return f.emit(mi) } + // In the common case, the typesets of src and dst are singletons + // and we emit an appropriate conversion. But if either contains + // a type parameter, the conversion may represent a cross product, + // in which case which we emit a MultiConvert. + dst_terms := typeSetOf(ut_dst) + src_terms := typeSetOf(ut_src) + + // conversionCase describes an instruction pattern that maybe emitted to + // model d <- s for d in dst_terms and s in src_terms. + // Multiple conversions can match the same pattern. + type conversionCase uint8 + const ( + changeType conversionCase = 1 << iota + sliceToArray + sliceToArrayPtr + sliceTo0Array + sliceTo0ArrayPtr + convert + ) + classify := func(s, d types.Type) conversionCase { + // Just a change of type, but not value or representation? + if isValuePreserving(s, d) { + return changeType + } + + // Conversion from slice to array or slice to array pointer? + if slice, ok := s.(*types.Slice); ok { + var arr *types.Array + var ptr bool + // Conversion from slice to array pointer? + switch d := d.(type) { + case *types.Array: + arr = d + case *types.Pointer: + arr, _ = d.Elem().Underlying().(*types.Array) + ptr = true + } + if arr != nil && types.Identical(slice.Elem(), arr.Elem()) { + if arr.Len() == 0 { + if ptr { + return sliceTo0ArrayPtr + } else { + return sliceTo0Array + } + } + if ptr { + return sliceToArrayPtr + } else { + return sliceToArray + } + } + } + + // The only remaining case in well-typed code is a representation- + // changing conversion of basic types (possibly with []byte/[]rune). + if !isBasic(s) && !isBasic(d) { + panic(fmt.Sprintf("in %s: cannot convert term %s (%s [within %s]) to type %s [within %s]", f, val, val.Type(), s, typ, d)) + } + return convert + } + + var classifications conversionCase + for _, s := range src_terms { + us := s.Type().Underlying() + for _, d := range dst_terms { + ud := d.Type().Underlying() + classifications |= classify(us, ud) + } + } + if classifications == 0 { + panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ)) + } + // Conversion of a compile-time constant value? if c, ok := val.(*Const); ok { - if isBasic(ut_dst) || c.Value == nil { + // Conversion to a basic type? + if isBasic(ut_dst) { // Conversion of a compile-time constant to // another constant type results in a new // constant of the destination type and @@ -257,42 +297,49 @@ func emitConv(f *Function, val Value, typ types.Type) Value { // We don't truncate the value yet. return NewConst(c.Value, typ) } + // Can we always convert from zero value without panicking? + const mayPanic = sliceToArray | sliceToArrayPtr + if c.Value == nil && classifications&mayPanic == 0 { + return NewConst(nil, typ) + } // We're converting from constant to non-constant type, // e.g. string -> []byte/[]rune. } - // Conversion from slice to array pointer? - slice2ptr := underIs(src_types, func(s types.Type) bool { - return underIs(dst_types, func(d types.Type) bool { - return s != nil && d != nil && isSliceToArrayPointer(s, d) // all (s->d) are slice to array pointer conversion. - }) - }) - if slice2ptr { + switch classifications { + case changeType: // representation-preserving change + c := &ChangeType{X: val} + c.setType(typ) + return f.emit(c) + + case sliceToArrayPtr, sliceTo0ArrayPtr: // slice to array pointer c := &SliceToArrayPointer{X: val} c.setType(typ) return f.emit(c) - } - // Conversion from slice to array? - slice2array := underIs(src_types, func(s types.Type) bool { - return underIs(dst_types, func(d types.Type) bool { - return s != nil && d != nil && isSliceToArray(s, d) // all (s->d) are slice to array conversion. - }) - }) - if slice2array { - return emitSliceToArray(f, val, typ) - } + case sliceToArray: // slice to arrays (not zero-length) + ptype := types.NewPointer(typ) + p := &SliceToArrayPointer{X: val} + p.setType(ptype) + x := f.emit(p) + unOp := &UnOp{Op: token.MUL, X: x} + unOp.setType(typ) + return f.emit(unOp) + + case sliceTo0Array: // slice to zero-length arrays (constant) + return zeroConst(typ) - // A representation-changing conversion? - // All of ut_src or ut_dst is basic, byte slice, or rune slice? - if isBasicConvTypes(src_types) || isBasicConvTypes(dst_types) { + case convert: // representation-changing conversion c := &Convert{X: val} c.setType(typ) return f.emit(c) - } - panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ)) + default: // multiple conversion + c := &MultiConvert{X: val, from: src_terms, to: dst_terms} + c.setType(typ) + return f.emit(c) + } } // emitTypeCoercion emits to f code to coerce the type of a @@ -490,48 +537,6 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast. return v } -// emitSliceToArray emits to f code to convert a slice value to an array value. -// -// Precondition: all types in type set of typ are arrays and convertible to all -// types in the type set of val.Type(). -func emitSliceToArray(f *Function, val Value, typ types.Type) Value { - // Emit the following: - // if val == nil && len(typ) == 0 { - // ptr = &[0]T{} - // } else { - // ptr = SliceToArrayPointer(val) - // } - // v = *ptr - - ptype := types.NewPointer(typ) - p := &SliceToArrayPointer{X: val} - p.setType(ptype) - ptr := f.emit(p) - - nilb := f.newBasicBlock("slicetoarray.nil") - nonnilb := f.newBasicBlock("slicetoarray.nonnil") - done := f.newBasicBlock("slicetoarray.done") - - cond := emitCompare(f, token.EQL, ptr, zeroConst(ptype), token.NoPos) - emitIf(f, cond, nilb, nonnilb) - f.currentBlock = nilb - - zero := f.addLocal(typ, token.NoPos) - emitJump(f, done) - f.currentBlock = nonnilb - - emitJump(f, done) - f.currentBlock = done - - phi := &Phi{Edges: []Value{zero, ptr}, Comment: "slicetoarray"} - phi.pos = val.Pos() - phi.setType(ptype) - x := f.emit(phi) - unOp := &UnOp{Op: token.MUL, X: x} - unOp.setType(typ) - return f.emit(unOp) -} - // zeroValue emits to f code to produce a zero value of type t, // and returns it. func zeroValue(f *Function, t types.Type) Value { diff --git a/go/ssa/interp/ops.go b/go/ssa/interp/ops.go index 0ea10da9e02..39830bc8fcb 100644 --- a/go/ssa/interp/ops.go +++ b/go/ssa/interp/ops.go @@ -932,6 +932,8 @@ func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value { } else { err = fmt.Sprintf("interface conversion: interface is %s, not %s", itf.t, instr.AssertedType) } + // Note: if instr.Underlying==true ever becomes reachable from interp check that + // types.Identical(itf.t.Underlying(), instr.AssertedType) if err != "" { if !instr.CommaOk { diff --git a/go/ssa/print.go b/go/ssa/print.go index e40bbfa2d21..8b783196e49 100644 --- a/go/ssa/print.go +++ b/go/ssa/print.go @@ -51,6 +51,14 @@ func relType(t types.Type, from *types.Package) string { return s } +func relTerm(term *typeparams.Term, from *types.Package) string { + s := relType(term.Type(), from) + if term.Tilde() { + return "~" + s + } + return s +} + func relString(m Member, from *types.Package) string { // NB: not all globals have an Object (e.g. init$guard), // so use Package().Object not Object.Package(). @@ -174,6 +182,24 @@ func (v *ChangeInterface) String() string { return printConv("change interfa func (v *SliceToArrayPointer) String() string { return printConv("slice to array pointer", v, v.X) } func (v *MakeInterface) String() string { return printConv("make", v, v.X) } +func (v *MultiConvert) String() string { + from := v.Parent().relPkg() + + var b strings.Builder + b.WriteString(printConv("multiconvert", v, v.X)) + b.WriteString(" [") + for i, s := range v.from { + for j, d := range v.to { + if i != 0 || j != 0 { + b.WriteString(" | ") + } + fmt.Fprintf(&b, "%s <- %s", relTerm(d, from), relTerm(s, from)) + } + } + b.WriteString("]") + return b.String() +} + func (v *MakeClosure) String() string { var b bytes.Buffer fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v)) diff --git a/go/ssa/sanity.go b/go/ssa/sanity.go index 994e2631f16..88ad374ded0 100644 --- a/go/ssa/sanity.go +++ b/go/ssa/sanity.go @@ -140,7 +140,7 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { s.errorf("convert %s -> %s: at least one type must be basic (or all basic, []byte, or []rune)", from, to) } } - + case *MultiConvert: case *Defer: case *Extract: case *Field: diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go index 3108de2e4a8..5904b817b31 100644 --- a/go/ssa/ssa.go +++ b/go/ssa/ssa.go @@ -661,6 +661,30 @@ type Convert struct { X Value } +// The MultiConvert instruction yields the conversion of value X to type +// Type(). Either X.Type() or Type() must be a type parameter. Each +// type in the type set of X.Type() can be converted to each type in the +// type set of Type(). +// +// See the documentation for Convert, ChangeType, and SliceToArrayPointer +// for the conversions that are permitted. Additionally conversions of +// slices to arrays are permitted. +// +// This operation can fail dynamically (see SliceToArrayPointer). +// +// Pos() returns the ast.CallExpr.Lparen, if the instruction arose +// from an explicit conversion in the source. +// +// Example printed form: +// +// t1 = multiconvert D <- S (t0) [*[2]rune <- []rune | string <- []rune] +type MultiConvert struct { + register + X Value + from []*typeparams.Term + to []*typeparams.Term +} + // ChangeInterface constructs a value of one interface type from a // value of another interface type known to be assignable to it. // This operation cannot fail. @@ -689,6 +713,9 @@ type ChangeInterface struct { // all types in the type set of Type() which must all be pointer to array // types. // +// This operation can fail dynamically if the length of the slice is less +// than the length of the array. +// // Example printed form: // // t1 = slice to array pointer *[4]byte <- []byte (t0) @@ -1024,6 +1051,9 @@ type Next struct { // is AssertedType's zero value. The components of the pair must be // accessed using the Extract instruction. // +// If Underlying: tests whether interface value X has the underlying +// type AssertedType. +// // If AssertedType is a concrete type, TypeAssert checks whether the // dynamic type in interface X is equal to it, and if so, the result // of the conversion is a copy of the value in the interface. @@ -1642,6 +1672,10 @@ func (v *Convert) Operands(rands []*Value) []*Value { return append(rands, &v.X) } +func (v *MultiConvert) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + func (v *SliceToArrayPointer) Operands(rands []*Value) []*Value { return append(rands, &v.X) } diff --git a/go/ssa/util.go b/go/ssa/util.go index aef57dc3ffb..db53aebee43 100644 --- a/go/ssa/util.go +++ b/go/ssa/util.go @@ -65,19 +65,19 @@ func isString(t types.Type) bool { return isBasic(t) && t.(*types.Basic).Info()&types.IsString != 0 } -// isByteSlice reports whether t is []byte. +// isByteSlice reports whether t is of the form []~bytes. func isByteSlice(t types.Type) bool { if b, ok := t.(*types.Slice); ok { - e, _ := b.Elem().(*types.Basic) + e, _ := b.Elem().Underlying().(*types.Basic) return e != nil && e.Kind() == types.Byte } return false } -// isRuneSlice reports whether t is []rune. +// isRuneSlice reports whether t is of the form []~runes. func isRuneSlice(t types.Type) bool { if b, ok := t.(*types.Slice); ok { - e, _ := b.Elem().(*types.Basic) + e, _ := b.Elem().Underlying().(*types.Basic) return e != nil && e.Kind() == types.Rune } return false