Skip to content

Latest commit

 

History

History
233 lines (147 loc) · 5.49 KB

page02.md

File metadata and controls

233 lines (147 loc) · 5.49 KB

参考源码位置 https://github.com/lightszero/neovmbook/tree/master/samples/neovm01

AVM从何而来-流程说明

这里讨论了生成AVM的两种方法

1.使用一个汇编器帮助类,通过汇编代码,在代码中直接合成AVM

2.使用编译器,从高级语言获得AVM

汇编器

这里需要谈论一下汇编了

这涉及几个概念,汇编语言 机器语言 汇编器

有趣的是,你不需要专门为此去学习汇编

还记得上文的

NOP
PUSH 1
PUSH 2
ADD
RET

吗,就是这种东西,我们可以把它称为汇编语言了

而NEOVM输入的内容,就是NEOVM模拟的机器语言了,是byte[] 格式的

所以上文这五条指令,是可以被变成一段NEOVM认识的byte[]的机器语言的

完成这个过程的工具叫做汇编器。

我们来做个汇编器吧

NEOVM 中有一个 ScriptBuilder.cs 他完成了汇编器大部分的工作,除了链接。

除了链接,而链接这个问题就比较复杂了,也是汇编器的一个工作重点,这需要我们对NEOVM这类虚拟机有更多的链接才好继续探讨,让我们先把精力集中在将那五条汇编指令变成byte[]吧。

*NEO曾经有一个官方的汇编器项目(neoa,年久失修 https://github.com/neo-project/neo-compiler/tree/master/neoa

要研究编译器,汇编器也是必经之路,也许我会再维护一个新的汇编器项目)

调用ScriptBuilder来生成NEO机器码(AVM)

直接上代码,这个程序位置在 samples/neovm01

注意引用的Neo3.0 的NeoVM,这系列文章都只针对NeoVM3.0,大可不必担心,其实NeoVM3.0并没有那么不同。

从nuget引入Neo.VM

然后用ScriptBuilder就能直接完成汇编器的工作,我们能得到

machinecode=0x6151529366

然后我们让neovm来执行这个

我们同样能得到retvalue=3

好了,到这里我们知道.avm 机器语言 是由汇编器从汇编语言汇编而来,虽然我们没谈链接,这个问题就比较复杂了,我们以后会专门讨论。

汇编语言从何而来

那么问题来了,总不能手写汇编吧,这里就要引入一个 编译器 的概念了

我们需要一个工具将

"1+2"

翻译成

NOP
PUSH 1
PUSH 2
ADD
RET

这就是编译器的工作了

ok,我们接下来专注如何实现这个自动将加法运算编译为汇编语言的工作。

用编译器生成NEO机器码(AVM)

还是直接上代码,这个程序位置在 samples/neovm02

这里有一个极端简单的编译器

他只能编译正整数的加法,比如

“1+2+4+5”

这个简单编译器的源码分为两个部分,一部分是将源码整理成抽象语法树,也就是AST 就是这个ParseSynatxNode函数

然后我们就得到”1+2+3+4“这个表达式的抽象语法树

接下来第二步,就是把抽象语法树变成我们实际要执行的代码

也非常简单,调用汇编器,深度遍历语法树,得到机器码

之后就是把这段代码用neovm执行,得到那个结果 12

流程分析

代码上完了,让我们来分析一下

一路上应该有这样几个流程,他们经常被笼统的称为编译器

分词->建立抽象语法树->转换为汇编代码->转换为机器代码

1.分词

分词是编译器的第一项工作

”1+2323+4”,编译器总不能一个字节一个字节的分析,首先要把字符串拆分为一个个的单词 “1”,"+","2323","+","4"

因为我们的测试编译器非常简单,一句 string.split 就完成了。

2.建立抽象语法树

然后是做语法分析,最常见的组织形式就是生成一个抽象语法树 ” 1+2+4+5“ 被整理成 一棵树

顶层节点是一个加法节点,左值是 ”1+2+4",右值是 5

“1+2+4“又被拆成一个加法节点,左值是”1+2“,右值是 4

”1+2“又被拆成一个加法节点,左值是1,右值是 2

一些脚本语言的编译器,就只做到这里,建立了抽象语法树,就已经可以做解释执行了。

比如四则运算字符串计算的常用算法:前缀表达式,其实前缀表达式就是一个ast(抽象语法树) 然后通过深度遍历树节点求值。

3.转换为汇编代码

还是来看这里

我们深度遍历这颗树,最深的节点是 1 2

PUSH 1

PUSH 2

然后是上层的加法

ADD

同级的4

PUSH 4

上层的加法

ADD

PUSH 5

上层的加法

ADD

整理一下

PUSH 1
PUSH 2
ADD
PUSH 4
ADD
PUSH 5
ADD

对照一下EmitCode的代码

我们直接用汇编器 把PUSH ADD 变成了机器码

但你想想我们如果先把指令存下来呢?

你就能得到

PUSH 1
PUSH 2
ADD
PUSH 4
ADD
PUSH 5
ADD

4.转换为机器代码

按照严格的流程来考虑,步骤3输出的应该是汇编

PUSH 1
PUSH 2
ADD
PUSH 4
ADD
PUSH 5
ADD

然后再用汇编器把他变成AVM

但写此文的时候,我们的独立汇编器项目还没有完成,所以只介绍了使用SricptBuilder这个简单的汇编助手。

通常编译器自己的汇编器不叫作汇编器,叫做连接器,主要负责地址转换的任务。熟悉c++的朋友,一定很了解c++编译器清晰的分为 compiler 和 link 两个过程。

这一篇主要是为了讲解AVM的产生流程,就不深入讲地址转换的细节了,另文深入。