彙編 div Solidity內聯彙編

2021-10-12 08:14:46 字數 4155 閱讀 6863

在用solidity開發以太坊智慧型合約時,使用彙編可以直接與evm互動,降低gas開銷成本,更精細的控制智慧型合約的行為,因此值得solidity開發者學習並加以利用。本文是solidity彙編開發的簡明教程,旨在幫助你快速熟悉如何在solidity智慧型合約**中嵌入彙編**。

以太坊虛擬機器evm有自己的指令集,該指令集中目前包含了144個操作碼,詳情參考geth源**

這些指令是solidity抽象出來的,可以在solidity內聯使用。例如:

contract assembler 

}}

evm是乙個棧虛擬機器,棧這種資料結構只允許兩個操作:壓入(push)或彈出(pop)資料。最後壓入的資料位於棧頂,因此將被第乙個彈出,這被稱為後進先出(lifo:last in, first out):

棧虛擬機器將所有的運算元儲存在棧上,關於棧虛擬機器的詳細資訊可以參考stack machine 基礎

為了能夠解決實際問題,棧結構機器需要實現一些額外的指令,例如add、substract等等。指令執行時通常會先從堆疊彈出乙個或多個值作為引數,再將執行結果壓回堆疊。這通常被稱為逆波蘭表示法(rpn:reverse polish notation):

a + b      // 標準表示法infix

a b add // 逆波蘭表示法rpn

可以在solidity中使用assembly{}來嵌入彙編**段,這被稱為內聯彙編:

assembly
assembly塊內的**開發語言被稱為yul,為了簡化我們稱其為彙編或evm彙編。

另乙個需要注意的問題時,彙編**塊之間不能通訊,也就是說在乙個彙編**塊裡定義的變數,在另乙個彙編**塊中不可以訪問。例如:

assembly         

assembly

上面的**編譯時會報如下錯誤:

// declarationerror: identifier not found

// let y := x

// ^

下面的**使用內聯彙編**計算函式的兩個引數的和並返回結果:

function addition(uint x, uint y) public pure returns (uint) 

}

讓我們重寫上面的**,補充一些更詳細的注釋,以便說明每個指令在evm內部的執行原理。

function addition(uint x, uint y) public pure returns (uint) 

}

在yul中,使用let關鍵字定義變數。使用:=操作符給變數賦值:

assembly
如果沒有使用:=操作符給變數賦值,那麼該變數自動初始化為0值:

assembly
你可以使用複雜的表示式為變數賦值,例如:

assembly
在evm的內部,let指令執行如下任務:

因此,使用let指令在彙編**塊中定義的變數,在該**塊外部是無法訪問的。

在yul彙編中注釋的寫法和solidity一樣,可以使用單行注釋//或多行注釋/* */。例如:

assembly
在solidity彙編中字面量的寫法與solidity一致。不過,字串字面量最多可以包含32個字元。

assembly
在solidity彙編中,變數的作用範圍遵循標準規則。乙個塊的範圍使用一對大括號標識。

在下面的示例中,y和z僅在定義所在塊範圍內有效。因此y變數的作用範圍是scope 1,z變數的作用範圍是scope 2。

assembly   // 到此處會銷毀y

// scope 2

// 到此處會銷毀z

}// declarationerror: identifier not found

// let z := y

// ^

作用範圍的唯一例外是函式和for迴圈,我們將在下面解釋。

在solidity彙編中,只需要使用變數名就可以訪問區域性變數,無論該變數是定義在彙編塊中,還是solidity**中,不過變數必須是函式的區域性變數:

function assembly_local_var_access() public pure     

assembly

}

先看一下solidity中迴圈的使用。下面的solidity函式**中計算變數的倍數n次,其中value和n是函式的引數:

function for_loop_solidity(uint n, uint value) public pure returns(uint)     

return value;

}

等效的solidity彙編**如下:

function for_loop_assembly(uint n, uint value) public pure returns (uint)  lt(i, n)    

mstore(0x0, value)

return(0x0, 32)

}

}

類似於其他開發語言中的for迴圈,在solidity彙編中,for迴圈也包含3個元素:

注意:for迴圈中變數的作用範圍略有不同。在初始化部分定義的變數在迴圈的其他部分都有效。

在solidity彙編中實際上是沒有while迴圈關鍵字的,但是可以使用for迴圈實現同樣的功能:只要留空for迴圈的初始化部分和迭代後續步驟即可。

assembly  lt(i, 0x100)  

}

solidity內聯彙編支援使用if語句來設定**執行的條件,但是沒有其他語言中的else部分。

assembly   // ok

if eq(value, 0) revert(0, 0) // error, 需要大括號

}

if語句強制要求**塊使用大括號,即使需要保護的**只有一行,也需要使用大括號。這和solidity不同。

如果需要在solidity內聯彙編中檢查多種條件,可以考慮使用switch語句。

evm彙編中也有switch語句,它將乙個表示式的值於多個常量進行對比,並選擇相應的**分支來執行。switch語句支援乙個預設分支default,當表示式的值不匹配任何其他分支條件時,將執行預設分支的**。

assembly 

default

sstore(0, div(x, 2))

}

switch語句有一些限制:

assembly 

case false

default

}

也可以在solidity內聯彙編中定義底層函式。呼叫這些自定義的函式和使用內建的操作碼一樣。

下面的彙編函式用來分配指定長度的記憶體,並返回記憶體指標pos:

assembly     

let free_memory_pointer := allocate(64)

}

彙編函式的執行機制如下:

和solidity函式不同,不需要指定彙編函式的可見性,例如public或private,因為彙編函式僅在定義所在的彙編**塊內有效。

evm操作碼可以分為以下幾類:

詳細的操作碼可以檢視solidity文件。

GCC內聯彙編

有時為了高效,有時為了直接控制硬體,有些模組我們不得不直接用組合語言來編寫,並且對外提供呼叫的介面,隱藏細節,這其實就是內聯彙編。如何使用內聯彙編?我們就以 gcc 為例,一窺其中奧秘!一 關鍵字 如何讓 gcc 知道 中內嵌的彙編呢?借助 關鍵字!來看下面的例子 asm volatile hlt ...

gcc內聯彙編

有時為了高效,有時為了直接控制硬體,有些模組我們不得不直接用組合語言來編寫,並且對外提供呼叫的介面,隱藏細節,這其實就是內聯彙編。如何使用內聯彙編?我們就以 gcc 為例,一窺其中奧秘!一 關鍵字 如何讓 gcc 知道 中內嵌的彙編呢?借助關鍵字!來看下面的例子 a volatile hlt a 表...

GNU內聯彙編

組合語言 gcc內聯彙編 gcc支援在c c 中嵌入彙編 這些 被稱作是 gcc inline asm gcc內聯彙編 一 基本內聯彙編 gcc中基本的內聯彙編非常易懂,格式如下 asm volatile instruction list 其中,1.asm 它是gcc定義的關鍵字asm的巨集定義 d...