diff options
author | Tim King <taking@google.com> | 2021-09-22 09:50:15 -0700 |
---|---|---|
committer | Tim King <taking@google.com> | 2022-04-22 17:17:20 +0000 |
commit | d567bc1c220c9afb5f71dc38550ef8b0262ff544 (patch) | |
tree | bf728634567d86d32e86038077f80345d88b71c8 | |
parent | 5d7ca8a1b5bc80222ed6375e345ba8b6e24e9515 (diff) | |
download | golang-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.go | 3 | ||||
-rw-r--r-- | cmd/ssadump/main.go | 10 | ||||
-rw-r--r-- | go/analysis/passes/buildssa/buildssa.go | 3 | ||||
-rw-r--r-- | go/ssa/builder.go | 165 | ||||
-rw-r--r-- | go/ssa/builder_test.go | 164 | ||||
-rw-r--r-- | go/ssa/create.go | 3 | ||||
-rw-r--r-- | go/ssa/example_test.go | 2 | ||||
-rw-r--r-- | go/ssa/func.go | 34 | ||||
-rw-r--r-- | go/ssa/instantiate.go | 26 | ||||
-rw-r--r-- | go/ssa/instantiate_test.go | 128 | ||||
-rw-r--r-- | go/ssa/interp/interp.go | 10 | ||||
-rw-r--r-- | go/ssa/interp/interp_test.go | 72 | ||||
-rw-r--r-- | go/ssa/interp/testdata/src/encoding/encoding.go | 15 | ||||
-rw-r--r-- | go/ssa/interp/testdata/src/log/log.go | 8 | ||||
-rw-r--r-- | go/ssa/mode.go | 7 | ||||
-rw-r--r-- | go/ssa/sanity.go | 11 | ||||
-rw-r--r-- | go/ssa/source_test.go | 6 | ||||
-rw-r--r-- | go/ssa/ssa.go | 16 | ||||
-rw-r--r-- | go/ssa/ssautil/load_test.go | 10 | ||||
-rw-r--r-- | go/ssa/ssautil/switch_test.go | 2 | ||||
-rw-r--r-- | go/ssa/stdlib_test.go | 1 | ||||
-rw-r--r-- | go/ssa/subst.go | 11 | ||||
-rw-r--r-- | go/ssa/util.go | 41 | ||||
-rw-r--r-- | go/ssa/wrappers.go | 45 |
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 +} |