4

Application Binary Interface (English: application binary interface, abbreviated as ABI), the documentation of this source code is the calling convention of passing parameters to the function and passing the return value to the stack or register

// 当前查看源代码的go版本
go version go1.17.3 windows/amd64

source code structure

  1. abiStep abiStep is an ABI instruction structure that describes whether the value should be stored on the stack or in a register, as well as his stack location and register index ID
  2. abiSeq abiSeq The abbreviation means a series of ABI instructions, it contains a abiStep list, and valueStart used as an index to locate the abiStep list used when the first parameter of the function is passed. He is also responsible for managing the current stack space usage, register usage
  3. abiDesc abiDesc is oriented to a function or a method, it wraps the input and return in two abiSeq sequences, and also includes the stack space they use, marking whether the passed in the stack is a pointer, whether the register is a pointer, Convenient exposure to GC

Meaning of package-level constants or variables

var (
    intArgRegs   = abi.IntArgRegs * goexperiment.RegabiArgsInt
    floatArgRegs = abi.FloatArgRegs * goexperiment.RegabiArgsInt
    floatRegSize = uintptr(abi.EffectiveFloatRegSize * goexperiment.RegabiArgsInt)
)
  1. intArgRegs is the number of integer registers
  2. floatArgRegs is the number of floating point registers
  3. floatRegSize The width of the floating-point register. If it is 0, it means that there is no floating-point register or softfloat is used. It will allocate floating-point parameters to the stack. The purpose is for compatibility. Because there are few usage scenarios, it is not necessary to consider its performance. problem. Because go supports 32-bit and 64-bit floating point numbers, its value may also be 4 or 8
// abiStepKind is the "op-code" for an abiStep instruction.
type abiStepKind int

const (
    abiStepBad      abiStepKind = iota
    abiStepStack                // copy to/from stack
    abiStepIntReg               // copy to/from integer register
    abiStepPointer              // copy pointer to/from integer register
    abiStepFloatReg             // copy to/from FP register
)

abiStepKind defines the operation type abiStep described by

  1. abiStepBad Invalid operation
  2. abiStepStack stack allocation
  3. abiStepIntReg Integer register value assignment
  4. abiStepPointer Integer register pointer allocation
  5. abiStepFloatReg Floating point register allocation

abiStepDetails

His structure is as follows, he is a basic ABI instruction description

type abiStep struct {
    kind abiStepKind // 操作类型,上面有说了

    // 值在内存中的偏移和大小
    // offset 这块的offset是相对于第一个参数来说的,好比一个字符串数据需要占用俩个寄存器,它的第一个元素dataptr的offset就是0,第二个元素len的offset就是8,是相对于数据结构起始的位置
    offset uintptr
    // size没什么好说的,它的大小
    size   uintptr // size in bytes of the part

    stkOff uintptr // 栈传递的话,它的偏移,相对于第一个栈传递参数的开始位置
    ireg   int     // 整数寄存器传递的话,它的寄存器索引
    freg   int     // 浮点数寄存器传递的话,它的寄存器索引
}

abiSeqDetails

abiSeq is a series of combinations of abiStep, a package structure that can complete a series of steps

type abiSeq struct {
    // 这块就是它的步骤列表
    steps      []abiStep
    // 这个标记了第i个参数或返回值的abiStep位置,好比当前是参数传递,要传第一个参数,那么就是steps[valueStart[0]],以此类推
    // valueStart和steps数量不对应的原因是,比如字符串它占一个vauleStart但是却需要俩步step,懂了吧
    valueStart []int

    // 栈使用空间情况,当进行栈分配的时候,他就会增加
    stackBytes   uintptr // stack space used
    // 寄存器的使用情况,会标记各种寄存器的当前用量,目的是和约定的总量进行对比,看能否继续从寄存器分配
    iregs, fregs int     // registers used
}
  1. func (a *abiSeq) stepsForValue(i int) []abiStep Get the ABI instruction description sequence for the ith parameter. A very simple function is to locate steps through valueStart, and do not look at this source code.

    func (a *abiSeq) stepsForValue(i int) []abiStep {
     // s是开始位置
     s := a.valueStart[i]
     var e int
     // a.steps和a.valueStart数量不对应,反正目的是为了获取下一个参数传递描述信息的一组ABI序列
     if i == len(a.valueStart)-1 {
         e = len(a.steps)
     } else {
         e = a.valueStart[i+1]
     }
     return a.steps[s:e]
    }
  2. func (a *abiSeq) stackAssign(size, alignment uintptr) stack allocation, it passes in a size and alignment, encapsulates it as a step and adds it to the steps list of seq.

    func (a *abiSeq) stackAssign(size, alignment uintptr) {
     // 1. 将内存对齐到相应规格
     a.stackBytes = align(a.stackBytes, alignment)
     // 封装step描述
     a.steps = append(a.steps, abiStep{
         kind:   abiStepStack,
         offset: 0, // 这块0上面解释过了
         size:   size,
         stkOff: a.stackBytes,
     })
     a.stackBytes += size
    }
  3. func (a *abiSeq) assignIntN(offset, size uintptr, n int, ptrMap uint8) bool Integer register allocation, it passes in an offset, size, number of elements, whether it contains a pointer or an interface, and returns whether the allocation is successful

    func (a *abiSeq) assignIntN(offset, size uintptr, n int, ptrMap uint8) bool {
     // 元素个数高于8个,就报错
     if n > 8 || n < 0 {
         panic("invalid n")
     }
     // ptrMap != 0代表着有指针或者是接口,同时它的大小还不与平台相对应
     if ptrMap != 0 && size != ptrSize {
         panic("non-empty pointer map passed for non-pointer-size values")
     }
     // a.iregs+n 是我们当前a占有的寄存器数量,intArgRegs是总数量
     if a.iregs+n > intArgRegs {
         return false
     }
     // 开始分配
     for i := 0; i < n; i++ {
         // 类型是一个整数寄存器
         kind := abiStepIntReg
         // 判断有没有指针或者是不是一个接口
         if ptrMap&(uint8(1)<<i) != 0 {
             kind = abiStepPointer
         }
         // 封装
         a.steps = append(a.steps, abiStep{
             kind:   kind,
             offset: offset + uintptr(i)*size, // 上层传进来的offset都是0,这块的偏移相对于他的0号元素来说的
             size:   size,
             ireg:   a.iregs, // 寄存器索引
         })
         // 更新寄存器数量,当然这也标记着下一个寄存器的索引
         a.iregs++
     }
     return true
    }
  4. func (a *abiSeq) assignFloatN(offset, size uintptr, n int) bool Floating-point register allocation is similar to the above, but there is no need to judge whether it is a pointer or not

    func (a *abiSeq) assignFloatN(offset, size uintptr, n int) bool {
     if n < 0 {
         panic("invalid n")
     }
     if a.fregs+n > floatArgRegs || floatRegSize < size {
         return false
     }
     for i := 0; i < n; i++ {
         a.steps = append(a.steps, abiStep{
             kind:   abiStepFloatReg,
             offset: offset + uintptr(i)*size,
             size:   size,
             freg:   a.fregs,
         })
         a.fregs++
     }
     return true
    }
  5. func (a *abiSeq) regAssign(t *rtype, offset uintptr) bool encapsulation of register allocation, given rtype type metadata and offset for analysis How to allocate registers

    func (a *abiSeq) regAssign(t *rtype, offset uintptr) bool {
     // 分析rtype的kind,判断他的底层类型是
     switch t.Kind() {
     case UnsafePointer, Ptr, Chan, Map, Func:
         // 和指针相关的整数,ptrMap=0b1
         return a.assignIntN(offset, t.size, 1, 0b1)
     case Bool, Int, Uint, Int8, Uint8, Int16, Uint16, Int32, Uint32, Uintptr:
         // 普通整数,就是0b0
         return a.assignIntN(offset, t.size, 1, 0b0)
     case Int64, Uint64:
         switch ptrSize {
         case 4:
             // 如果平台字长是4,那么他需要2个4才能存储
             return a.assignIntN(offset, 4, 2, 0b0)
         case 8:
             // 如果平台字长是8,那么他需要1个8就能存储
             return a.assignIntN(offset, 8, 1, 0b0)
         }
     case Float32, Float64:
         return a.assignFloatN(offset, t.size, 1)
     case Complex64:
         return a.assignFloatN(offset, 4, 2)
     case Complex128:
         return a.assignFloatN(offset, 8, 2)
     case String:
         // 字符串 [dataptr, len],dataptr就是指针
         return a.assignIntN(offset, ptrSize, 2, 0b01)
     case Interface:
         // 接口的ptr是0b10说明他有俩个指针
         return a.assignIntN(offset, ptrSize, 2, 0b10)
     case Slice:
         // slice同理,有一个指向底层数组
         return a.assignIntN(offset, ptrSize, 3, 0b01)
     case Array:
         // array会在rtype后续内存中存放和自己的类型相关的信息,我们对该块内存进行重新组织成arrayType,方便我们获取特有的信息
         tt := (*arrayType)(unsafe.Pointer(t))
         // 判断tt的长度,如果长度为0,不用分配,长度为1,就分配一个数据,如果长度大于1,就返回失败,通过栈分配吧
         switch tt.len {
         case 0:
             // There's nothing to assign, so don't modify
             // a.steps but succeed so the caller doesn't
             // try to stack-assign this value.
             return true
         case 1:
             return a.regAssign(tt.elem, offset)
         default:
             return false
         }
     case Struct:
         // 结构体数据,也是先映射回去,再把所有的一个一个存进去,如果有一个失败,就返回失败,外面会回滚的
         st := (*structType)(unsafe.Pointer(t))
         for i := range st.fields {
             f := &st.fields[i]
             // 你看这块的offset开始要添加fields的offset了,再次说明他这个offset是相对已0号元素来说的
             if !a.regAssign(f.typ, offset+f.offset()) {
                 return false
             }
         }
         return true
     default:
         print("t.Kind == ", t.Kind(), "\n")
         panic("unknown type kind")
     }
     panic("unhandled register assignment path")
    }
  6. func (a *abiSeq) addRcvr(rcvr *rtype) (*abiStep, bool) Add method receiver to steps step list, if it is stack allocation, return abiStep. Returns true if there is a pointer

    func (a *abiSeq) addRcvr(rcvr *rtype) (*abiStep, bool) {
     a.valueStart = append(a.valueStart, len(a.steps))
     var ok, ptr bool
     
     if ifaceIndir(rcvr) || rcvr.pointers() {
         // 返回rcvr是否在接口中或者其中是否包含指针,如果有指针或者是在接口中,进行寄存器传值时ptrMap 0b1,二进制的1代表这个是一个指针,涉及到GC回收
         ok = a.assignIntN(0, ptrSize, 1, 0b1)
         ptr = true
     } else {
         // TODO(mknyszek): Is this case even possible?
         // The interface data work never contains a non-pointer
         // value. This case was copied over from older code
         // in the reflect package which only conditionally added
         // a pointer bit to the reflect.(Value).Call stack frame's
         // GC bitmap.
         // 如果接受者不在接口中并且不含指针
         ok = a.assignIntN(0, ptrSize, 1, 0b0)
         ptr = false
     }
     if !ok {
         // 分配失败,就进行栈分配
         a.stackAssign(ptrSize, ptrSize)
         return &a.steps[len(a.steps)-1], ptr
     }
     return nil, ptr
    }
  7. func (a *abiSeq) addArg(t *rtype) *abiStep Add a parameter to the steps list of steps

    func (a *abiSeq) addArg(t *rtype) *abiStep {
     pStart := len(a.steps)
     a.valueStart = append(a.valueStart, pStart)
     if t.size == 0 {
         // 如果这个类型大小为0,为了降级到ABI0(稳定的ABI版本),我们需要栈分配,虽然他不占据空间,但他会影响下一次内存对齐,因此我们还是需要执行此操作,但没有必要生成一个step
         a.stackBytes = align(a.stackBytes, uintptr(t.align))
         return nil
     }
     // 保留a的副本,方便我们寄存器分配失败时回滚
     aOld := *a
     // 寄存器分配开始,offset=0,分配失败的话,就需要回滚,然后进行栈分配
     if !a.regAssign(t, 0) {
         // 回滚并进行栈分配
         *a = aOld
         // 栈分配成功会返回最后一个步骤的step
         a.stackAssign(t.size, uintptr(t.align))
         return &a.steps[len(a.steps)-1]
     }
     return nil
    }

abiDescDetails

abiDesc starts to face functions or methods. The front is a little mess. This is the overall package. It is a complete process and records the pointer status of all parameters or return values.

type abiDesc struct {
    // 这就是两个部分,调用和返回俩个步骤,都是单独的abiSeq
    call, ret abiSeq

    // 这些字段是为了给调用者分配栈空间,stackCallArgsSize是参数空间,retOffset是返回值开始的偏移量,spill是保留的额外空间的大小
    stackCallArgsSize, retOffset, spill uintptr

    // 一个位图,指ABI参数和返回值是否是一个指针,用作栈空间反射调用堆指针空间位图,栈中分配的指针位图
    // 与runtime.bitvector.一致
    stackPtrs *bitVector

    // 寄存器中分配的指针位图
    // inRegPtrs,入参 使用makeFuncStub methodValueCall使对GC可见
    // outRegPtrs,返回 使用reflectcall使对GC可见
    // 第i位是否包含指针
    // 这俩个和GC有关,用来暴露给GC
    // 这个IntArgRegBitmap是[(IntArgRegs + 7) / 8]uint8
    inRegPtrs, outRegPtrs abi.IntArgRegBitmap
}

he doesn't have his own method, he has a constructor

func newAbiDesc(t *funcType, rcvr *rtype) abiDesc {
    // We need to add space for this argument to
    // the frame so that it can spill args into it.
    //
    // The size of this space is just the sum of the sizes
    // of each register-allocated type.
    //
    // TODO(mknyszek): Remove this when we no longer have
    // caller reserved spill space.
    // 我们需要分配一个空间,可以将参数溢出到这里
    // 这个大小只是每个寄存器分配类型的大小总和
    // 当我们不再有调用者时删除他
    spill := uintptr(0)

    // 栈分配指针位图
    stackPtrs := new(bitVector)

    // 入参指针位图
    inRegPtrs := abi.IntArgRegBitmap{}

    // 包装inSeq
    var in abiSeq
    if rcvr != nil {
        // 如果有接受者,就当做第一个参数传入
        stkStep, isPtr := in.addRcvr(rcvr)
        if stkStep != nil {
            // !=nil 证明栈分配,判断是否有指针,并进行位图修改
            if isPtr {
                stackPtrs.append(1)
            } else {
                stackPtrs.append(0)
            }
        } else {
            // 如果是寄存器,则spill添加这个平台字长,记录寄存器使用总大小
            spill += ptrSize
        }
    }
    // 开始遍历函数的入参列表
    for i, arg := range t.in() {
        stkStep := in.addArg(arg)
        if stkStep != nil {
            // 如果是栈分配,就更新位图
            addTypeBits(stackPtrs, stkStep.stkOff, arg)
        } else {
            // 如果是寄存器,就相应的修改spill空间大小
            spill = align(spill, uintptr(arg.align))
            spill += arg.size
            for _, st := range in.stepsForValue(i) {
                if st.kind == abiStepPointer {
                    // 如果有指针,就修改入参寄存器指针位图
                    inRegPtrs.Set(st.ireg)
                }
            }
        }
    }
    // 对齐到平台字长
    spill = align(spill, ptrSize)

    // 字面意思
    stackCallArgsSize := in.stackBytes
    // 从这块我们也能看出retOffset是相对于第一个参数来说的
    retOffset := align(in.stackBytes, ptrSize)

    // Compute the stack frame pointer bitmap and register
    // pointer bitmap for return values.
    outRegPtrs := abi.IntArgRegBitmap{}

    // Compute abiSeq for output parameters.
    var out abiSeq
    // Stack-assigned return values do not share
    // space with arguments like they do with registers,
    // so we need to inject a stack offset here.
    // Fake it by artificially extending stackBytes by
    // the return offset.
    out.stackBytes = retOffset
    for i, res := range t.out() {
        stkStep := out.addArg(res)
        if stkStep != nil {
            addTypeBits(stackPtrs, stkStep.stkOff, res)
        } else {
            for _, st := range out.stepsForValue(i) {
                if st.kind == abiStepPointer {
                    outRegPtrs.Set(st.ireg)
                }
            }
        }
    }
    // Undo the faking from earlier so that stackBytes
    // is accurate.
    out.stackBytes -= retOffset
    return abiDesc{in, out, stackCallArgsSize, retOffset, spill, stackPtrs, inRegPtrs, outRegPtrs}
}

Summarize

It's not always connected, it's just source code analysis


sccc
17 声望2 粉丝

我自己记录学习过程的,有误的话还望大家指正一下。