syscall/asm_linux_amd64.s
文件中有注释说明://
// System calls for AMD64, Linux
//
// func Syscall(trap int64, a1, a2, a3 uintptr) (r1, r2, err uintptr);
// Trap # in AX, args in DI SI DX R10 R8 R9, return in AX DX
// Note that this differs from "standard" ABI convention, which
// would pass 4th arg in CX, not R10.
syscall.Syscall
函数的内部注释,简要说明了Linux系统调用的规范。系统调用的前6个参数直接由DI、SI、DX、R10、R8和R9
寄存器传输,结果由AX和DX
寄存器返回。macOS等类UINX系统调用的参数传输大多数都采用类似的规则。/usr/include/sys/syscall.h
头文件,Linux的系统调用号在/usr/include/asm/unistd.h
头文件。虽然在UNIX家族中是系统调用的参数和返回值的传输规则类似,但是不同操作系统提供的系统调用却不是完全相同的,因此系统调用编号也有很大的差异。以UNIX系统中著名的write系统调用为例,在macOS的系统调用编号为4,而在Linux的系统调用编号却是1。// func SyscallWrite_Darwin(fd int, msg string) int
TEXT ·SyscallWrite_Darwin(SB), NOSPLIT, $0
MOVQ $(0x2000000+4), AX // #define SYS_write 4
MOVQ fd+0(FP), DI
MOVQ msg_data+8(FP), SI
MOVQ msg_len+16(FP), DX
SYSCALL
MOVQ AX, ret+0(FP)
RET
reflect.StringHeader
结构定义,第一成员是8字节的数据指针,第二个成员是8字节的数据长度。在macOS系统中,执行系统调用时还需要将系统调用的编号加上0x2000000后再行传入AX。然后再将fd、数据地址和长度作为write系统调用的三个参数输入,分别对应DI、SI和DX
三个寄存器。最后通过SYSCALL指令执行系统调用,系统调用返回后从AX获取返回值。func SyscallWrite_Darwin(fd int, msg string) int
func main() {
if runtime.GOOS == "darwin" {
SyscallWrite_Darwin(1, "hello syscall!\n")
}
}
#include <stdint.h>
int64_t myadd(int64_t a, int64_t b) {
return a+b;
}
func asmCallCAdd(cfun uintptr, a, b int64) int64
// System V AMD64 ABI
// func asmCallCAdd(cfun uintptr, a, b int64) int64
TEXT ·asmCallCAdd(SB), NOSPLIT, $0
MOVQ cfun+0(FP), AX // cfun
MOVQ a+8(FP), DI // a
MOVQ b+16(FP), SI // b
CALL AX
MOVQ AX, ret+24(FP)
RET
CX、DX、R8和R9
四个寄存器传递参数(如果是浮点数则需要通过XMM寄存器传送),返回值依然通过AX返回。虽然是可以通过寄存器传输参数,但是调用这依然要为前四个参数准备栈空间。需要注意的是,Windows x64的系统调用和C语言函数可能是采用相同的调用规则。因为没有Windows测试环境,我们这里就不提供了Windows版本的代码实现了,Windows用户可以自己尝试实现类似功能。/*
#include <stdint.h>
int64_t myadd(int64_t a, int64_t b) {
return a+b;
}
*/
import "C"
import (
asmpkg "path/to/asm"
)
func main() {
if runtime.GOOS != "windows" {
println(asmpkg.asmCallCAdd(
uintptr(unsafe.Pointer(C.myadd)),
123, 456,
))
}
}
C.myadd
获取C函数的地址,然后转换为合适的类型再传人asmCallCAdd函数。在这个例子中,汇编函数假设调用的C语言函数需要的栈很小,可以直接复用Go函数中多余的空间。如果C语言函数可能需要较大的栈,可以尝试像CGO那样切换到系统线程的栈上运行。internal/cpu
包提供了CPU是否支持某些高级指令的基本信息,但是只有标准库才能引用这个包(因为internal路径的限制)。该包底层是通过X86提供的CPUID指令来识别处理器的详细信息。最简便的方法是直接将internal/cpu
包克隆一份。不过这个包为了避免复杂的依赖没有使用init函数自动初始化,因此需要根据情况手工调整代码执行doinit函数初始化。package cpu
var X86 x86
// The booleans in x86 contain the correspondingly named cpuid feature bit.
// HasAVX and HasAVX2 are only set if the OS does support XMM and YMM registers
// in addition to the cpuid feature bit being set.
// The struct is padded to avoid false sharing.
type x86 struct {
HasAES bool
HasADX bool
HasAVX bool
HasAVX2 bool
HasBMI1 bool
HasBMI2 bool
HasERMS bool
HasFMA bool
HasOSXSAVE bool
HasPCLMULQDQ bool
HasPOPCNT bool
HasSSE2 bool
HasSSE3 bool
HasSSSE3 bool
HasSSE41 bool
HasSSE42 bool
}
import (
cpu "path/to/cpu"
)
func main() {
if cpu.X86.HasAVX2 {
// support AVX2
}
}
// func CopySlice_AVX2(dst, src []byte, len int)
TEXT ·CopySlice_AVX2(SB), NOSPLIT, $0
MOVQ dst_data+0(FP), DI
MOVQ src_data+24(FP), SI
MOVQ len+32(FP), BX
MOVQ $0, AX
LOOP:
VMOVDQU 0(SI)(AX*1), Y0
VMOVDQU Y0, 0(DI)(AX*1)
ADDQ $32, AX
CMPQ AX, BX
JL LOOP
RET
0(SI)(AX*1)
地址开始的32字节数据复制到Y0寄存器中,然后再复制到0(DI)(AX*1)
对应的目标内存中。VMOVDQU指令操作的数据地址可以不用对齐。原文:https://www.cnblogs.com/binHome/p/13052328.html