行內組語
外觀
行內組語(英語:Inline assembly)是部分編譯器支援的一種功能。其將非常低階的匯編語言內嵌在高階語言源始碼中。實施行內組語通常是為了以下理由:
- 執行效率最佳化:將演算法中最攸關效能的部份使用手寫組語取代高階程式碼,優點是不會受到編譯器的限制。
- 使用處理器特有指令:某些處理器提供特定的指令,比如Compare-and-swap和Test-and-set指令可以直接用以實作訊號機制。因為多工系統都需要訊號機制,幾乎所有現代的處理器都支援前述的兩個指令。其它的一些指令集如SPARC架構的VIS指令集,Intel處理器的MMX指令集以及SSE指令集等。
- 系統調用(System Call):高階語言鮮少提供直接調用system calls的機制,故使用組語來進行這項工作。
使用處理器持有指令優化範例
[編輯]下面是一段在D語言進行行內組語的程式碼。該程式碼使用x86架構的浮點運算器指令來計算。此實作快於編譯器產生的一系列浮點數運算,行內組語也使編程人員得以使用fldpi
指令來載入在x86架構下可得到的最佳之pi估計值。
// 計算tan(x)
real tan(real x)
{
asm
{
fld x[EBP] ; // 將x值載入浮點運算器的堆疊上
fxam ; // 測試堆疊頂端的值是否是合法、可計算浮點數
fstsw AX ;
sahf ;
jc trigerr ; // 若x是NAN,正負無限,或空值
// 387可以處理denormals數值
SC18: fptan ; // 為與8087運算器相容fptan會使堆疊頂端為1.0,再來才是tan值,
fstp ST(0) ; // 丟棄堆疊頂端的值
fstsw AX ;
sahf ;
jnp Lret ; // C2為FPU狀態變數,C2 == 1表示x超出允許的範圍
; // tan之週期為pi,下面的程式碼就是將x縮至允許的範圍
fldpi ;
fxch ;
SC17: fprem1 ;
fstsw AX ;
sahf ;
jp SC17 ;
fstp ST(1) ; // 將pi值移出堆疊
jmp SC18 ;
}
trigerr:
return real.nan;
Lret:
;
}
系統調用(system calls)範例
[編輯]在保護模式運行的應用程式無法直接呼叫專屬於OS的功能。因為OS的行程空間包含核心空間(kernel mode)與用戶空間(user mode);運行在用戶空間的程式只能透過中斷來參照專屬於作業系統的功能。通常高階語言都不提供這項功能,所以要運用行內組語將呼叫system calls的過程包裝為高階語言可辨認、呼叫的函數。
下面的C語言片段即含有一個system call的包裝函數。其組語語法為GNU組譯器使用的AT&T組語語法。一般這類程式都會使用巨集實作,不過為了清楚展示觀念,在此列出了完整的程式碼。
GNU組譯器的行內組語語法相當直覺,基本型式如下:
asm("assembly code");
例如:
asm("movl %ecx, %eax"); /* 複製ecx暫存器的內容至eax暫存器*/
或
__asm_("movb %bh, (%eax)"); /* 從bh暫存器複製1位元組的資料到eax暫存器所指的記憶體區塊*/
必須注意的是,AT&T語法中的運算元順序與Windows平台下常用的MASM剛好相反。
asm
跟__asm__
都是合法型式;當asm
與程式碼中某些變數命名起衝突時可使用後者。
extern int errno;
int funcname(int arg1, int *arg2, int arg3)
{
int res;
__asm__ volatile(
"int $0x80" /* 向OS拋出請求*/
: "=a" (res) /* 請編譯器將結果將儲存於eax ("a")*/
"+b"(arg1), /* 請編譯器將arg1存於ebx ("b")*/
"+c"(arg2), /* 請編譯器將arg2存於ecx ("c")*/
"+d"(arg3) /* 請編譯器將arg3存於edx ("d")*/
: "a"(128) /* 該system call的編號放於eax ("a")*/
: "memory", "cc"); /* 通知編譯器,記憶體與condition code register(決定分支的暫存器)己被更改*/
/* 若OS回傳負值表示錯誤;則包裝函式要設定errno global variable並回傳 -1 */
if (-125 <= res && res < 0) {
errno = -res;
res = -1;
}
return res;
}