# 1.4 Plan 9 匯編語言
本節我們快速介紹 Go 語言使用的 Plan 9 匯編,以方便在后續章節中能夠流暢的閱讀 Go 源碼中關于匯編的部分。
對于一段 Go 程序,我們可以通過下面的命令來獲得編譯后的匯編代碼:
```
go build -gcflags "-N -l" -ldflags=-compressdwarf=false -o main.out main.go
go tool objdump -s "main.main" main.out > main.S
# or
go tool compile -S main.go
# or
go build -gcflags -S main.go
```
FUNCDATA 和 PCDATA 指令包含了由垃圾回收器使用的信息,他們由編譯器引入。
## 常量
## 符號
## 指令
全局數據符號由以 DATA 指令開頭的序列 全局數據符號由一系列以 DATA 指令起始和一個 GLOBL 指令定義。 每個 DATA 指令初始化相應內存的一部分。未明確初始化的內存為零。 該 DATA 指令的一般形式是
~~~
DATA symbol+offset(SB)/width, value
~~~
在給定的 offset 和 width 處初始化該符號的內存為 value。 DATA 必須使用增加的偏移量來寫入給定符號的指令。
該 GLOBL 指令聲明符號是全局的。參數是可選標志,并且數據的大小被聲明為全局, 除非 DATA 指令已初始化,否則初始值將全為零。該 GLOBL 指令必須遵循任何相應的 DATA 指令。
例如:
~~~
DATA divtab<>+0x00(SB)/4, $0xf4f8fcff
DATA divtab<>+0x04(SB)/4, $0xe6eaedf0
...
DATA divtab<>+0x3c(SB)/4, $0x81828384
GLOBL divtab<>(SB), RODATA, $64
GLOBL runtime·tlsoffset(SB), NOPTR, $4
~~~
| 指令 | 操作符 | 解釋 |
| :-- | :-- | :-- |
| JMP | | |
| MOVL | | |
| MOVQ | | |
| MOVEQ | | |
| LEAQ | | |
| SUBQ | | |
| ANDQ | | |
| CALL | | |
| PUSHQ | | |
| POPQ | | |
| CLD | | |
| CMPQ | | |
| CPUID | | |
| JEQ | | |
## 運行時協調
為保證垃圾回收正確運行,在大多數棧幀中,運行時必須知道所有全局數據的指針。 Go 編譯器會將這部分信息耦合到 Go 源碼文件中,但匯編程序必須進行顯式定義。
被標記為`NOPTR`標志的數據符號會視為不包含指向運行時分配數據的指針。 帶有`R0DATA`標志的數據符號在只讀存儲器中分配,因此被隱式標記為`NOPTR`。 總大小小于指針的數據符號也被視為隱式標記`NOPTR`。 在一份匯編源文件中是無法定義包含指針的符號的,因此這種符號必須定義在 Go 原文件中。 一個良好的經驗法則是`R0DATA`在 Go 中定義所有非符號而不是在匯編中定義。
每個函數還需要注釋,在其參數,結果和本地堆棧框架中給出實時指針的位置。 對于沒有指針結果且沒有本地堆棧幀或沒有函數調用的匯編函數, 唯一的要求是在同一個包中的 Go 源文件中為函數定義 Go 原型。 匯編函數的名稱不能包含包名稱組件 (例如,`syscall`包中的函數`Syscall`應使用名稱`·Syscall`而不是`syscall·Syscall`其TEXT指令中的等效名稱)。 對于更復雜的情況,需要顯式注釋。 這些注釋使用標準`#include`文件中定義的偽指令`funcdata.h`。
如果函數沒有參數且沒有結果,則可以省略指針信息。這是由一個參數大小`$n-0`注釋指示`TEXT`對指令。 否則,指針信息必須由Go源文件中的函數的Go原型提供,即使對于未直接從Go調用的匯編函數也是如此。 (原型也將`go vet`檢查參數引用。)在函數的開頭,假定參數被初始化但結果假定未初始化。 如果結果將在調用指令期間保存實時指針,則該函數應首先將結果歸零, 然后執行偽指令`GO_RESULTS_INITIALIZED`。 此指令記錄結果現在已初始化,應在堆棧移動和垃圾回收期間進行掃描。 通常更容易安排匯編函數不返回指針或不包含調用指令; 標準庫中沒有匯編函數使用`GO_RESULTS_INITIALIZED`。
如果函數沒有本地堆棧幀,則可以省略指針信息。這由`TEXT`指令上的本地幀大小`$0-n`注釋表示。如果函數不包含調用指令,也可以省略指針信息。否則,本地堆棧幀不能包含指針,并且匯編必須通過執行偽指令`TEXTNO_LOCAL_POINTERS`來確認這一事實。因為通過移動堆棧來實現堆棧大小調整,所以堆棧指針可能在任何函數調用期間發生變化:甚至指向堆棧數據的指針也不能保存在局部變量中。
匯編函數應始終給出 Go 原型,既可以提供參數和結果的指針信息,也可以`go vet`檢查用于訪問它們的偏移量是否正確。
## 寄存器
### 通用寄存器
Plan 9 中的通用寄存器包括:
AX BX CX DX DI SI BP SP R8 R9 R10 R11 R12 R13 R14 PC
### 偽寄存器
偽寄存器不是真正的寄存器,而是由工具鏈維護的虛擬寄存器,例如幀指針。
FP, Frame Pointer:幀指針,參數和本地 PC, Program Counter: 程序計數器,跳轉和分支 SB, Static Base: 靜態基指針, 全局符號 SP, Stack Pointer: 當前棧幀開始的地方
所有用戶定義的符號都作為偏移量寫入偽寄存器 FP 和 SB。
## 尋址模式
匯編語言的一個很重要的概念就是它的尋址模式,Plan 9 匯編也不例外,它支持如下尋址模式:
~~~
R0 數據寄存器
A0 地址寄存器
F0 浮點寄存器
CAAR, CACR, 等 特殊名字
$con 常量
$fcon 浮點數常量
name+o(SB) 外部符號
name<>+o(SB) 局部符號
name+o(SP) 自動符號
name+o(FP) 實際參數
$name+o(SB) 外部地址
$name<>+o(SB) 局部地址
(A0)+ 間接后增量
-(A0) 間接前增量
o(A0)
o()(R0.s)
symbol+offset(SP) 引用函數的局部變量,offset 的合法取值是 [-framesize, 0)
局部變量都是 8 字節,那么第一個局部變量就可以用 localvar0-8(SP) 來表示
如果是 symbol+offset(SP) 形式,則表示偽寄存器 SP
如果是 offset(SP) 則表示硬件寄存器 SP
~~~
```
TEXT pkgname·funcname(SB),NOSPLIT,$-8
JMP _rt0_amd64(SB)
```
## 進一步閱讀的參考文獻
* [A Quick Guide to Go's Assembler](https://golang.org/doc/asm)
* [Rob Pike, How to Use the Plan 9 C Compiler](http://doc.cat-v.org/plan_9/2nd_edition/papers/comp)
* [Rob Pike, A Manual for the Plan 9 assembler](https://9p.io/sys/doc/asm.html)
* [Debugging Go Code with GDB](https://golang.org/doc/gdb)
- 第一部分 :基礎篇
- 第1章 Go語言的前世今生
- 1.2 Go語言綜述
- 1.3 順序進程通訊
- 1.4 Plan9匯編語言
- 第2章 程序生命周期
- 2.1 從go命令談起
- 2.2 Go程序編譯流程
- 2.3 Go 程序啟動引導
- 2.4 主Goroutine的生與死
- 第3 章 語言核心
- 3.1 數組.切片與字符串
- 3.2 散列表
- 3.3 函數調用
- 3.4 延遲語句
- 3.5 恐慌與恢復內建函數
- 3.6 通信原語
- 3.7 接口
- 3.8 運行時類型系統
- 3.9 類型別名
- 3.10 進一步閱讀的參考文獻
- 第4章 錯誤
- 4.1 問題的演化
- 4.2 錯誤值檢查
- 4.3 錯誤格式與上下文
- 4.4 錯誤語義
- 4.5 錯誤處理的未來
- 4.6 進一步閱讀的參考文獻
- 第5章 同步模式
- 5.1 共享內存式同步模式
- 5.2 互斥鎖
- 5.3 原子操作
- 5.4 條件變量
- 5.5 同步組
- 5.6 緩存池
- 5.7 并發安全散列表
- 5.8 上下文
- 5.9 內存一致模型
- 5.10 進一步閱讀的文獻參考
- 第二部分 運行時篇
- 第6章 并發調度
- 6.1 隨機調度的基本概念
- 6.2 工作竊取式調度
- 6.3 MPG模型與并發調度單
- 6.4 調度循環
- 6.5 線程管理
- 6.6 信號處理機制
- 6.7 執行棧管理
- 6.8 協作與搶占
- 6.9 系統監控
- 6.10 網絡輪詢器
- 6.11 計時器
- 6.12 非均勻訪存下的調度模型
- 6.13 進一步閱讀的參考文獻
- 第7章 內存分配
- 7.1 設計原則
- 7.2 組件
- 7.3 初始化
- 7.4 大對象分配
- 7.5 小對象分配
- 7.6 微對象分配
- 7.7 頁分配器
- 7.8 內存統計
- 第8章 垃圾回收
- 8.1 垃圾回收的基本想法
- 8.2 寫屏幕技術
- 8.3 調步模型與強弱觸發邊界
- 8.4 掃描標記與標記輔助
- 8.5 免清掃式位圖技術
- 8.6 前進保障與終止檢測
- 8.7 安全點分析
- 8.8 分代假設與代際回收
- 8.9 請求假設與實務制導回收
- 8.10 終結器
- 8.11 過去,現在與未來
- 8.12 垃圾回收統一理論
- 8.13 進一步閱讀的參考文獻
- 第三部分 工具鏈篇
- 第9章 代碼分析
- 9.1 死鎖檢測
- 9.2 競爭檢測
- 9.3 性能追蹤
- 9.4 代碼測試
- 9.5 基準測試
- 9.6 運行時統計量
- 9.7 語言服務協議
- 第10章 依賴管理
- 10.1 依賴管理的難點
- 10.2 語義化版本管理
- 10.3 最小版本選擇算法
- 10.4 Vgo 與dep之爭
- 第12章 泛型
- 12.1 泛型設計的演進
- 12.2 基于合約的泛型
- 12.3 類型檢查技術
- 12.4 泛型的未來
- 12.5 進一步閱讀的的參考文獻
- 第13章 編譯技術
- 13.1 詞法與文法
- 13.2 中間表示
- 13.3 優化器
- 13.4 指針檢查器
- 13.5 逃逸分析
- 13.6 自舉
- 13.7 鏈接器
- 13.8 匯編器
- 13.9 調用規約
- 13.10 cgo與系統調用
- 結束語: Go去向何方?