[dev.typeparams] cmd/compile: add dictionary argument to generic functions

When converting from a generic function to a concrete implementation,
add a dictionary argument to the generic function (both an actual
argument at each callsite, and a formal argument of each
implementation).

The dictionary argument comes before all other arguments (including
any receiver).

The dictionary argument is checked for validity, but is otherwise unused.
Subsequent CLs will start using the dictionary for, e.g., converting a
value of generic type to interface{}.

Import/export required adding support for LINKSYMOFFSET, which is used
by the dictionary checking code.

Change-Id: I16a7a8d23c7bd6a897e0da87c69f273be9103bd7
Reviewed-on: https://go-review.googlesource.com/c/go/+/323272
Trust: Keith Randall <khr@golang.org>
Trust: Dan Scales <danscales@google.com>
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Dan Scales <danscales@google.com>
This commit is contained in:
Keith Randall 2021-04-16 14:06:50 -07:00
parent aa9cfdf775
commit 7b876def6c
10 changed files with 688 additions and 113 deletions

View file

@ -105,6 +105,8 @@ type irgen struct {
// Fully-instantiated generic types whose methods should be instantiated
instTypeList []*types.Type
dnum int // for generating unique dictionary variables
}
func (g *irgen) generate(noders []*noder) {

View file

@ -10,9 +10,11 @@ package noder
import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/reflectdata"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"fmt"
"go/constant"
"strings"
)
@ -70,72 +72,89 @@ func (g *irgen) stencil() {
// instantiated function if it hasn't been created yet, and change
// to calling that function directly.
modified := false
foundFuncInst := false
closureRequired := false
ir.Visit(decl, func(n ir.Node) {
if n.Op() == ir.OFUNCINST {
// We found a function instantiation that is not
// immediately called.
foundFuncInst = true
// generic F, not immediately called
closureRequired = true
}
if n.Op() != ir.OCALL || n.(*ir.CallExpr).X.Op() != ir.OFUNCINST {
return
if n.Op() == ir.OMETHEXPR && len(n.(*ir.SelectorExpr).X.Type().RParams()) > 0 {
// T.M, T a type which is generic, not immediately called
closureRequired = true
}
// We have found a function call using a generic function
// instantiation.
call := n.(*ir.CallExpr)
inst := call.X.(*ir.InstExpr)
// Replace the OFUNCINST with a direct reference to the
// new stenciled function
st := g.getInstantiationForNode(inst)
call.X = st.Nname
if inst.X.Op() == ir.OCALLPART {
// When we create an instantiation of a method
// call, we make it a function. So, move the
// receiver to be the first arg of the function
// call.
withRecv := make([]ir.Node, len(call.Args)+1)
dot := inst.X.(*ir.SelectorExpr)
withRecv[0] = dot.X
copy(withRecv[1:], call.Args)
call.Args = withRecv
if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OFUNCINST {
// We have found a function call using a generic function
// instantiation.
call := n.(*ir.CallExpr)
inst := call.X.(*ir.InstExpr)
st := g.getInstantiationForNode(inst)
// Replace the OFUNCINST with a direct reference to the
// new stenciled function
call.X = st.Nname
if inst.X.Op() == ir.OCALLPART {
// When we create an instantiation of a method
// call, we make it a function. So, move the
// receiver to be the first arg of the function
// call.
call.Args.Prepend(inst.X.(*ir.SelectorExpr).X)
}
// Add dictionary to argument list.
dict := reflectdata.GetDictionaryForInstantiation(inst)
call.Args.Prepend(dict)
// Transform the Call now, which changes OCALL
// to OCALLFUNC and does typecheckaste/assignconvfn.
transformCall(call)
modified = true
}
if n.Op() == ir.OCALLMETH && n.(*ir.CallExpr).X.Op() == ir.ODOTMETH && len(deref(n.(*ir.CallExpr).X.Type().Recv().Type).RParams()) > 0 {
// Method call on a generic type, which was instantiated by stenciling.
// Method calls on explicitly instantiated types will have an OFUNCINST
// and are handled above.
call := n.(*ir.CallExpr)
meth := call.X.(*ir.SelectorExpr)
targs := deref(meth.Type().Recv().Type).RParams()
t := meth.X.Type()
baseSym := deref(t).OrigSym
baseType := baseSym.Def.(*ir.Name).Type()
var gf *ir.Name
for _, m := range baseType.Methods().Slice() {
if meth.Sel == m.Sym {
gf = m.Nname.(*ir.Name)
break
}
}
st := g.getInstantiation(gf, targs, true)
call.SetOp(ir.OCALL)
call.X = st.Nname
dict := reflectdata.GetDictionaryForMethod(gf, targs)
call.Args.Prepend(dict, meth.X)
// Transform the Call now, which changes OCALL
// to OCALLFUNC and does typecheckaste/assignconvfn.
transformCall(call)
modified = true
}
// Transform the Call now, which changes OCALL
// to OCALLFUNC and does typecheckaste/assignconvfn.
transformCall(call)
modified = true
})
// If we found an OFUNCINST without a corresponding call in the
// above decl, then traverse the nodes of decl again (with
// If we found a reference to a generic instantiation that wasn't an
// immediate call, then traverse the nodes of decl again (with
// EditChildren rather than Visit), where we actually change the
// OFUNCINST node to an ONAME for the instantiated function.
// reference to the instantiation to a closure that captures the
// dictionary, then does a direct call.
// EditChildren is more expensive than Visit, so we only do this
// in the infrequent case of an OFUNCINST without a corresponding
// call.
if foundFuncInst {
if closureRequired {
var edit func(ir.Node) ir.Node
edit = func(x ir.Node) ir.Node {
if x.Op() == ir.OFUNCINST {
// inst.X is either a function name node
// or a selector expression for a method.
inst := x.(*ir.InstExpr)
st := g.getInstantiationForNode(inst)
modified = true
if inst.X.Op() == ir.ONAME {
return st.Nname
}
assert(inst.X.Op() == ir.OCALLPART)
// Return a new selector expression referring
// to the newly stenciled function.
oldse := inst.X.(*ir.SelectorExpr)
newse := ir.NewSelectorExpr(oldse.Pos(), ir.OCALLPART, oldse.X, oldse.Sel)
newse.Selection = types.NewField(oldse.Pos(), st.Sym(), st.Type())
newse.Selection.Nname = st
typed(inst.Type(), newse)
return newse
}
ir.EditChildren(x, edit)
switch {
case x.Op() == ir.OFUNCINST:
return g.buildClosure(decl.(*ir.Func), x)
case x.Op() == ir.OMETHEXPR && len(deref(x.(*ir.SelectorExpr).X.Type()).RParams()) > 0: // TODO: test for ptr-to-method case
return g.buildClosure(decl.(*ir.Func), x)
}
return x
}
edit(decl)
@ -153,6 +172,228 @@ func (g *irgen) stencil() {
}
// buildClosure makes a closure to implement x, a OFUNCINST or OMETHEXPR
// of generic type. outer is the containing function.
func (g *irgen) buildClosure(outer *ir.Func, x ir.Node) ir.Node {
pos := x.Pos()
var target *ir.Func // target instantiated function/method
var dictValue ir.Node // dictionary to use
var rcvrValue ir.Node // receiver, if a method value
typ := x.Type() // type of the closure
if x.Op() == ir.OFUNCINST {
inst := x.(*ir.InstExpr)
// Type arguments we're instantiating with.
targs := typecheck.TypesOf(inst.Targs)
// Find the generic function/method.
var gf *ir.Name
if inst.X.Op() == ir.ONAME {
// Instantiating a generic function call.
gf = inst.X.(*ir.Name)
} else if inst.X.Op() == ir.OCALLPART {
// Instantiating a method value x.M.
se := inst.X.(*ir.SelectorExpr)
rcvrValue = se.X
gf = se.Selection.Nname.(*ir.Name)
} else {
panic("unhandled")
}
// target is the instantiated function we're trying to call.
// For functions, the target expects a dictionary as its first argument.
// For method values, the target expects a dictionary and the receiver
// as its first two arguments.
target = g.getInstantiation(gf, targs, rcvrValue != nil)
// The value to use for the dictionary argument.
if rcvrValue == nil {
dictValue = reflectdata.GetDictionaryForFunc(gf, targs)
} else {
dictValue = reflectdata.GetDictionaryForMethod(gf, targs)
}
} else { // ir.OMETHEXPR
// Method expression T.M where T is a generic type.
// TODO: Is (*T).M right?
se := x.(*ir.SelectorExpr)
targs := se.X.Type().RParams()
if len(targs) == 0 {
if se.X.Type().IsPtr() {
targs = se.X.Type().Elem().RParams()
if len(targs) == 0 {
panic("bad")
}
}
}
t := se.X.Type()
baseSym := t.OrigSym
baseType := baseSym.Def.(*ir.Name).Type()
var gf *ir.Name
for _, m := range baseType.Methods().Slice() {
if se.Sel == m.Sym {
gf = m.Nname.(*ir.Name)
break
}
}
target = g.getInstantiation(gf, targs, true)
dictValue = reflectdata.GetDictionaryForMethod(gf, targs)
}
// Build a closure to implement a function instantiation.
//
// func f[T any] (int, int) (int, int) { ...whatever... }
//
// Then any reference to f[int] not directly called gets rewritten to
//
// .dictN := ... dictionary to use ...
// func(a0, a1 int) (r0, r1 int) {
// return .inst.f[int](.dictN, a0, a1)
// }
//
// Similarly for method expressions,
//
// type g[T any] ....
// func (rcvr g[T]) f(a0, a1 int) (r0, r1 int) { ... }
//
// Any reference to g[int].f not directly called gets rewritten to
//
// .dictN := ... dictionary to use ...
// func(rcvr g[int], a0, a1 int) (r0, r1 int) {
// return .inst.g[int].f(.dictN, rcvr, a0, a1)
// }
//
// Also method values
//
// var x g[int]
//
// Any reference to x.f not directly called gets rewritten to
//
// .dictN := ... dictionary to use ...
// x2 := x
// func(a0, a1 int) (r0, r1 int) {
// return .inst.g[int].f(.dictN, x2, a0, a1)
// }
// Make a new internal function.
fn := ir.NewFunc(pos)
fn.SetIsHiddenClosure(true)
// This is the dictionary we want to use.
// Note: for now this is a compile-time constant, so we don't really need a closure
// to capture it (a wrapper function would work just as well). But eventually it
// will be a read of a subdictionary from the parent dictionary.
dictVar := ir.NewNameAt(pos, typecheck.LookupNum(".dict", g.dnum))
g.dnum++
dictVar.Class = ir.PAUTO
typed(types.Types[types.TUINTPTR], dictVar)
dictVar.Curfn = outer
dictAssign := ir.NewAssignStmt(pos, dictVar, dictValue)
dictAssign.SetTypecheck(1)
dictVar.Defn = dictAssign
outer.Dcl = append(outer.Dcl, dictVar)
// assign the receiver to a temporary.
var rcvrVar *ir.Name
var rcvrAssign ir.Node
if rcvrValue != nil {
rcvrVar = ir.NewNameAt(pos, typecheck.LookupNum(".rcvr", g.dnum))
g.dnum++
rcvrVar.Class = ir.PAUTO
typed(rcvrValue.Type(), rcvrVar)
rcvrVar.Curfn = outer
rcvrAssign = ir.NewAssignStmt(pos, rcvrVar, rcvrValue)
rcvrAssign.SetTypecheck(1)
rcvrVar.Defn = rcvrAssign
outer.Dcl = append(outer.Dcl, rcvrVar)
}
// Build formal argument and return lists.
var formalParams []*types.Field // arguments of closure
var formalResults []*types.Field // returns of closure
for i := 0; i < typ.NumParams(); i++ {
t := typ.Params().Field(i).Type
arg := ir.NewNameAt(pos, typecheck.LookupNum("a", i))
arg.Class = ir.PPARAM
typed(t, arg)
arg.Curfn = fn
fn.Dcl = append(fn.Dcl, arg)
f := types.NewField(pos, arg.Sym(), t)
f.Nname = arg
formalParams = append(formalParams, f)
}
for i := 0; i < typ.NumResults(); i++ {
t := typ.Results().Field(i).Type
result := ir.NewNameAt(pos, typecheck.LookupNum("r", i)) // TODO: names not needed?
result.Class = ir.PPARAMOUT
typed(t, result)
result.Curfn = fn
fn.Dcl = append(fn.Dcl, result)
f := types.NewField(pos, result.Sym(), t)
f.Nname = result
formalResults = append(formalResults, f)
}
// Build an internal function with the right signature.
closureType := types.NewSignature(x.Type().Pkg(), nil, nil, formalParams, formalResults)
sym := typecheck.ClosureName(outer)
sym.SetFunc(true)
fn.Nname = ir.NewNameAt(pos, sym)
fn.Nname.Func = fn
fn.Nname.Defn = fn
typed(closureType, fn.Nname)
fn.SetTypecheck(1)
// Build body of closure. This involves just calling the wrapped function directly
// with the additional dictionary argument.
// First, capture the dictionary variable for use in the closure.
dict2Var := ir.CaptureName(pos, fn, dictVar)
// Also capture the receiver variable.
var rcvr2Var *ir.Name
if rcvrValue != nil {
rcvr2Var = ir.CaptureName(pos, fn, rcvrVar)
}
// Build arguments to call inside the closure.
var args []ir.Node
// First the dictionary argument.
args = append(args, dict2Var)
// Then the receiver.
if rcvrValue != nil {
args = append(args, rcvr2Var)
}
// Then all the other arguments (including receiver for method expressions).
for i := 0; i < typ.NumParams(); i++ {
args = append(args, formalParams[i].Nname.(*ir.Name))
}
// Build call itself.
var innerCall ir.Node = ir.NewCallExpr(pos, ir.OCALL, target.Nname, args)
if len(formalResults) > 0 {
innerCall = ir.NewReturnStmt(pos, []ir.Node{innerCall})
}
// Finish building body of closure.
ir.CurFunc = fn
// TODO: set types directly here instead of using typecheck.Stmt
typecheck.Stmt(innerCall)
ir.CurFunc = nil
fn.Body = []ir.Node{innerCall}
// We're all done with the captured dictionary (and receiver, for method values).
ir.FinishCaptureNames(pos, outer, fn)
// Make a closure referencing our new internal function.
c := ir.NewClosureExpr(pos, fn)
init := []ir.Node{dictAssign}
if rcvrValue != nil {
init = append(init, rcvrAssign)
}
c.SetInit(init)
typed(x.Type(), c)
return c
}
// instantiateMethods instantiates all the methods of all fully-instantiated
// generic types that have been added to g.instTypeList.
func (g *irgen) instantiateMethods() {
@ -167,14 +408,17 @@ func (g *irgen) instantiateMethods() {
// not be set on imported instantiated types.
baseSym := typ.OrigSym
baseType := baseSym.Def.(*ir.Name).Type()
for j, m := range typ.Methods().Slice() {
name := m.Nname.(*ir.Name)
for j, _ := range typ.Methods().Slice() {
baseNname := baseType.Methods().Slice()[j].Nname.(*ir.Name)
// Note: we are breaking an invariant here:
// m.Nname is now not equal m.Nname.Func.Nname.
// m.Nname has the type of a method, whereas m.Nname.Func.Nname has
// the type of a function, since it is an function instantiation.
name.Func = g.getInstantiation(baseNname, typ.RParams(), true)
// Eagerly generate the instantiations that implement these methods.
// We don't use the instantiations here, just generate them (and any
// further instantiations those generate, etc.).
// Note that we don't set the Func for any methods on instantiated
// types. Their signatures don't match so that would be confusing.
// Direct method calls go directly to the instantiations, implemented above.
// Indirect method calls use wrappers generated in reflectcall. Those wrappers
// will use these instantiations if they are needed (for interface tables or reflection).
_ = g.getInstantiation(baseNname, typ.RParams(), true)
}
}
g.instTypeList = nil
@ -287,10 +531,7 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*type
vars: make(map[*ir.Name]*ir.Name),
}
newf.Dcl = make([]*ir.Name, len(gf.Dcl))
for i, n := range gf.Dcl {
newf.Dcl[i] = subst.localvar(n)
}
newf.Dcl = make([]*ir.Name, 0, len(gf.Dcl)+1)
// Replace the types in the function signature.
// Ugly: also, we have to insert the Name nodes of the parameters/results into
@ -298,18 +539,40 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*type
// because it came via conversion from the types2 type.
oldt := nameNode.Type()
// We also transform a generic method type to the corresponding
// instantiated function type where the receiver is the first parameter.
// instantiated function type where the dictionary is the first parameter.
dictionarySym := types.LocalPkg.Lookup(".dict")
dictionaryType := types.Types[types.TUINTPTR]
dictionaryName := ir.NewNameAt(gf.Pos(), dictionarySym)
typed(dictionaryType, dictionaryName)
dictionaryName.Class = ir.PPARAM
dictionaryName.Curfn = newf
newf.Dcl = append(newf.Dcl, dictionaryName)
for _, n := range gf.Dcl {
if n.Sym().Name == ".dict" {
panic("already has dictionary")
}
newf.Dcl = append(newf.Dcl, subst.localvar(n))
}
dictionaryArg := types.NewField(gf.Pos(), dictionarySym, dictionaryType)
dictionaryArg.Nname = dictionaryName
var args []*types.Field
args = append(args, dictionaryArg)
args = append(args, oldt.Recvs().FieldSlice()...)
args = append(args, oldt.Params().FieldSlice()...)
newt := types.NewSignature(oldt.Pkg(), nil, nil,
subst.fields(ir.PPARAM, append(oldt.Recvs().FieldSlice(), oldt.Params().FieldSlice()...), newf.Dcl),
subst.fields(ir.PPARAM, args, newf.Dcl),
subst.fields(ir.PPARAMOUT, oldt.Results().FieldSlice(), newf.Dcl))
newf.Nname.SetType(newt)
typed(newt, newf.Nname)
ir.MarkFunc(newf.Nname)
newf.SetTypecheck(1)
newf.Nname.SetTypecheck(1)
// Make sure name/type of newf is set before substituting the body.
newf.Body = subst.list(gf.Body)
// Add code to check that the dictionary is correct.
newf.Body.Prepend(g.checkDictionary(dictionaryName, targs)...)
ir.CurFunc = savef
return newf
@ -334,6 +597,44 @@ func (subst *subster) localvar(name *ir.Name) *ir.Name {
return m
}
// checkDictionary returns code that does runtime consistency checks
// between the dictionary and the types it should contain.
func (g *irgen) checkDictionary(name *ir.Name, targs []*types.Type) (code []ir.Node) {
if false {
return // checking turned off
}
// TODO: when moving to GCshape, this test will become harder. Call into
// runtime to check the expected shape is correct?
pos := name.Pos()
// Convert dictionary to *[N]uintptr
d := ir.NewConvExpr(pos, ir.OCONVNOP, types.Types[types.TUNSAFEPTR], name)
d.SetTypecheck(1)
d = ir.NewConvExpr(pos, ir.OCONVNOP, types.NewArray(types.Types[types.TUINTPTR], int64(len(targs))).PtrTo(), d)
d.SetTypecheck(1)
// Check that each type entry in the dictionary is correct.
for i, t := range targs {
want := reflectdata.TypePtr(t)
typed(types.Types[types.TUINTPTR], want)
deref := ir.NewStarExpr(pos, d)
typed(d.Type().Elem(), deref)
idx := ir.NewConstExpr(constant.MakeUint64(uint64(i)), name) // TODO: what to set orig to?
typed(types.Types[types.TUINTPTR], idx)
got := ir.NewIndexExpr(pos, deref, idx)
typed(types.Types[types.TUINTPTR], got)
cond := ir.NewBinaryExpr(pos, ir.ONE, want, got)
typed(types.Types[types.TBOOL], cond)
panicArg := ir.NewNilExpr(pos)
typed(types.NewInterface(types.LocalPkg, nil), panicArg)
then := ir.NewUnaryExpr(pos, ir.OPANIC, panicArg)
then.SetTypecheck(1)
x := ir.NewIfStmt(pos, cond, []ir.Node{then}, nil)
x.SetTypecheck(1)
code = append(code, x)
}
return
}
// node is like DeepCopy(), but substitutes ONAME nodes based on subst.vars, and
// also descends into closures. It substitutes type arguments for type parameters
// in all the new nodes.
@ -837,13 +1138,14 @@ func (subst *subster) typ(t *types.Type) *types.Type {
t2 := subst.typ(f.Type)
oldsym := f.Nname.Sym()
newsym := typecheck.MakeInstName(oldsym, subst.targs, true)
// TODO: use newsym?
var nname *ir.Name
if newsym.Def != nil {
nname = newsym.Def.(*ir.Name)
} else {
nname = ir.NewNameAt(f.Pos, newsym)
nname = ir.NewNameAt(f.Pos, oldsym)
nname.SetType(t2)
newsym.Def = nname
oldsym.Def = nname
}
newfields[i] = types.NewField(f.Pos, f.Sym, t2)
newfields[i].Nname = nname

View file

@ -340,12 +340,12 @@ assignOK:
}
}
// Corresponds to typecheck.typecheckargs.
// Corresponds to, but slightly more general than, typecheck.typecheckargs.
func transformArgs(n ir.InitNode) {
var list []ir.Node
switch n := n.(type) {
default:
base.Fatalf("typecheckargs %+v", n.Op())
base.Fatalf("transformArgs %+v", n.Op())
case *ir.CallExpr:
list = n.Args
if n.IsDDD {
@ -354,25 +354,31 @@ func transformArgs(n ir.InitNode) {
case *ir.ReturnStmt:
list = n.Results
}
if len(list) != 1 {
// Look to see if we have any multi-return functions as arguments.
extra := 0
for _, arg := range list {
t := arg.Type()
if t.IsFuncArgStruct() {
num := t.Fields().Len()
if num <= 1 {
base.Fatalf("multi-return type with only %d parts", num)
}
extra += num - 1
}
}
// If not, nothing to do.
if extra == 0 {
return
}
t := list[0].Type()
if t == nil || !t.IsFuncArgStruct() {
return
}
// Rewrite f(g()) into t1, t2, ... = g(); f(t1, t2, ...).
// Rewrite f(..., g(), ...) into t1, ..., tN = g(); f(..., t1, ..., tN, ...).
// Save n as n.Orig for fmt.go.
if ir.Orig(n) == n {
n.(ir.OrigNode).SetOrig(ir.SepCopy(n))
}
as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil)
as.Rhs.Append(list...)
// If we're outside of function context, then this call will
// be executed during the generated init function. However,
// init.go hasn't yet created it. Instead, associate the
@ -382,27 +388,42 @@ func transformArgs(n ir.InitNode) {
if static {
ir.CurFunc = typecheck.InitTodoFunc
}
list = nil
for _, f := range t.FieldSlice() {
t := typecheck.Temp(f.Type)
as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, t))
as.Lhs.Append(t)
list = append(list, t)
// Expand multi-return function calls.
// The spec only allows a multi-return function as an argument
// if it is the only argument. This code must handle calls to
// stenciled generic functions which have extra arguments
// (like the dictionary) so it must handle a slightly more general
// cases, like f(n, g()) where g is multi-return.
newList := make([]ir.Node, 0, len(list)+extra)
for _, arg := range list {
t := arg.Type()
if t.IsFuncArgStruct() {
as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, []ir.Node{arg})
for _, f := range t.FieldSlice() {
t := typecheck.Temp(f.Type)
as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, t))
as.Lhs.Append(t)
newList = append(newList, t)
}
transformAssign(as, as.Lhs, as.Rhs)
as.SetTypecheck(1)
n.PtrInit().Append(as)
} else {
newList = append(newList, arg)
}
}
if static {
ir.CurFunc = nil
}
switch n := n.(type) {
case *ir.CallExpr:
n.Args = list
n.Args = newList
case *ir.ReturnStmt:
n.Results = list
n.Results = newList
}
transformAssign(as, as.Lhs, as.Rhs)
as.SetTypecheck(1)
n.PtrInit().Append(as)
}
// assignconvfn converts node n for assignment to type t. Corresponds to
@ -562,6 +583,11 @@ func transformDot(n *ir.SelectorExpr, isCall bool) ir.Node {
if (n.Op() == ir.ODOTINTER || n.Op() == ir.ODOTMETH) && !isCall {
n.SetOp(ir.OCALLPART)
if len(n.X.Type().RParams()) > 0 || n.X.Type().IsPtr() && len(n.X.Type().Elem().RParams()) > 0 {
// TODO: MethodValueWrapper needed for generics?
// Or did we successfully desugar all that at stencil time?
return n
}
n.SetType(typecheck.MethodValueWrapper(n).Type())
}
return n

View file

@ -321,13 +321,6 @@ func methods(t *types.Type) []*typeSig {
}
typecheck.CalcMethods(mt)
// type stored in interface word
it := t
if !types.IsDirectIface(it) {
it = types.NewPtr(t)
}
// make list of methods for t,
// generating code if necessary.
var ms []*typeSig
@ -355,8 +348,8 @@ func methods(t *types.Type) []*typeSig {
sig := &typeSig{
name: f.Sym,
isym: methodWrapper(it, f),
tsym: methodWrapper(t, f),
isym: methodWrapper(t, f, true),
tsym: methodWrapper(t, f, false),
type_: typecheck.NewMethodType(f.Type, t),
mtype: typecheck.NewMethodType(f.Type, nil),
}
@ -394,7 +387,7 @@ func imethods(t *types.Type) []*typeSig {
// IfaceType.Method is not in the reflect data.
// Generate the method body, so that compiled
// code can refer to it.
methodWrapper(t, f)
methodWrapper(t, f, false)
}
return methods
@ -1765,7 +1758,28 @@ func CollectPTabs() {
//
// rcvr - U
// method - M func (t T)(), a TFIELD type struct
func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym {
//
// Also wraps methods on instantiated generic types for use in itab entries.
// For an instantiated generic type G[int], we generate wrappers like:
// G[int] pointer shaped:
// func (x G[int]) f(arg) {
// .inst.G[int].f(dictionary, x, arg)
// }
// G[int] not pointer shaped:
// func (x *G[int]) f(arg) {
// .inst.G[int].f(dictionary, *x, arg)
// }
// These wrappers are always fully stenciled.
func methodWrapper(rcvr *types.Type, method *types.Field, forItab bool) *obj.LSym {
orig := rcvr
if forItab && !types.IsDirectIface(rcvr) {
rcvr = rcvr.PtrTo()
}
generic := false
if !rcvr.IsInterface() && len(rcvr.RParams()) > 0 || rcvr.IsPtr() && len(rcvr.Elem().RParams()) > 0 { // TODO: right detection?
// TODO: check that we do the right thing when rcvr.IsInterface().
generic = true
}
newnam := ir.MethodSym(rcvr, method.Sym)
lsym := newnam.Linksym()
if newnam.Siggen() {
@ -1773,7 +1787,7 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym {
}
newnam.SetSiggen(true)
if types.Identical(rcvr, method.Type.Recv().Type) {
if !generic && types.Identical(rcvr, method.Type.Recv().Type) {
return lsym
}
@ -1808,9 +1822,10 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym {
nthis := ir.AsNode(tfn.Type().Recv().Nname)
methodrcvr := method.Type.Recv().Type
indirect := rcvr.IsPtr() && rcvr.Elem() == methodrcvr
// generate nil pointer check for better error
if rcvr.IsPtr() && rcvr.Elem() == methodrcvr {
if indirect {
// generating wrapper from *T to T.
n := ir.NewIfStmt(base.Pos, nil, nil, nil)
n.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, nthis, typecheck.NodNil())
@ -1832,7 +1847,7 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym {
// Disable tailcall for RegabiArgs for now. The IR does not connect the
// arguments with the OTAILCALL node, and the arguments are not marshaled
// correctly.
if !base.Flag.Cfg.Instrumenting && rcvr.IsPtr() && methodrcvr.IsPtr() && method.Embedded != 0 && !types.IsInterfaceMethod(method.Type) && !(base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink) && !buildcfg.Experiment.RegabiArgs {
if !base.Flag.Cfg.Instrumenting && rcvr.IsPtr() && methodrcvr.IsPtr() && method.Embedded != 0 && !types.IsInterfaceMethod(method.Type) && !(base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink) && !buildcfg.Experiment.RegabiArgs && !generic {
// generate tail call: adjust pointer receiver and jump to embedded method.
left := dot.X // skip final .M
if !left.Type().IsPtr() {
@ -1843,8 +1858,44 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym {
fn.Body.Append(ir.NewTailCallStmt(base.Pos, method.Nname.(*ir.Name)))
} else {
fn.SetWrapper(true) // ignore frame for panic+recover matching
call := ir.NewCallExpr(base.Pos, ir.OCALL, dot, nil)
call.Args = ir.ParamNames(tfn.Type())
var call *ir.CallExpr
if generic {
var args []ir.Node
var targs []*types.Type
if rcvr.IsPtr() { // TODO: correct condition?
targs = rcvr.Elem().RParams()
} else {
targs = rcvr.RParams()
}
if strings.HasPrefix(ir.MethodSym(orig, method.Sym).Name, ".inst.") {
fmt.Printf("%s\n", ir.MethodSym(orig, method.Sym).Name)
panic("multiple .inst.")
}
args = append(args, getDictionary(".inst."+ir.MethodSym(orig, method.Sym).Name, targs)) // TODO: remove .inst.
if indirect {
args = append(args, ir.NewStarExpr(base.Pos, nthis))
} else {
args = append(args, nthis)
}
args = append(args, ir.ParamNames(tfn.Type())...)
// TODO: Once we enter the gcshape world, we'll need a way to look up
// the stenciled implementation to use for this concrete type. Essentially,
// erase the concrete types and replace them with gc shape representatives.
sym := typecheck.MakeInstName(ir.MethodSym(methodrcvr, method.Sym), targs, true)
if sym.Def == nil {
// Currently we make sure that we have all the instantiations
// we need by generating them all in ../noder/stencil.go:instantiateMethods
// TODO: maybe there's a better, more incremental way to generate
// only the instantiations we need?
base.Fatalf("instantiation %s not found", sym.Name)
}
target := ir.AsNode(sym.Def)
call = ir.NewCallExpr(base.Pos, ir.OCALL, target, args)
} else {
call = ir.NewCallExpr(base.Pos, ir.OCALL, dot, nil)
call.Args = ir.ParamNames(tfn.Type())
}
call.IsDDD = tfn.Type().IsVariadic()
if method.Type.NumResults() > 0 {
ret := ir.NewReturnStmt(base.Pos, nil)
@ -1909,3 +1960,71 @@ func MarkUsedIfaceMethod(n *ir.CallExpr) {
r.Add = InterfaceMethodOffset(ityp, midx)
r.Type = objabi.R_USEIFACEMETHOD
}
// getDictionaryForInstantiation returns the dictionary that should be used for invoking
// the concrete instantiation described by inst.
func GetDictionaryForInstantiation(inst *ir.InstExpr) ir.Node {
targs := typecheck.TypesOf(inst.Targs)
if meth, ok := inst.X.(*ir.SelectorExpr); ok {
return GetDictionaryForMethod(meth.Selection.Nname.(*ir.Name), targs)
}
return GetDictionaryForFunc(inst.X.(*ir.Name), targs)
}
func GetDictionaryForFunc(fn *ir.Name, targs []*types.Type) ir.Node {
return getDictionary(typecheck.MakeInstName(fn.Sym(), targs, false).Name, targs)
}
func GetDictionaryForMethod(meth *ir.Name, targs []*types.Type) ir.Node {
return getDictionary(typecheck.MakeInstName(meth.Sym(), targs, true).Name, targs)
}
// getDictionary returns the dictionary for the given named generic function
// or method, with the given type arguments.
// TODO: pass a reference to the generic function instead? We might need
// that to look up protodictionaries.
func getDictionary(name string, targs []*types.Type) ir.Node {
if len(targs) == 0 {
base.Fatalf("%s should have type arguments", name)
}
// The dictionary for this instantiation is named after the function
// and concrete types it is instantiated with.
// TODO: decouple this naming from the instantiation naming. The instantiation
// naming will be based on GC shapes, this naming must be fully stenciled.
if !strings.HasPrefix(name, ".inst.") {
base.Fatalf("%s should start in .inst.", name)
}
name = ".dict." + name[6:]
// Get a symbol representing the dictionary.
sym := typecheck.Lookup(name)
// Initialize the dictionary, if we haven't yet already.
if lsym := sym.Linksym(); len(lsym.P) == 0 {
off := 0
// Emit an entry for each concrete type.
for _, t := range targs {
s := TypeLinksym(t)
off = objw.SymPtr(lsym, off, s, 0)
}
// TODO: subdictionaries
objw.Global(lsym, int32(off), obj.DUPOK|obj.RODATA)
}
// Make a node referencing the dictionary symbol.
n := typecheck.NewName(sym)
n.SetType(types.Types[types.TUINTPTR]) // should probably be [...]uintptr, but doesn't really matter
n.SetTypecheck(1)
n.Class = ir.PEXTERN
sym.Def = n
// Return the address of the dictionary.
np := typecheck.NodAddr(n)
// Note: treat dictionary pointers as uintptrs, so they aren't pointers
// with respect to GC. That saves on stack scanning work, write barriers, etc.
// We can get away with it because dictionaries are global variables.
// TODO: use a cast, or is typing directly ok?
np.SetType(types.Types[types.TUINTPTR])
np.SetTypecheck(1)
return np
}

View file

@ -1904,6 +1904,14 @@ func (w *exportWriter) expr(n ir.Node) {
w.op(ir.OEND)
}
case ir.OLINKSYMOFFSET:
n := n.(*ir.LinksymOffsetExpr)
w.op(ir.OLINKSYMOFFSET)
w.pos(n.Pos())
w.string(n.Linksym.Name)
w.uint64(uint64(n.Offset_))
w.typ(n.Type())
// unary expressions
case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV:
n := n.(*ir.UnaryExpr)
@ -2068,7 +2076,7 @@ func (w *exportWriter) localIdent(s *types.Sym) {
}
// TODO(mdempsky): Fix autotmp hack.
if i := strings.LastIndex(name, "."); i >= 0 && !strings.HasPrefix(name, ".autotmp_") {
if i := strings.LastIndex(name, "."); i >= 0 && !strings.HasPrefix(name, ".autotmp_") && !strings.HasPrefix(name, ".dict") { // TODO: just use autotmp names for dictionaries?
base.Fatalf("unexpected dot in identifier: %v", name)
}

View file

@ -1467,6 +1467,13 @@ func (r *importReader) node() ir.Node {
n.Args.Append(r.exprList()...)
return n
case ir.OLINKSYMOFFSET:
pos := r.pos()
name := r.string()
off := r.uint64()
typ := r.typ()
return ir.NewLinksymOffsetExpr(pos, Lookup(name).Linksym(), int64(off), typ)
// unary expressions
case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV:
n := ir.NewUnaryExpr(r.pos(), op, r.expr())

View file

@ -901,6 +901,14 @@ func TypesOf(x []ir.Node) []*types.Type {
// '(*genType[int,bool]).methodName' for methods
func MakeInstName(fnsym *types.Sym, targs []*types.Type, hasBrackets bool) *types.Sym {
b := bytes.NewBufferString("")
// marker to distinguish generic instantiations from fully stenciled wrapper functions.
// Once we move to GC shape implementations, this prefix will not be necessary as the
// GC shape naming will distinguish them.
// e.g. f[8bytenonpointer] vs. f[int].
// For now, we use .inst.f[int] vs. f[int].
b.WriteString(".inst.")
name := fnsym.Name
i := strings.Index(name, "[")
assert(hasBrackets == (i >= 0))
@ -924,10 +932,13 @@ func MakeInstName(fnsym *types.Sym, targs []*types.Type, hasBrackets bool) *type
}
b.WriteString("]")
if i >= 0 {
i2 := strings.Index(name[i:], "]")
i2 := strings.LastIndex(name[i:], "]")
assert(i2 >= 0)
b.WriteString(name[i+i2+1:])
}
if strings.HasPrefix(b.String(), ".inst..inst.") {
panic(fmt.Sprintf("multiple .inst. prefix in %s", b.String()))
}
return fnsym.Pkg.Lookup(b.String())
}

View file

@ -958,7 +958,7 @@ func (t *Type) FuncArgs() *Type {
return t.Extra.(FuncArgs).T
}
// IsFuncArgStruct reports whether t is a struct representing function parameters.
// IsFuncArgStruct reports whether t is a struct representing function parameters or results.
func (t *Type) IsFuncArgStruct() bool {
return t.kind == TSTRUCT && t.Extra.(*Struct).Funarg != FunargNone
}

View file

@ -8,8 +8,8 @@
package atomic
import (
"unsafe"
"internal/cpu"
"unsafe"
)
const (

View file

@ -0,0 +1,100 @@
// run -gcflags=-G=3
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test situations where functions/methods are not
// immediately called and we need to capture the dictionary
// required for later invocation.
// TODO: copy this test file, add -l to gcflags.
package main
func main() {
functions()
methodExpressions()
methodValues()
interfaceMethods()
}
func g0[T any](x T) {
}
func g1[T any](x T) T {
return x
}
func g2[T any](x T) (T, T) {
return x, x
}
func functions() {
f0 := g0[int]
f0(7)
f1 := g1[int]
is7(f1(7))
f2 := g2[int]
is77(f2(7))
}
func is7(x int) {
if x != 7 {
println(x)
panic("assertion failed")
}
}
func is77(x, y int) {
if x != 7 || y != 7 {
println(x,y)
panic("assertion failed")
}
}
type s[T any] struct {
a T
}
func (x s[T]) g0() {
}
func (x s[T]) g1() T {
return x.a
}
func (x s[T]) g2() (T, T) {
return x.a, x.a
}
func methodExpressions() {
x := s[int]{a:7}
f0 := s[int].g0
f0(x)
f1 := s[int].g1
is7(f1(x))
f2 := s[int].g2
is77(f2(x))
}
func methodValues() {
x := s[int]{a:7}
f0 := x.g0
f0()
f1 := x.g1
is7(f1())
f2 := x.g2
is77(f2())
}
var x interface{
g0()
g1()int
g2()(int,int)
} = s[int]{a:7}
var y interface{} = s[int]{a:7}
func interfaceMethods() {
x.g0()
is7(x.g1())
is77(x.g2())
y.(interface{g0()}).g0()
is7(y.(interface{g1()int}).g1())
is77(y.(interface{g2()(int,int)}).g2())
}