[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 // Fully-instantiated generic types whose methods should be instantiated
instTypeList []*types.Type instTypeList []*types.Type
dnum int // for generating unique dictionary variables
} }
func (g *irgen) generate(noders []*noder) { func (g *irgen) generate(noders []*noder) {

View file

@ -10,9 +10,11 @@ package noder
import ( import (
"cmd/compile/internal/base" "cmd/compile/internal/base"
"cmd/compile/internal/ir" "cmd/compile/internal/ir"
"cmd/compile/internal/reflectdata"
"cmd/compile/internal/typecheck" "cmd/compile/internal/typecheck"
"cmd/compile/internal/types" "cmd/compile/internal/types"
"fmt" "fmt"
"go/constant"
"strings" "strings"
) )
@ -70,72 +72,89 @@ func (g *irgen) stencil() {
// instantiated function if it hasn't been created yet, and change // instantiated function if it hasn't been created yet, and change
// to calling that function directly. // to calling that function directly.
modified := false modified := false
foundFuncInst := false closureRequired := false
ir.Visit(decl, func(n ir.Node) { ir.Visit(decl, func(n ir.Node) {
if n.Op() == ir.OFUNCINST { if n.Op() == ir.OFUNCINST {
// We found a function instantiation that is not // generic F, not immediately called
// immediately called. closureRequired = true
foundFuncInst = true
} }
if n.Op() != ir.OCALL || n.(*ir.CallExpr).X.Op() != ir.OFUNCINST { if n.Op() == ir.OMETHEXPR && len(n.(*ir.SelectorExpr).X.Type().RParams()) > 0 {
return // T.M, T a type which is generic, not immediately called
closureRequired = true
} }
// We have found a function call using a generic function if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OFUNCINST {
// instantiation. // We have found a function call using a generic function
call := n.(*ir.CallExpr) // instantiation.
inst := call.X.(*ir.InstExpr) call := n.(*ir.CallExpr)
// Replace the OFUNCINST with a direct reference to the inst := call.X.(*ir.InstExpr)
// new stenciled function st := g.getInstantiationForNode(inst)
st := g.getInstantiationForNode(inst) // Replace the OFUNCINST with a direct reference to the
call.X = st.Nname // new stenciled function
if inst.X.Op() == ir.OCALLPART { call.X = st.Nname
// When we create an instantiation of a method if inst.X.Op() == ir.OCALLPART {
// call, we make it a function. So, move the // When we create an instantiation of a method
// receiver to be the first arg of the function // call, we make it a function. So, move the
// call. // receiver to be the first arg of the function
withRecv := make([]ir.Node, len(call.Args)+1) // call.
dot := inst.X.(*ir.SelectorExpr) call.Args.Prepend(inst.X.(*ir.SelectorExpr).X)
withRecv[0] = dot.X }
copy(withRecv[1:], call.Args) // Add dictionary to argument list.
call.Args = withRecv 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 // If we found a reference to a generic instantiation that wasn't an
// above decl, then traverse the nodes of decl again (with // immediate call, then traverse the nodes of decl again (with
// EditChildren rather than Visit), where we actually change the // 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 // EditChildren is more expensive than Visit, so we only do this
// in the infrequent case of an OFUNCINST without a corresponding // in the infrequent case of an OFUNCINST without a corresponding
// call. // call.
if foundFuncInst { if closureRequired {
var edit func(ir.Node) ir.Node var edit func(ir.Node) ir.Node
edit = func(x 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) 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 return x
} }
edit(decl) 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 // instantiateMethods instantiates all the methods of all fully-instantiated
// generic types that have been added to g.instTypeList. // generic types that have been added to g.instTypeList.
func (g *irgen) instantiateMethods() { func (g *irgen) instantiateMethods() {
@ -167,14 +408,17 @@ func (g *irgen) instantiateMethods() {
// not be set on imported instantiated types. // not be set on imported instantiated types.
baseSym := typ.OrigSym baseSym := typ.OrigSym
baseType := baseSym.Def.(*ir.Name).Type() baseType := baseSym.Def.(*ir.Name).Type()
for j, m := range typ.Methods().Slice() { for j, _ := range typ.Methods().Slice() {
name := m.Nname.(*ir.Name)
baseNname := baseType.Methods().Slice()[j].Nname.(*ir.Name) baseNname := baseType.Methods().Slice()[j].Nname.(*ir.Name)
// Note: we are breaking an invariant here: // Eagerly generate the instantiations that implement these methods.
// m.Nname is now not equal m.Nname.Func.Nname. // We don't use the instantiations here, just generate them (and any
// m.Nname has the type of a method, whereas m.Nname.Func.Nname has // further instantiations those generate, etc.).
// the type of a function, since it is an function instantiation. // Note that we don't set the Func for any methods on instantiated
name.Func = g.getInstantiation(baseNname, typ.RParams(), true) // 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 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), vars: make(map[*ir.Name]*ir.Name),
} }
newf.Dcl = make([]*ir.Name, len(gf.Dcl)) newf.Dcl = make([]*ir.Name, 0, len(gf.Dcl)+1)
for i, n := range gf.Dcl {
newf.Dcl[i] = subst.localvar(n)
}
// Replace the types in the function signature. // Replace the types in the function signature.
// Ugly: also, we have to insert the Name nodes of the parameters/results into // 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. // because it came via conversion from the types2 type.
oldt := nameNode.Type() oldt := nameNode.Type()
// We also transform a generic method type to the corresponding // 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, 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)) subst.fields(ir.PPARAMOUT, oldt.Results().FieldSlice(), newf.Dcl))
newf.Nname.SetType(newt) typed(newt, newf.Nname)
ir.MarkFunc(newf.Nname) ir.MarkFunc(newf.Nname)
newf.SetTypecheck(1) newf.SetTypecheck(1)
newf.Nname.SetTypecheck(1)
// Make sure name/type of newf is set before substituting the body. // Make sure name/type of newf is set before substituting the body.
newf.Body = subst.list(gf.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 ir.CurFunc = savef
return newf return newf
@ -334,6 +597,44 @@ func (subst *subster) localvar(name *ir.Name) *ir.Name {
return m 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 // node is like DeepCopy(), but substitutes ONAME nodes based on subst.vars, and
// also descends into closures. It substitutes type arguments for type parameters // also descends into closures. It substitutes type arguments for type parameters
// in all the new nodes. // in all the new nodes.
@ -837,13 +1138,14 @@ func (subst *subster) typ(t *types.Type) *types.Type {
t2 := subst.typ(f.Type) t2 := subst.typ(f.Type)
oldsym := f.Nname.Sym() oldsym := f.Nname.Sym()
newsym := typecheck.MakeInstName(oldsym, subst.targs, true) newsym := typecheck.MakeInstName(oldsym, subst.targs, true)
// TODO: use newsym?
var nname *ir.Name var nname *ir.Name
if newsym.Def != nil { if newsym.Def != nil {
nname = newsym.Def.(*ir.Name) nname = newsym.Def.(*ir.Name)
} else { } else {
nname = ir.NewNameAt(f.Pos, newsym) nname = ir.NewNameAt(f.Pos, oldsym)
nname.SetType(t2) nname.SetType(t2)
newsym.Def = nname oldsym.Def = nname
} }
newfields[i] = types.NewField(f.Pos, f.Sym, t2) newfields[i] = types.NewField(f.Pos, f.Sym, t2)
newfields[i].Nname = nname 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) { func transformArgs(n ir.InitNode) {
var list []ir.Node var list []ir.Node
switch n := n.(type) { switch n := n.(type) {
default: default:
base.Fatalf("typecheckargs %+v", n.Op()) base.Fatalf("transformArgs %+v", n.Op())
case *ir.CallExpr: case *ir.CallExpr:
list = n.Args list = n.Args
if n.IsDDD { if n.IsDDD {
@ -354,25 +354,31 @@ func transformArgs(n ir.InitNode) {
case *ir.ReturnStmt: case *ir.ReturnStmt:
list = n.Results 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 return
} }
t := list[0].Type() // Rewrite f(..., g(), ...) into t1, ..., tN = g(); f(..., t1, ..., tN, ...).
if t == nil || !t.IsFuncArgStruct() {
return
}
// Rewrite f(g()) into t1, t2, ... = g(); f(t1, t2, ...).
// Save n as n.Orig for fmt.go. // Save n as n.Orig for fmt.go.
if ir.Orig(n) == n { if ir.Orig(n) == n {
n.(ir.OrigNode).SetOrig(ir.SepCopy(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 // If we're outside of function context, then this call will
// be executed during the generated init function. However, // be executed during the generated init function. However,
// init.go hasn't yet created it. Instead, associate the // init.go hasn't yet created it. Instead, associate the
@ -382,27 +388,42 @@ func transformArgs(n ir.InitNode) {
if static { if static {
ir.CurFunc = typecheck.InitTodoFunc ir.CurFunc = typecheck.InitTodoFunc
} }
list = nil
for _, f := range t.FieldSlice() { // Expand multi-return function calls.
t := typecheck.Temp(f.Type) // The spec only allows a multi-return function as an argument
as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, t)) // if it is the only argument. This code must handle calls to
as.Lhs.Append(t) // stenciled generic functions which have extra arguments
list = append(list, t) // (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 { if static {
ir.CurFunc = nil ir.CurFunc = nil
} }
switch n := n.(type) { switch n := n.(type) {
case *ir.CallExpr: case *ir.CallExpr:
n.Args = list n.Args = newList
case *ir.ReturnStmt: 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 // 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 { if (n.Op() == ir.ODOTINTER || n.Op() == ir.ODOTMETH) && !isCall {
n.SetOp(ir.OCALLPART) 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()) n.SetType(typecheck.MethodValueWrapper(n).Type())
} }
return n return n

View file

@ -321,13 +321,6 @@ func methods(t *types.Type) []*typeSig {
} }
typecheck.CalcMethods(mt) typecheck.CalcMethods(mt)
// type stored in interface word
it := t
if !types.IsDirectIface(it) {
it = types.NewPtr(t)
}
// make list of methods for t, // make list of methods for t,
// generating code if necessary. // generating code if necessary.
var ms []*typeSig var ms []*typeSig
@ -355,8 +348,8 @@ func methods(t *types.Type) []*typeSig {
sig := &typeSig{ sig := &typeSig{
name: f.Sym, name: f.Sym,
isym: methodWrapper(it, f), isym: methodWrapper(t, f, true),
tsym: methodWrapper(t, f), tsym: methodWrapper(t, f, false),
type_: typecheck.NewMethodType(f.Type, t), type_: typecheck.NewMethodType(f.Type, t),
mtype: typecheck.NewMethodType(f.Type, nil), mtype: typecheck.NewMethodType(f.Type, nil),
} }
@ -394,7 +387,7 @@ func imethods(t *types.Type) []*typeSig {
// IfaceType.Method is not in the reflect data. // IfaceType.Method is not in the reflect data.
// Generate the method body, so that compiled // Generate the method body, so that compiled
// code can refer to it. // code can refer to it.
methodWrapper(t, f) methodWrapper(t, f, false)
} }
return methods return methods
@ -1765,7 +1758,28 @@ func CollectPTabs() {
// //
// rcvr - U // rcvr - U
// method - M func (t T)(), a TFIELD type struct // 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) newnam := ir.MethodSym(rcvr, method.Sym)
lsym := newnam.Linksym() lsym := newnam.Linksym()
if newnam.Siggen() { if newnam.Siggen() {
@ -1773,7 +1787,7 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym {
} }
newnam.SetSiggen(true) newnam.SetSiggen(true)
if types.Identical(rcvr, method.Type.Recv().Type) { if !generic && types.Identical(rcvr, method.Type.Recv().Type) {
return lsym return lsym
} }
@ -1808,9 +1822,10 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym {
nthis := ir.AsNode(tfn.Type().Recv().Nname) nthis := ir.AsNode(tfn.Type().Recv().Nname)
methodrcvr := method.Type.Recv().Type methodrcvr := method.Type.Recv().Type
indirect := rcvr.IsPtr() && rcvr.Elem() == methodrcvr
// generate nil pointer check for better error // generate nil pointer check for better error
if rcvr.IsPtr() && rcvr.Elem() == methodrcvr { if indirect {
// generating wrapper from *T to T. // generating wrapper from *T to T.
n := ir.NewIfStmt(base.Pos, nil, nil, nil) n := ir.NewIfStmt(base.Pos, nil, nil, nil)
n.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, nthis, typecheck.NodNil()) 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 // Disable tailcall for RegabiArgs for now. The IR does not connect the
// arguments with the OTAILCALL node, and the arguments are not marshaled // arguments with the OTAILCALL node, and the arguments are not marshaled
// correctly. // 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. // generate tail call: adjust pointer receiver and jump to embedded method.
left := dot.X // skip final .M left := dot.X // skip final .M
if !left.Type().IsPtr() { 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))) fn.Body.Append(ir.NewTailCallStmt(base.Pos, method.Nname.(*ir.Name)))
} else { } else {
fn.SetWrapper(true) // ignore frame for panic+recover matching fn.SetWrapper(true) // ignore frame for panic+recover matching
call := ir.NewCallExpr(base.Pos, ir.OCALL, dot, nil) var call *ir.CallExpr
call.Args = ir.ParamNames(tfn.Type()) 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() call.IsDDD = tfn.Type().IsVariadic()
if method.Type.NumResults() > 0 { if method.Type.NumResults() > 0 {
ret := ir.NewReturnStmt(base.Pos, nil) ret := ir.NewReturnStmt(base.Pos, nil)
@ -1909,3 +1960,71 @@ func MarkUsedIfaceMethod(n *ir.CallExpr) {
r.Add = InterfaceMethodOffset(ityp, midx) r.Add = InterfaceMethodOffset(ityp, midx)
r.Type = objabi.R_USEIFACEMETHOD 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) 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 // unary expressions
case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV: case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV:
n := n.(*ir.UnaryExpr) n := n.(*ir.UnaryExpr)
@ -2068,7 +2076,7 @@ func (w *exportWriter) localIdent(s *types.Sym) {
} }
// TODO(mdempsky): Fix autotmp hack. // 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) 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()...) n.Args.Append(r.exprList()...)
return n 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 // unary expressions
case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV: case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV:
n := ir.NewUnaryExpr(r.pos(), op, r.expr()) 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 // '(*genType[int,bool]).methodName' for methods
func MakeInstName(fnsym *types.Sym, targs []*types.Type, hasBrackets bool) *types.Sym { func MakeInstName(fnsym *types.Sym, targs []*types.Type, hasBrackets bool) *types.Sym {
b := bytes.NewBufferString("") 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 name := fnsym.Name
i := strings.Index(name, "[") i := strings.Index(name, "[")
assert(hasBrackets == (i >= 0)) assert(hasBrackets == (i >= 0))
@ -924,10 +932,13 @@ func MakeInstName(fnsym *types.Sym, targs []*types.Type, hasBrackets bool) *type
} }
b.WriteString("]") b.WriteString("]")
if i >= 0 { if i >= 0 {
i2 := strings.Index(name[i:], "]") i2 := strings.LastIndex(name[i:], "]")
assert(i2 >= 0) assert(i2 >= 0)
b.WriteString(name[i+i2+1:]) 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()) return fnsym.Pkg.Lookup(b.String())
} }

View file

@ -958,7 +958,7 @@ func (t *Type) FuncArgs() *Type {
return t.Extra.(FuncArgs).T 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 { func (t *Type) IsFuncArgStruct() bool {
return t.kind == TSTRUCT && t.Extra.(*Struct).Funarg != FunargNone return t.kind == TSTRUCT && t.Extra.(*Struct).Funarg != FunargNone
} }

View file

@ -8,8 +8,8 @@
package atomic package atomic
import ( import (
"unsafe"
"internal/cpu" "internal/cpu"
"unsafe"
) )
const ( 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())
}