go 内联源码分析

本文深入剖析了Go语言中函数能否被内联的关键因素,如go:noinline标记、内存逃逸、预算限制等,并通过实例展示了如何通过节点操作来判断内联可能性。重点讲解了内联函数的决策过程,包括预算消耗和操作符类型的影响。
摘要由CSDN通过智能技术生成
  1. 分析基于go版本1.16.7
  2. 对于内建函数来说,在编译时候,Main函数会先把内建函数标上对应的Op
    src/cmd/compile/internal/gc/universe.go:87
    // initUniverse initializes the universe block.
    func initUniverse() {
    	lexinit()
    	typeinit()
    	lexinit1()
    }
    
    
    // lexinit initializes known symbols and the basic types.
    func lexinit() {
    	for _, s := range &basicTypes {
    		etype := s.etype
    		if int(etype) >= len(types.Types) {
    			Fatalf("lexinit: %s bad etype", s.name)
    		}
    		s2 := builtinpkg.Lookup(s.name)
    		t := types.Types[etype]
    		if t == nil {
    			t = types.New(etype)
    			t.Sym = s2
    			if etype != TANY && etype != TSTRING {
    				dowidth(t)
    			}
    			types.Types[etype] = t
    		}
    		s2.Def = asTypesNode(typenod(t))
    		asNode(s2.Def).Name = new(Name)
    	}
    
    	for _, s := range &builtinFuncs {
    		s2 := builtinpkg.Lookup(s.name)
    		s2.Def = asTypesNode(newname(s2))
    		asNode(s2.Def).SetSubOp(s.op)
    	}
    
    	for _, s := range &unsafeFuncs {
    		s2 := unsafepkg.Lookup(s.name)
    		s2.Def = asTypesNode(newname(s2))
    		asNode(s2.Def).SetSubOp(s.op)
    	}
    
    	types.UntypedString = types.New(TSTRING)
    	types.UntypedBool = types.New(TBOOL)
    	types.Types[TANY] = types.New(TANY)
    
    	s := builtinpkg.Lookup("true")
    	s.Def = asTypesNode(nodbool(true))
    	asNode(s.Def).Sym = lookup("true")
    	asNode(s.Def).Name = new(Name)
    	asNode(s.Def).Type = types.UntypedBool
    
    	s = builtinpkg.Lookup("false")
    	s.Def = asTypesNode(nodbool(false))
    	asNode(s.Def).Sym = lookup("false")
    	asNode(s.Def).Name = new(Name)
    	asNode(s.Def).Type = types.UntypedBool
    
    	s = lookup("_")
    	s.Block = -100
    	s.Def = asTypesNode(newname(s))
    	types.Types[TBLANK] = types.New(TBLANK)
    	asNode(s.Def).Type = types.Types[TBLANK]
    	nblank = asNode(s.Def)
    
    	s = builtinpkg.Lookup("_")
    	s.Block = -100
    	s.Def = asTypesNode(newname(s))
    	types.Types[TBLANK] = types.New(TBLANK)
    	asNode(s.Def).Type = types.Types[TBLANK]
    
    	types.Types[TNIL] = types.New(TNIL)
    	s = builtinpkg.Lookup("nil")
    	var v Val
    	v.U = new(NilVal)
    	s.Def = asTypesNode(nodlit(v))
    	asNode(s.Def).Sym = s
    	asNode(s.Def).Name = new(Name)
    
    	s = builtinpkg.Lookup("iota")
    	s.Def = asTypesNode(nod(OIOTA, nil, nil))
    	asNode(s.Def).Sym = s
    	asNode(s.Def).Name = new(Name)
    }
    
    src/cmd/compile/internal/gc/universe.go:45
    var builtinFuncs = [...]struct {
    	name string
    	op   Op
    }{
    	{"append", OAPPEND},
    	{"cap", OCAP},
    	{"close", OCLOSE},
    	{"complex", OCOMPLEX},
    	{"copy", OCOPY},
    	{"delete", ODELETE},
    	{"imag", OIMAG},
    	{"len", OLEN},
    	{"make", OMAKE},
    	{"new", ONEW},
    	{"panic", OPANIC},
    	{"print", OPRINT},
    	{"println", OPRINTN},
    	{"real", OREAL},
    	{"recover", ORECOVER},
    }
    
  3. 能否内联主要在caninl: src/cmd/compile/internal/gc/inl.go
    // Caninl determines whether fn is inlineable.
    // If so, caninl saves fn->nbody in fn->inl and substitutes it with a copy.
    // fn and ->nbody will already have been typechecked.
    func caninl(fn *Node) {
    	if fn.Op != ODCLFUNC {
    		Fatalf("caninl %v", fn)
    	}
    	if fn.Func.Nname == nil {
    		Fatalf("caninl no nname %+v", fn)
    	}
    
    	var reason string // reason, if any, that the function was not inlined
    	if Debug.m > 1 || logopt.Enabled() {
    		defer func() {
    			if reason != "" {
    				if Debug.m > 1 {
    					fmt.Printf("%v: cannot inline %v: %s\n", fn.Line(), fn.Func.Nname, reason)
    				}
    				if logopt.Enabled() {
    					logopt.LogOpt(fn.Pos, "cannotInlineFunction", "inline", fn.funcname(), reason)
    				}
    			}
    		}()
    	}
    
    	// If marked "go:noinline", don't inline
    	if fn.Func.Pragma&Noinline != 0 {
    		reason = "marked go:noinline"
    		return
    	}
    
    	// If marked "go:norace" and -race compilation, don't inline.
    	if flag_race && fn.Func.Pragma&Norace != 0 {
    		reason = "marked go:norace with -race compilation"
    		return
    	}
    
    	// If marked "go:nocheckptr" and -d checkptr compilation, don't inline.
    	if Debug_checkptr != 0 && fn.Func.Pragma&NoCheckPtr != 0 {
    		reason = "marked go:nocheckptr"
    		return
    	}
    
    	// If marked "go:cgo_unsafe_args", don't inline, since the
    	// function makes assumptions about its argument frame layout.
    	if fn.Func.Pragma&CgoUnsafeArgs != 0 {
    		reason = "marked go:cgo_unsafe_args"
    		return
    	}
    
    	// If marked as "go:uintptrescapes", don't inline, since the
    	// escape information is lost during inlining.
    	if fn.Func.Pragma&UintptrEscapes != 0 {
    		reason = "marked as having an escaping uintptr argument"
    		return
    	}
    
    	// The nowritebarrierrec checker currently works at function
    	// granularity, so inlining yeswritebarrierrec functions can
    	// confuse it (#22342). As a workaround, disallow inlining
    	// them for now.
    	if fn.Func.Pragma&Yeswritebarrierrec != 0 {
    		reason = "marked go:yeswritebarrierrec"
    		return
    	}
    
    	// If fn has no body (is defined outside of Go), cannot inline it.
    	if fn.Nbody.Len() == 0 {
    		reason = "no function body"
    		return
    	}
    
    	if fn.Typecheck() == 0 {
    		Fatalf("caninl on non-typechecked function %v", fn)
    	}
    
    	n := fn.Func.Nname
    	if n.Func.InlinabilityChecked() {
    		return
    	}
    	defer n.Func.SetInlinabilityChecked(true)
    
    	cc := int32(inlineExtraCallCost)
    	if Debug.l == 4 {
    		cc = 1 // this appears to yield better performance than 0.
    	}
    
    	// At this point in the game the function we're looking at may
    	// have "stale" autos, vars that still appear in the Dcl list, but
    	// which no longer have any uses in the function body (due to
    	// elimination by deadcode). We'd like to exclude these dead vars
    	// when creating the "Inline.Dcl" field below; to accomplish this,
    	// the hairyVisitor below builds up a map of used/referenced
    	// locals, and we use this map to produce a pruned Inline.Dcl
    	// list. See issue 25249 for more context.
    
    	visitor := hairyVisitor{
    		budget:        inlineMaxBudget,
    		extraCallCost: cc,
    		usedLocals:    make(map[*Node]bool),
    	}
    	if visitor.visitList(fn.Nbody) {
    		reason = visitor.reason
    		return
    	}
    	if visitor.budget < 0 {
    		reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-visitor.budget, inlineMaxBudget)
    		return
    	}
    
    	n.Func.Inl = &Inline{
    		Cost: inlineMaxBudget - visitor.budget,
    		Dcl:  inlcopylist(pruneUnusedAutos(n.Name.Defn.Func.Dcl, &visitor)),
    		Body: inlcopylist(fn.Nbody.Slice()),
    	}
    
    	// hack, TODO, check for better way to link method nodes back to the thing with the ->inl
    	// this is so export can find the body of a method
    	fn.Type.FuncType().Nname = asTypesNode(n)
    
    	if Debug.m > 1 {
    		fmt.Printf("%v: can inline %#v with cost %d as: %#v { %#v }\n", fn.Line(), n, inlineMaxBudget-visitor.budget, fn.Type, asNodes(n.Func.Inl.Body))
    	} else if Debug.m != 0 {
    		fmt.Printf("%v: can inline %v\n", fn.Line(), n)
    	}
    	if logopt.Enabled() {
    		logopt.LogOpt(fn.Pos, "canInlineFunction", "inline", fn.funcname(), fmt.Sprintf("cost: %d", inlineMaxBudget-visitor.budget))
    	}
    }
    
  4. 除了显示指出go:noinline等,能否内联主要靠visit分析函数消耗的节点是否超过80: src/cmd/compile/internal/gc/inl.go
    func (v *hairyVisitor) visit(n *Node) bool {
    	if n == nil {
    		return false
    	}
    
    	switch n.Op {
    	// Call is okay if inlinable and we have the budget for the body.
    	case OCALLFUNC:
    		// Functions that call runtime.getcaller{pc,sp} can not be inlined
    		// because getcaller{pc,sp} expect a pointer to the caller's first argument.
    		//
    		// runtime.throw is a "cheap call" like panic in normal code.
    		if n.Left.Op == ONAME && n.Left.Class() == PFUNC && isRuntimePkg(n.Left.Sym.Pkg) {
    			fn := n.Left.Sym.Name
    			if fn == "getcallerpc" || fn == "getcallersp" {
    				v.reason = "call to " + fn
    				return true
    			}
    			if fn == "throw" {
    				v.budget -= inlineExtraThrowCost
    				break
    			}
    		}
    
    		if isIntrinsicCall(n) {
    			// Treat like any other node.
    			break
    		}
    
    		if fn := inlCallee(n.Left); fn != nil && fn.Func.Inl != nil {
    			v.budget -= fn.Func.Inl.Cost
    			break
    		}
    
    		// Call cost for non-leaf inlining.
    		v.budget -= v.extraCallCost
    
    	// Call is okay if inlinable and we have the budget for the body.
    	case OCALLMETH:
    		t := n.Left.Type
    		if t == nil {
    			Fatalf("no function type for [%p] %+v\n", n.Left, n.Left)
    		}
    		if t.Nname() == nil {
    			Fatalf("no function definition for [%p] %+v\n", t, t)
    		}
    		if isRuntimePkg(n.Left.Sym.Pkg) {
    			fn := n.Left.Sym.Name
    			if fn == "heapBits.nextArena" {
    				// Special case: explicitly allow
    				// mid-stack inlining of
    				// runtime.heapBits.next even though
    				// it calls slow-path
    				// runtime.heapBits.nextArena.
    				break
    			}
    		}
    		if inlfn := asNode(t.FuncType().Nname).Func; inlfn.Inl != nil {
    			v.budget -= inlfn.Inl.Cost
    			break
    		}
    		// Call cost for non-leaf inlining.
    		v.budget -= v.extraCallCost
    
    	// Things that are too hairy, irrespective of the budget
    	case OCALL, OCALLINTER:
    		// Call cost for non-leaf inlining.
    		v.budget -= v.extraCallCost
    
    	case OPANIC:
    		v.budget -= inlineExtraPanicCost
    
    	case ORECOVER:
    		// recover matches the argument frame pointer to find
    		// the right panic value, so it needs an argument frame.
    		v.reason = "call to recover"
    		return true
    
    	case OCLOSURE,
    		ORANGE,
    		OSELECT,
    		OGO,
    		ODEFER,
    		ODCLTYPE, // can't print yet
    		ORETJMP:
    		v.reason = "unhandled op " + n.Op.String()
    		return true
    
    	case OAPPEND:
    		v.budget -= inlineExtraAppendCost
    
    	case ODCLCONST, OEMPTY, OFALL:
    		// These nodes don't produce code; omit from inlining budget.
    		return false
    
    	case OLABEL:
    		// TODO(mdempsky): Add support for inlining labeled control statements.
    		if n.labeledControl() != nil {
    			v.reason = "labeled control"
    			return true
    		}
    
    	case OBREAK, OCONTINUE:
    		if n.Sym != nil {
    			// Should have short-circuited due to labeledControl above.
    			Fatalf("unexpected labeled break/continue: %v", n)
    		}
    
    	case OIF:
    		if Isconst(n.Left, CTBOOL) {
    			// This if and the condition cost nothing.
    			return v.visitList(n.Ninit) || v.visitList(n.Nbody) ||
    				v.visitList(n.Rlist)
    		}
    
    	case ONAME:
    		if n.Class() == PAUTO {
    			v.usedLocals[n] = true
    		}
    
    	}
    
    	v.budget--
    
    	// When debugging, don't stop early, to get full cost of inlining this function
    	if v.budget < 0 && Debug.m < 2 && !logopt.Enabled() {
    		return true
    	}
    
    	return v.visit(n.Left) || v.visit(n.Right) ||
    		v.visitList(n.List) || v.visitList(n.Rlist) ||
    		v.visitList(n.Ninit) || v.visitList(n.Nbody)
    }
    
    inlineMaxBudget       = 80
    
  5. func从funcDecl转成node
    func (p *noder) funcDecl(fun *syntax.FuncDecl) *Node {
    	name := p.name(fun.Name)
    	t := p.signature(fun.Recv, fun.Type)
    	f := p.nod(fun, ODCLFUNC, nil, nil)
    
    	if fun.Recv == nil {
    		if name.Name == "init" {
    			name = renameinit()
    			if t.List.Len() > 0 || t.Rlist.Len() > 0 {
    				yyerrorl(f.Pos, "func init must have no arguments and no return values")
    			}
    		}
    
    		if localpkg.Name == "main" && name.Name == "main" {
    			if t.List.Len() > 0 || t.Rlist.Len() > 0 {
    				yyerrorl(f.Pos, "func main must have no arguments and no return values")
    			}
    		}
    	} else {
    		f.Func.Shortname = name
    		name = nblank.Sym // filled in by typecheckfunc
    	}
    
    	f.Func.Nname = newfuncnamel(p.pos(fun.Name), name)
    	f.Func.Nname.Name.Defn = f
    	f.Func.Nname.Name.Param.Ntype = t
    
    	if pragma, ok := fun.Pragma.(*Pragma); ok {
    		f.Func.Pragma = pragma.Flag & FuncPragmas
    		if pragma.Flag&Systemstack != 0 && pragma.Flag&Nosplit != 0 {
    			yyerrorl(f.Pos, "go:nosplit and go:systemstack cannot be combined")
    		}
    		pragma.Flag &^= FuncPragmas
    		p.checkUnused(pragma)
    	}
    
    	if fun.Recv == nil {
    		declare(f.Func.Nname, PFUNC)
    	}
    
    	p.funcBody(f, fun.Body)
    
    	if fun.Body != nil {
    		if f.Func.Pragma&Noescape != 0 {
    			yyerrorl(f.Pos, "can only use //go:noescape with external func implementations")
    		}
    	} else {
    		if pure_go || strings.HasPrefix(f.funcname(), "init.") {
    			// Linknamed functions are allowed to have no body. Hopefully
    			// the linkname target has a body. See issue 23311.
    			isLinknamed := false
    			for _, n := range p.linknames {
    				if f.funcname() == n.local {
    					isLinknamed = true
    					break
    				}
    			}
    			if !isLinknamed {
    				yyerrorl(f.Pos, "missing function body")
    			}
    		}
    	}
    
    	return f
    }
    
    func (p *noder) funcBody(fn *Node, block *syntax.BlockStmt) {
    	oldScope := p.scope
    	p.scope = 0
    	funchdr(fn)
    
    	if block != nil {
    		body := p.stmts(block.List)
    		if body == nil {
    			body = []*Node{nod(OEMPTY, nil, nil)}
    		}
    		fn.Nbody.Set(body)
    
    		lineno = p.makeXPos(block.Rbrace)
    		fn.Func.Endlineno = lineno
    	}
    
    	funcbody()
    	p.scope = oldScope
    }
    
    可以看到funcNode的Op是ODCLFUNC,left,right是nil
  6. 具体常用语句的例子: go build -gcflags="-m -m" inline.go,语法分析之后生成node的源码位置:src/cmd/compile/internal/gc/noder.go/func (p *noder) stmtFall(stmt syntax.Stmt, fallOK bool) *Node
    4.1 空函数
    package inline
    var a int
    func get(a, b int) {
    
    }
    
    
    .\inline.go:3:6: can inline get with cost 0 as: func(int, int) {  }
    // visit的是函数的body,body里没有内容,所以是0,和参数没有关系
    
    4.2 返回语句
    package inline
    var global int
    func get(a, b int) int{
    
    	return 0
    }
    
    .\inline.go:3:6: can inline get with cost 2 as: func(int, int) int { return 0 }
    
    
    case *syntax.ReturnStmt:
    		var results []*Node
    		if stmt.Results != nil {
    			results = p.exprList(stmt.Results)
    		}
    		n := p.nod(stmt, ORETURN, nil, nil)
    		n.List.Set(results)
    		if n.List.Len() == 0 && Curfn != nil {
    			for _, ln := range Curfn.Func.Dcl {
    				if ln.Class() == PPARAM {
    					continue
    				}
    				if ln.Class() != PPARAMOUT {
    					break
    				}
    				if asNode(ln.Sym.Def) != ln {
    					yyerror("%s is shadowed during return", ln.Sym.Name)
    				}
    			}
    		}
    		return n
    
    retrun语句的Op为ORETURN,left,right为nil,list为返回的结果切片,所以return语句消耗的节点数为1+return的个数
    4.3 赋值语句
    case *syntax.AssignStmt:
    	if stmt.Op != 0 && stmt.Op != syntax.Def {
    		n := p.nod(stmt, OASOP, p.expr(stmt.Lhs), p.expr(stmt.Rhs))
    		n.SetImplicit(stmt.Rhs == syntax.ImplicitOne)
    		n.SetSubOp(p.binOp(stmt.Op))
    		return n
    	}
    
    	n := p.nod(stmt, OAS, nil, nil) // assume common case
    
    	rhs := p.exprList(stmt.Rhs)
    	lhs := p.assignList(stmt.Lhs, n, stmt.Op == syntax.Def)
    
    	if len(lhs) == 1 && len(rhs) == 1 {
    		// common case
    		n.Left = lhs[0]
    		n.Right = rhs[0]
    	} else {
    		n.Op = OAS2
    		n.List.Set(lhs)
    		n.Rlist.Set(rhs)
    	}
    	return n
    
    
    
    func (p *noder) assignList(expr syntax.Expr, defn *Node, colas bool) []*Node {
    	if !colas {
    		return p.exprList(expr)
    	}
    
    	defn.SetColas(true)
    
    	var exprs []syntax.Expr
    	if list, ok := expr.(*syntax.ListExpr); ok {
    		exprs = list.ElemList
    	} else {
    		exprs = []syntax.Expr{expr}
    	}
    
    	res := make([]*Node, len(exprs))
    	seen := make(map[*types.Sym]bool, len(exprs))
    
    	newOrErr := false
    	for i, expr := range exprs {
    		p.setlineno(expr)
    		res[i] = nblank
    
    		name, ok := expr.(*syntax.Name)
    		if !ok {
    			p.yyerrorpos(expr.Pos(), "non-name %v on left side of :=", p.expr(expr))
    			newOrErr = true
    			continue
    		}
    
    		sym := p.name(name)
    		if sym.IsBlank() {
    			continue
    		}
    
    		if seen[sym] {
    			p.yyerrorpos(expr.Pos(), "%v repeated on left side of :=", sym)
    			newOrErr = true
    			continue
    		}
    		seen[sym] = true
    
    		if sym.Block == types.Block {
    			res[i] = oldname(sym)
    			continue
    		}
    
    		newOrErr = true
    		n := newname(sym)
    		declare(n, dclcontext)
    		n.Name.Defn = defn
    		defn.Ninit.Append(nod(ODCL, n, nil))
    		res[i] = n
    	}
    
    	if !newOrErr {
    		yyerrorl(defn.Pos, "no new variables on left side of :=")
    	}
    	return res
    }
    
    package inline
    
    var global int
    func get(a, b int) {
    	global = 1
    }
    
    
    .\inline.go:4:6: can inline get with cost 3 as: func(int, int) { global = 1 }
    
    可以看到直接用等号赋值的话消耗的节点数为3,因为该node Op为OASOP,左子树为ONAME,右子树为BasicExpr(如果等号右边为复杂表达式的话将右子树的消耗节点数换成对应表达式消耗的节点数即可)
    package inline
    
    func get(a, b int) int{
    	c := 1
    	return c
    }
    
    .\inline.go:3:6: can inline get with cost 7 as: func(int, int) int { c := 1; return c }
    
    如果用:=赋值的话,可以看到该语句消耗的节点数为5(7-2(2为return语句消耗)),根据上述代码可以看到该节点为OAS,左子树为name,NinIt是声明节点,右子树为基础变量
    在这里插入图片描述
    // visit相当于前序遍历,先当前节点的budget--,然后从Left,Right,Ninit遍历叶子节点
    return v.visit(n.Left) || v.visit(n.Right) ||
    	v.visitList(n.List) || v.visitList(n.Rlist) ||
    	v.visitList(n.Ninit) || v.visitList(n.Nbody)
    
    利用上一讲修改源码的方法我们在canInl这里加一行打印,打印当前的花销和Op值,可以验证上面的node图
    在这里插入图片描述
    在这里插入图片描述
    4.4 声明语句
    case *syntax.DeclStmt:
    		return liststmt(p.decls(stmt.DeclList))
    
    func (p *noder) decls(decls []syntax.Decl) (l []*Node) {
    	var cs constState
    
    	for _, decl := range decls {
    		p.setlineno(decl)
    		switch decl := decl.(type) {
    		case *syntax.ImportDecl:
    			p.importDecl(decl)
    
    		case *syntax.VarDecl:
    			l = append(l, p.varDecl(decl)...)
    
    		case *syntax.ConstDecl:
    			l = append(l, p.constDecl(decl, &cs)...)
    
    		case *syntax.TypeDecl:
    			l = append(l, p.typeDecl(decl))
    
    		case *syntax.FuncDecl:
    			l = append(l, p.funcDecl(decl))
    
    		default:
    			panic("unhandled Decl")
    		}
    	}
    
    	return
    }
    
    // declare variables from grammar
    // new_name_list (type | [type] = expr_list)
    func variter(vl []*Node, t *Node, el []*Node) []*Node {
    	var init []*Node
    	doexpr := len(el) > 0
    
    	if len(el) == 1 && len(vl) > 1 {
    		e := el[0]
    		as2 := nod(OAS2, nil, nil)
    		as2.List.Set(vl)
    		as2.Rlist.Set1(e)
    		for _, v := range vl {
    			v.Op = ONAME
    			declare(v, dclcontext)
    			v.Name.Param.Ntype = t
    			v.Name.Defn = as2
    			if Curfn != nil {
    				init = append(init, nod(ODCL, v, nil))
    			}
    		}
    
    		return append(init, as2)
    	}
    
    	nel := len(el)
    	for _, v := range vl {
    		var e *Node
    		if doexpr {
    			if len(el) == 0 {
    				yyerror("assignment mismatch: %d variables but %d values", len(vl), nel)
    				break
    			}
    			e = el[0]
    			el = el[1:]
    		}
    
    		v.Op = ONAME
    		declare(v, dclcontext)
    		v.Name.Param.Ntype = t
    
    		if e != nil || Curfn != nil || v.isBlank() {
    			if Curfn != nil {
    				init = append(init, nod(ODCL, v, nil))
    			}
    			e = nod(OAS, v, e)
    			init = append(init, e)
    			if e.Right != nil {
    				v.Name.Defn = e
    			}
    		}
    	}
    
    	if len(el) != 0 {
    		yyerror("assignment mismatch: %d variables but %d values", len(vl), nel)
    	}
    	return init
    }
    
    func liststmt(l []*Node) *Node {
    	n := nod(OBLOCK, nil, nil)
    	n.List.Set(l)
    	if len(l) != 0 {
    		n.Pos = l[0].Pos
    	}
    	return n
    }
    
    // 如果s.Op == OBLOCK && s.Ninit.Len() == 0,即声明语句的时候节点只有List
    func (p *noder) stmtsFall(stmts []syntax.Stmt, fallOK bool) []*Node {
    	var nodes []*Node
    	for i, stmt := range stmts {
    		s := p.stmtFall(stmt, fallOK && i+1 == len(stmts))
    		if s == nil {
    		} else if s.Op == OBLOCK && s.Ninit.Len() == 0 {
    			nodes = append(nodes, s.List.Slice()...)
    		} else {
    			nodes = append(nodes, s)
    		}
    	}
    	return nodes
    }
    
    package inline
    
    func get(a, b int) int{
    	var c int
    	return c
    }
    
    .\inline.go:3:6: can inline get with cost 6 as: func(int, int) int { var c int; c = <N>; return c }
    
    
    package inline
    
    func get(a, b int) int{
    	var c = 1
    	return c
    }
    
    .\inline.go:3:6: can inline get with cost 7 as: func(int, int) int { var c int; c = 1; return c }
    
    
    可以看到声明语句被改写为了声明和赋值两种语句,节点关系如下图,其中如果没有赋初值的话OAS节点的右子树为空,不赋初值的话消耗4,赋初值消耗5
    在这里插入图片描述
    4.5 if语句
    case *syntax.IfStmt:
    		return p.ifStmt(stmt)
    
    func (p *noder) ifStmt(stmt *syntax.IfStmt) *Node {
    	p.openScope(stmt.Pos())
    	n := p.nod(stmt, OIF, nil, nil)
    	if stmt.Init != nil {
    		n.Ninit.Set1(p.stmt(stmt.Init))
    	}
    	if stmt.Cond != nil {
    		n.Left = p.expr(stmt.Cond)
    	}
    	n.Nbody.Set(p.blockStmt(stmt.Then))
    	if stmt.Else != nil {
    		e := p.stmt(stmt.Else)
    		if e.Op == OBLOCK && e.Ninit.Len() == 0 {
    			n.Rlist.Set(e.List.Slice())
    		} else {
    			n.Rlist.Set1(e)
    		}
    	}
    	p.closeAnotherScope()
    	return n
    }
    
    可以看到if语句的节点是Ninit为可选的初始化语句,Left 为条件语句,Nbody为if语句块内的语句,Rlist为else语句块内的语句,需注意的是else自己本身不是node
    func get(a, b int) {
    	if c := a; c < 1 {
    		c = 2
    	} else {
    		c = 1
    	}
    }
    
    .\inline.go:3:6: can inline get with cost 15 as: func(int, int) { if c < 1 { c = 2 } else { c = 1 } }
    
    
    结合前面的分析,if消耗1,c := a 消耗5,c < 1消耗3,c = 2消耗3,c = 1消耗3,总计15
    4.6 switch语句
    case *syntax.SwitchStmt:
    		return p.switchStmt(stmt)
    
    func (p *noder) switchStmt(stmt *syntax.SwitchStmt) *Node {
    	p.openScope(stmt.Pos())
    	n := p.nod(stmt, OSWITCH, nil, nil)
    	if stmt.Init != nil {
    		n.Ninit.Set1(p.stmt(stmt.Init))
    	}
    	if stmt.Tag != nil {
    		n.Left = p.expr(stmt.Tag)
    	}
    
    	tswitch := n.Left
    	if tswitch != nil && tswitch.Op != OTYPESW {
    		tswitch = nil
    	}
    	n.List.Set(p.caseClauses(stmt.Body, tswitch, stmt.Rbrace))
    
    	p.closeScope(stmt.Rbrace)
    	return n
    }
    
    
    func (p *noder) caseClauses(clauses []*syntax.CaseClause, tswitch *Node, rbrace syntax.Pos) []*Node {
    	nodes := make([]*Node, 0, len(clauses))
    	for i, clause := range clauses {
    		p.setlineno(clause)
    		if i > 0 {
    			p.closeScope(clause.Pos())
    		}
    		p.openScope(clause.Pos())
    
    		n := p.nod(clause, OCASE, nil, nil)
    		if clause.Cases != nil {
    			n.List.Set(p.exprList(clause.Cases))
    		}
    		if tswitch != nil && tswitch.Left != nil {
    			nn := newname(tswitch.Left.Sym)
    			declare(nn, dclcontext)
    			n.Rlist.Set1(nn)
    			// keep track of the instances for reporting unused
    			nn.Name.Defn = tswitch
    		}
    
    		// Trim trailing empty statements. We omit them from
    		// the Node AST anyway, and it's easier to identify
    		// out-of-place fallthrough statements without them.
    		body := clause.Body
    		for len(body) > 0 {
    			if _, ok := body[len(body)-1].(*syntax.EmptyStmt); !ok {
    				break
    			}
    			body = body[:len(body)-1]
    		}
    
    		n.Nbody.Set(p.stmtsFall(body, true))
    		if l := n.Nbody.Len(); l > 0 && n.Nbody.Index(l-1).Op == OFALL {
    			if tswitch != nil {
    				yyerror("cannot fallthrough in type switch")
    			}
    			if i+1 == len(clauses) {
    				yyerror("cannot fallthrough final case in switch")
    			}
    		}
    
    		nodes = append(nodes, n)
    	}
    	if len(clauses) > 0 {
    		p.closeScope(rbrace)
    	}
    	return nodes
    }
    
    可以看到switch的Ninit为可能的初始化语句,Left 为可能的tag,List为switch的语句块,case是OCASE的node
    package inline
    
    func get(a, b int) {
    	switch c := a; c {
    	case 1,2:
    		c=1
    	case 3:
    		c=2
    	default:
    
    	}
    }
    
    .\inline.go:3:6: can inline get with cost 19 as: func(int, int) { switch statement }
    
    
    switch消耗1,c := a消耗5,c消耗1,case 1,2消耗3,c=1消耗3,case 3消耗2,c=2消耗3,default消耗1,总计19
    4.7 函数调用,如果直接函数调用是exprStmt,其中X是CallExpr
    func main() {
    	fset := token.NewFileSet()
    	f, err := parser.ParseFile(fset, "func.go", src, parser.AllErrors)
    	if err != nil {
    		log.Fatal(err)
    		return
    	}
    
    	ast.Print(nil, f.Decls[0].(*ast.FuncDecl).Body)
    }
    
    const src = `package pkgname
    func main() {
    	a = get(1)
    	get1()
    }  `
    
     0  *ast.BlockStmt {
     1  .  Lbrace: 29
     2  .  List: []ast.Stmt (len = 2) {
     3  .  .  0: *ast.AssignStmt {
     4  .  .  .  Lhs: []ast.Expr (len = 1) {
     5  .  .  .  .  0: *ast.Ident {
     6  .  .  .  .  .  NamePos: 32
     7  .  .  .  .  .  Name: "a"
     8  .  .  .  .  }
     9  .  .  .  }
    10  .  .  .  TokPos: 34
    11  .  .  .  Tok: =
    12  .  .  .  Rhs: []ast.Expr (len = 1) {
    13  .  .  .  .  0: *ast.CallExpr {
    14  .  .  .  .  .  Fun: *ast.Ident {
    15  .  .  .  .  .  .  NamePos: 36
    16  .  .  .  .  .  .  Name: "get"
    17  .  .  .  .  .  }
    18  .  .  .  .  .  Lparen: 39
    19  .  .  .  .  .  Args: []ast.Expr (len = 1) {
    20  .  .  .  .  .  .  0: *ast.BasicLit {
    21  .  .  .  .  .  .  .  ValuePos: 40
    22  .  .  .  .  .  .  .  Kind: INT
    23  .  .  .  .  .  .  .  Value: "1"
    24  .  .  .  .  .  .  }
    25  .  .  .  .  .  }
    26  .  .  .  .  .  Ellipsis: 0
    27  .  .  .  .  .  Rparen: 41
    28  .  .  .  .  }
    29  .  .  .  }
    30  .  .  }
    31  .  .  1: *ast.ExprStmt {
    32  .  .  .  X: *ast.CallExpr {
    33  .  .  .  .  Fun: *ast.Ident {
    34  .  .  .  .  .  NamePos: 44
    35  .  .  .  .  .  Name: "get1"
    36  .  .  .  .  }
    37  .  .  .  .  Lparen: 48
    38  .  .  .  .  Ellipsis: 0
    39  .  .  .  .  Rparen: 49
    40  .  .  .  }
    41  .  .  }
    42  .  }
    43  .  Rbrace: 51
    44  }
    
    case *syntax.ExprStmt:
    		return p.wrapname(stmt, p.expr(stmt.X))
    
    case *syntax.CallExpr:
    		n := p.nod(expr, OCALL, p.expr(expr.Fun), nil)
    		n.List.Set(p.exprs(expr.ArgList))
    		n.SetIsDDD(expr.HasDots)
    		return n
    
    其中node的Op为OCALL,Left为Fun(ONAME),List为参数
    case OCALLFUNC:
    		// Functions that call runtime.getcaller{pc,sp} can not be inlined
    		// because getcaller{pc,sp} expect a pointer to the caller's first argument.
    		//
    		// runtime.throw is a "cheap call" like panic in normal code.
    		if n.Left.Op == ONAME && n.Left.Class() == PFUNC && isRuntimePkg(n.Left.Sym.Pkg) {
    			fn := n.Left.Sym.Name
    			if fn == "getcallerpc" || fn == "getcallersp" {
    				v.reason = "call to " + fn
    				return true
    			}
    			if fn == "throw" {
    				v.budget -= inlineExtraThrowCost
    				break
    			}
    		}
    
    		if isIntrinsicCall(n) {
    			// Treat like any other node.
    			break
    		}
    
    		if fn := inlCallee(n.Left); fn != nil && fn.Func.Inl != nil {
    			v.budget -= fn.Func.Inl.Cost
    			break
    		}
    
    		// Call cost for non-leaf inlining.
    		v.budget -= v.extraCallCost
    
    
    inlineExtraCallCost  = 57              // 57 was benchmarked to provided most benefit with no bad surprises
    
    在canInl的visit中,对于OCALLFUNC,如果函数中调用的函数可以被内联,则要把内联函数的消耗减掉(因为内联函数会展开),如果不能内联,v.budget -= v.extraCallCost,即减57
    package inline
    
    func get(a, b int) {
    	get1(0)
    }
    
    func get1(a int) int{
    	return 0
    }
    
    .\inline.go:7:6: can inline get1 with cost 2 as: func(int) int { return 0 }
    
    .\inline.go:3:6: can inline get with cost 5 as: func(int, int) { get1(0) }
    .\inline.go:4:6: inlining call to get1 func(int) int { return 0 }
    
    
    get调用get1(0)时,OCALLFUNC会消耗1,get1 ONAME消耗1,0参数消耗1,get1()会被内联,get1()自身消耗2,所以共消耗5
    package inline
    
    import "fmt"
    
    func get(a, b int) {
    	a = 1
    	get1(0)
    }
    
    func get1(a int) int{
    	a = 1
    	fmt.Println("1234")
    	return a
    }
    
    .\inline.go:10:6: cannot inline get1: function too complex: cost 81 exceeds budget 80
    
    .\inline.go:5:6: can inline get with cost 63 as: func(int, int) { a = 1; get1(0) }
    .\inline.go:12:14: "1234" escapes to heap:
    
    由于get1消耗81没有内联,所以get消耗的节点数为a = 1消耗3,OCALLFUNC会消耗1,get1 ONAME消耗1,0参数消耗1,inlineExtraCallCost 为57,共计63
    在这里插入图片描述
    我们加的打印在budget–后,所以在调用get1()之后,先v.budget -= v.extraCallCost,然后v.budget,相邻两个节点差58,符合预期
    // call and call like
    	case OCALL:
    		typecheckslice(n.Ninit.Slice(), ctxStmt) // imported rewritten f(g()) calls (#30907)
    		n.Left = typecheck(n.Left, ctxExpr|ctxType|ctxCallee)
    		if n.Left.Diag() {
    			n.SetDiag(true)
    		}
    
    		l := n.Left
    
    		if l.Op == ONAME && l.SubOp() != 0 {
    			if n.IsDDD() && l.SubOp() != OAPPEND {
    				yyerror("invalid use of ... with builtin %v", l)
    			}
    
    			// builtin: OLEN, OCAP, etc.
    			n.Op = l.SubOp()
    			n.Left = n.Right
    			n.Right = nil
    			n = typecheck1(n, top)
    			return n
    		}
    
    		n.Left = defaultlit(n.Left, nil)
    		l = n.Left
    		if l.Op == OTYPE {
    			if n.IsDDD() {
    				if !l.Type.Broke() {
    					yyerror("invalid use of ... in type conversion to %v", l.Type)
    				}
    				n.SetDiag(true)
    			}
    
    			// pick off before type-checking arguments
    			ok |= ctxExpr
    
    			// turn CALL(type, arg) into CONV(arg) w/ type
    			n.Left = nil
    
    			n.Op = OCONV
    			n.Type = l.Type
    			if !onearg(n, "conversion to %v", l.Type) {
    				n.Type = nil
    				return n
    			}
    			n = typecheck1(n, top)
    			return n
    		}
    
    		typecheckargs(n)
    		t := l.Type
    		if t == nil {
    			n.Type = nil
    			return n
    		}
    		checkwidth(t)
    
    		switch l.Op {
    		case ODOTINTER:
    			n.Op = OCALLINTER
    
    		case ODOTMETH:
    			n.Op = OCALLMETH
    
    			// typecheckaste was used here but there wasn't enough
    			// information further down the call chain to know if we
    			// were testing a method receiver for unexported fields.
    			// It isn't necessary, so just do a sanity check.
    			tp := t.Recv().Type
    
    			if l.Left == nil || !types.Identical(l.Left.Type, tp) {
    				Fatalf("method receiver")
    			}
    
    		default:
    			n.Op = OCALLFUNC
    			if t.Etype != TFUNC {
    				name := l.String()
    				if isBuiltinFuncName(name) && l.Name.Defn != nil {
    					// be more specific when the function
    					// name matches a predeclared function
    					yyerror("cannot call non-function %s (type %v), declared at %s",
    						name, t, linestr(l.Name.Defn.Pos))
    				} else {
    					yyerror("cannot call non-function %s (type %v)", name, t)
    				}
    				n.Type = nil
    				return n
    			}
    		}
    
    src/cmd/compile/internal/gc/typecheck.go:326
    
    OCALL会在语法分析完进行类型检查时替换成相应的OCALLFUNC或OCALLMETH等
    4.8 for循环
    case *syntax.ForStmt:
    		return p.forStmt(stmt)
    
    func (p *noder) forStmt(stmt *syntax.ForStmt) *Node {
    	p.openScope(stmt.Pos())
    	var n *Node
    	if r, ok := stmt.Init.(*syntax.RangeClause); ok {
    		if stmt.Cond != nil || stmt.Post != nil {
    			panic("unexpected RangeClause")
    		}
    
    		n = p.nod(r, ORANGE, nil, p.expr(r.X))
    		if r.Lhs != nil {
    			n.List.Set(p.assignList(r.Lhs, n, r.Def))
    		}
    	} else {
    		n = p.nod(stmt, OFOR, nil, nil)
    		if stmt.Init != nil {
    			n.Ninit.Set1(p.stmt(stmt.Init))
    		}
    		if stmt.Cond != nil {
    			n.Left = p.expr(stmt.Cond)
    		}
    		if stmt.Post != nil {
    			n.Right = p.stmt(stmt.Post)
    		}
    	}
    	n.Nbody.Set(p.blockStmt(stmt.Body))
    	p.closeAnotherScope()
    	return n
    }
    
    其中如果没有range语句,node Op为OFOR,Init 为初始化部分,Left 为条件部分,Right 为执行部分(第二个分号后面内容),Nbody为语句块内容
    func get(a, b int) {
    	for i:=1;i<5;i++{
    		a++
    	}
    }
    .\inline.go:3:6: can inline get with cost 15 as: func(int, int) { for loop }
    
    for消耗1,i:=1消耗5,i<5消耗3,i++消耗3,a++消耗3,共计15
    在这里插入图片描述
    4.9 其他
    case ORECOVER:
    		// recover matches the argument frame pointer to find
    		// the right panic value, so it needs an argument frame.
    		v.reason = "call to recover"
    		return true
    
    	case OCLOSURE,
    		ORANGE,
    		OSELECT,
    		OGO,
    		ODEFER,
    		ODCLTYPE, // can't print yet
    		ORETJMP:
    		v.reason = "unhandled op " + n.Op.String()
    		return true
    
    对于这些Op都不会内联
    package inline
    
    func get(a, b int) {
    	var s = []int{1,2}
    	for i:=range s {
    		a += i
    	}
    }
    
    .\inline.go:3:6: cannot inline get: unhandled op RANGE
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值