Go 組合語言快速指南
Go 組合語言快速指南
這份文件簡要說明 gc
Go 編譯器所使用的組合語言的特殊形式。本文件並非全面性。
組譯器基於 Plan 9 組譯器的輸入樣式,其詳細文件記載於 其他地方。如果您計畫撰寫組譯語言,您應該閱讀該文件,即使其中大部分內容是 Plan 9 特有的。目前的這份文件提供語法摘要和該文件說明內容的差異,並說明與 Go 互動時撰寫組譯碼時適用的特殊性。
關於 Go 組譯器最重要的資訊是,它並非底層機器碼的直接表示。有些細節會精確對應到機器碼,但有些則不會。這是因為編譯器套件(請參閱 此說明)在一般管線中不需要組譯器傳遞。相反地,編譯器會作用於一種半抽象指令集,而且指令選擇會在產生程式碼後的部分進行。組譯器會作用於半抽象形式,因此當您看到像 MOV
這樣的指令時,工具鏈實際為該操作產生的內容可能根本不是移動指令,可能是清除或載入。或者它可能完全對應於具有該名稱的機器碼指令。一般而言,特定於機器的操作傾向於以其本身的形式出現,而較為通用的概念,例如記憶體移動、子程式呼叫和傳回,則較為抽象。詳細內容會因架構而異,對於不精確之處,我們深感抱歉;此情況並未明確定義。
組譯器程式是一種解析該半抽象指令集說明的方式,並將其轉換為要輸入至連結器的指令。如果您想查看組譯器中指令在特定架構(例如 amd64)中的樣貌,標準函式庫的來源中有許多範例,例如 runtime
和 math/big
等套件。您也可以檢查編譯器發出哪些組譯碼(實際輸出可能與您在此看到的不同)
$ cat x.go package main func main() { println(3) } $ GOOS=linux GOARCH=amd64 go tool compile -S x.go # or: go build -gcflags -S x.go "".main STEXT size=74 args=0x0 locals=0x10 0x0000 00000 (x.go:3) TEXT "".main(SB), $16-0 0x0000 00000 (x.go:3) MOVQ (TLS), CX 0x0009 00009 (x.go:3) CMPQ SP, 16(CX) 0x000d 00013 (x.go:3) JLS 67 0x000f 00015 (x.go:3) SUBQ $16, SP 0x0013 00019 (x.go:3) MOVQ BP, 8(SP) 0x0018 00024 (x.go:3) LEAQ 8(SP), BP 0x001d 00029 (x.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x001d 00029 (x.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x001d 00029 (x.go:3) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 0x001d 00029 (x.go:4) PCDATA $0, $0 0x001d 00029 (x.go:4) PCDATA $1, $0 0x001d 00029 (x.go:4) CALL runtime.printlock(SB) 0x0022 00034 (x.go:4) MOVQ $3, (SP) 0x002a 00042 (x.go:4) CALL runtime.printint(SB) 0x002f 00047 (x.go:4) CALL runtime.printnl(SB) 0x0034 00052 (x.go:4) CALL runtime.printunlock(SB) 0x0039 00057 (x.go:5) MOVQ 8(SP), BP 0x003e 00062 (x.go:5) ADDQ $16, SP 0x0042 00066 (x.go:5) RET 0x0043 00067 (x.go:5) NOP 0x0043 00067 (x.go:3) PCDATA $1, $-1 0x0043 00067 (x.go:3) PCDATA $0, $-1 0x0043 00067 (x.go:3) CALL runtime.morestack_noctxt(SB) 0x0048 00072 (x.go:3) JMP 0 ...
FUNCDATA
和 PCDATA
指令包含垃圾收集器使用的資訊;它們是由編譯器引入的。
若要查看連結後在二進位檔中放入哪些內容,請使用 go tool objdump
$ go build -o x.exe x.go $ go tool objdump -s main.main x.exe TEXT main.main(SB) /tmp/x.go x.go:3 0x10501c0 65488b0c2530000000 MOVQ GS:0x30, CX x.go:3 0x10501c9 483b6110 CMPQ 0x10(CX), SP x.go:3 0x10501cd 7634 JBE 0x1050203 x.go:3 0x10501cf 4883ec10 SUBQ $0x10, SP x.go:3 0x10501d3 48896c2408 MOVQ BP, 0x8(SP) x.go:3 0x10501d8 488d6c2408 LEAQ 0x8(SP), BP x.go:4 0x10501dd e86e45fdff CALL runtime.printlock(SB) x.go:4 0x10501e2 48c7042403000000 MOVQ $0x3, 0(SP) x.go:4 0x10501ea e8e14cfdff CALL runtime.printint(SB) x.go:4 0x10501ef e8ec47fdff CALL runtime.printnl(SB) x.go:4 0x10501f4 e8d745fdff CALL runtime.printunlock(SB) x.go:5 0x10501f9 488b6c2408 MOVQ 0x8(SP), BP x.go:5 0x10501fe 4883c410 ADDQ $0x10, SP x.go:5 0x1050202 c3 RET x.go:3 0x1050203 e83882ffff CALL runtime.morestack_noctxt(SB) x.go:3 0x1050208 ebb6 JMP main.main(SB)
常數
儘管組譯器遵循 Plan 9 組譯器的指導,但它是一個不同的程式,因此有些差異。其中一個差異在於常數評估。組譯器中的常數表達式使用 Go 的運算子優先順序來解析,而不是原始的類似 C 的優先順序。因此 3&1<<2
是 4,而不是 0 — 它解析為 (3&1)<<2
而不是 3&(1<<2)
。此外,常數總是評估為 64 位元無符號整數。因此 -2
不是整數值減去 2,而是具有相同位元模式的 64 位元無符號整數。這種區別很少重要,但為了避免歧義,會拒絕高位元組設定為右運算元的高位元組的除法或右移。
符號
某些符號,例如 R1
或 LR
,是預先定義的,並指註冊器。確切的設定取決於架構。
有四個預先宣告的符號指偽註冊器。這些不是真正的註冊器,而是由工具鏈維護的虛擬註冊器,例如框架指標。偽註冊器的設定對所有架構都是相同的
-
FP
:框架指標:引數和區域變數。 -
PC
:程式計數器:跳躍和分支。 -
SB
:靜態基礎指標:全域符號。 -
SP
:堆疊指標:區域堆疊框架中的最高位址。
所有使用者定義的符號都寫為偽註冊器 FP
(引數和區域變數)和 SB
(全域變數)的位移。
SB
偽暫存器可以視為記憶體的開頭,因此符號 foo(SB)
是記憶體中一個名為 foo
的位址。此形式用於命名全域函式和資料。在名稱中加入 <>
,例如 foo<>(SB)
,會讓名稱只在目前的原始檔中可見,就像 C 檔案中的頂層 static
宣告。在名稱中加入偏移量,是指從符號的位址開始的偏移量,因此 foo+4(SB)
是 foo
開頭往後四個位元組。
FP
偽暫存器是一個虛擬的框架指標,用於參照函式引數。編譯器會維護一個虛擬框架指標,並參照堆疊上的引數,作為該偽暫存器的偏移量。因此,0(FP)
是函式的第一個引數,8(FP)
是第二個引數(在 64 位元機器上),依此類推。不過,當以這種方式參照函式引數時,必須在開頭加上一個名稱,例如 first_arg+0(FP)
和 second_arg+8(FP)
。(偏移量的意義,也就是從框架指標的偏移量,與在 SB
中的使用不同,後者是從符號的偏移量。)組譯器會強制執行此慣例,拒絕純粹的 0(FP)
和 8(FP)
。實際的名稱在語意上無關緊要,但應使用來記錄引數的名稱。值得強調的是,FP
永遠是一個偽暫存器,而不是硬體暫存器,即使在具有硬體框架指標的架構上也是如此。
對於具有 Go 原型的組譯函式,go
vet
會檢查引數名稱和偏移量是否相符。在 32 位元系統上,64 位元值的低 32 位元和高 32 位元會透過在名稱中加入 _lo
或 _hi
字尾來區分,例如 arg_lo+0(FP)
或 arg_hi+4(FP)
。如果 Go 原型沒有為其結果命名,預期的組譯名稱是 ret
。
SP
偽暫存器是一個虛擬的堆疊指標,用於參照框架內部變數和準備用於函式呼叫的引數。它指向局部堆疊框架中的最高位址,因此參照應使用 [−framesize, 0) 範圍內的負偏移量:x-8(SP)
、y-4(SP)
等。
在具有名為 SP
的硬體暫存器的架構上,名稱前綴區分了對虛擬堆疊指標的參照和對架構 SP
暫存器的參照。也就是說,x-8(SP)
和 -8(SP)
是不同的記憶體位置:第一個是指向虛擬堆疊指標偽暫存器,而第二個是指向硬體的 SP
暫存器。
在傳統上 SP
和 PC
是實體編號暫存器的別名的機器上,在 Go 組合語言中,名稱 SP
和 PC
仍被特別處理;例如,對 SP
的參照需要一個符號,很像 FP
。若要存取實際的硬體暫存器,請使用真正的 R
名稱。例如,在 ARM 架構上,硬體 SP
和 PC
可分別透過 R13
和 R15
存取。
分支和直接跳躍總是寫成對 PC 的偏移量,或寫成對標籤的跳躍
label: MOVW $0, R1 JMP label
每個標籤只在定義它的函式中可見。因此,允許檔案中的多個函式定義和使用相同的標籤名稱。直接跳躍和呼叫指令可以針對文字符號,例如 name(SB)
,但不能針對符號的偏移量,例如 name+4(SB)
。
指令、暫存器和組合語言指令總是使用大寫字母,以提醒您組合語言程式設計是一項艱鉅的任務。(例外:ARM 上的 g
暫存器重新命名。)
在 Go 物件檔案和二進位檔案中,符號的全名是套件路徑,後面接一個句點和符號名稱:fmt.Printf
或 math/rand.Int
。由於組譯器的剖析器將句點和斜線視為標點符號,因此這些字串無法直接用作識別碼名稱。組譯器允許識別碼中使用中點字元 U+00B7 和除號 U+2215,並將它們改寫為一般的句點和斜線。在組譯器原始碼檔案中,上述符號寫成 fmt·Printf
和 math∕rand·Int
。編譯器在使用 -S
旗標時產生的組譯清單會直接顯示句點和斜線,而不是組譯器所需的 Unicode 取代字元。
大多數手寫組譯器檔案不會在符號名稱中包含完整的套件路徑,因為連結器會在任何以句點開頭的名稱開頭插入目前物件檔案的套件路徑:在 math/rand 套件實作中的組譯器原始碼檔案中,套件的 Int 函式可以稱為 ·Int
。此慣例避免在套件自己的原始碼中硬寫入入匯路徑,讓程式碼更容易從一個位置移到另一個位置。
指令
組譯器使用各種指令來將文字和資料繫結到符號名稱。例如,以下是簡單的完整函式定義。TEXT
指令宣告符號 runtime·profileloop
,而後面的指令形成函式的本體。TEXT
區塊中的最後一個指令必須是某種跳躍,通常是 RET
(偽)指令。(如果不是,連結器會附加一個跳躍到自身的指令;TEXT
中沒有直落執行。)在符號之後,參數是旗標(見下文)和框架大小,一個常數(但見下文)
TEXT runtime·profileloop(SB),NOSPLIT,$8 MOVQ $runtime·profileloop1(SB), CX MOVQ CX, 0(SP) CALL runtime·externalthreadhandler(SB) RET
一般來說,框架大小後面是參數大小,以減號分隔。(這不是減法,只是特殊語法。)框架大小 $24-8
表示函式有一個 24 位元組的框架,並使用 8 位元組的參數呼叫,這些參數存在於呼叫者的框架中。如果未為 TEXT
指定 NOSPLIT
,則必須提供參數大小。對於具有 Go 原型的組譯函式,go
vet
會檢查參數大小是否正確。
請注意符號名稱使用中間點來區分組成部分,並指定為靜態基本偽暫存器 SB
的偏移量。此函式會透過簡單名稱 profileloop
從套件 runtime
的 Go 來源呼叫。
全域資料符號是由一連串初始化 DATA
指令定義,後接一個 GLOBL
指令。每個 DATA
指令會初始化對應記憶體的一個區段。未明確初始化的記憶體會歸零。DATA
指令的一般形式為
DATA symbol+offset(SB)/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
宣告並初始化 divtab<>
,一個唯讀的 64 位元組 4 位元組整數值表格,並宣告 runtime·tlsoffset
,一個 4 位元組、隱含歸零且不包含指標的變數。
指令可能有一個或兩個引數。如果有兩個,第一個是旗標的位元遮罩,可以寫成數值表達式、相加或或運算,或可以象徵性地設定,以利人類理解。其值定義在標準 #include
檔案 textflag.h
中,如下:
-
NOPROF
= 1
(適用於TEXT
項目。)不要對標記函式進行剖析。此旗標已棄用。 -
DUPOK
= 2
在單一二進位檔中擁有此符號的多次執行個體是合法的。連結器會選擇其中一個重複項使用。 -
NOSPLIT
= 4
(針對TEXT
項目。)不要插入前置作業,以檢查堆疊是否必須拆分。常式架構,加上它呼叫的任何內容,都必須符合目前堆疊區段中剩餘的備用空間。用於保護常式,例如堆疊拆分程式碼本身。 -
RODATA
= 8
(針對DATA
和GLOBL
項目。)將此資料放入唯讀區段。 -
NOPTR
= 16
(針對DATA
和GLOBL
項目。)此資料不包含指標,因此不需要垃圾收集器掃描。 -
WRAPPER
= 32
(針對TEXT
項目。)這是包裝器函式,不應視為停用recover
。 -
NEEDCTXT
= 64
(針對TEXT
項目。)此函式是閉包,因此它使用其輸入的內容文字常數。 -
LOCAL
= 128
此符號是動態共用物件的區域。 -
TLSBSS
= 256
(針對DATA
和GLOBL
項目。)將此資料放入執行緒區域儲存體。 -
NOFRAME
= 512
(針對TEXT
項目。)不要插入指令來配置堆疊架構並儲存/回復傳回位址,即使這不是葉函式。僅對宣告架構大小為 0 的函式有效。 -
TOPFRAME
= 2048
(針對TEXT
項目。)函式是呼叫堆疊的最外層架構。追蹤應在此函式停止。
特殊指令
PCALIGN
偽指令用於指出下一個指令應透過使用無動作指令填補,以對齊至指定邊界。
它目前在 arm64、amd64、ppc64、loong64 和 riscv64 上受支援。例如,下列 MOVD
指令的開頭對齊至 32 位元組
PCALIGN $32 MOVD $2, R0
與 Go 型別和常數互動
如果一個套件有任何 .s 檔案,則 go build
會指示編譯器發出一個稱為 go_asm.h
的特殊標頭,然後 .s 檔案可以 #include
。該檔案包含用於 Go 結構欄位偏移量、Go 結構類型大小和目前套件中定義的大多數 Go const
宣告的符號 #define
常數。Go 組合語言應避免對 Go 類型的配置進行假設,而應改用這些常數。這會改善組合語言程式碼的可讀性,並使其對 Go 類型定義或 Go 編譯器使用的配置規則中的資料配置變更保持穩健。
常數的格式為 const_name
。例如,給定 Go 宣告 const bufSize = 1024
,組合語言程式碼可以將此常數的值稱為 const_bufSize
。
欄位偏移量的格式為 type_field
。結構大小的格式為 type__size
。例如,考量以下 Go 定義
type reader struct { buf [bufSize]byte r int }
組合語言可以將此結構的大小稱為 reader__size
,並將兩個欄位的偏移量稱為 reader_buf
和 reader_r
。因此,如果暫存器 R1
包含一個指向 reader
的指標,則組合語言可以將 r
欄位稱為 reader_r(R1)
。
如果任何這些 #define
名稱是模稜兩可的(例如,一個具有 _size
欄位的結構),則 #include "go_asm.h"
會失敗,並出現「巨集重新定義」錯誤。
執行時期協調
為了讓垃圾回收正確執行,執行時期必須知道所有全域資料和大多數堆疊框架中指標的位置。Go 編譯器在編譯 Go 原始檔時會發出此資訊,但組合語言程式必須明確定義它。
標記有 NOPTR
旗標的資料符號(見上文)會被視為不包含指向執行時期配置資料的指標。標記有 RODATA
旗標的資料符號會配置在唯讀記憶體中,因此會被視為隱含標記 NOPTR
。總大小小於指標的資料符號也會被視為隱含標記 NOPTR
。無法在組譯原始檔中定義包含指標的符號;此類符號必須在 Go 原始檔中定義。即使沒有 DATA
和 GLOBL
指令,組譯原始檔仍可透過名稱來參照符號。一個良好的經驗法則是在 Go 中定義所有非 RODATA
符號,而不是在組譯中定義。
每個函式也需要註解,說明其引數、結果和區域堆疊架構中動態指標的位置。對於沒有指標結果,且沒有區域堆疊架構或函式呼叫的組譯函式,唯一的要求是在同一個套件的 Go 原始檔中為函式定義一個 Go 原型。組譯函式的名稱不得包含套件名稱元件(例如,套件 syscall
中的函式 Syscall
應使用名稱 ·Syscall
,而不是在其 TEXT
指令中使用等效名稱 syscall·Syscall
)。對於更複雜的情況,需要明確註解。這些註解使用標準 #include
檔案 funcdata.h
中定義的偽指令。
如果函式沒有參數和結果,則可以省略指標資訊。這由 TEXT
指令上的 $n-0
參數大小註解表示。否則,必須由 Go 原始檔中函式的 Go 原型提供指標資訊,即使是未直接從 Go 呼叫的組譯函式也是如此。(原型也會讓 go
vet
檢查參數參考。)在函式開始時,假設參數已初始化,但結果假設未初始化。如果結果將在呼叫指令期間持有動態指標,則函式應從將結果歸零開始,然後執行偽指令 GO_RESULTS_INITIALIZED
。此指令記錄結果現在已初始化,且應在堆疊移動和垃圾回收期間掃描。通常較容易安排組譯函式不傳回指標或不包含呼叫指令;標準函式庫中沒有組譯函式使用 GO_RESULTS_INITIALIZED
。
如果函式沒有本機堆疊架構,則可以省略指標資訊。這由 TEXT
指令上的 $0-n
本機架構大小註解表示。如果函式不包含任何呼叫指令,也可以省略指標資訊。否則,本機堆疊架構不得包含指標,且組譯必須執行偽指令 NO_LOCAL_POINTERS
來確認此事實。由於堆疊調整大小是透過移動堆疊來實作,因此堆疊指標可能會在任何函式呼叫期間變更:即使是堆疊資料的指標也不得保留在本地變數中。
組譯函式應始終提供 Go 原型,以提供參數和結果的指標資訊,並讓 go
vet
檢查用於存取它們的位移是否正確。
特定於架構的詳細資訊
列出每部電腦的所有指令和其他詳細資訊並不實際。若要查看為特定電腦定義哪些指令,例如 ARM,請查看位於目錄 src/cmd/internal/obj/arm
中,該架構的 obj
支援函式庫的原始碼。該目錄中有一個檔案 a.out.go
;它包含一個以 A
開頭的常數清單,如下所示
const ( AAND = obj.ABaseARM + obj.A_ARCHSPECIFIC + iota AEOR ASUB ARSB AADD ...
這是組譯器和連結器已知的指令和拼寫清單,適用於該架構。此清單中的每項指令都以開頭大寫的 A
開頭,因此 AAND
代表位元與指令,AND
(不帶開頭的 A
),且在組譯原始碼中寫為 AND
。列舉順序大部分是按字母順序排列。(在 cmd/internal/obj
套件中定義的與架構無關的 AXXX
代表無效指令)。A
名稱的順序與機器指令的實際編碼無關。cmd/internal/obj
套件會處理該細節。
386 和 AMD64 架構的指令都列在 cmd/internal/obj/x86/a.out.go
中。
這些架構共用常見定址模式的語法,例如 (R1)
(暫存器間接)、4(R1)
(暫存器間接,帶有偏移量)和 $foo(SB)
(絕對位址)。組譯器還支援一些(不一定全部)特定於每個架構的定址模式。以下各節會列出這些模式。
從前幾節的範例中可以看出的一個細節是,指令中的資料從左到右流動:MOVQ
$0,
CX
會清除 CX
。即使在慣例表示法使用相反方向的架構上,此規則也適用。
以下是支援架構的一些 Go 特定重要細節的說明。
32 位元 Intel 386
執行時期指向 g
結構的指標會透過 MMU 中一個未使用的 (就 Go 而論) 暫存器的值來維護。在執行時期套件中,組譯碼可以包含 go_tls.h
,其中定義了一個與作業系統和架構相關的巨集 get_tls
,用於存取這個暫存器。get_tls
巨集會接收一個引數,也就是要將 g
指標載入其中的暫存器。
例如,使用 CX
載入 g
和 m
的順序如下所示
#include "go_tls.h" #include "go_asm.h" ... get_tls(CX) MOVL g(CX), AX // Move g into AX. MOVL g_m(AX), BX // Move g.m into BX.
get_tls
巨集也在 amd64 中定義。
定址模式
-
(DI)(BX*2)
:位址DI
加上BX*2
的位置。 -
64(DI)(BX*2)
:位址DI
加上BX*2
加上 64 的位置。這些模式只接受 1、2、4 和 8 作為比例因子。
在使用編譯器和組譯器的 -dynlink
或 -shared
模式時,任何載入或儲存固定記憶體位置(例如全域變數)都必須假設會覆寫 CX
。因此,為了安全地使用這些模式,組譯來源通常應避免在記憶體參照之間使用 CX。
64 位元 Intel 386(又稱 amd64)
這兩個架構在組譯器層級的行為大致相同。在 64 位元版本上存取 m
和 g
指標的組譯碼與 32 位元 386 上相同,只不過它使用 MOVQ
而不是 MOVL
get_tls(CX) MOVQ g(CX), AX // Move g into AX. MOVQ g_m(AX), BX // Move g.m into BX.
暫存器 BP
是呼叫者儲存的。當框架大小大於零時,組譯器會自動插入 BP
儲存/復原。允許使用 BP
作為一般用途暫存器,但它可能會干擾基於抽樣的剖析。
ARM
編譯器和連結器保留暫存器 R10
和 R11
。
R10
指向 g
(goroutine)結構。在組譯器原始碼中,此指標必須稱為 g
;名稱 R10
不會被辨識。
為了讓人員和編譯器更容易撰寫組譯,ARM 連結器允許一般尋址格式和偽運算,例如 DIV
或 MOD
,而這些格式和偽運算可能無法使用單一硬體指令表示。它會將這些格式實作為多個指令,通常使用 R11
暫存器來儲存暫時值。手寫組譯可以使用 R11
,但這麼做時必須確定連結器不會也使用它來實作函式中的任何其他指令。
在定義 TEXT
時,指定框架大小 $-4
會告訴連結器,這是一個葉函式,在進入時不需要儲存 LR
。
名稱 SP
始終是指前面所述的虛擬堆疊指標。對於硬體暫存器,請使用 R13
。
條件碼語法是將句點和一或兩個字母的碼附加到指令,例如 MOVW.EQ
。可以附加多個碼:MOVM.IA.W
。碼修改項的順序無關緊要。
定址模式
-
R0->16
R0>>16
R0<<16
R0@>16
:對於<<
,將R0
左移 16 位元。其他碼為->
(算術右移)、>>
(邏輯右移)和@>
(右旋轉)。 -
R0->R1
R0>>R1
R0<<R1
R0@>R1
:對於<<
,將R0
左移R1
中的計數。其他碼為->
(算術右移)、>>
(邏輯右移)和@>
(右旋轉)。 -
[R0,g,R12-R15]
:對於多重註冊指令,包含R0
、g
,以及R12
到R15
(含)的集合。 -
(R5, R6)
:目的地註冊器對。
ARM64
R18
是「平台註冊器」,保留在 Apple 平台上。為防止意外誤用,註冊器命名為 R18_PLATFORM
。R27
和 R28
由編譯器和連結器保留。R29
是堆疊指標。R30
是連結註冊器。
指令修改器附加在指令之後,中間以句點分隔。唯一的修改器為 P
(後遞增)和 W
(前遞增):MOVW.P
、MOVW.W
定址模式
-
R0->16
R0>>16
R0<<16
R0@>16
:這些與 32 位元 ARM 相同。 -
$(8<<12)
:將立即值8
左移12
位元。 -
8(R0)
:將R0
和8
的值相加。 -
(R2)(R0)
:R0
加上R2
的位置。 -
R0.UXTB
R0.UXTB<<imm
:UXTB
:從R0
的低階位元中擷取 8 位元值,並將其零延伸至R0
的大小。R0.UXTB<<imm
:將R0.UXTB
的結果左移imm
位元。imm
值可以是 0、1、2、3 或 4。其他延伸包括UXTH
(16 位元)、UXTW
(32 位元)和UXTX
(64 位元)。 -
R0.SXTB
R0.SXTB<<imm
:SXTB
:從R0
的低階位元中萃取 8 位元值,並將其符號延伸至R0
的大小。R0.SXTB<<imm
:將R0.SXTB
的結果向左位移imm
位元。imm
值可以是 0、1、2、3 或 4。其他延伸包括SXTH
(16 位元)、SXTW
(32 位元)和SXTX
(64 位元)。 -
(R5, R6)
:LDAXP
/LDP
/LDXP
/STLXP
/STP
/STP
的暫存器對。
PPC64
此組譯器由 GOARCH 值 ppc64 和 ppc64le 使用。
IBM z/Architecture,又稱 s390x
暫存器 R10
和 R11
已保留。組譯器在組譯某些指令時會使用它們來存放暫時值。
R13
指向 g
(goroutine)結構。此暫存器必須稱為 g
;名稱 R13
不會被辨識。
R15
指向堆疊框架,通常應僅使用虛擬暫存器 SP
和 FP
來存取。
載入和儲存多個指令會在暫存器範圍上執行。暫存器範圍由起始暫存器和結束暫存器指定。例如,LMG
(R9),
R5,
R7
會分別使用 0(R9)
、8(R9)
和 16(R9)
的 64 位元值載入 R5
、R6
和 R7
。
儲存和儲存指令(例如 MVC
和 XC
)會將長度寫為第一個引數。例如,XC
$8,
(R9),
(R9)
會清除 R9
中指定位址的八個位元組。
如果向量指令將長度或索引作為引數,則它會是第一個引數。例如,VLEIF
$1,
$16,
V2
會將值十六載入 V2
的索引一。使用向量指令時應小心,以確保它們在執行階段可用。若要使用向量指令,機器必須同時具有向量功能(功能列表中的位元 129)和核心支援。如果沒有核心支援,向量指令將不會產生任何作用(它會等同於 NOP
指令)。
定址模式
-
(R5)(R6*1)
:位於R5
加上R6
的位置。這是一種縮放模式,如同 x86,但允許的唯一縮放是1
。
MIPS、MIPS64
一般用途暫存器命名為 R0
到 R31
,浮點暫存器為 F0
到 F31
。
R30
保留為指向 g
。R23
用作臨時暫存器。
在 TEXT
指令中,MIPS 的 frame 大小 $-4
或 MIPS64 的 $-8
指示連結器不要儲存 LR
。
SP
指的是虛擬堆疊指標。對於硬體暫存器,請使用 R29
。
定址模式
-
16(R1)
:位於R1
加上 16 的位置。 -
(R1)
:0(R1)
的別名。
GOMIPS
環境變數的值(hardfloat
或 softfloat
)可透過預先定義 GOMIPS_hardfloat
或 GOMIPS_softfloat
而提供給組譯碼。
GOMIPS64
環境變數的值(hardfloat
或 softfloat
)可透過預先定義 GOMIPS64_hardfloat
或 GOMIPS64_softfloat
而提供給組譯碼。
不支援的 opcode
組譯器設計為支援編譯器,因此並非所有硬體指令都定義給所有架構:如果編譯器未產生它,它可能不存在。如果您需要使用遺漏的指令,有兩種方法可以進行。一種方法是更新組譯器以支援該指令,這很簡單,但只有在該指令可能會再次使用時才值得。相反地,對於簡單的一次性案例,可以使用 BYTE
和 WORD
指令在 TEXT
中將明確資料配置到指令串流中。以下是 386 執行時期定義 64 位元原子載入函數的方式。
// uint64 atomicload64(uint64 volatile* addr); // so actually // void atomicload64(uint64 *res, uint64 volatile *addr); TEXT runtime·atomicload64(SB), NOSPLIT, $0-12 MOVL ptr+0(FP), AX TESTL $7, AX JZ 2(PC) MOVL 0, AX // crash with nil ptr deref LEAL ret_lo+4(FP), BX // MOVQ (%EAX), %MM0 BYTE $0x0f; BYTE $0x6f; BYTE $0x00 // MOVQ %MM0, 0(%EBX) BYTE $0x0f; BYTE $0x7f; BYTE $0x03 // EMMS BYTE $0x0F; BYTE $0x77 RET