aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim King <taking@google.com>2021-09-22 09:50:15 -0700
committerTim King <taking@google.com>2022-04-22 17:17:20 +0000
commitd567bc1c220c9afb5f71dc38550ef8b0262ff544 (patch)
treebf728634567d86d32e86038077f80345d88b71c8
parent5d7ca8a1b5bc80222ed6375e345ba8b6e24e9515 (diff)
downloadgolang-x-tools-d567bc1c220c9afb5f71dc38550ef8b0262ff544.tar.gz
go/ssa: monomorphize generic instantiations.
Monomorphize the instantiation of generic functions. Applies type substitution while building the function instantiation. Adds a new BuilderMode, ssa.InstantiateGenerics, to enable monomorphization. InstantiateGenerics is turned on by the flag 'G' in tools that specify the BuilderMode. Adds a parameterized field to Program to detect when a MethodValue is parameterized. Thunk creation creates new MethodExpr selections. Adds a new methodExpr type to construct a MethodExpr from outside of types, and selection interface to generalize a *methodExpr and *types.Selection. Tests x/tools/go/ssa/interp against the runnable examples in $GOROOT/test/typeparam/*.go. Some additional models to support files. Misc. cleanup: - adding (*canonizer).instantiateMethod to create a canonical representative of a method. - documenting builder.go - adding (*subster).types that applies type substitution to a list. Updates golang/go#48525 Change-Id: I885a4223900feaa3664e35caf8618d11ba16a2a7 Reviewed-on: https://go-review.googlesource.com/c/tools/+/356315 Reviewed-by: Dominik Honnef <dominik@honnef.co> Reviewed-by: Robert Findley <rfindley@google.com> Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com> Run-TryBot: Tim King <taking@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
-rw-r--r--cmd/callgraph/main.go3
-rw-r--r--cmd/ssadump/main.go10
-rw-r--r--go/analysis/passes/buildssa/buildssa.go3
-rw-r--r--go/ssa/builder.go165
-rw-r--r--go/ssa/builder_test.go164
-rw-r--r--go/ssa/create.go3
-rw-r--r--go/ssa/example_test.go2
-rw-r--r--go/ssa/func.go34
-rw-r--r--go/ssa/instantiate.go26
-rw-r--r--go/ssa/instantiate_test.go128
-rw-r--r--go/ssa/interp/interp.go10
-rw-r--r--go/ssa/interp/interp_test.go72
-rw-r--r--go/ssa/interp/testdata/src/encoding/encoding.go15
-rw-r--r--go/ssa/interp/testdata/src/log/log.go8
-rw-r--r--go/ssa/mode.go7
-rw-r--r--go/ssa/sanity.go11
-rw-r--r--go/ssa/source_test.go6
-rw-r--r--go/ssa/ssa.go16
-rw-r--r--go/ssa/ssautil/load_test.go10
-rw-r--r--go/ssa/ssautil/switch_test.go2
-rw-r--r--go/ssa/stdlib_test.go1
-rw-r--r--go/ssa/subst.go11
-rw-r--r--go/ssa/util.go41
-rw-r--r--go/ssa/wrappers.go45
24 files changed, 557 insertions, 236 deletions
diff --git a/cmd/callgraph/main.go b/cmd/callgraph/main.go
index f83be0ea5..b5533ac7a 100644
--- a/cmd/callgraph/main.go
+++ b/cmd/callgraph/main.go
@@ -187,7 +187,8 @@ func doCallgraph(dir, gopath, algo, format string, tests bool, args []string) er
}
// Create and build SSA-form program representation.
- prog, pkgs := ssautil.AllPackages(initial, 0)
+ mode := ssa.InstantiateGenerics // instantiate generics by default for soundness
+ prog, pkgs := ssautil.AllPackages(initial, mode)
prog.Build()
// -- call graph construction ------------------------------------------
diff --git a/cmd/ssadump/main.go b/cmd/ssadump/main.go
index fee931b1c..138e7f69f 100644
--- a/cmd/ssadump/main.go
+++ b/cmd/ssadump/main.go
@@ -47,7 +47,7 @@ func init() {
}
const usage = `SSA builder and interpreter.
-Usage: ssadump [-build=[DBCSNFL]] [-test] [-run] [-interp=[TR]] [-arg=...] package...
+Usage: ssadump [-build=[DBCSNFLG]] [-test] [-run] [-interp=[TR]] [-arg=...] package...
Use -help flag to display options.
Examples:
@@ -55,7 +55,8 @@ Examples:
% ssadump -build=F -test fmt # dump SSA form of a package and its tests
% ssadump -run -interp=T hello.go # interpret a program, with tracing
-The -run flag causes ssadump to run the first package named main.
+The -run flag causes ssadump to build the code in a runnable form and run the first
+package named main.
Interpretation of the standard "testing" package is no longer supported.
`
@@ -130,6 +131,11 @@ func doMain() error {
return fmt.Errorf("packages contain errors")
}
+ // Turn on instantiating generics during build if the program will be run.
+ if *runFlag {
+ mode |= ssa.InstantiateGenerics
+ }
+
// Create SSA-form program representation.
prog, pkgs := ssautil.AllPackages(initial, mode)
diff --git a/go/analysis/passes/buildssa/buildssa.go b/go/analysis/passes/buildssa/buildssa.go
index 02b7b18b3..4ec0e73ff 100644
--- a/go/analysis/passes/buildssa/buildssa.go
+++ b/go/analysis/passes/buildssa/buildssa.go
@@ -48,7 +48,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
// Some Analyzers may need GlobalDebug, in which case we'll have
// to set it globally, but let's wait till we need it.
- mode := ssa.BuilderMode(0)
+ // Monomorphize at least until type parameters are available.
+ mode := ssa.InstantiateGenerics
prog := ssa.NewProgram(pass.Fset, mode)
diff --git a/go/ssa/builder.go b/go/ssa/builder.go
index 4a3eed971..7bfca0207 100644
--- a/go/ssa/builder.go
+++ b/go/ssa/builder.go
@@ -24,14 +24,62 @@ package ssa
// TODO(adonovan): indeed, building functions is now embarrassingly parallel.
// Audit for concurrency then benchmark using more goroutines.
//
-// The builder's and Program's indices (maps) are populated and
+// State:
+//
+// The Package's and Program's indices (maps) are populated and
// mutated during the CREATE phase, but during the BUILD phase they
// remain constant. The sole exception is Prog.methodSets and its
// related maps, which are protected by a dedicated mutex.
//
+// Generic functions declared in a package P can be instantiated from functions
+// outside of P. This happens independently of the CREATE and BUILD phase of P.
+//
+// Locks:
+//
+// Mutexes are currently acquired according to the following order:
+// Prog.methodsMu ⊃ canonizer.mu ⊃ printMu
+// where x ⊃ y denotes that y can be acquired while x is held
+// and x cannot be acquired while y is held.
+//
+// Synthetics:
+//
+// During the BUILD phase new functions can be created and built. These include:
+// - wrappers (wrappers, bounds, thunks)
+// - generic function instantiations
+// These functions do not belong to a specific Pkg (Pkg==nil). Instead the
+// Package that led to them being CREATED is obligated to ensure these
+// are BUILT during the BUILD phase of the Package.
+//
+// Runtime types:
+//
+// A concrete type is a type that is fully monomorphized with concrete types,
+// i.e. it cannot reach a TypeParam type.
+// Some concrete types require full runtime type information. Cases
+// include checking whether a type implements an interface or
+// interpretation by the reflect package. All such types that may require
+// this information will have all of their method sets built and will be added to Prog.methodSets.
+// A type T is considered to require runtime type information if it is
+// a runtime type and has a non-empty method set and either:
+// - T flows into a MakeInterface instructions,
+// - T appears in a concrete exported member, or
+// - T is a type reachable from a type S that has non-empty method set.
+// For any such type T, method sets must be created before the BUILD
+// phase of the package is done.
+//
+// Function literals:
+//
+// The BUILD phase of a function literal (anonymous function) is tied to the
+// BUILD phase of the enclosing parent function. The FreeVars of an anonymous
+// function are discovered by building the anonymous function. This in turn
+// changes which variables must be bound in a MakeClosure instruction in the
+// parent. Anonymous functions also track where they are referred to in their
+// parent function.
+//
// Happens-before:
//
-// The happens-before constraints (with X<Y denoting X happens-before Y) are:
+// The above discussion leads to the following happens-before relation for
+// the BUILD and CREATE phases.
+// The happens-before relation (with X<Y denoting X happens-before Y) are:
// - CREATE fn < fn.startBody() < fn.finishBody() < fn.built
// for any function fn.
// - anon.parent.startBody() < CREATE anon, and
@@ -43,6 +91,16 @@ package ssa
// for any function fn created during the CREATE or BUILD phase of a package
// pkg. This includes declared and synthetic functions.
//
+// Program.MethodValue:
+//
+// Program.MethodValue may trigger new wrapper and instantiation functions to
+// be created. It has the same obligation to BUILD created functions as a
+// Package.
+//
+// Program.NewFunction:
+//
+// This is a low level operation for creating functions that do not exist in
+// the source. Use with caution.
import (
"fmt"
@@ -75,7 +133,7 @@ var (
tString = types.Typ[types.String]
tUntypedNil = types.Typ[types.UntypedNil]
tRangeIter = &opaqueType{nil, "iter"} // the type of all "range" iterators
- tEface = types.NewInterface(nil, nil).Complete()
+ tEface = types.NewInterfaceType(nil, nil).Complete()
// SSA Value constants.
vZero = intConst(0)
@@ -248,6 +306,7 @@ func (b *builder) exprN(fn *Function, e ast.Expr) Value {
// the caller should treat this like an ordinary library function
// call.
func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ types.Type, pos token.Pos) Value {
+ typ = fn.typ(typ)
switch obj.Name() {
case "make":
switch typ.Underlying().(type) {
@@ -538,7 +597,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
// Is expression a constant?
if tv.Value != nil {
- return NewConst(tv.Value, tv.Type)
+ return NewConst(tv.Value, fn.typ(tv.Type))
}
var v Value
@@ -563,14 +622,18 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
case *ast.FuncLit:
fn2 := &Function{
- name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)),
- Signature: fn.typeOf(e.Type).Underlying().(*types.Signature),
- pos: e.Type.Func,
- parent: fn,
- Pkg: fn.Pkg,
- Prog: fn.Prog,
- syntax: e,
- info: fn.info,
+ name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)),
+ Signature: fn.typeOf(e.Type).Underlying().(*types.Signature),
+ pos: e.Type.Func,
+ parent: fn,
+ Pkg: fn.Pkg,
+ Prog: fn.Prog,
+ syntax: e,
+ _Origin: nil, // anon funcs do not have an origin.
+ _TypeParams: fn._TypeParams, // share the parent's type parameters.
+ _TypeArgs: fn._TypeArgs, // share the parent's type arguments.
+ info: fn.info,
+ subst: fn.subst, // share the parent's type substitutions.
}
fn.AnonFuncs = append(fn.AnonFuncs, fn2)
b.created.Add(fn2)
@@ -581,7 +644,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
return fn2
}
v := &MakeClosure{Fn: fn2}
- v.setType(tv.Type)
+ v.setType(fn.typ(tv.Type))
for _, fv := range fn2.FreeVars {
v.Bindings = append(v.Bindings, fv.outer)
fv.outer = nil
@@ -589,13 +652,13 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
return fn.emit(v)
case *ast.TypeAssertExpr: // single-result form only
- return emitTypeAssert(fn, b.expr(fn, e.X), tv.Type, e.Lparen)
+ return emitTypeAssert(fn, b.expr(fn, e.X), fn.typ(tv.Type), e.Lparen)
case *ast.CallExpr:
if fn.info.Types[e.Fun].IsType() {
// Explicit type conversion, e.g. string(x) or big.Int(x)
x := b.expr(fn, e.Args[0])
- y := emitConv(fn, x, tv.Type)
+ y := emitConv(fn, x, fn.typ(tv.Type))
if y != x {
switch y := y.(type) {
case *Convert:
@@ -613,7 +676,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
// Call to "intrinsic" built-ins, e.g. new, make, panic.
if id, ok := unparen(e.Fun).(*ast.Ident); ok {
if obj, ok := fn.info.Uses[id].(*types.Builtin); ok {
- if v := b.builtin(fn, obj, e.Args, tv.Type, e.Lparen); v != nil {
+ if v := b.builtin(fn, obj, e.Args, fn.typ(tv.Type), e.Lparen); v != nil {
return v
}
}
@@ -621,7 +684,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
// Regular function call.
var v Call
b.setCall(fn, e, &v.Call)
- v.setType(tv.Type)
+ v.setType(fn.typ(tv.Type))
return fn.emit(&v)
case *ast.UnaryExpr:
@@ -644,7 +707,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
X: b.expr(fn, e.X),
}
v.setPos(e.OpPos)
- v.setType(tv.Type)
+ v.setType(fn.typ(tv.Type))
return fn.emit(v)
default:
panic(e.Op)
@@ -657,12 +720,12 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
case token.SHL, token.SHR:
fallthrough
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
- return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), tv.Type, e.OpPos)
+ return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), fn.typ(tv.Type), e.OpPos)
case token.EQL, token.NEQ, token.GTR, token.LSS, token.LEQ, token.GEQ:
cmp := emitCompare(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), e.OpPos)
// The type of x==y may be UntypedBool.
- return emitConv(fn, cmp, types.Default(tv.Type))
+ return emitConv(fn, cmp, types.Default(fn.typ(tv.Type)))
default:
panic("illegal op in BinaryExpr: " + e.Op.String())
}
@@ -695,7 +758,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
Max: max,
}
v.setPos(e.Lbrack)
- v.setType(tv.Type)
+ v.setType(fn.typ(tv.Type))
return fn.emit(v)
case *ast.Ident:
@@ -703,9 +766,9 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
// Universal built-in or nil?
switch obj := obj.(type) {
case *types.Builtin:
- return &Builtin{name: obj.Name(), sig: tv.Type.(*types.Signature)}
+ return &Builtin{name: obj.Name(), sig: fn.instanceType(e).(*types.Signature)}
case *types.Nil:
- return nilConst(tv.Type)
+ return nilConst(fn.instanceType(e))
}
// Package-level func or var?
if v := fn.Prog.packageLevelMember(obj); v != nil {
@@ -714,7 +777,8 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
}
callee := v.(*Function) // (func)
if len(callee._TypeParams) > 0 {
- callee = fn.Prog.needsInstance(callee, fn.instanceArgs(e), b.created)
+ targs := fn.subst.types(instanceArgs(fn.info, e))
+ callee = fn.Prog.needsInstance(callee, targs, b.created)
}
return callee
}
@@ -726,7 +790,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
if !ok {
// builtin unsafe.{Add,Slice}
if obj, ok := fn.info.Uses[e.Sel].(*types.Builtin); ok {
- return &Builtin{name: obj.Name(), sig: tv.Type.(*types.Signature)}
+ return &Builtin{name: obj.Name(), sig: fn.typ(tv.Type).(*types.Signature)}
}
// qualified identifier
return b.expr(fn, e.Sel)
@@ -735,13 +799,23 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
case types.MethodExpr:
// (*T).f or T.f, the method f from the method-set of type T.
// The result is a "thunk".
- return emitConv(fn, makeThunk(fn.Prog, sel, b.created), tv.Type)
+
+ sel := selection(sel)
+ if base := fn.typ(sel.Recv()); base != sel.Recv() {
+ // instantiate sel as sel.Recv is not equal after substitution.
+ pkg := fn.declaredPackage().Pkg
+ // mv is the instantiated method value.
+ mv := types.NewMethodSet(base).Lookup(pkg, sel.Obj().Name())
+ sel = toMethodExpr(mv)
+ }
+ thunk := makeThunk(fn.Prog, sel, b.created)
+ return emitConv(fn, thunk, fn.typ(tv.Type))
case types.MethodVal:
// e.f where e is an expression and f is a method.
// The result is a "bound".
obj := sel.Obj().(*types.Func)
- rt := recvType(obj)
+ rt := fn.typ(recvType(obj))
wantAddr := isPointer(rt)
escaping := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel)
@@ -751,12 +825,16 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
// We use: typeassert v.(I).
emitTypeAssert(fn, v, rt, token.NoPos)
}
+ if targs := receiverTypeArgs(obj); len(targs) > 0 {
+ // obj is generic.
+ obj = fn.Prog.canon.instantiateMethod(obj, fn.subst.types(targs), fn.Prog.ctxt)
+ }
c := &MakeClosure{
Fn: makeBound(fn.Prog, obj, b.created),
Bindings: []Value{v},
}
c.setPos(e.Sel.Pos())
- c.setType(tv.Type)
+ c.setType(fn.typ(tv.Type))
return fn.emit(c)
case types.FieldVal:
@@ -782,6 +860,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
return b.expr(fn, ident)
}
}
+ // not a generic instantiation.
switch t := fn.typeOf(e.X).Underlying().(type) {
case *types.Array:
// Non-addressable array (in a register).
@@ -798,14 +877,12 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
return fn.emit(v)
case *types.Map:
- // Maps are not addressable.
- mapt := fn.typeOf(e.X).Underlying().(*types.Map)
v := &Lookup{
X: b.expr(fn, e.X),
- Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key()),
+ Index: emitConv(fn, b.expr(fn, e.Index), t.Key()),
}
v.setPos(e.Lbrack)
- v.setType(mapt.Elem())
+ v.setType(t.Elem())
return fn.emit(v)
case *types.Basic: // => string
@@ -883,7 +960,14 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
sel, ok := fn.info.Selections[selector]
if ok && sel.Kind() == types.MethodVal {
obj := sel.Obj().(*types.Func)
+ if recv := fn.typ(sel.Recv()); recv != sel.Recv() {
+ // adjust obj if the sel.Recv() changed during monomorphization.
+ pkg := fn.declaredPackage().Pkg
+ method, _, _ := types.LookupFieldOrMethod(recv, true, pkg, sel.Obj().Name())
+ obj = method.(*types.Func)
+ }
recv := recvType(obj)
+
wantAddr := isPointer(recv)
escaping := true
v := b.receiver(fn, selector.X, wantAddr, escaping, sel)
@@ -1532,12 +1616,12 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
case *ast.SendStmt: // ch<- i
ch := b.expr(fn, comm.Chan)
+ chtyp := fn.typ(ch.Type()).Underlying().(*types.Chan)
st = &SelectState{
Dir: types.SendOnly,
Chan: ch,
- Send: emitConv(fn, b.expr(fn, comm.Value),
- ch.Type().Underlying().(*types.Chan).Elem()),
- Pos: comm.Arrow,
+ Send: emitConv(fn, b.expr(fn, comm.Value), chtyp.Elem()),
+ Pos: comm.Arrow,
}
if debugInfo {
st.DebugNode = comm
@@ -1589,7 +1673,8 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
vars = append(vars, varIndex, varOk)
for _, st := range states {
if st.Dir == types.RecvOnly {
- tElem := st.Chan.Type().Underlying().(*types.Chan).Elem()
+ chtyp := fn.typ(st.Chan.Type()).Underlying().(*types.Chan)
+ tElem := chtyp.Elem()
vars = append(vars, anonVar(tElem))
}
}
@@ -2208,13 +2293,17 @@ func (b *builder) buildFunctionBody(fn *Function) {
if fn.Blocks != nil {
return // building already started
}
+
var recvField *ast.FieldList
var body *ast.BlockStmt
var functype *ast.FuncType
switch n := fn.syntax.(type) {
case nil:
// TODO(taking): Temporarily this can be the body of a generic function.
- return // not a Go source function. (Synthetic, or from object file.)
+ if fn.Params != nil {
+ return // not a Go source function. (Synthetic, or from object file.)
+ }
+ // fn.Params == nil is handled within body == nil case.
case *ast.FuncDecl:
functype = n.Type
recvField = n.Recv
@@ -2345,6 +2434,8 @@ func (p *Package) build() {
}
// Ensure we have runtime type info for all exported members.
+ // Additionally filter for just concrete types that can be runtime types.
+ //
// TODO(adonovan): ideally belongs in memberFromObject, but
// that would require package creation in topological order.
for name, mem := range p.Members {
diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go
index 3fd9a8ae2..6b9c79803 100644
--- a/go/ssa/builder_test.go
+++ b/go/ssa/builder_test.go
@@ -42,7 +42,7 @@ import (
func main() {
var t testing.T
- t.Parallel() // static call to external declared method
+ t.Parallel() // static call to external declared method
t.Fail() // static call to promoted external declared method
testing.Short() // static call to external package-level function
@@ -61,8 +61,9 @@ func main() {
// Build an SSA program from the parsed file.
// Load its dependencies from gc binary export data.
+ mode := ssa.SanityCheckFunctions
mainPkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
- types.NewPackage("main", ""), []*ast.File{f}, ssa.SanityCheckFunctions)
+ types.NewPackage("main", ""), []*ast.File{f}, mode)
if err != nil {
t.Error(err)
return
@@ -230,8 +231,9 @@ func TestRuntimeTypes(t *testing.T) {
// Create a single-file main package.
// Load dependencies from gc binary export data.
+ mode := ssa.SanityCheckFunctions
ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset,
- types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions)
+ types.NewPackage("p", ""), []*ast.File{f}, mode)
if err != nil {
t.Errorf("test %q: %s", test.input[:15], err)
continue
@@ -378,7 +380,7 @@ var (
}
// Create and build SSA
- prog := ssautil.CreateProgram(lprog, 0)
+ prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0))
prog.Build()
// Enumerate reachable synthetic functions
@@ -484,7 +486,7 @@ func h(error)
}
// Create and build SSA
- prog := ssautil.CreateProgram(lprog, 0)
+ prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0))
p := prog.Package(lprog.Package("p").Pkg)
p.Build()
g := p.Func("g")
@@ -553,7 +555,7 @@ func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
}
// Create and build SSA
- prog := ssautil.CreateProgram(lprog, 0)
+ prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0))
p := prog.Package(lprog.Package("p").Pkg)
p.Build()
@@ -608,83 +610,85 @@ var indirect = R[int].M
t.Fatalf("Load: %v", err)
}
- // Create and build SSA
- prog := ssautil.CreateProgram(lprog, 0)
- p := prog.Package(lprog.Package("p").Pkg)
- p.Build()
-
- for _, entry := range []struct {
- name string // name of the package variable
- typ string // type of the package variable
- wrapper string // wrapper function to which the package variable is set
- callee string // callee within the wrapper function
- }{
- {
- "bound",
- "*func() int",
- "(p.S[int]).M$bound",
- "(p.S[int]).M[[int]]",
- },
- {
- "thunk",
- "*func(p.S[int]) int",
- "(p.S[int]).M$thunk",
- "(p.S[int]).M[[int]]",
- },
- {
- "indirect",
- "*func(p.R[int]) int",
- "(p.R[int]).M$thunk",
- "(p.S[int]).M[[int]]",
- },
- } {
- entry := entry
- t.Run(entry.name, func(t *testing.T) {
- v := p.Var(entry.name)
- if v == nil {
- t.Fatalf("Did not find variable for %q in %s", entry.name, p.String())
- }
- if v.Type().String() != entry.typ {
- t.Errorf("Expected type for variable %s: %q. got %q", v, entry.typ, v.Type())
- }
+ for _, mode := range []ssa.BuilderMode{ssa.BuilderMode(0), ssa.InstantiateGenerics} {
+ // Create and build SSA
+ prog := ssautil.CreateProgram(lprog, mode)
+ p := prog.Package(lprog.Package("p").Pkg)
+ p.Build()
+
+ for _, entry := range []struct {
+ name string // name of the package variable
+ typ string // type of the package variable
+ wrapper string // wrapper function to which the package variable is set
+ callee string // callee within the wrapper function
+ }{
+ {
+ "bound",
+ "*func() int",
+ "(p.S[int]).M$bound",
+ "(p.S[int]).M[[int]]",
+ },
+ {
+ "thunk",
+ "*func(p.S[int]) int",
+ "(p.S[int]).M$thunk",
+ "(p.S[int]).M[[int]]",
+ },
+ {
+ "indirect",
+ "*func(p.R[int]) int",
+ "(p.R[int]).M$thunk",
+ "(p.S[int]).M[[int]]",
+ },
+ } {
+ entry := entry
+ t.Run(entry.name, func(t *testing.T) {
+ v := p.Var(entry.name)
+ if v == nil {
+ t.Fatalf("Did not find variable for %q in %s", entry.name, p.String())
+ }
+ if v.Type().String() != entry.typ {
+ t.Errorf("Expected type for variable %s: %q. got %q", v, entry.typ, v.Type())
+ }
- // Find the wrapper for v. This is stored exactly once in init.
- var wrapper *ssa.Function
- for _, bb := range p.Func("init").Blocks {
- for _, i := range bb.Instrs {
- if store, ok := i.(*ssa.Store); ok && v == store.Addr {
- switch val := store.Val.(type) {
- case *ssa.Function:
- wrapper = val
- case *ssa.MakeClosure:
- wrapper = val.Fn.(*ssa.Function)
+ // Find the wrapper for v. This is stored exactly once in init.
+ var wrapper *ssa.Function
+ for _, bb := range p.Func("init").Blocks {
+ for _, i := range bb.Instrs {
+ if store, ok := i.(*ssa.Store); ok && v == store.Addr {
+ switch val := store.Val.(type) {
+ case *ssa.Function:
+ wrapper = val
+ case *ssa.MakeClosure:
+ wrapper = val.Fn.(*ssa.Function)
+ }
}
}
}
- }
- if wrapper == nil {
- t.Fatalf("failed to find wrapper function for %s", entry.name)
- }
- if wrapper.String() != entry.wrapper {
- t.Errorf("Expected wrapper function %q. got %q", wrapper, entry.wrapper)
- }
+ if wrapper == nil {
+ t.Fatalf("failed to find wrapper function for %s", entry.name)
+ }
+ if wrapper.String() != entry.wrapper {
+ t.Errorf("Expected wrapper function %q. got %q", wrapper, entry.wrapper)
+ }
- // Find the callee within the wrapper. There should be exactly one call.
- var callee *ssa.Function
- for _, bb := range wrapper.Blocks {
- for _, i := range bb.Instrs {
- if call, ok := i.(*ssa.Call); ok {
- callee = call.Call.StaticCallee()
+ // Find the callee within the wrapper. There should be exactly one call.
+ var callee *ssa.Function
+ for _, bb := range wrapper.Blocks {
+ for _, i := range bb.Instrs {
+ if call, ok := i.(*ssa.Call); ok {
+ callee = call.Call.StaticCallee()
+ }
}
}
- }
- if callee == nil {
- t.Fatalf("failed to find callee within wrapper %s", wrapper)
- }
- if callee.String() != entry.callee {
- t.Errorf("Expected callee in wrapper %q is %q. got %q", v, entry.callee, callee)
- }
- })
+ if callee == nil {
+ t.Fatalf("failed to find callee within wrapper %s", wrapper)
+ }
+ if callee.String() != entry.callee {
+ t.Errorf("Expected callee in wrapper %q is %q. got %q", v, entry.callee, callee)
+ }
+ })
+ }
}
}
@@ -707,6 +711,9 @@ func TestTypeparamTest(t *testing.T) {
}
for _, entry := range list {
+ if entry.Name() == "issue376214.go" {
+ continue // investigate variadic + New signature.
+ }
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
continue // Consider standalone go files.
}
@@ -746,7 +753,8 @@ func TestTypeparamTest(t *testing.T) {
t.Fatalf("conf.Load(%s) failed: %s", input, err)
}
- prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
+ mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics
+ prog := ssautil.CreateProgram(iprog, mode)
prog.Build()
})
}
@@ -784,7 +792,7 @@ func sliceMax(s []int) []int { return s[a():b():c()] }
}
// Create and build SSA
- prog := ssautil.CreateProgram(lprog, 0)
+ prog := ssautil.CreateProgram(lprog, ssa.BuilderMode(0))
p := prog.Package(lprog.Package("p").Pkg)
p.Build()
diff --git a/go/ssa/create.go b/go/ssa/create.go
index 403baae28..345d9acfb 100644
--- a/go/ssa/create.go
+++ b/go/ssa/create.go
@@ -38,6 +38,7 @@ func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
h := typeutil.MakeHasher() // protected by methodsMu, in effect
prog.methodSets.SetHasher(h)
+ prog.runtimeTypes.SetHasher(h)
return prog
}
@@ -106,8 +107,8 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
pos: obj.Pos(),
Pkg: pkg,
Prog: pkg.Prog,
- info: pkg.info,
_TypeParams: tparams,
+ info: pkg.info,
}
pkg.created.Add(fn)
if syntax == nil {
diff --git a/go/ssa/example_test.go b/go/ssa/example_test.go
index 15578319c..492a02f76 100644
--- a/go/ssa/example_test.go
+++ b/go/ssa/example_test.go
@@ -158,7 +158,7 @@ func Example_loadWholeProgram() {
}
// Create SSA packages for well-typed packages and their dependencies.
- prog, pkgs := ssautil.AllPackages(initial, ssa.PrintPackages)
+ prog, pkgs := ssautil.AllPackages(initial, ssa.PrintPackages|ssa.InstantiateGenerics)
_ = pkgs
// Build SSA code for the whole program.
diff --git a/go/ssa/func.go b/go/ssa/func.go
index 48ed5700d..c864b495e 100644
--- a/go/ssa/func.go
+++ b/go/ssa/func.go
@@ -33,23 +33,23 @@ func (f *Function) objectOf(id *ast.Ident) types.Object {
// Only valid during f's create and build phases.
func (f *Function) typeOf(e ast.Expr) types.Type {
if T := f.info.TypeOf(e); T != nil {
- return T
+ return f.typ(T)
}
panic(fmt.Sprintf("no type for %T @ %s", e, f.Prog.Fset.Position(e.Pos())))
}
-// instanceArgs returns the Instance[id].TypeArgs within the context of fn.
-func (fn *Function) instanceArgs(id *ast.Ident) []types.Type {
- targList := typeparams.GetInstances(fn.info)[id].TypeArgs
- if targList == nil {
- return nil
- }
+// typ is the locally instantiated type of T. T==typ(T) if f is not an instantiation.
+func (f *Function) typ(T types.Type) types.Type {
+ return f.subst.typ(T)
+}
- targs := make([]types.Type, targList.Len())
- for i, n := 0, targList.Len(); i < n; i++ {
- targs[i] = targList.At(i)
+// If id is an Instance, returns info.Instances[id].Type.
+// Otherwise returns f.typeOf(id).
+func (f *Function) instanceType(id *ast.Ident) types.Type {
+ if t, ok := typeparams.GetInstances(f.info)[id]; ok {
+ return t.Type
}
- return targs
+ return f.typeOf(id)
}
// Destinations associated with unlabelled for/switch/select stmts.
@@ -103,7 +103,7 @@ func (f *Function) addParamObj(obj types.Object) *Parameter {
if name == "" {
name = fmt.Sprintf("arg%d", len(f.Params))
}
- param := f.addParam(name, obj.Type(), obj.Pos())
+ param := f.addParam(name, f.typ(obj.Type()), obj.Pos())
param.object = obj
return param
}
@@ -114,7 +114,7 @@ func (f *Function) addParamObj(obj types.Object) *Parameter {
func (f *Function) addSpilledParam(obj types.Object) {
param := f.addParamObj(obj)
spill := &Alloc{Comment: obj.Name()}
- spill.setType(types.NewPointer(obj.Type()))
+ spill.setType(types.NewPointer(param.Type()))
spill.setPos(obj.Pos())
f.objects[obj] = spill
f.Locals = append(f.Locals, spill)
@@ -273,7 +273,7 @@ func (f *Function) finishBody() {
f.info = nil
f.subst = nil
- numberRegisters(f)
+ numberRegisters(f) // uses f.namedRegisters
}
// After this, function is done with BUILD phase.
@@ -350,6 +350,7 @@ func (f *Function) addLocalForIdent(id *ast.Ident) *Alloc {
// addLocal creates an anonymous local variable of type typ, adds it
// to function f and returns it. pos is the optional source location.
func (f *Function) addLocal(typ types.Type, pos token.Pos) *Alloc {
+ typ = f.typ(typ)
v := &Alloc{}
v.setType(types.NewPointer(typ))
v.setPos(pos)
@@ -482,9 +483,8 @@ func (fn *Function) declaredPackage() *Package {
switch {
case fn.Pkg != nil:
return fn.Pkg // non-generic function
- // generics:
- // case fn.Origin != nil:
- // return fn.Origin.pkg // instance of a named generic function
+ case fn._Origin != nil:
+ return fn._Origin.Pkg // instance of a named generic function
case fn.parent != nil:
return fn.parent.declaredPackage() // instance of an anonymous [generic] function
default:
diff --git a/go/ssa/instantiate.go b/go/ssa/instantiate.go
index 9934839ba..65c1e9970 100644
--- a/go/ssa/instantiate.go
+++ b/go/ssa/instantiate.go
@@ -102,11 +102,12 @@ func (insts *instanceSet) lookupOrCreate(targs []types.Type, cr *creator) *Funct
if inst, ok := insts.instances[key]; ok {
return inst
}
- instance := createInstance(insts.fn, targs, insts.info, insts.syntax, cr)
-
- // TODO(taking): Allow for the function to be built after monomorphization is supported.
- instance.syntax = nil // treat instance as an external function to prevent building.
+ var syntax ast.Node
+ if insts.syntax != nil {
+ syntax = insts.syntax
+ }
+ instance := createInstance(insts.fn, targs, insts.info, syntax, cr)
insts.instances[key] = instance
return instance
}
@@ -120,19 +121,8 @@ func createInstance(fn *Function, targs []types.Type, info *types.Info, syntax a
var obj *types.Func
if recv := fn.Signature.Recv(); recv != nil {
// method
- // instantiates m with targs and returns a canonical representative for this method.
m := fn.object.(*types.Func)
- recv := recvType(m)
- if p, ok := recv.(*types.Pointer); ok {
- recv = p.Elem()
- }
- named := recv.(*types.Named)
- inst, err := typeparams.Instantiate(prog.ctxt, typeparams.NamedTypeOrigin(named), targs, false)
- if err != nil {
- panic(err)
- }
- canon, _, _ := types.LookupFieldOrMethod(prog.canon.Type(inst), true, m.Pkg(), m.Name())
- obj = canon.(*types.Func)
+ obj = prog.canon.instantiateMethod(m, targs, prog.ctxt)
sig = obj.Type().(*types.Signature)
} else {
instSig, err := typeparams.Instantiate(prog.ctxt, fn.Signature, targs, false)
@@ -154,7 +144,6 @@ func createInstance(fn *Function, targs []types.Type, info *types.Info, syntax a
object: obj,
Signature: sig,
Synthetic: synthetic,
- syntax: syntax, // on synthetic packages syntax is nil.
_Origin: fn,
pos: obj.Pos(),
Pkg: nil,
@@ -164,6 +153,9 @@ func createInstance(fn *Function, targs []types.Type, info *types.Info, syntax a
info: info, // on synthetic packages info is nil.
subst: makeSubster(prog.ctxt, fn._TypeParams, targs, false),
}
+ if prog.mode&InstantiateGenerics != 0 {
+ instance.syntax = syntax // otherwise treat instance as an external function.
+ }
cr.Add(instance)
return instance
}
diff --git a/go/ssa/instantiate_test.go b/go/ssa/instantiate_test.go
index 6dbe0246c..0da8c6304 100644
--- a/go/ssa/instantiate_test.go
+++ b/go/ssa/instantiate_test.go
@@ -59,68 +59,70 @@ func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer)
t.Fatalf("Load: %v", err)
}
- // Create and build SSA
- prog := NewProgram(lprog.Fset, 0)
-
- for _, info := range lprog.AllPackages {
- prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
- }
-
- p := prog.Package(lprog.Package("p").Pkg)
- p.Build()
-
- ptr := p.Type("Pointer").Type().(*types.Named)
- if ptr.NumMethods() != 1 {
- t.Fatalf("Expected Pointer to have 1 method. got %d", ptr.NumMethods())
- }
-
- obj := ptr.Method(0)
- if obj.Name() != "Load" {
- t.Errorf("Expected Pointer to have method named 'Load'. got %q", obj.Name())
- }
-
- meth := prog.FuncValue(obj)
-
- var cr creator
- intSliceTyp := types.NewSlice(types.Typ[types.Int])
- instance := prog.needsInstance(meth, []types.Type{intSliceTyp}, &cr)
- if len(cr) != 1 {
- t.Errorf("Expected first instance to create a function. got %d created functions", len(cr))
- }
- if instance._Origin != meth {
- t.Errorf("Expected Origin of %s to be %s. got %s", instance, meth, instance._Origin)
- }
- if len(instance._TypeArgs) != 1 || !types.Identical(instance._TypeArgs[0], intSliceTyp) {
- t.Errorf("Expected TypeArgs of %s to be %v. got %v", instance, []types.Type{intSliceTyp}, instance._TypeArgs)
- }
- instances := prog._Instances(meth)
- if want := []*Function{instance}; !reflect.DeepEqual(instances, want) {
- t.Errorf("Expected instances of %s to be %v. got %v", meth, want, instances)
- }
-
- // A second request with an identical type returns the same Function.
- second := prog.needsInstance(meth, []types.Type{types.NewSlice(types.Typ[types.Int])}, &cr)
- if second != instance || len(cr) != 1 {
- t.Error("Expected second identical instantiation to not create a function")
- }
-
- // Add a second instance.
- inst2 := prog.needsInstance(meth, []types.Type{types.NewSlice(types.Typ[types.Uint])}, &cr)
- instances = prog._Instances(meth)
-
- // Note: instance.Name() < inst2.Name()
- sort.Slice(instances, func(i, j int) bool {
- return instances[i].Name() < instances[j].Name()
- })
- if want := []*Function{instance, inst2}; !reflect.DeepEqual(instances, want) {
- t.Errorf("Expected instances of %s to be %v. got %v", meth, want, instances)
- }
-
- // build and sanity check manually created instance.
- var b builder
- b.buildFunction(instance)
- var buf bytes.Buffer
- if !sanityCheck(instance, &buf) {
- t.Errorf("sanityCheck of %s failed with: %s", instance, buf.String())
+ for _, mode := range []BuilderMode{BuilderMode(0), InstantiateGenerics} {
+ // Create and build SSA
+ prog := NewProgram(lprog.Fset, mode)
+
+ for _, info := range lprog.AllPackages {
+ prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable)
+ }
+
+ p := prog.Package(lprog.Package("p").Pkg)
+ p.Build()
+
+ ptr := p.Type("Pointer").Type().(*types.Named)
+ if ptr.NumMethods() != 1 {
+ t.Fatalf("Expected Pointer to have 1 method. got %d", ptr.NumMethods())
+ }
+
+ obj := ptr.Method(0)
+ if obj.Name() != "Load" {
+ t.Errorf("Expected Pointer to have method named 'Load'. got %q", obj.Name())
+ }
+
+ meth := prog.FuncValue(obj)
+
+ var cr creator
+ intSliceTyp := types.NewSlice(types.Typ[types.Int])
+ instance := prog.needsInstance(meth, []types.Type{intSliceTyp}, &cr)
+ if len(cr) != 1 {
+ t.Errorf("Expected first instance to create a function. got %d created functions", len(cr))
+ }
+ if instance._Origin != meth {
+ t.Errorf("Expected Origin of %s to be %s. got %s", instance, meth, instance._Origin)
+ }
+ if len(instance._TypeArgs) != 1 || !types.Identical(instance._TypeArgs[0], intSliceTyp) {
+ t.Errorf("Expected TypeArgs of %s to be %v. got %v", instance, []types.Type{intSliceTyp}, instance._TypeArgs)
+ }
+ instances := prog._Instances(meth)
+ if want := []*Function{instance}; !reflect.DeepEqual(instances, want) {
+ t.Errorf("Expected instances of %s to be %v. got %v", meth, want, instances)
+ }
+
+ // A second request with an identical type returns the same Function.
+ second := prog.needsInstance(meth, []types.Type{types.NewSlice(types.Typ[types.Int])}, &cr)
+ if second != instance || len(cr) != 1 {
+ t.Error("Expected second identical instantiation to not create a function")
+ }
+
+ // Add a second instance.
+ inst2 := prog.needsInstance(meth, []types.Type{types.NewSlice(types.Typ[types.Uint])}, &cr)
+ instances = prog._Instances(meth)
+
+ // Note: instance.Name() < inst2.Name()
+ sort.Slice(instances, func(i, j int) bool {
+ return instances[i].Name() < instances[j].Name()
+ })
+ if want := []*Function{instance, inst2}; !reflect.DeepEqual(instances, want) {
+ t.Errorf("Expected instances of %s to be %v. got %v", meth, want, instances)
+ }
+
+ // build and sanity check manually created instance.
+ var b builder
+ b.buildFunction(instance)
+ var buf bytes.Buffer
+ if !sanityCheck(instance, &buf) {
+ t.Errorf("sanityCheck of %s failed with: %s", instance, buf.String())
+ }
}
}
diff --git a/go/ssa/interp/interp.go b/go/ssa/interp/interp.go
index 0f6a21f92..2b21aad70 100644
--- a/go/ssa/interp/interp.go
+++ b/go/ssa/interp/interp.go
@@ -51,6 +51,7 @@ import (
"os"
"reflect"
"runtime"
+ "strings"
"sync/atomic"
"golang.org/x/tools/go/ssa"
@@ -505,7 +506,11 @@ func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function,
return ext(fr, args)
}
if fn.Blocks == nil {
- panic("no code for function: " + name)
+ var reason string // empty by default
+ if strings.HasPrefix(fn.Synthetic, "instantiation") {
+ reason = " (interp requires ssa.BuilderMode to include InstantiateGenerics on generics)"
+ }
+ panic("no code for function: " + name + reason)
}
}
fr.env = make(map[ssa.Value]value)
@@ -637,6 +642,9 @@ func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) {
// gc does), or the argument to os.Exit for normal termination.
//
// The SSA program must include the "runtime" package.
+//
+// Type parameterized functions must have been built with
+// InstantiateGenerics in the ssa.BuilderMode to be interpreted.
func Interpret(mainpkg *ssa.Package, mode Mode, sizes types.Sizes, filename string, args []string) (exitCode int) {
i := &interpreter{
prog: mainpkg.Prog,
diff --git a/go/ssa/interp/interp_test.go b/go/ssa/interp/interp_test.go
index 91ecb9793..4da3ffe0b 100644
--- a/go/ssa/interp/interp_test.go
+++ b/go/ssa/interp/interp_test.go
@@ -31,6 +31,7 @@ import (
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/interp"
"golang.org/x/tools/go/ssa/ssautil"
+ "golang.org/x/tools/internal/typeparams"
)
// Each line contains a space-separated list of $GOROOT/test/
@@ -182,7 +183,9 @@ func run(t *testing.T, input string) bool {
return false
}
- prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
+ bmode := ssa.InstantiateGenerics | ssa.SanityCheckFunctions
+ // bmode |= ssa.PrintFunctions // enable for debugging
+ prog := ssautil.CreateProgram(iprog, bmode)
prog.Build()
mainPkg := prog.Package(iprog.Created[0].Pkg)
@@ -194,7 +197,10 @@ func run(t *testing.T, input string) bool {
sizes := types.SizesFor("gc", ctx.GOARCH)
hint = fmt.Sprintf("To trace execution, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -build=C -test -run --interp=T %s\n", input)
- exitCode := interp.Interpret(mainPkg, 0, sizes, input, []string{})
+ var imode interp.Mode // default mode
+ // imode |= interp.DisableRecover // enable for debugging
+ // imode |= interp.EnableTracing // enable for debugging
+ exitCode := interp.Interpret(mainPkg, imode, sizes, input, []string{})
if exitCode != 0 {
t.Fatalf("interpreting %s: exit code was %d", input, exitCode)
}
@@ -248,3 +254,65 @@ func TestGorootTest(t *testing.T) {
}
printFailures(failures)
}
+
+// TestTypeparamTest runs the interpreter on runnable examples
+// in $GOROOT/test/typeparam/*.go.
+
+func TestTypeparamTest(t *testing.T) {
+ if !typeparams.Enabled {
+ return
+ }
+
+ // Skip known failures for the given reason.
+ // TODO(taking): Address these.
+ skip := map[string]string{
+ "chans.go": "interp tests do not support runtime.SetFinalizer",
+ "issue23536.go": "unknown reason",
+ "issue376214.go": "unknown issue with variadic cast on bytes",
+ "issue48042.go": "interp tests do not handle reflect.Value.SetInt",
+ "issue47716.go": "interp tests do not handle unsafe.Sizeof",
+ "issue50419.go": "interp tests do not handle dispatch to String() correctly",
+ "issue51733.go": "interp does not handle unsafe casts",
+ "ordered.go": "math.NaN() comparisons not being handled correctly",
+ "orderedmap.go": "interp tests do not support runtime.SetFinalizer",
+ "stringer.go": "unknown reason",
+ "issue48317.go": "interp tests do not support encoding/json",
+ "issue48318.go": "interp tests do not support encoding/json",
+ }
+ // Collect all of the .go files in dir that are runnable.
+ dir := filepath.Join(build.Default.GOROOT, "test", "typeparam")
+ list, err := os.ReadDir(dir)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var inputs []string
+ for _, entry := range list {
+ if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") {
+ continue // Consider standalone go files.
+ }
+ if reason := skip[entry.Name()]; reason != "" {
+ t.Logf("skipping %q due to %s.", entry.Name(), reason)
+ continue
+ }
+ input := filepath.Join(dir, entry.Name())
+ src, err := os.ReadFile(input)
+ if err != nil {
+ t.Fatal(err)
+ }
+ // Only build test files that can be compiled, or compiled and run.
+ if bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// rundir")) {
+ inputs = append(inputs, input)
+ } else {
+ t.Logf("Not a `// run` file: %s", entry.Name())
+ }
+ }
+
+ var failures []string
+ for _, input := range inputs {
+ t.Log("running", input)
+ if !run(t, input) {
+ failures = append(failures, input)
+ }
+ }
+ printFailures(failures)
+}
diff --git a/go/ssa/interp/testdata/src/encoding/encoding.go b/go/ssa/interp/testdata/src/encoding/encoding.go
new file mode 100644
index 000000000..73e9de494
--- /dev/null
+++ b/go/ssa/interp/testdata/src/encoding/encoding.go
@@ -0,0 +1,15 @@
+package encoding
+
+type BinaryMarshaler interface {
+ MarshalBinary() (data []byte, err error)
+}
+type BinaryUnmarshaler interface {
+ UnmarshalBinary(data []byte) error
+}
+
+type TextMarshaler interface {
+ MarshalText() (text []byte, err error)
+}
+type TextUnmarshaler interface {
+ UnmarshalText(text []byte) error
+}
diff --git a/go/ssa/interp/testdata/src/log/log.go b/go/ssa/interp/testdata/src/log/log.go
index 8897c1d21..9a57e8c1c 100644
--- a/go/ssa/interp/testdata/src/log/log.go
+++ b/go/ssa/interp/testdata/src/log/log.go
@@ -8,8 +8,16 @@ import (
func Println(v ...interface{}) {
fmt.Println(v...)
}
+func Printf(format string, v ...interface{}) {
+ fmt.Printf(format, v...)
+}
func Fatalln(v ...interface{}) {
Println(v...)
os.Exit(1)
}
+
+func Fatalf(format string, v ...interface{}) {
+ Printf(format, v...)
+ os.Exit(1)
+}
diff --git a/go/ssa/mode.go b/go/ssa/mode.go
index b17d5658b..8381639a5 100644
--- a/go/ssa/mode.go
+++ b/go/ssa/mode.go
@@ -28,6 +28,7 @@ const (
BuildSerially // Build packages serially, not in parallel.
GlobalDebug // Enable debug info for all packages
BareInits // Build init functions without guards or calls to dependent inits
+ InstantiateGenerics // Instantiate generics functions (monomorphize) while building
)
const BuilderModeDoc = `Options controlling the SSA builder.
@@ -40,6 +41,7 @@ S log [S]ource locations as SSA builder progresses.
L build distinct packages seria[L]ly instead of in parallel.
N build [N]aive SSA form: don't replace local loads/stores with registers.
I build bare [I]nit functions: no init guards or calls to dependent inits.
+G instantiate [G]eneric function bodies via monomorphization
`
func (m BuilderMode) String() string {
@@ -68,6 +70,9 @@ func (m BuilderMode) String() string {
if m&BareInits != 0 {
buf.WriteByte('I')
}
+ if m&InstantiateGenerics != 0 {
+ buf.WriteByte('G')
+ }
return buf.String()
}
@@ -92,6 +97,8 @@ func (m *BuilderMode) Set(s string) error {
mode |= BuildSerially
case 'I':
mode |= BareInits
+ case 'G':
+ mode |= InstantiateGenerics
default:
return fmt.Errorf("unknown BuilderMode option: %q", c)
}
diff --git a/go/ssa/sanity.go b/go/ssa/sanity.go
index ba08c41d6..7d7130275 100644
--- a/go/ssa/sanity.go
+++ b/go/ssa/sanity.go
@@ -402,6 +402,8 @@ func (s *sanity) checkFunction(fn *Function) bool {
// - check params match signature
// - check transient fields are nil
// - warn if any fn.Locals do not appear among block instructions.
+
+ // TODO(taking): Sanity check _Origin, _TypeParams, and _TypeArgs.
s.fn = fn
if fn.Prog == nil {
s.errorf("nil Prog")
@@ -418,14 +420,19 @@ func (s *sanity) checkFunction(fn *Function) bool {
strings.HasPrefix(fn.Synthetic, "bound ") ||
strings.HasPrefix(fn.Synthetic, "thunk ") ||
strings.HasSuffix(fn.name, "Error") ||
- strings.HasPrefix(fn.Synthetic, "instantiation") {
+ strings.HasPrefix(fn.Synthetic, "instantiation") ||
+ (fn.parent != nil && len(fn._TypeArgs) > 0) /* anon fun in instance */ {
// ok
} else {
s.errorf("nil Pkg")
}
}
if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn {
- s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
+ if strings.HasPrefix(fn.Synthetic, "instantiation") && fn.Prog.mode&InstantiateGenerics != 0 {
+ // ok
+ } else {
+ s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn)
+ }
}
for i, l := range fn.Locals {
if l.Parent() != fn {
diff --git a/go/ssa/source_test.go b/go/ssa/source_test.go
index 81bda8805..eb266edd1 100644
--- a/go/ssa/source_test.go
+++ b/go/ssa/source_test.go
@@ -89,7 +89,7 @@ func TestObjValueLookup(t *testing.T) {
return
}
- prog := ssautil.CreateProgram(iprog, 0 /*|ssa.PrintFunctions*/)
+ prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0) /*|ssa.PrintFunctions*/)
mainInfo := iprog.Created[0]
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
@@ -247,7 +247,7 @@ func testValueForExpr(t *testing.T, testfile string) {
mainInfo := iprog.Created[0]
- prog := ssautil.CreateProgram(iprog, 0)
+ prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0))
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
@@ -403,7 +403,7 @@ func TestEnclosingFunction(t *testing.T) {
t.Error(err)
continue
}
- prog := ssautil.CreateProgram(iprog, 0)
+ prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0))
pkg := prog.Package(iprog.Created[0].Pkg)
pkg.Build()
diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go
index fe3f497ff..0b252531f 100644
--- a/go/ssa/ssa.go
+++ b/go/ssa/ssa.go
@@ -293,10 +293,21 @@ type Node interface {
// Syntax.Pos() always returns the position of the declaring "func" token.
//
// Type() returns the function's Signature.
+//
+// A function is generic iff it has a non-empty TypeParams list and an
+// empty TypeArgs list. TypeParams lists the type parameters of the
+// function's Signature or the receiver's type parameters for a method.
+//
+// The instantiation of a generic function is a concrete function. These
+// are a list of n>0 TypeParams and n TypeArgs. An instantiation will
+// have a generic Origin function. There is at most one instantiation
+// of each origin type per Identical() type list. Instantiations do not
+// belong to any Pkg. The generic function and the instantiations will
+// share the same source Pos for the functions and the instructions.
type Function struct {
name string
- object types.Object // a declared *types.Func or one of its wrappers
- method *types.Selection // info about provenance of synthetic methods
+ object types.Object // a declared *types.Func or one of its wrappers
+ method selection // info about provenance of synthetic methods
Signature *types.Signature
pos token.Pos
@@ -1252,6 +1263,7 @@ type MapUpdate struct {
// ; var x float64 @ 109:72 is x
// ; address of *ast.CompositeLit @ 216:10 is t0
type DebugRef struct {
+ // TODO(generics): Reconsider what DebugRefs are for generics.
anInstruction
Expr ast.Expr // the referring expression (never *ast.ParenExpr)
object types.Object // the identity of the source var/func
diff --git a/go/ssa/ssautil/load_test.go b/go/ssa/ssautil/load_test.go
index 55684e0a6..9b7eba9b8 100644
--- a/go/ssa/ssautil/load_test.go
+++ b/go/ssa/ssautil/load_test.go
@@ -16,6 +16,7 @@ import (
"testing"
"golang.org/x/tools/go/packages"
+ "golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/internal/testenv"
)
@@ -40,7 +41,8 @@ func TestBuildPackage(t *testing.T) {
}
pkg := types.NewPackage("hello", "")
- ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, pkg, []*ast.File{f}, 0)
+ mode := ssa.InstantiateGenerics
+ ssapkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer.Default()}, fset, pkg, []*ast.File{f}, mode)
if err != nil {
t.Fatal(err)
}
@@ -65,7 +67,7 @@ func TestPackages(t *testing.T) {
t.Fatal("there were errors")
}
- prog, pkgs := ssautil.Packages(initial, 0)
+ prog, pkgs := ssautil.Packages(initial, ssa.InstantiateGenerics)
bytesNewBuffer := pkgs[0].Func("NewBuffer")
bytesNewBuffer.Pkg.Build()
@@ -102,7 +104,7 @@ func TestBuildPackage_MissingImport(t *testing.T) {
}
pkg := types.NewPackage("bad", "")
- ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0)
+ ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, ssa.BuilderMode(0))
if err == nil || ssapkg != nil {
t.Fatal("BuildPackage succeeded unexpectedly")
}
@@ -120,6 +122,6 @@ func TestIssue28106(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- prog, _ := ssautil.Packages(pkgs, 0)
+ prog, _ := ssautil.Packages(pkgs, ssa.BuilderMode(0))
prog.Build() // no crash
}
diff --git a/go/ssa/ssautil/switch_test.go b/go/ssa/ssautil/switch_test.go
index bad8bdd6a..6db410524 100644
--- a/go/ssa/ssautil/switch_test.go
+++ b/go/ssa/ssautil/switch_test.go
@@ -34,7 +34,7 @@ func TestSwitches(t *testing.T) {
return
}
- prog := ssautil.CreateProgram(iprog, 0)
+ prog := ssautil.CreateProgram(iprog, ssa.BuilderMode(0))
mainPkg := prog.Package(iprog.Created[0].Pkg)
mainPkg.Build()
diff --git a/go/ssa/stdlib_test.go b/go/ssa/stdlib_test.go
index aaa158076..7e02f97a7 100644
--- a/go/ssa/stdlib_test.go
+++ b/go/ssa/stdlib_test.go
@@ -76,6 +76,7 @@ func TestStdlib(t *testing.T) {
// Comment out these lines during benchmarking. Approx SSA build costs are noted.
mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time
mode |= ssa.GlobalDebug // +30% space, +18% time
+ mode |= ssa.InstantiateGenerics // + 0% space, + 2% time (unlikely to reproduce outside of stdlib)
prog, _ := ssautil.Packages(pkgs, mode)
t2 := time.Now()
diff --git a/go/ssa/subst.go b/go/ssa/subst.go
index 0e9263fd2..b29130ea0 100644
--- a/go/ssa/subst.go
+++ b/go/ssa/subst.go
@@ -143,6 +143,15 @@ func (subst *subster) typ(t types.Type) (res types.Type) {
}
}
+// types returns the result of {subst.typ(ts[i])}.
+func (subst *subster) types(ts []types.Type) []types.Type {
+ res := make([]types.Type, len(ts))
+ for i := range ts {
+ res[i] = subst.typ(ts[i])
+ }
+ return res
+}
+
func (subst *subster) tuple(t *types.Tuple) *types.Tuple {
if t != nil {
if vars := subst.varlist(t); vars != nil {
@@ -360,7 +369,7 @@ func (subst *subster) signature(t *types.Signature) types.Type {
params := subst.tuple(t.Params())
results := subst.tuple(t.Results())
if recv != t.Recv() || params != t.Params() || results != t.Results() {
- return types.NewSignature(recv, params, results, t.Variadic())
+ return typeparams.NewSignatureType(recv, nil, nil, params, results, t.Variadic())
}
return t
}
diff --git a/go/ssa/util.go b/go/ssa/util.go
index 8c711cb66..dfeaeebdb 100644
--- a/go/ssa/util.go
+++ b/go/ssa/util.go
@@ -164,6 +164,31 @@ func receiverTypeArgs(obj *types.Func) []types.Type {
return targs
}
+// recvAsFirstArg takes a method signature and returns a function
+// signature with receiver as the first parameter.
+func recvAsFirstArg(sig *types.Signature) *types.Signature {
+ params := make([]*types.Var, 0, 1+sig.Params().Len())
+ params = append(params, sig.Recv())
+ for i := 0; i < sig.Params().Len(); i++ {
+ params = append(params, sig.Params().At(i))
+ }
+ return typeparams.NewSignatureType(nil, nil, nil, types.NewTuple(params...), sig.Results(), sig.Variadic())
+}
+
+// instanceArgs returns the Instance[id].TypeArgs as a slice.
+func instanceArgs(info *types.Info, id *ast.Ident) []types.Type {
+ targList := typeparams.GetInstances(info)[id].TypeArgs
+ if targList == nil {
+ return nil
+ }
+
+ targs := make([]types.Type, targList.Len())
+ for i, n := 0, targList.Len(); i < n; i++ {
+ targs[i] = targList.At(i)
+ }
+ return targs
+}
+
// Mapping of a type T to a canonical instance C s.t. types.Indentical(T, C).
// Thread-safe.
type canonizer struct {
@@ -268,3 +293,19 @@ func (m *typeListMap) hash(ts []types.Type) uint32 {
}
return h
}
+
+// instantiateMethod instantiates m with targs and returns a canonical representative for this method.
+func (canon *canonizer) instantiateMethod(m *types.Func, targs []types.Type, ctxt *typeparams.Context) *types.Func {
+ recv := recvType(m)
+ if p, ok := recv.(*types.Pointer); ok {
+ recv = p.Elem()
+ }
+ named := recv.(*types.Named)
+ inst, err := typeparams.Instantiate(ctxt, typeparams.NamedTypeOrigin(named), targs, false)
+ if err != nil {
+ panic(err)
+ }
+ rep := canon.Type(inst)
+ obj, _, _ := types.LookupFieldOrMethod(rep, true, m.Pkg(), m.Name())
+ return obj.(*types.Func)
+}
diff --git a/go/ssa/wrappers.go b/go/ssa/wrappers.go
index deaa87f19..f3305fcae 100644
--- a/go/ssa/wrappers.go
+++ b/go/ssa/wrappers.go
@@ -42,7 +42,7 @@ import (
// - the result may be a thunk or a wrapper.
//
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
-func makeWrapper(prog *Program, sel *types.Selection, cr *creator) *Function {
+func makeWrapper(prog *Program, sel selection, cr *creator) *Function {
obj := sel.Obj().(*types.Func) // the declared function
sig := sel.Type().(*types.Signature) // type of this wrapper
@@ -255,7 +255,7 @@ func makeBound(prog *Program, obj *types.Func, cr *creator) *Function {
// than inlining the stub.
//
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
-func makeThunk(prog *Program, sel *types.Selection, cr *creator) *Function {
+func makeThunk(prog *Program, sel selection, cr *creator) *Function {
if sel.Kind() != types.MethodExpr {
panic(sel)
}
@@ -302,3 +302,44 @@ type boundsKey struct {
obj types.Object // t.meth
inst *typeList // canonical type instantiation list.
}
+
+// methodExpr is an copy of a *types.Selection.
+// This exists as there is no way to create MethodExpr's for an instantiation.
+type methodExpr struct {
+ recv types.Type
+ typ types.Type
+ obj types.Object
+ index []int
+ indirect bool
+}
+
+func (*methodExpr) Kind() types.SelectionKind { return types.MethodExpr }
+func (m *methodExpr) Type() types.Type { return m.typ }
+func (m *methodExpr) Recv() types.Type { return m.recv }
+func (m *methodExpr) Obj() types.Object { return m.obj }
+func (m *methodExpr) Index() []int { return m.index }
+func (m *methodExpr) Indirect() bool { return m.indirect }
+
+// create MethodExpr from a MethodValue.
+func toMethodExpr(mv *types.Selection) *methodExpr {
+ if mv.Kind() != types.MethodVal {
+ panic(mv)
+ }
+ return &methodExpr{
+ recv: mv.Recv(),
+ typ: recvAsFirstArg(mv.Type().(*types.Signature)),
+ obj: mv.Obj(),
+ index: mv.Index(),
+ indirect: mv.Indirect(),
+ }
+}
+
+// generalization of a *types.Selection and a methodExpr.
+type selection interface {
+ Kind() types.SelectionKind
+ Type() types.Type
+ Recv() types.Type
+ Obj() types.Object
+ Index() []int
+ Indirect() bool
+}