8.4.5 Unit Test

内联的 UT 可以参考IR Tree, 因为内联操作修改的便是 IR Tree. 例如如果我们想要测试如下代码:

package p

func C() {
    println("C")
    D()
}

func D() {
    println("D")
    C()
}

func main() {
    C()
}

为了方便,这里我们将所有的测试代码拷贝到测试文件 cmd/compile/internal/noder/inl_test.go, 完整内容如下:

package noder

import (
    "bufio"
    "cmd/compile/internal/amd64"
    "cmd/compile/internal/base"
    "cmd/compile/internal/deadcode"
    "cmd/compile/internal/escape"
    "cmd/compile/internal/inline"
    "cmd/compile/internal/ir"
    "cmd/compile/internal/reflectdata"
    "cmd/compile/internal/ssa"
    "cmd/compile/internal/ssagen"
    "cmd/compile/internal/syntax"
    "cmd/compile/internal/typecheck"
    "cmd/compile/internal/types"
    "cmd/compile/internal/types2"
    "cmd/internal/obj"
    "cmd/internal/objabi"
    "cmd/internal/src"
    "fmt"
    "os"
    "strings"
    "testing"
)

func parseSrc(path, src string) (*syntax.File, error) {
    var mode syntax.Mode
    mode = syntax.AllowGenerics | syntax.CheckBranches
    errh := func(error) {} // dummy error handler so that parsing continues in presence of errors
    return syntax.Parse(syntax.NewFileBase(path), strings.NewReader(src), errh, nil, mode)
}

func defaultImporter() *gcimports {
    return &gcimports{
        packages: map[string]*types2.Package{},
    }
}

func init() {
    amd64.Init(&ssagen.Arch)

    base.Ctxt = obj.Linknew(ssagen.Arch.LinkArch)
    base.Ctxt.DiagFunc = base.Errorf
    base.Ctxt.DiagFlush = base.FlushErrors
    base.Ctxt.Bso = bufio.NewWriter(os.Stdout)

    base.Ctxt.UseBASEntries = base.Ctxt.Headtype != objabi.Hdarwin

    types.LocalPkg = types.NewPkg("", "")
    types.LocalPkg.Prefix = "\"\""

    types.LocalPkg.Height = types.MaxPkgHeight

    types.BuiltinPkg = types.NewPkg("go.builtin", "") // TODO(gri) name this package go.builtin?
    types.BuiltinPkg.Prefix = "go.builtin"            // not go%2ebuiltin

    // pseudo-package, accessed by import "unsafe"
    ir.Pkgs.Unsafe = types.NewPkg("unsafe", "unsafe")

    ir.Pkgs.Runtime = types.NewPkg("go.runtime", "runtime")
    ir.Pkgs.Runtime.Prefix = "runtime"

    // pseudo-packages used in symbol tables
    ir.Pkgs.Itab = types.NewPkg("go.itab", "go.itab")
    ir.Pkgs.Itab.Prefix = "go.itab" // not go%2eitab

    // pseudo-package used for methods with anonymous receivers
    ir.Pkgs.Go = types.NewPkg("go", "")

    base.DebugSSA = ssa.PhaseOption

    ssagen.Arch.LinkArch.Init(base.Ctxt)
    ir.EscFmt = escape.Fmt
    ir.IsIntrinsicCall = ssagen.IsIntrinsicCall
    inline.SSADumpInline = ssagen.DumpInline
    ssagen.InitEnv()
    ssagen.InitTables()

    types.PtrSize = ssagen.Arch.LinkArch.PtrSize
    types.RegSize = ssagen.Arch.LinkArch.RegSize
    types.MaxWidth = ssagen.Arch.MAXWIDTH

    typecheck.Target = new(ir.Package)

    typecheck.NeedITab = func(t, iface *types.Type) { reflectdata.ITabAddr(t, iface) }
    typecheck.NeedRuntimeType = reflectdata.NeedRuntimeType // TODO(rsc): TypeSym for lock?

    base.AutogeneratedPos = base.Ctxt.PosTable.XPos(src.MakePos(src.NewFileBase("<autogenerated>", "<autogenerated>"), 1, 0))

    typecheck.InitUniverse()
}

func dumpIrTree(nodes []ir.Node) {
    for _, n := range nodes {
        s := fmt.Sprintf("\nafter noder2 %v", n)
        ir.Dump(s, n)
    }
}

func TestIrTree(t *testing.T) {
    code := `
package p

func C( )  {
    println("C")
D()
}

func D( )  {
    println("D")
C()
}


func main() {
C()
}
`
    base.Flag.LowerM = 2
    f, err := parseSrc("Inline", code)

    if err != nil {
        panic(err)
    }

    var conf types2.Config
    conf.Trace = false
    conf.Importer = defaultImporter()
    conf.Error = func(err error) {
        fmt.Printf("Typecheck Error: %v\n", err)
    }

    info := types2.Info{
        Types:      make(map[syntax.Expr]types2.TypeAndValue),
        Defs:       make(map[*syntax.Name]types2.Object),
        Uses:       make(map[*syntax.Name]types2.Object),
        Selections: make(map[*syntax.SelectorExpr]*types2.Selection),
        Implicits:  make(map[syntax.Node]types2.Object),
        Scopes:     make(map[syntax.Node]*types2.Scope),
        Inferred:   make(map[syntax.Expr]types2.Inferred),
    }

    pkg, err := conf.Check("<no package>", []*syntax.File{f}, &info)
    if err != nil {
        panic(err)
    }

    var m posMap
    g := irgen{
        target: typecheck.Target,
        self:   pkg,
        info:   &info,
        posMap: m,
        objs:   make(map[types2.Object]*ir.Name),
        typs:   make(map[types2.Type]*types.Type),
    }

    p := &noder{
        err:         make(chan syntax.Error),
        trackScopes: base.Flag.Dwarf,
        file:        f,
    }
    g.generate([]*noder{p})

    println("Before Inline:")
    dumpIrTree(typecheck.Target.Decls)

    inline.InlinePackage()

    println("After Inline:")
    dumpIrTree(typecheck.Target.Decls)
}

运行该测试用例,内联后的函数 C 与 D 的结构与前文我们分析的一致:

after noder2 C [0xc0001ec2c0]
.   DCLFUNC tc(1) Iota:-1 ABI:ABIInternal InlinabilityChecked FUNC-func() # Inline:4
.   DCLFUNC-body
.   .   PRINTN tc(1) Use:3 # Inline:5
.   .   PRINTN-Args
.   .   .   LITERAL-“C” tc(1) string # Inline:5
.   .   CALLFUNC tc(1) Use:3 # Inline:6
.   .   .   NAME-p.D tc(1) Class:PFUNC Offset:0 FUNC-func() # Inline:9

after noder2 D [0xc0001ec420]
.   DCLFUNC tc(1) Iota:-1 Label:1 ABI:ABIInternal InlinabilityChecked FUNC-func() # Inline:9
.   DCLFUNC-body
.   .   PRINTN tc(1) Use:3 # Inline:10
.   .   PRINTN-Args
.   .   .   LITERAL-“D” tc(1) string # Inline:10
.   .   BLOCK # Inline:11
.   .   BLOCK-List
.   .   .   INLMARK # +Inline:11
.   .   .   PRINTN tc(1) Use:3 # Inline:5
.   .   .   PRINTN-Args
.   .   .   .   LITERAL-“C” tc(1) string # Inline:5
.   .   .   CALLFUNC tc(1) Use:3 # Inline:6
.   .   .   .   NAME-p.D tc(1) Class:PFUNC Offset:0 FUNC-func() # Inline:9
.   .   .   LABEL # Inline:11 p..i0

读者可以尝试着调试更加复杂的代码,以便查看编译器在内联时如何处理参数以及返回值的,这里不再赘述。

最后更新于