应用二进制接口(英语:application binary interface,缩写为ABI),这块源码的文档是与函数传参以及返回值传递到底是分配在栈还是寄存器上的调用规约
// 当前查看源代码的go版本
go version go1.17.3 windows/amd64
源码结构
abiStep
abiStep是一个ABI指令结构,它描述了值应该存储在栈中还是寄存器中,以及他的栈位置和寄存器索引IDabiSeq
abiSeq看缩写意思就是一系列的ABI指令,它包含了一个abiStep
列表,用valueStart
作为索引来定位函数第I个参数传递时所要用到的abiStep
列表,他还负责管理当前栈空间使用情况,寄存器使用情况abiDesc
abiDesc面向一个函数或者一个方法,它将入参和返回包装在了两个abiSeq
序列中,还包含了他们使用的栈空间,标记传递在栈中的是否为指针,寄存器中的是否为指针,方便暴露给GC
包级常量或变量的含义
var (
intArgRegs = abi.IntArgRegs * goexperiment.RegabiArgsInt
floatArgRegs = abi.FloatArgRegs * goexperiment.RegabiArgsInt
floatRegSize = uintptr(abi.EffectiveFloatRegSize * goexperiment.RegabiArgsInt)
)
intArgRegs
是整数寄存器的数量floatArgRegs
是浮点数寄存器的数量floatRegSize
浮点数寄存器的宽度,如果是0的话说明没有浮点寄存器或者使用softfloat,它会将浮点型参数分配到栈上,目的是为了兼容,因为使用场景很少,不太用考虑它的性能问题。因为go支持32位和64位的浮点数,它的值还可能是4或者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
定义了abiStep
描述的操作类型
abiStepBad
无效操作abiStepStack
栈分配abiStepIntReg
整数寄存器值分配abiStepPointer
整数寄存器指针分配abiStepFloatReg
浮点数寄存器分配
abiStep详细
他的结构如下,他就是一条基本的ABI指令描述
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 // 浮点数寄存器传递的话,它的寄存器索引
}
abiSeq详细
abiSeq就是abiStep的一系列组合,可以完成一系列步骤的一个封装结构
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
}
func (a *abiSeq) stepsForValue(i int) []abiStep
获取第i个参数的ABI指令描述序列。很简单的一个函数,就是通过valueStart定位steps,就不看这个源码了。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] }
func (a *abiSeq) stackAssign(size, alignment uintptr)
栈分配,它传入一个大小和对齐方式,封装成一个step添加到seq的steps列表后边就好了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 }
func (a *abiSeq) assignIntN(offset, size uintptr, n int, ptrMap uint8) bool
整数寄存器分配,它传入一个偏移,大小,元素个数,是否包含指针或者是一个接口,返回是否分配成功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)<
func (a *abiSeq) assignFloatN(offset, size uintptr, n int) bool
浮点数寄存器分配,跟上面差不多,就是不用判断是不是指针了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 }
func (a *abiSeq) regAssign(t *rtype, offset uintptr) bool
寄存器分配的封装,给定rtype类型元数据和偏移量进行分析如何分配寄存器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") }
func (a *abiSeq) addRcvr(rcvr *rtype) (*abiStep, bool)
添加方法接受者到steps步骤列表,如果是栈分配,就返回abiStep。如果有指针就返回truefunc (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 }
func (a *abiSeq) addArg(t *rtype) *abiStep
添加一个参数到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 }
abiDesc详细
abiDesc就开始面向函数或方法了,前面都是小打小闹,这块是整体封装,它就是一个完整的流程,并记录了所有参数或返回值的指针状态
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
}
他没有自己的方法,他有一个构造函数
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}
}
总结
不总接了,就是源码分析而已