go 函数参数nil_go内置函数make

本文详细分析了Go语言内置函数`make`在编译过程中的处理,从词法分析、语法分析、语义分析到替换runtime实现的步骤。在词法分析阶段,`make`被识别为普通函数调用;语法分析时,识别其创建slice、map、chan的能力;语义分析确保参数正确性;最后通过`makemap`等函数在runtime中实现。举例展示了`make`创建map的反编译结果。
摘要由CSDN通过智能技术生成

go内置函数make主要用于创建map, slice, chan等数据结构。下面简要分析下编译器对于make的处理过程。

一 内置函数的定义

universe.go源文件定义了go内置函数列表,Main函数调用initUniverse,进而调用lexinit对builtinpkg进行了初始化。SetSubOp调用会将Node结构(下文给出)中的Op设置为对应的值,用于标识该Node结构为内置函数。

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},
}

func lexinit(){
    for _, s := range builtinFuncs {
        s2 := builtinpkg.Lookup(s.name)
        s2.Def = asTypesNode(newname(s2))
        asNode(s2.Def).SetSubOp(s.op)
    }
}

初始化builtinpkg包后,调用下面的代码片码使得内置函数对当前包(localpkg)可见。这样使得在后续解析源码时,可以识别出make为内置函数。

for _, s := range builtinpkg.Syms {
    if s.Def == nil {
        continue
    }
    s1 := lookup(s.Name)
    if s1.Def != nil {
        continue
    }
    s1.Def = s.Def
    s1.Block = s.Block
}

二 词法分析

编译器在词法分析阶段会用如下的CallExpr结构来保存make函数调用。

// Fun(ArgList[0], ArgList[1], ...)
CallExpr struct {
    Fun     Expr
    ArgList []Expr // nil means no arguments
    HasDots bool   // last argument is followed by ...
    expr
}

待词法解析阶段完成后,编译器将CallExpr结构转成用Node结构表示的语法树。

type Node struct {
    Left  *Node
    Right *Node
    Ninit Nodes
    Nbody Nodes
    List  Nodes

    Op  Op
}

转换代码如下所示,将Node结构中的Op设置为OCALL,Left设置为函数名,List设置为函数参数列表。

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

当然到目前为止,make只是被识别为一个普通的函数调用。

三 语法分析

编译器在解析函数调用OCALL时,会识别出OCALL结构的Left结点的Op值为OMAKE,那么会如下图所示代码片段进行改写。最终将OCALL结构成为OMAKE结构。

case OCALL:
    typecheckslice(n.Ninit.Slice(), ctxStmt) // imported rewritten f(g()) calls (#30907)
    n.Left = typecheck(n.Left, ctxExpr|Etype|ctxCallee)
    l := n.Left
    if l.Op == ONAME && l.SubOp() != 0 {
        // builtin: OLEN, OCAP, etc.
        n.Op = l.SubOp()
        n.Left = n.Right
        n.Right = nil
        n = typecheck1(n, top)
        return n
    }

ce1f8ab4541a1827f1c62824412dc9d5.png
OCALL转成OMAKE示意图

四 语义分析

接下来对make进行语义分析,make目前只能创建slice, map, chan三种数据结构,其他数据结构无法创建,因此在下述代码中default分支将报错。在TMAP分支中,代码检查make的第二个参数是否是整型。最后将Node.Op改成OMAKEMAP。在TSLICE和TCHAN分支中也会做类似的检查,确保参数是正确的。

case OMAKE:
    args := n.List.Slice()
    if len(args) == 0 {
        yyerror("missing argument to make")
        n.Type = nil
        return n
    }

    n.List.Set(nil)
    l := args[0]
    l = typecheck(l, Etype)
    t := l.Type
    
    i := 1
    switch t.Etype {
    default:
        yyerror("cannot make type %v", t)
        n.Type = nil
        return n

    case TSLICE:
        ....

    case TMAP:
        if i < len(args) {
            l = args[i]
            i++
            l = typecheck(l, ctxExpr)
            l = defaultlit(l, types.Types[TINT])
            if l.Type == nil {
                n.Type = nil
                return n
            }
            if !checkmake(t, "size", l) {
                n.Type = nil
                return n
            }
                n.Left = l
            } else {
                n.Left = nodintconst(0)
            }
            n.Op = OMAKEMAP

    case TCHAN:
        ....

    if i < len(args) {
        yyerror("too many arguments to make(%v)", t)
        n.Op = OMAKE
        n.Type = nil
        return n
    }

    n.Type = t

五 替换runtime实现

walkexpr函数会在语义分析后将make函数替换成runtime包中定义的对应的函数。创建chan是由makechan等完成,创建map是由makemap等完成,创建slice是由makeslice等完成的。下面代码以OMAKEMAP对应的makemap函数为例进行分析。

首先,在builtin.go源文件中预定义了若干内置函数,如下代码片段所示,67,68,69对应makemap64,makemap, makemap_small,都是用于创建map的内置函数。

var runtimeDecls = [...]struct {
    name string
    tag  int
    typ  int
}{
    ...
    {"makemap64", funcTag, 67},
    {"makemap", funcTag, 68},
    {"makemap_small", funcTag, 69},
    ...
}

func runtimeTypes() []*types.Type {
    var typs [117]*types.Type
    ...
    typs[67] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[19]), anonfield(typs[3])}, []*Node{anonfield(typs[66])})
    typs[68] = functype(nil, []*Node{anonfield(typs[1]), anonfield(typs[11]), anonfield(typs[3])}, []*Node{anonfield(typs[66])})
    typs[69] = functype(nil, nil, []*Node{anonfield(typs[66])})
    ...
}

其次,Main调用loadsys初始化Runtimepkg。

func loadsys() {
    typs := runtimeTypes()
    for _, d := range runtimeDecls {
        sym := Runtimepkg.Lookup(d.name)
        typ := typs[d.typ]
        switch d.tag {
        case funcTag:
            importfunc(Runtimepkg, src.NoXPos, sym, typ)
        case varTag:
            importvar(Runtimepkg, src.NoXPos, sym, typ)
        default:
            Fatalf("unhandled declaration tag %v", d.tag)
        }
    }
}

然后,walkexp调用syslook函数从Runtimepkg中找出对应的Node结点,调用substArgTypes函数替换原先的通用的函数原型为特定的函数原型,并在调用typename函数时将创建的map类型(包括key类型,value类型)保存在signatslice中。signatslice保存了调用make(map[int32]string, 1000)中的map类型(key为int32, value为string)。调用mkcall1创建了OCALL结构。

fnname := "makemap64"
argtype := types.Types[TINT64]
if hint.Type.IsKind(TIDEAL) || maxintval[hint.Type.Etype].Cmp(maxintval[TUINT]) <= 0 {
    fnname = "makemap"
    argtype = types.Types[TINT]
}
fn := syslook(fnname)
fn = substArgTypes(fn, hmapType, t.Key(), t.Elem())
n = mkcall1(fn, n.Type, init, typename(n.Type), conv(hint, argtype), h)

最后,编译器会将signatslice中保存的map类型写入到文件中。这个是在dtypesym中完成的。

func dtypesym(t *types.Type) *obj.LSym {
    switch t.Etype {
    case TMAP:
        s1 := dtypesym(t.Key())
        s2 := dtypesym(t.Elem())
        s3 := dtypesym(bmap(t))
        ot = dcommontype(lsym, t)
        ot = dsymptr(lsym, ot, s1, 0)
        ot = dsymptr(lsym, ot, s2, 0)
        ot = dsymptr(lsym, ot, s3, 0)
        ...
    }
}

dtypesym按照runtime包中定义的数据结构写组织数据。这一步准备的数据是用于makemap函数的第一个参数。

type _type struct {
    size       uintptr
    ptrdata    uintptr // size of memory prefix holding all pointers
    hash       uint32
    tflag      tflag
    align      uint8
    fieldalign uint8
    kind       uint8
    alg        *typeAlg
    // gcdata stores the GC type data for the garbage collector.
    // If the KindGCProg bit is set in kind, gcdata is a GC program.
    // Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}

type maptype struct {
    typ        _type
    key        *_type
    elem       *_type
    bucket     *_type // internal type representing a hash bucket
    keysize    uint8  // size of key slot
    valuesize  uint8  // size of value slot
    bucketsize uint16 // size of bucket
    flags      uint32
}

六 一个例子

创建test.go文件如下,mytestfunc创建了一个map结构,大小为0x2222。

package main

func mytestfunc() {
    var m = make(map[int64]int64, 0x2222)
    _ = m

}

func main() {
    mytestfunc()
}

编译好go文件后,再使用objdump反编译ELF文件。main.mytestfunc中mytestfunc函数的反编译。44f9a5将45e180这个值写到RAX寄存器,RAX存储着makemap(runtime.makemap)的第一个参数,该参数是一个指针。44f9b0将0x2222写入到内存,这个值是调用make时指定的值。

000000000044f970 <main.mytestfunc>:
  44f970:       64 48 8b 0c 25 f8 ff    mov    %fs:0xfffffffffffffff8,%rcx
  44f977:       ff ff
  44f979:       48 3b 61 10             cmp    0x10(%rcx),%rsp
  44f97d:       76 5d                   jbe    44f9dc <main.mytestfunc+0x6c>
  44f97f:       48 83 ec 60             sub    $0x60,%rsp
  44f983:       48 89 6c 24 58          mov    %rbp,0x58(%rsp)
  44f988:       48 8d 6c 24 58          lea    0x58(%rsp),%rbp
  44f98d:       0f 57 c0                xorps  %xmm0,%xmm0
  44f990:       0f 11 44 24 28          movups %xmm0,0x28(%rsp)
  44f995:       0f 57 c0                xorps  %xmm0,%xmm0
  44f998:       0f 11 44 24 38          movups %xmm0,0x38(%rsp)
  44f99d:       0f 57 c0                xorps  %xmm0,%xmm0
  44f9a0:       0f 11 44 24 48          movups %xmm0,0x48(%rsp)
  44f9a5:       48 8d 05 d4 e7 00 00    lea    0xe7d4(%rip),%rax        # 45e180 <type.*+0xe180>
  44f9ac:       48 89 04 24             mov    %rax,(%rsp)
  44f9b0:       48 c7 44 24 08 22 22    movq   $0x2222,0x8(%rsp)
  44f9b7:       00 00
  44f9b9:       48 8d 44 24 28          lea    0x28(%rsp),%rax
  44f9be:       48 89 44 24 10          mov    %rax,0x10(%rsp)
  44f9c3:       e8 f8 b8 fb ff          callq  40b2c0 <runtime.makemap>
  44f9c8:       48 8b 44 24 18          mov    0x18(%rsp),%rax
  44f9cd:       48 89 44 24 20          mov    %rax,0x20(%rsp)
  44f9d2:       48 8b 6c 24 58          mov    0x58(%rsp),%rbp
  44f9d7:       48 83 c4 60             add    $0x60,%rsp
  44f9db:       c3                      retq
  44f9dc:       e8 9f 7e ff ff          callq  447880 <runtime.morestack_noctxt>
  44f9e1:       eb 8d                   jmp    44f970 <main.mytestfunc>

45e180处的数据如下。45e1c8, 45e1c9处的两个0x08表示map的key和value的大小都是8字节。其实的值可以按照maptype定义逐个分析。

  45e180:       08 00                   or     %al,(%rax)
  45e182:       00 00                   add    %al,(%rax)
  45e184:       00 00                   add    %al,(%rax)
  45e186:       00 00                   add    %al,(%rax)
  45e188:       08 00                   or     %al,(%rax)
  45e18a:       00 00                   add    %al,(%rax)
  45e18c:       00 00                   add    %al,(%rax)
  45e18e:       00 00                   add    %al,(%rax)
  45e190:       9c                      pushfq
  45e191:       10 d3                   adc    %dl,%bl
  45e193:       02 02                   add    (%rdx),%al
  45e195:       08 08                   or     %cl,(%rax)
  45e197:       35 a0 fe 4b 00          xor    $0x4bfea0,%eax
  45e19c:       00 00                   add    %al,(%rax)
  45e19e:       00 00                   add    %al,(%rax)
  45e1a0:       6c                      insb   (%dx),%es:(%rdi)
  45e1a1:       bd 47 00 00 00          mov    $0x47,%ebp
  45e1a6:       00 00                   add    %al,(%rax)
  45e1a8:       ce                      (bad)
  45e1a9:       44 00 00                add    %r8b,(%rax)
  45e1ac:       00 00                   add    %al,(%rax)
  45e1ae:       00 00                   add    %al,(%rax)
  45e1b0:       20 ac 45 00 00 00 00    and    %ch,0x0(%rbp,%rax,2)
  45e1b7:       00 20                   add    %ah,(%rax)
  45e1b9:       ac                      lods   %ds:(%rsi),%al
  45e1ba:       45 00 00                add    %r8b,(%r8)
  45e1bd:       00 00                   add    %al,(%rax)
  45e1bf:       00 20                   add    %ah,(%rax)
  45e1c1:       57                      push   %rdi
  45e1c2:       46 00 00                rex.RX add %r8b,(%rax)
  45e1c5:       00 00                   add    %al,(%rax)
  45e1c7:       00 08                   add    %cl,(%rax)
  45e1c9:       08 90 00 04 00 00       or     %dl,0x400(%rax)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值