| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | package devirtualize |
| |
|
| | import ( |
| | "cmd/compile/internal/base" |
| | "cmd/compile/internal/ir" |
| | "cmd/compile/internal/typecheck" |
| | "cmd/compile/internal/types" |
| | ) |
| |
|
| | const go126ImprovedConcreteTypeAnalysis = true |
| |
|
| | |
| | |
| | func StaticCall(s *State, call *ir.CallExpr) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if call.GoDefer { |
| | return |
| | } |
| |
|
| | if call.Op() != ir.OCALLINTER { |
| | return |
| | } |
| |
|
| | sel := call.Fun.(*ir.SelectorExpr) |
| | var typ *types.Type |
| | if go126ImprovedConcreteTypeAnalysis { |
| | typ = concreteType(s, sel.X) |
| | if typ == nil { |
| | return |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if !typecheck.Implements(typ, sel.X.Type()) { |
| | return |
| | } |
| | } else { |
| | r := ir.StaticValue(sel.X) |
| | if r.Op() != ir.OCONVIFACE { |
| | return |
| | } |
| | recv := r.(*ir.ConvExpr) |
| | typ = recv.X.Type() |
| | if typ.IsInterface() { |
| | return |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | if typ.IsShape() { |
| | return |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if typ.HasShape() { |
| | if base.Flag.LowerM != 0 { |
| | base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped receiver %v", call, typ) |
| | } |
| | return |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if sel.X.Type().HasShape() { |
| | if base.Flag.LowerM != 0 { |
| | base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped interface %v", call, sel.X.Type()) |
| | } |
| | return |
| | } |
| |
|
| | dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, typ) |
| |
|
| | if go126ImprovedConcreteTypeAnalysis { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | dt.UseNilPanic = true |
| | dt.SetPos(call.Pos()) |
| | } |
| |
|
| | x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true) |
| | switch x.Op() { |
| | case ir.ODOTMETH: |
| | if base.Flag.LowerM != 0 { |
| | base.WarnfAt(call.Pos(), "devirtualizing %v to %v", sel, typ) |
| | } |
| | call.SetOp(ir.OCALLMETH) |
| | call.Fun = x |
| | case ir.ODOTINTER: |
| | |
| | if base.Flag.LowerM != 0 { |
| | base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ) |
| | } |
| | call.SetOp(ir.OCALLINTER) |
| | call.Fun = x |
| | default: |
| | base.FatalfAt(call.Pos(), "failed to devirtualize %v (%v)", x, x.Op()) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | types.CheckSize(x.Type()) |
| | switch ft := x.Type(); ft.NumResults() { |
| | case 0: |
| | case 1: |
| | call.SetType(ft.Result(0).Type) |
| | default: |
| | call.SetType(ft.ResultsTuple()) |
| | } |
| |
|
| | |
| | typecheck.FixMethodCall(call) |
| | } |
| |
|
| | const concreteTypeDebug = false |
| |
|
| | |
| | |
| | |
| | func concreteType(s *State, n ir.Node) (typ *types.Type) { |
| | typ = concreteType1(s, n, make(map[*ir.Name]struct{})) |
| | if typ == &noType { |
| | return nil |
| | } |
| | if typ != nil && typ.IsInterface() { |
| | base.FatalfAt(n.Pos(), "typ.IsInterface() = true; want = false; typ = %v", typ) |
| | } |
| | return typ |
| | } |
| |
|
| | |
| | var noType types.Type |
| |
|
| | |
| | |
| | |
| | func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (outT *types.Type) { |
| | nn := n |
| |
|
| | if concreteTypeDebug { |
| | defer func() { |
| | t := "&noType" |
| | if outT != &noType { |
| | t = outT.String() |
| | } |
| | base.Warn("concreteType1(%v) -> %v", nn, t) |
| | }() |
| | } |
| |
|
| | for { |
| | if concreteTypeDebug { |
| | base.Warn("concreteType1(%v): analyzing %v", nn, n) |
| | } |
| |
|
| | if !n.Type().IsInterface() { |
| | return n.Type() |
| | } |
| |
|
| | switch n1 := n.(type) { |
| | case *ir.ConvExpr: |
| | if n1.Op() == ir.OCONVNOP { |
| | if !n1.Type().IsInterface() || !types.Identical(n1.Type().Underlying(), n1.X.Type().Underlying()) { |
| | |
| | |
| | base.FatalfAt(n1.Pos(), "not identical/interface types found n1.Type = %v; n1.X.Type = %v", n1.Type(), n1.X.Type()) |
| | } |
| | n = n1.X |
| | continue |
| | } |
| | if n1.Op() == ir.OCONVIFACE { |
| | n = n1.X |
| | continue |
| | } |
| | case *ir.InlinedCallExpr: |
| | if n1.Op() == ir.OINLCALL { |
| | n = n1.SingleResult() |
| | continue |
| | } |
| | case *ir.ParenExpr: |
| | n = n1.X |
| | continue |
| | case *ir.TypeAssertExpr: |
| | n = n1.X |
| | continue |
| | } |
| | break |
| | } |
| |
|
| | if n.Op() != ir.ONAME { |
| | return nil |
| | } |
| |
|
| | name := n.(*ir.Name).Canonical() |
| | if name.Class != ir.PAUTO { |
| | return nil |
| | } |
| |
|
| | if name.Op() != ir.ONAME { |
| | base.FatalfAt(name.Pos(), "name.Op = %v; want = ONAME", n.Op()) |
| | } |
| |
|
| | |
| | if name.Curfn == nil { |
| | base.FatalfAt(name.Pos(), "name.Curfn = nil; want not nil") |
| | } |
| |
|
| | if name.Addrtaken() { |
| | return nil |
| | } |
| |
|
| | if _, ok := seen[name]; ok { |
| | return &noType |
| | } |
| | seen[name] = struct{}{} |
| |
|
| | if concreteTypeDebug { |
| | base.Warn("concreteType1(%v): analyzing assignments to %v", nn, name) |
| | } |
| |
|
| | var typ *types.Type |
| | for _, v := range s.assignments(name) { |
| | var t *types.Type |
| | switch v := v.(type) { |
| | case *types.Type: |
| | t = v |
| | case ir.Node: |
| | t = concreteType1(s, v, seen) |
| | if t == &noType { |
| | continue |
| | } |
| | } |
| | if t == nil || (typ != nil && !types.Identical(typ, t)) { |
| | return nil |
| | } |
| | typ = t |
| | } |
| |
|
| | if typ == nil { |
| | |
| | return &noType |
| | } |
| |
|
| | return typ |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | type assignment any |
| |
|
| | |
| | type State struct { |
| | |
| | |
| | |
| | ifaceAssignments map[*ir.Name][]assignment |
| |
|
| | |
| | |
| | ifaceCallExprAssigns map[*ir.CallExpr][]ifaceAssignRef |
| |
|
| | |
| | analyzedFuncs map[*ir.Func]struct{} |
| | } |
| |
|
| | type ifaceAssignRef struct { |
| | name *ir.Name |
| | assignmentIndex int |
| | returnIndex int |
| | } |
| |
|
| | |
| | func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, inlinedCall *ir.InlinedCallExpr) { |
| | if _, ok := s.analyzedFuncs[fun]; !ok { |
| | |
| | |
| | return |
| | } |
| |
|
| | |
| | s.analyze(inlinedCall.Init()) |
| | s.analyze(inlinedCall.Body) |
| |
|
| | refs, ok := s.ifaceCallExprAssigns[origCall] |
| | if !ok { |
| | return |
| | } |
| | delete(s.ifaceCallExprAssigns, origCall) |
| |
|
| | |
| | for _, ref := range refs { |
| | vt := &s.ifaceAssignments[ref.name][ref.assignmentIndex] |
| | if *vt != nil { |
| | base.Fatalf("unexpected non-nil assignment") |
| | } |
| | if concreteTypeDebug { |
| | base.Warn( |
| | "InlinedCall(%v, %v): replacing interface node in (%v,%v) to %v (typ %v)", |
| | origCall, inlinedCall, ref.name, ref.assignmentIndex, |
| | inlinedCall.ReturnVars[ref.returnIndex], |
| | inlinedCall.ReturnVars[ref.returnIndex].Type(), |
| | ) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | *vt = inlinedCall.ReturnVars[ref.returnIndex] |
| | } |
| | } |
| |
|
| | |
| | func (s *State) assignments(n *ir.Name) []assignment { |
| | fun := n.Curfn |
| | if fun == nil { |
| | base.FatalfAt(n.Pos(), "n.Curfn = <nil>") |
| | } |
| | if n.Class != ir.PAUTO { |
| | base.FatalfAt(n.Pos(), "n.Class = %v; want = PAUTO", n.Class) |
| | } |
| |
|
| | if !n.Type().IsInterface() { |
| | base.FatalfAt(n.Pos(), "name passed to assignments is not of an interface type: %v", n.Type()) |
| | } |
| |
|
| | |
| | if _, ok := s.analyzedFuncs[fun]; !ok { |
| | if concreteTypeDebug { |
| | base.Warn("assignments(): analyzing assignments in %v func", fun) |
| | } |
| | if s.analyzedFuncs == nil { |
| | s.ifaceAssignments = make(map[*ir.Name][]assignment) |
| | s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef) |
| | s.analyzedFuncs = make(map[*ir.Func]struct{}) |
| | } |
| | s.analyzedFuncs[fun] = struct{}{} |
| | s.analyze(fun.Init()) |
| | s.analyze(fun.Body) |
| | } |
| |
|
| | return s.ifaceAssignments[n] |
| | } |
| |
|
| | |
| | func (s *State) analyze(nodes ir.Nodes) { |
| | assign := func(name ir.Node, assignment assignment) (*ir.Name, int) { |
| | if name == nil || name.Op() != ir.ONAME || ir.IsBlank(name) { |
| | return nil, -1 |
| | } |
| |
|
| | n, ok := ir.OuterValue(name).(*ir.Name) |
| | if !ok || n.Curfn == nil { |
| | return nil, -1 |
| | } |
| |
|
| | |
| | |
| | if !n.Type().IsInterface() { |
| | return nil, -1 |
| | } |
| |
|
| | n = n.Canonical() |
| | if n.Op() != ir.ONAME { |
| | base.FatalfAt(n.Pos(), "n.Op = %v; want = ONAME", n.Op()) |
| | } |
| | if n.Class != ir.PAUTO { |
| | return nil, -1 |
| | } |
| |
|
| | switch a := assignment.(type) { |
| | case nil: |
| | case *types.Type: |
| | if a != nil && a.IsInterface() { |
| | assignment = nil |
| | } |
| | case ir.Node: |
| | |
| | if ir.IsNil(a) { |
| | return nil, -1 |
| | } |
| | default: |
| | base.Fatalf("unexpected type: %v", assignment) |
| | } |
| |
|
| | if concreteTypeDebug { |
| | base.Warn("analyze(): assignment found %v = %v", name, assignment) |
| | } |
| |
|
| | s.ifaceAssignments[n] = append(s.ifaceAssignments[n], assignment) |
| | return n, len(s.ifaceAssignments[n]) - 1 |
| | } |
| |
|
| | var do func(n ir.Node) |
| | do = func(n ir.Node) { |
| | switch n.Op() { |
| | case ir.OAS: |
| | n := n.(*ir.AssignStmt) |
| | if rhs := n.Y; rhs != nil { |
| | for { |
| | if r, ok := rhs.(*ir.ParenExpr); ok { |
| | rhs = r.X |
| | continue |
| | } |
| | break |
| | } |
| | if call, ok := rhs.(*ir.CallExpr); ok && call.Fun != nil { |
| | retTyp := call.Fun.Type().Results()[0].Type |
| | n, idx := assign(n.X, retTyp) |
| | if n != nil && retTyp.IsInterface() { |
| | |
| | |
| | |
| | s.ifaceCallExprAssigns[call] = append(s.ifaceCallExprAssigns[call], ifaceAssignRef{n, idx, 0}) |
| | } |
| | } else { |
| | assign(n.X, rhs) |
| | } |
| | } |
| | case ir.OAS2: |
| | n := n.(*ir.AssignListStmt) |
| | for i, p := range n.Lhs { |
| | if n.Rhs[i] != nil { |
| | assign(p, n.Rhs[i]) |
| | } |
| | } |
| | case ir.OAS2DOTTYPE: |
| | n := n.(*ir.AssignListStmt) |
| | if n.Rhs[0] == nil { |
| | base.FatalfAt(n.Pos(), "n.Rhs[0] == nil; n = %v", n) |
| | } |
| | assign(n.Lhs[0], n.Rhs[0]) |
| | assign(n.Lhs[1], nil) |
| | case ir.OAS2MAPR, ir.OAS2RECV, ir.OSELRECV2: |
| | n := n.(*ir.AssignListStmt) |
| | if n.Rhs[0] == nil { |
| | base.FatalfAt(n.Pos(), "n.Rhs[0] == nil; n = %v", n) |
| | } |
| | assign(n.Lhs[0], n.Rhs[0].Type()) |
| | assign(n.Lhs[1], nil) |
| | case ir.OAS2FUNC: |
| | n := n.(*ir.AssignListStmt) |
| | rhs := n.Rhs[0] |
| | for { |
| | if r, ok := rhs.(*ir.ParenExpr); ok { |
| | rhs = r.X |
| | continue |
| | } |
| | break |
| | } |
| | if call, ok := rhs.(*ir.CallExpr); ok { |
| | for i, p := range n.Lhs { |
| | retTyp := call.Fun.Type().Results()[i].Type |
| | n, idx := assign(p, retTyp) |
| | if n != nil && retTyp.IsInterface() { |
| | |
| | |
| | |
| | s.ifaceCallExprAssigns[call] = append(s.ifaceCallExprAssigns[call], ifaceAssignRef{n, idx, i}) |
| | } |
| | } |
| | } else if call, ok := rhs.(*ir.InlinedCallExpr); ok { |
| | for i, p := range n.Lhs { |
| | assign(p, call.ReturnVars[i]) |
| | } |
| | } else { |
| | base.FatalfAt(n.Pos(), "unexpected type %T in OAS2FUNC Rhs[0]", call) |
| | } |
| | case ir.ORANGE: |
| | n := n.(*ir.RangeStmt) |
| | xTyp := n.X.Type() |
| |
|
| | |
| | if xTyp.IsPtr() && xTyp.Elem().IsArray() { |
| | xTyp = xTyp.Elem() |
| | } |
| |
|
| | if xTyp.IsArray() || xTyp.IsSlice() { |
| | assign(n.Key, nil) |
| | assign(n.Value, xTyp.Elem()) |
| | } else if xTyp.IsChan() { |
| | assign(n.Key, xTyp.Elem()) |
| | base.AssertfAt(n.Value == nil, n.Pos(), "n.Value != nil in range over chan") |
| | } else if xTyp.IsMap() { |
| | assign(n.Key, xTyp.Key()) |
| | assign(n.Value, xTyp.Elem()) |
| | } else if xTyp.IsInteger() || xTyp.IsString() { |
| | |
| | assign(n.Key, nil) |
| | assign(n.Value, nil) |
| | } else { |
| | |
| | |
| | base.FatalfAt(n.Pos(), "range over unexpected type %v", n.X.Type()) |
| | } |
| | case ir.OSWITCH: |
| | n := n.(*ir.SwitchStmt) |
| | if guard, ok := n.Tag.(*ir.TypeSwitchGuard); ok { |
| | for _, v := range n.Cases { |
| | if v.Var == nil { |
| | base.Assert(guard.Tag == nil) |
| | continue |
| | } |
| | assign(v.Var, guard.X) |
| | } |
| | } |
| | case ir.OCLOSURE: |
| | n := n.(*ir.ClosureExpr) |
| | if _, ok := s.analyzedFuncs[n.Func]; !ok { |
| | s.analyzedFuncs[n.Func] = struct{}{} |
| | ir.Visit(n.Func, do) |
| | } |
| | } |
| | } |
| | ir.VisitList(nodes, do) |
| | } |
| |
|