玩一玩 golang 汇编(二)

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


上次玩 golang 汇编是使用了一个 python 的 intel 汇编转换 plan9 汇编的工具,很不好用。
本次试试一些 golang 实现的工具。

因为主要是想用汇编优化 linux amd64 平台下的代码,所以主要围绕这个平台展开。

0. Macos M2 下安装linux/amd64 开发容器

因为我用的开发机器是 macbook m2,为了便于模拟 linux/amd64 环境,使用 docker 安装开发容器先:

# 拉取镜像
docker pull golang:1.21.3 --platform=linux/amd64
# 启动容器
docker run -d -it --name amd64_go -v ~/code:/Users/ahfuzhang/code/ --cpus=4 -m=1g golang:1.21.3 bash
# 进入容器
docker container exec -it amd64_go bash

1.安装各种工具

go get -u github.com/minio/asm2plan9s
go get -u github.com/minio/c2goasm
go get -u github.com/klauspost/asmfmt/cmd/asmfmt
apt-get install build-essential
apt-get install clang

2.写一个 c 函数

本例子中使用凯撒加密的字符查表的实现:
pkg/caesar/c/caesar.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <immintrin.h>
#include <avx2intrin.h>
#include <inttypes.h>
#include <assert.h>

typedef uint8_t Table[26][256];

void caesarEncode(uint8_t* out, uint8_t* in, int len, int rot, Table* table){
    rot = rot % 26;
    uint8_t* end = in + len; 
    uint8_t* line = (uint8_t*)((*table)[rot]);
    for (;in<end; in++, out++){
        *out = (uint8_t)line[*in];
    }
}

以上代码编译为intel汇编:

clang -mno-red-zone -fno-asynchronous-unwind-tables -fno-exceptions \
      -fno-rtti -fno-stack-protector -nostdlib -O3 \
	  -masm=intel -mstackrealign -mllvm -inline-threshold=1000 \
	  -fno-jump-tables \
	  -msse4 -mavx -mavx2 -mavx512f -mavx512vl -mavx512bw \
	  -DUSE_AVX=1 -DUSE_AVX2=1 -DNO_TEST \
	  -S pkg/caesar/c/caesar.c -o pkg/caesar/c/caesar.s

3.使用 c2goasm 来转换为 plan9 汇编

3.1 先准备导出用的 go 文件

pkg/caesar/caesar_amd64.go:

package caesar

import (
	"unsafe"
)

//go:nosplit
//go:noescape
func _caesarEncode(out unsafe.Pointer, in unsafe.Pointer, len uint64, rot uint64, table unsafe.Pointer)
// !!! 注意:相比  c 中的函数名,这里多了一个下划线

func CaesarEncode(in []byte, out []byte, rot int) {
	_caesarEncode(unsafe.Pointer(&out[0]), unsafe.Pointer(&in[0]), uint64(len(in)), uint64(rot), unsafe.Pointer(&tableOfCaesar[0][0]))
}

3.2 执行命令行

c2goasm -a pkg/caesar/c/caesar.s pkg/caesar/caesar_amd64.s

最终生成了这样一个汇编文件:

//+build !noasm !appengine
// AUTO-GENERATED BY C2GOASM -- DO NOT EDIT

TEXT ·_caesarEncode(SB), $0-40

    MOVQ out+0(FP), DI
    MOVQ in+8(FP), SI
    MOVQ len+16(FP), DX
    MOVQ rot+24(FP), CX
    MOVQ table+32(FP), R8

    WORD $0xd285                 // test    edx, edx
	JLE LBB0_3
    WORD $0x634c; BYTE $0xc9     // movsxd    r9, ecx
    LONG $0x4fc16949; WORD $0xc4ec; BYTE $0x4e // imul    rax, r9, 1321528399
    WORD $0x8948; BYTE $0xc1     // mov    rcx, rax
    LONG $0x3fe9c148             // shr    rcx, 63
    LONG $0x23f8c148             // sar    rax, 35
    WORD $0xc801                 // add    eax, ecx
    WORD $0x0c8d; BYTE $0x80     // lea    ecx, [rax + 4*rax]
    WORD $0x0c8d; BYTE $0x89     // lea    ecx, [rcx + 4*rcx]
    WORD $0xc101                 // add    ecx, eax
    WORD $0x2941; BYTE $0xc9     // sub    r9d, ecx
    WORD $0x6348; BYTE $0xc2     // movsxd    rax, edx
    WORD $0x0148; BYTE $0xf0     // add    rax, rsi
    WORD $0x6349; BYTE $0xc9     // movsxd    rcx, r9d
    LONG $0x08e1c148             // shl    rcx, 8
    WORD $0x0149; BYTE $0xc8     // add    r8, rcx
LBB0_2:
    WORD $0xb60f; BYTE $0x0e     // movzx    ecx, byte [rsi]
    LONG $0x0cb60f42; BYTE $0x01 // movzx    ecx, byte [rcx + r8]
    WORD $0x0f88                 // mov    byte [rdi], cl
    LONG $0x01c68348             // add    rsi, 1
    LONG $0x01c78348             // add    rdi, 1
    WORD $0x3948; BYTE $0xc6     // cmp    rsi, rax
	JB LBB0_2
LBB0_3:
    RET

执行单元测试,一切正常!
Have func. 😃


补充:当 c 代码中使用 avx512 的函数的时候,c2goasm 无法翻译这些代码,在这些代码前面加上了 //注释。就算手动删除这些注释,go build 过程中也无法编译这些指令。
具体原因还要再查。

posted on 2023-10-19 15:49  ahfuzhang  阅读(137)  评论(0)    收藏  举报