Go 組合語言快速指南

Go 組合語言快速指南

這份文件簡要說明 gc Go 編譯器所使用的組合語言的特殊形式。本文件並非全面性。

組譯器基於 Plan 9 組譯器的輸入樣式,其詳細文件記載於 其他地方。如果您計畫撰寫組譯語言,您應該閱讀該文件,即使其中大部分內容是 Plan 9 特有的。目前的這份文件提供語法摘要和該文件說明內容的差異,並說明與 Go 互動時撰寫組譯碼時適用的特殊性。

關於 Go 組譯器最重要的資訊是,它並非底層機器碼的直接表示。有些細節會精確對應到機器碼,但有些則不會。這是因為編譯器套件(請參閱 此說明)在一般管線中不需要組譯器傳遞。相反地,編譯器會作用於一種半抽象指令集,而且指令選擇會在產生程式碼後的部分進行。組譯器會作用於半抽象形式,因此當您看到像 MOV 這樣的指令時,工具鏈實際為該操作產生的內容可能根本不是移動指令,可能是清除或載入。或者它可能完全對應於具有該名稱的機器碼指令。一般而言,特定於機器的操作傾向於以其本身的形式出現,而較為通用的概念,例如記憶體移動、子程式呼叫和傳回,則較為抽象。詳細內容會因架構而異,對於不精確之處,我們深感抱歉;此情況並未明確定義。

組譯器程式是一種解析該半抽象指令集說明的方式,並將其轉換為要輸入至連結器的指令。如果您想查看組譯器中指令在特定架構(例如 amd64)中的樣貌,標準函式庫的來源中有許多範例,例如 runtimemath/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
...

FUNCDATAPCDATA 指令包含垃圾收集器使用的資訊;它們是由編譯器引入的。

若要查看連結後在二進位檔中放入哪些內容,請使用 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 位元無符號整數。這種區別很少重要,但為了避免歧義,會拒絕高位元組設定為右運算元的高位元組的除法或右移。

符號

某些符號,例如 R1LR,是預先定義的,並指註冊器。確切的設定取決於架構。

有四個預先宣告的符號指偽註冊器。這些不是真正的註冊器,而是由工具鏈維護的虛擬註冊器,例如框架指標。偽註冊器的設定對所有架構都是相同的

所有使用者定義的符號都寫為偽註冊器 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 暫存器。

在傳統上 SPPC 是實體編號暫存器的別名的機器上,在 Go 組合語言中,名稱 SPPC 仍被特別處理;例如,對 SP 的參照需要一個符號,很像 FP。若要存取實際的硬體暫存器,請使用真正的 R 名稱。例如,在 ARM 架構上,硬體 SPPC 可分別透過 R13R15 存取。

分支和直接跳躍總是寫成對 PC 的偏移量,或寫成對標籤的跳躍

label:
	MOVW $0, R1
	JMP label

每個標籤只在定義它的函式中可見。因此,允許檔案中的多個函式定義和使用相同的標籤名稱。直接跳躍和呼叫指令可以針對文字符號,例如 name(SB),但不能針對符號的偏移量,例如 name+4(SB)

指令、暫存器和組合語言指令總是使用大寫字母,以提醒您組合語言程式設計是一項艱鉅的任務。(例外:ARM 上的 g 暫存器重新命名。)

在 Go 物件檔案和二進位檔案中,符號的全名是套件路徑,後面接一個句點和符號名稱:fmt.Printfmath/rand.Int。由於組譯器的剖析器將句點和斜線視為標點符號,因此這些字串無法直接用作識別碼名稱。組譯器允許識別碼中使用中點字元 U+00B7 和除號 U+2215,並將它們改寫為一般的句點和斜線。在組譯器原始碼檔案中,上述符號寫成 fmt·Printfmath∕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 中,如下:

特殊指令

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_bufreader_r。因此,如果暫存器 R1 包含一個指向 reader 的指標,則組合語言可以將 r 欄位稱為 reader_r(R1)

如果任何這些 #define 名稱是模稜兩可的(例如,一個具有 _size 欄位的結構),則 #include "go_asm.h" 會失敗,並出現「巨集重新定義」錯誤。

執行時期協調

為了讓垃圾回收正確執行,執行時期必須知道所有全域資料和大多數堆疊框架中指標的位置。Go 編譯器在編譯 Go 原始檔時會發出此資訊,但組合語言程式必須明確定義它。

標記有 NOPTR 旗標的資料符號(見上文)會被視為不包含指向執行時期配置資料的指標。標記有 RODATA 旗標的資料符號會配置在唯讀記憶體中,因此會被視為隱含標記 NOPTR。總大小小於指標的資料符號也會被視為隱含標記 NOPTR。無法在組譯原始檔中定義包含指標的符號;此類符號必須在 Go 原始檔中定義。即使沒有 DATAGLOBL 指令,組譯原始檔仍可透過名稱來參照符號。一個良好的經驗法則是在 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 載入 gm 的順序如下所示

#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 中定義。

定址模式

在使用編譯器和組譯器的 -dynlink-shared 模式時,任何載入或儲存固定記憶體位置(例如全域變數)都必須假設會覆寫 CX。因此,為了安全地使用這些模式,組譯來源通常應避免在記憶體參照之間使用 CX。

64 位元 Intel 386(又稱 amd64)

這兩個架構在組譯器層級的行為大致相同。在 64 位元版本上存取 mg 指標的組譯碼與 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

編譯器和連結器保留暫存器 R10R11

R10 指向 g(goroutine)結構。在組譯器原始碼中,此指標必須稱為 g;名稱 R10 不會被辨識。

為了讓人員和編譯器更容易撰寫組譯,ARM 連結器允許一般尋址格式和偽運算,例如 DIVMOD,而這些格式和偽運算可能無法使用單一硬體指令表示。它會將這些格式實作為多個指令,通常使用 R11 暫存器來儲存暫時值。手寫組譯可以使用 R11,但這麼做時必須確定連結器不會也使用它來實作函式中的任何其他指令。

在定義 TEXT 時,指定框架大小 $-4 會告訴連結器,這是一個葉函式,在進入時不需要儲存 LR

名稱 SP 始終是指前面所述的虛擬堆疊指標。對於硬體暫存器,請使用 R13

條件碼語法是將句點和一或兩個字母的碼附加到指令,例如 MOVW.EQ。可以附加多個碼:MOVM.IA.W。碼修改項的順序無關緊要。

定址模式

ARM64

R18 是「平台註冊器」,保留在 Apple 平台上。為防止意外誤用,註冊器命名為 R18_PLATFORMR27R28 由編譯器和連結器保留。R29 是堆疊指標。R30 是連結註冊器。

指令修改器附加在指令之後,中間以句點分隔。唯一的修改器為 P(後遞增)和 W(前遞增):MOVW.PMOVW.W

定址模式

參考:Go ARM64 組合語言指令參考手冊

PPC64

此組譯器由 GOARCH 值 ppc64 和 ppc64le 使用。

參考:Go PPC64 組合語言指令參考手冊

IBM z/Architecture,又稱 s390x

暫存器 R10R11 已保留。組譯器在組譯某些指令時會使用它們來存放暫時值。

R13 指向 g(goroutine)結構。此暫存器必須稱為 g;名稱 R13 不會被辨識。

R15 指向堆疊框架,通常應僅使用虛擬暫存器 SPFP 來存取。

載入和儲存多個指令會在暫存器範圍上執行。暫存器範圍由起始暫存器和結束暫存器指定。例如,LMG (R9), R5, R7 會分別使用 0(R9)8(R9)16(R9) 的 64 位元值載入 R5R6R7

儲存和儲存指令(例如 MVCXC)會將長度寫為第一個引數。例如,XC $8, (R9), (R9) 會清除 R9 中指定位址的八個位元組。

如果向量指令將長度或索引作為引數,則它會是第一個引數。例如,VLEIF $1, $16, V2 會將值十六載入 V2 的索引一。使用向量指令時應小心,以確保它們在執行階段可用。若要使用向量指令,機器必須同時具有向量功能(功能列表中的位元 129)和核心支援。如果沒有核心支援,向量指令將不會產生任何作用(它會等同於 NOP 指令)。

定址模式

MIPS、MIPS64

一般用途暫存器命名為 R0R31,浮點暫存器為 F0F31

R30 保留為指向 gR23 用作臨時暫存器。

TEXT 指令中,MIPS 的 frame 大小 $-4 或 MIPS64 的 $-8 指示連結器不要儲存 LR

SP 指的是虛擬堆疊指標。對於硬體暫存器,請使用 R29

定址模式

GOMIPS 環境變數的值(hardfloatsoftfloat)可透過預先定義 GOMIPS_hardfloatGOMIPS_softfloat 而提供給組譯碼。

GOMIPS64 環境變數的值(hardfloatsoftfloat)可透過預先定義 GOMIPS64_hardfloatGOMIPS64_softfloat 而提供給組譯碼。

不支援的 opcode

組譯器設計為支援編譯器,因此並非所有硬體指令都定義給所有架構:如果編譯器未產生它,它可能不存在。如果您需要使用遺漏的指令,有兩種方法可以進行。一種方法是更新組譯器以支援該指令,這很簡單,但只有在該指令可能會再次使用時才值得。相反地,對於簡單的一次性案例,可以使用 BYTEWORD 指令在 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