上一篇《Neo 編譯器》中說明了Neo編譯器是怎麼把CIL轉成neo虛擬機器的opcode,那麼vm虛擬機器又是怎麼處理這些程式碼的,這篇文章我們看一下虛擬機器的程式碼。
框架
虛擬機器所處的位置
在框架圖中,我們可以看出Virtual Machine有以下作用
- 讀取Opcode(smart contract),在Execution Engine中執行
- Execution Engine可以進行邏輯運算
- Interop Service可以呼叫External Data
- 系統呼叫(OP_SYSCALL)可以訪問區塊鏈賬本的資訊
下面我們先看一下虛擬機器如何讀取Opcode。
VM物件關係
下面展示的圖不是UML, UML太麻煩,還是腦圖比較符合思維邏輯的發展。
關鍵的幾個物件
- Execution Engine:執行引擎
- Execution Context:執行上下文
- Stack Item:堆疊的一條資料
- Crypto:C#的加密庫
執行引擎
- IScriptTable裡面存貯了AppCall命令可以呼叫的其他contract的程式碼,這一塊需要研究一下區塊鏈的實現,這個以後再仔細研究。
- InteropService專門用來響應SYSCALL,具體有哪些是系統呼叫,用來幹什麼的,後面通過例子再來說明。
- InvocationStack是呼叫棧,傳入引數,呼叫其他合約都會有一個新的呼叫棧
- EvaluationStack是計算棧,用來執行操作
- AltStack是備用棧,計算棧算出的中間結果可以儲存在備用棧
執行上下文
執行上下文
每個變數都蠻好理解的,重點是下面看看怎麼用的。
vm執行流程
vm程式碼執行流程
- 構造,此時可以傳入script container,script table,後面我們看看在區塊鏈上這些都是從哪裡來的,這裡只專注於vm的執行流程,暫且不深究了。
- 2.載入.avm,avm是編譯器編譯出來的一串數字,通過engine.LoadScript可以載入。
- execute開始執行, 下面看一下程式碼
public void Execute()
{
State &= ~VMState.BREAK;
while (!State.HasFlag(VMState.HALT) && !State.HasFlag(VMState.FAULT) && !State.HasFlag(VMState.BREAK))
StepInto();
}
複製程式碼
public void StepInto()
{
if (InvocationStack.Count == 0) State |= VMState.HALT;
if (State.HasFlag(VMState.HALT) || State.HasFlag(VMState.FAULT)) return;
OpCode opcode = CurrentContext.InstructionPointer >= CurrentContext.Script.Length ? OpCode.RET : (OpCode)CurrentContext.OpReader.ReadByte();
try
{
ExecuteOp(opcode, CurrentContext);
}
catch
{
State |= VMState.FAULT;
}
}
複製程式碼
看一下這行程式碼,OpCode opcode = CurrentContext.InstructionPointer >= CurrentContext.Script.Length ? OpCode.RET : (OpCode)CurrentContext.OpReader.ReadByte();
- 程式碼執行完了以後,插入OpCode.RET
- 如果不是RET,則read一個位元組的opcode
ExecuteOp函式就是具體的執行OpCode的語義,我們通過一個例子來說明
具體的一個例子
還是上次的那個程式碼
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Services.Neo;
public class Sum : SmartContract
{
public static int Main(int a, int b)
{
return a + b;
}
}
複製程式碼
測試虛擬機器的程式碼
using System;
using System.IO;
using System.Linq;
using Neo;
using Neo.VM;
using Neo.Cryptography;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var engine = new ExecutionEngine(null, Crypto.Default);
engine.LoadScript(File.ReadAllBytes(@"C:\……\Test1.avm"));
using (ScriptBuilder sb = new ScriptBuilder())
{
sb.EmitPush(4); // 對應形參 b
sb.EmitPush(3); // 對應形參 a
engine.LoadScript(sb.ToArray());
}
engine.Execute(); // 開始執行
var result = engine.EvaluationStack.Peek().GetBigInteger(); // 在這裡設定返回值
Console.WriteLine($"執行結果 {result}");
Console.ReadLine();
}
}
}
複製程式碼
執行的具體過程
生成的程式碼太長了,需要有點耐心才能看完,如果圖片不清晰,可以去程式碼倉庫下載pdf
具體的執行過程
總結
文章只是過了一下一個簡單的程式碼,後面我們需要研究一下系統呼叫和訪問外部存貯,智慧合約之間互相呼叫的情況。
作者:沈寅
原文連結:www.jianshu.com/p/b7a50b7e0…