# 8.4.2 内联判断

整个内联功能可以通过编译参数 `-l` 来禁止，对于某个特定函数，也可以通过编译命令 `go:noinline` 来禁止内联。除此之外，还有一些特定的情况下函数不能内联，例如函数体为空，或者函数包含一些特定的编译命令例如 `go:cgo_unsafe_args` 等。

除了这些明确的场景，一个函数能否内联取决于该函数的复杂度，前文中提到，函数调用对小函数影响更大，因此如何衡量函数的大小及复杂度，是编译器需要量化的事情。该逻辑由结构体 `hairyVisitor` 驱动：

```go
type hairyVisitor struct {
    // 内联“预算”，起始值为 80, 由常量 inlineMaxBudget 定义，函数体内各种操作都会有对应的“代价（cost）”，总体“代价”超过“预算”的话，那么该函数就会由于太复杂而无法内联
    budget int32
    // 保存不能内联的理由
    reason string
    // 方法调用的开销，默认为 57, 由常量 inlineExtraCallCost 定义
    extraCallCost int32
    // 用来记录当前函数的局部变量，函数内联时需要使用
    usedLocals ir.NameSet
    // 遍历函数并进行复杂度计算，实际为方法 hairyVisitor.doNode()
    do func(ir.Node) bool
}
```

编译器定义了一个“内联代价（Inline Cost）”来表达函数复杂度，内联代价与函数 AST 的节点数目正相关，每个子节点的 cost 为 1, 如果该节点还需要额外的操作，则还需要减掉对应的Cost, 例如函数调用的 Cost 是 57, 由常量 `inlineExtraCallCost` 定义；内联总代价小于 80 的函数可以内联，该数字被称为函数的“内联预算（Budget）”，由常量 `inlineMaxBudget` 定义。

计算内联代价的方法是 `hairyVisitor.doNode()`, 该方法对函数的 AST(IR Tree) 进行深度优先（DFS）遍历，并从“内联预算（budget 字段）”中减掉函数体的各种“代价（cost）”，如果最终预算还有盈余，则函数可被内联。例如下列函数：

```go
func B() {
    println("B")
    println("B")
}

// go:noinline
func C() {
    println("C")
}

func A() {
    println("A")
    C()
}
```

对于函数 B，语句 `println("B")` 总体的代价是 2, 因此整个函数的代价便是 4, 该函数没有用完内联预算，因此是可以内联的。而对于函数 A，其内联代价的计算如下：

> &#x20;Cost(A) = 2 + 2 + 57 = 61&#x20;
>
> 2: 语句 `println("A")` 的代价
>
> 2: 语句 `C()` 本身的代价&#x20;
>
> 57: 由于函数 C 无法内联，所以需要减去一个 extraCallCost, 默认为 57

A 仍然可以内联，但同时可以发现，如果 A 调用了两个以上不可内联的函数的话，那么他就会耗尽“预算”而无法内联了。

归总起来，判断函数能否内联的逻辑封装在函数 `CanInline` 中，精简代码之后其总体逻辑如下：

```go
func CanInline(fn *ir.Func) {
    // Part 1: 判断各种特定情况，
    // If marked "go:noinline", don't inline
    if fn.Pragma&ir.Noinline != 0 {
        reason = "marked go:noinline"
        return
    }
    // 省略判断其它各种情况的代码

    // Part 2: 根据函数的大小判断能否内联
    visitor := hairyVisitor{
        budget:        inlineMaxBudget,
        extraCallCost: cc,
    }
    if visitor.tooHairy(fn) {
        reason = visitor.reason
        return
    }

    // Part 3: 对于可以内联的函数，创建内联节点。该节点在后面内联操作时使用
    n.Func.Inl = &ir.Inline{
        Cost: inlineMaxBudget - visitor.budget,
        Dcl:  pruneUnusedAutos(n.Defn.(*ir.Func).Dcl, &visitor),
        Body: inlcopylist(fn.Body),
    }
}
```

注意最后一部分逻辑，对于可以内联的函数， `CanInline` 会为该函数创建一个 `ir.Inline` 对象。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://gocompiler.shizhz.me/8.-golang-bian-yi-qi-inline/8.4.2-nei-lian-pan-duan.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
