Solidity語言學習筆記————39、獨立彙編

FLy_鵬程萬里發表於2018-07-08

獨立彙編(Standalone Assembly)

上面介紹的在Solidity中嵌入的內聯組合語言也可以單獨使用。實際上,它是被計劃用來作為編譯器的一種中間語言。在這個目的下,它嘗試達到下述的目標:

  • 使用它編寫的程式碼要可讀,即使程式碼是從Solidity編譯得到的。
  • 從組合語言轉為位元組碼應該儘可能的少坑。
  • 控制流應該容易檢測來幫助進行形式驗證與優化。

為了達到第一條和最後一條的目標,Solidity組合語言提供了高層級的元件比如,forifswitch語句和函式呼叫。這樣的話,可以不直接使用SWAP,DUP,JUMP,JUMPI語句,因為前兩個有混淆的資料流,後兩個有混淆的控制流。此外,函式形式的語句如mul(add(x, y), 7)比純的指令碼的形式7 y x add num更加可讀。

第二個目標是通過引入一個絕對階段來實現,該階段只能以非常規則的方式去除較高階別的構造,並且仍允許檢查生成的低階彙編程式碼。Solidity組合語言提供的非原生的操作是使用者定義的識別符號的命名查詢(函式名,變數名等),這些都遵循簡單和常規的作用域規則,會清理棧上的區域性變數。

作用域:一個識別符號(標籤,變數,函式,彙編)在定義的地方,均只有塊級作用域(作用域會延伸到,所在塊所巢狀的塊)。跨函式邊界訪問區域性變數是不合法的,即使可能在作用域內(譯者注:這裡可能說的是,函式內定義多個函式的情況,JavaScript有這種語法)。不允許shadowing。區域性變數不能在定義前被訪問,但標籤,函式和彙編可以。彙編是非常特殊的塊結構可以用來,如,返回執行時的程式碼,或建立合約。外部定義的彙編變數在子彙編內不可見。

如果控制流來到了塊的結束,區域性變數數匹配的pop指令會插入到棧底(譯者注:移除區域性變數,因為區域性變數失效了)。無論何時引用區域性變數,程式碼生成器需要知道其當前在堆疊中的相對位置,因此需要跟蹤當前所謂的堆疊高度。由於所有的區域性變數在塊結束時會被移除,因此在進入塊之前和之後的棧高應該是不變的,如果不是這樣的,將會丟擲一個警告。

使用switchfor和函式,可以在不用jumpjumpi的情況下寫出來複雜的程式碼。這會讓分析控制流更加容易,也可以進行更多的形式驗證及優化。

此外,如果手動使用jumps,計算棧高是非常複雜的。棧內所有的區域性變數的位置必須是已知的,否則指向本地變數的引用,或者在塊結束時自動刪除區域性變數都不會正常工作。離線處理機制正確的在塊內不可達的地方插入合適的操作以修正棧高來避免出現jump時非連續的控制流帶來的棧高計算不準確的問題。

我們來看看從Solidity編譯到彙編的示例。我們可以一起來考慮下下述Soldity程式的位元組碼:

pragma solidity ^0.4.16;

contract C {
  function f(uint x) public pure returns (uint y) {
    y = 1;
    for (uint i = 0; i < x; i++)
      y = 2 * y;
  }
}
將生成以下彙編:
{
  mstore(0x40, 0x60) // store the "free memory pointer"
  // function dispatcher
  switch div(calldataload(0), exp(2, 226))
  case 0xb3de648b {
    let r := f(calldataload(4))
    let ret := $allocate(0x20)
    mstore(ret, r)
    return(ret, 0x20)
  }
  default { revert(0, 0) }
  // memory allocator
  function $allocate(size) -> pos {
    pos := mload(0x40)
    mstore(0x40, add(pos, size))
  }
  // the contract function
  function f(x) -> y {
    y := 1
    for { let i := 0 } lt(i, x) { i := add(i, 1) } {
      y := mul(2, y)
    }
  }
}

彙編語法

語法分析器的任務如下:

  • 將位元組流轉為符號流,去掉其中的C++風格的註釋(一種特殊的原始碼引用的註釋,這裡不打算深入討論)。
  • 將符號流轉為下述定義的語法結構的AST。
  • 註冊塊中定義的識別符號,標註從哪裡開始(根據AST節點的註解),變數可以被訪問。

組合詞典遵循由Solidity本身定義的片語。

空格用於分隔標記,它由空格,製表符和換行符組成。 註釋是常規的JavaScript / C ++註釋,並以與Whitespace相同的方式進行解釋。

語法:

AssemblyBlock = '{' AssemblyItem* '}'
AssemblyItem =
    Identifier |
    AssemblyBlock |
    AssemblyExpression |
    AssemblyLocalDefinition |
    AssemblyAssignment |
    AssemblyStackAssignment |
    LabelDefinition |
    AssemblyIf |
    AssemblySwitch |
    AssemblyFunctionDefinition |
    AssemblyFor |
    'break' |
    'continue' |
    SubAssembly
AssemblyExpression = AssemblyCall | Identifier | AssemblyLiteral
AssemblyLiteral = NumberLiteral | StringLiteral | HexLiteral
Identifier = [a-zA-Z_$] [a-zA-Z_0-9]*
AssemblyCall = Identifier '(' ( AssemblyExpression ( ',' AssemblyExpression )* )? ')'
AssemblyLocalDefinition = 'let' IdentifierOrList ( ':=' AssemblyExpression )?
AssemblyAssignment = IdentifierOrList ':=' AssemblyExpression
IdentifierOrList = Identifier | '(' IdentifierList ')'
IdentifierList = Identifier ( ',' Identifier)*
AssemblyStackAssignment = '=:' Identifier
LabelDefinition = Identifier ':'
AssemblyIf = 'if' AssemblyExpression AssemblyBlock
AssemblySwitch = 'switch' AssemblyExpression AssemblyCase*
    ( 'default' AssemblyBlock )?
AssemblyCase = 'case' AssemblyExpression AssemblyBlock
AssemblyFunctionDefinition = 'function' Identifier '(' IdentifierList? ')'
    ( '->' '(' IdentifierList ')' )? AssemblyBlock
AssemblyFor = 'for' ( AssemblyBlock | AssemblyExpression )
    AssemblyExpression ( AssemblyBlock | AssemblyExpression ) AssemblyBlock
SubAssembly = 'assembly' Identifier AssemblyBlock
NumberLiteral = HexNumber | DecimalNumber
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+

相關文章