go中写汇编是为优化关键路径而非炫技,须与go源码共存、共享符号、服从gc规则;汇编文件名需带架构后缀如add_amd64.s,函数名用·add(sb),同包go文件中必须声明对应签名,且需正确使用fp/sp/sb、funcdata、noptr等机制。

Go 中写汇编不是为了炫技,而是当 go tool compile -S 显示出关键路径有寄存器溢出、栈帧过大或调用开销不可接受时,才值得动手。Plan9 汇编不是独立语言,它必须和 Go 源码共存、共享符号、服从 GC 规则——跳过这点,代码能编译但 runtime 会崩溃。
如何让 Go 编译器识别并链接你的汇编文件
Go 要求汇编文件名必须带架构后缀,比如 add_amd64.s;文件内函数名不能含包路径,用 ·Add 而非 math·Add;且必须在同包的 Go 文件中声明对应函数签名(哪怕空实现),否则 go build 会报 undefined: Add。
- 汇编文件需放在与 Go 源码同一目录、同一包下,且文件名格式为
xxx_$(GOARCH).s(如utils_amd64.s) - 函数定义用
TEXT ·Add(SB), NOSPLIT, $0:其中·表示包级符号,SB是静态基址伪寄存器,NOSPLIT告诉编译器不插入栈分裂检查(避免递归调用时 panic) - 若函数有参数或返回值,必须在 Go 文件中定义完整签名,例如
func Add(a, b int) int,否则链接失败
DATA 和 GLOBL 的顺序与语义不能颠倒
GLOBL 不是“声明变量”,而是向链接器宣告该符号全局可见、需分配存储;DATA 才是真正写入初始值。二者顺序错乱或类型标记错误,会导致 GC 扫描崩溃或读取垃圾值。
-
GLOBL必须紧跟在所有对应DATA指令之后,且size参数必须等于所有DATA写入字节数之和 - 标记
NOPTR表示该变量不含指针(如int、uint64),GC 会跳过扫描;若误标,可能造成对象被提前回收 - 只读数据应优先用
RODATA标志(如字符串常量),它隐式带NOPTR,且加载到内存只读段,运行时修改会 segfault
SP、FP、SB 这三个伪寄存器用错就直接 crash
Plan9 汇编里没有 push/pop,栈操作全靠 ADDQ/SUBQ 修改 SP;而 FP 和 SB 是只读伪寄存器,写入它们会导致链接失败或地址错乱。
-
symbol+8(FP)访问第二个参数(FP指向第一个参数起始),但FP本身不可写,也不能参与算术运算 -
0(SP)指硬件栈顶(即当前RSP寄存器值),buf+8(SP)是局部变量偏移,两者混用极易越界 -
symbol(SB)是全局符号基址,用于访问DATA/GLOBL定义的变量,写成symbol(SP)就变成栈上随机地址
最易被忽略的是:所有汇编函数若涉及指针(比如返回 *int 或接收 []byte),必须显式添加 FUNCDATA 和 PCDATA 指令,告诉 GC 哪些栈偏移处存活着指针。漏掉这个,程序可能跑几分钟才 panic,而且堆栈无迹可寻。











