自学内容网 自学内容网

TON(三)空合约Func汇编为fift的底层(1)——PROC指令

系列文章目录

TON(二)

TON(一)


前言

在上文中我们详细介绍了toncli的编译命令,这次我们将深入底层来剖析一下,一个空的func合约是在做什么

一、func编译到fift

我们使用func -w main.func -o output.fif 编译下面的文件

() main(int msg_value, cell in_msg, slice in_msg_body) impure {
    
}

得到

"Asm.fif" include
// automatically generated from `/home/zqy/tonlearn/my_first_contract/contracts/main.fc` 
PROGRAM{
  DECLPROC main
  main PROC:<{
    3 BLKDROP
  }>
}END>c

二、详细分析fift文件

1. PROC指令

因为fift没有成熟的vscode拓展,所以需要我们将asm.fif和FIFT.fif添加到根目录下,搜索解析,博主手动解析如下

{ 1000 @def-proc } : PROC
{ // i s l
  dup 0< {
    negate
    @was-split @ { drop 0 } if
  } if
  @adj-long-proc over @procdict @ @procdictkeylen
  idict!+ not abort"cannot define procedure, redefined?"
  @procdict !  2 2 @procinfo~!
}
variable @was-split
variable @procdict
variable @procinfo

{ over sbits < { s>c <b swap ref, b> <s } if } : @adj-long-proc

sbits (s - x), returns the number of data bits x remaining in Slice s,

19 constant @procdictkeylen

{ not 2 pick @ and xor swap ! } : ~!


idict! (v x D n – D′ −1 or D 0), adds a new value v (represented by a
Slice) with key given by signed big-endian n-bit integer x into dictionary
D (represented by a Cell or a Null ) with n-bit keys, and returns the new
dictionary D′ and −1 on success. Otherwise the unchanged dictionary
D and 0 are returned
if (x e – ), executes e (which must be an execution token, i.e., a
WordDef ),5 but only if Integer x is non-zero

这段代码是 Fift 语言的一部分,它定义了一个简单的 Fift 解释器中的一些核心词(words),这些词是 Fift 脚本的基础,用于定义和操作程序字典(procedure dictionary)。

定义 PROC

{ 1000 @def-proc } : PROC

这行代码定义了一个名为 PROC 的词(word),它是一个工厂词(factory word),用于创建新的词。这里的 { 1000 @def-proc } 是一个代码块(colon definition),它调用了 @def-proc 词,并传入 1000 作为参数。@def-proc 词的作用是定义一个新的词,并将其存储在程序字典中。

定义 @def-proc

{ // i s l
  dup 0< {
    negate
    @was-split @ { drop 0 } if
  } if
  @adj-long-proc over @procdict @ @procdictkeylen
  idict!+ not abort"cannot define procedure, redefined?"
  @procdict !  2 2 @procinfo~!
} variable @was-split
variable @procdict
variable @procinfo

这部分代码定义了 @def-proc 词,它接受三个参数:i(一个整数),s(一个字符串),和 l(一个切片)。这个代码块执行以下操作:

  • dup 0<:复制 i 并检查它是否小于 0。这意味着,我们传入的总是会变成无符号整数,或绝对值。记住这里的算法。在 fift 语言中,if 语句是一种控制流结构,用于根据条件执行不同的代码路径。

  • if:这是 Fift 中的条件语句关键字,用于引入一个条件判断。 (x e – ):这是 Fift 语法中的条件执行结构。括号 (...) 用于将多个操作组合在一起。在这个结构中:

    • x:这是一个整数值,用作条件判断。如果 x 非零,条件判断为真;如果 x 为零,条件判断为假。

    • e:这是一个执行令牌(execution token),通常是一个词(word)的定义,例如 WordDef。在 Fift 中,一个词的定义(WordDef)是一个特殊的数据结构,它包含了词的名称、代码块和其他元数据。

    • :这个符号在 Fift 中表示“丢弃并执行”的操作。它用于指示 Fift 解释器忽略 x 的值,并执行 e 指定的代码。

    • executes e:这意味着如果条件 x 非零,Fift 解释器将执行 e 指定的代码。如果 x 为零,则跳过 e 指定的代码。

    • only if Integer x is non-zero:这是一个条件,指定只有当 x 为非零整数时,才会执行 e。如果 x 为零,则不会执行 e

假设我们有一个 Fift 词 my-word,我们想在某个整数 x 非零时执行它。我们可以这样写:

: my-word
  ." Hello, World! " cr
;

0 my-word if

在这个例子中,my-word 将不会被执行,因为 if 语句的条件 0 是假的。

如果我们将条件改为一个非零值:

: my-word
  ." Hello, World! " cr
;

1 my-word if

这次,my-word 将被执行,因为 if 语句的条件 1 是真的。

dup bool {e} if //{e}被执行当且仅当,bool是true , 也就是-1。
  • negate:如果 i 小于 0,则取其相反数。
  • @was-split @ { drop 0 } if:如果 @was-split 变量的值非零,则丢弃它并设置为 0。PS(这里的意思就是不管是啥都按照零来算。但其实有些多此一举。完全可以不使用这个变量。)
@was-split @ //这里是很常用的方法@是为了取出变量当中的值
  • @adj-long-proc:调整长整数的处理。
  • over @procdict @ @procdictkeylen:获取程序字典和键长度。
  • idict!+:将新的词添加到程序字典中。如果添加失败(即词已存在),则返回 0。
  • abort"cannot define procedure, redefined?":如果词已存在,则抛出错误。
  • @procdict !:将新的程序字典存储在 @procdict 变量中。
  • 2 2 @procinfo~!:更新 @procinfo 变量。

3. 定义 @adj-long-proc

{ over sbits < { s>c <b swap ref, b> <s } if } : @adj-long-proc

这个代码块定义了 @adj-long-proc 词,它接受两个参数:over(一个切片)和 sbits(一个整数)。

4. 定义 sbits

sbits (s - x), returns the number of data bits x remaining in Slice s,

在 Fift 语言中,sbits 是一个用于操作切片(slice)的内置词(word)。切片是 Fift 和其他 Forth 语言中用于表示数据片段的一种数据结构,它可以包含任意长度的二进制数据。

sbits 这个词的作用是返回切片中剩余的位数(bits)。在 Fift 中,切片通常用于处理数据,比如智能合约的代码或者数据,因此知道切片中剩余多少位数据是非常重要的。使用 ``sbits` 的使用非常简单,它通常接受一个切片作为参数,并返回该切片中剩余的位数。例如:

my-slice sbits . cr

这里,my-slice 是一个切片变量,sbits 被用来获取这个切片中的剩余位数,并通过 . cr 打印出来。. 是打印栈顶元素的 Fift 词,而 cr 是换行的 Fift 词。 sbits 的内部工作

在 Fift 的实现中,切片通常由两个主要部分组成:元数据和数据本身。元数据包含了切片的大小、容量等信息,而数据部分则是实际的二进制数据。

当你对一个切片使用 sbits 时,Fift 解释器会查看切片的元数据,确定切片中已经使用了多少位,然后从切片的总大小中减去这个数值,得到剩余的位数。

  • sbits 返回的是切片中剩余的位数,而不是字节数。如果你需要字节数,可能需要将结果除以 8。
  • 在使用 sbits 时,确保你了解切片的当前状态,比如它是否已经被修改过,或者是否包含了预期的数据。sbits 是 Fift 语言中处理切片数据时的一个有用工具,它提供了一种快速检查切片中剩余数据量的方法。

这个代码块执行以下操作:

  • over sbits <:检查切片中的位数是否小于 sbits
  • s>c <b swap ref, b> <s:如果小于,则将切片转换为字符,执行某些操作,然后转换回切片。
    在 Fift 语言中,s>c 是一个将切片(Slice)转换为单元(Cell)的内置词(word)。切片和单元是 Fift 中处理数据的两种基本数据结构,它们在 TON 区块链中也扮演着重要的角色。

5 s>c 词的解释

  • s>c:这个操作接受一个切片(Slice)作为输入,并创建一个新的单元(Cell)c,该单元直接包含切片的数据。
  • (s – c):这表示 s>c 词的栈效应,即它从栈中弹出一个切片 s 并返回一个新的单元 c
  • <b:这是一个编译时操作,它开始一个新的字节序列的编译。
  • swap s,:这个操作首先将切片 s 压入栈中,然后交换栈顶的两个元素,使得字节序列编译器和切片 s 相邻。
  • b>:这个操作结束字节序列的编译,并将编译后的字节序列转换为一个新的单元。
    所以,s>c 的等效操作可以写成:
<b swap s, b>

这个序列首先开始编译一个新的字节序列,然后将切片 s 压入栈中并交换位置,最后结束编译并创建一个新的单元。

s>c 词通常用于以下场景:

  • 当你需要将切片中的数据转换为单元时,这在处理复杂的数据结构或者在将数据存储到区块链上时非常有用。
  • 当你需要在切片和单元之间进行转换,以便利用 Fift 提供的各种单元操作词来进一步处理数据。

一个切片 my-slice,将其转换为单元 my-cell,可以这样写:

my-slice s>c my-cell

这行代码将创建一个新的单元 my-cell,它包含了 my-slice 中的数据。

  • 当使用 s>c 时,确保切片中的数据是有效的,因为单元一旦创建,其内容将与切片相同,且不可更改。
  • s>c 操作不会修改原始切片,它只是创建了一个新的单元。

6. 定义 @procdictkeylen 常量

19 constant @procdictkeylen

这行代码定义了一个常量 @procdictkeylen,其值为 19。这个常量用于指定程序字典中的键长度。

7. 定义 ~!

{ not 2 pick @ and xor swap ! } : ~!

在 Fift 语言中,~! 是一个操作符,用于执行按位取反(bitwise NOT)操作后,将结果存储回原变量。这个操作符结合了按位取反操作和赋值操作。

  • ~!:这是一个双目操作符,意味着它接受两个操作数。

  • 第一个操作数通常是一个字面量或变量,它将被按位取反。

  • 第二个操作数是一个变量,其值将被更新为第一个操作数的按位取反结果。

  • ( x – )~! 操作符从栈中弹出一个值 x,执行按位取反操作,但不返回任何值。

  1. 取反操作~ 是按位取反操作符,它将操作数的每个位取反。例如,如果操作数是 0011 1010(二进制表示),取反后将变为 1100 0101
  2. 赋值操作! 是赋值操作符,它将取反后的结果存回原变量。

假设我们有一个变量 flag,其值为 10(二进制表示为 1010),我们想对其执行按位取反操作:

10 flag ~! 

执行这行代码后,flag 的值将变为 0101(十进制表示为 5)。

9 词定义:idict!

  • idict!:这个词的作用是将一个值 v(一个切片)与一个整数键 x 一起添加到字典 D 中。
  • (v x D n – D′ −1 or D 0):这是 idict! 的栈效应,描述了操作前后栈的变化。
    • v:要添加到字典中的值,通常是一个切片(Slice)。
    • x:与值 v 关联的键,是一个有符号的大端整数。
    • D:要添加键值对的字典,可以是一个单元(Cell)或者空值(Null)。
    • n:键 x 的位数,表示键是一个 n 位的整数。
    • D′:如果操作成功,D′ 是更新后的字典。
    • -1:如果操作成功,返回 -1 作为成功的标志。
    • 0:如果操作失败(例如,键已存在),返回 0
  1. 参数入栈:首先,将值 v、键 x、字典 D 和键的位数 n 压入栈中。
  2. 检查字典:检查 D 是否为空。如果 D 为空,说明字典尚未初始化,可能需要先创建一个新的字典。
  3. 添加键值对:将值 v 和键 x 添加到字典 D 中。这个操作会检查键 x 是否已经存在于字典中。
    • 如果键不存在,将其添加到字典中,并返回更新后的字典 D′-1 作为成功的标志。
    • 如果键已存在,操作失败,返回原始字典 D0
  4. 返回结果:操作完成后,根据操作的成功与否,返回相应的值。

假设我们有一个字典 my-dict,我们想添加一个键值对,其中键是一个 32 位的整数 0x00000001,值是一个切片 my-slice

my-slice 1 32 my-dict idict!

这行代码将尝试将键 0x00000001 和值 my-slice 添加到字典 my-dict 中。如果添加成功,my-dict 将被更新,并且栈上将留下 -1。如果键已存在,my-dict 将保持不变,并且栈上将留下 0

  • idict! 操作是幂等的,意味着多次添加相同的键值对不会改变字典的状态。
  • 在使用 idict! 之前,确保字典 D 已经初始化,否则可能需要先创建一个新的字典。
  • 操作的成功与否取决于键是否已经存在于字典中,这可以通过返回值 -10 来判断。

idict! 是 Fift 语言中处理字典数据结构的一个强大工具,它允许开发者在字典中存储和管理键值对。


总结

在 Fift 语言中,PROC 是一个工厂词(factory word),它用于创建新的词(word)。在 Forth 类语言中,词是基本的编程构建块,类似于其他语言中的函数或子程序。PROC 提供了一种简便的方式来定义新的词,同时处理与词创建相关的内部细节,如管理程序字典和确保词的唯一性。

  1. 封装词创建过程PROC 隐藏了创建新词的复杂性,提供了一个简单的接口来定义新词。用户只需要提供词的行为(即执行体),而 PROC 负责将这个行为注册到程序字典中。

  2. 管理程序字典:程序字典是一个数据结构,它存储了所有已定义的词及其关联的执行令牌。PROC 通过 idict! 词来管理这个字典,确保每个新词都能被正确地添加到字典中。

  3. 确保词的唯一性:在添加新词到程序字典时,PROC 会检查这个词是否已经存在。如果词已存在,PROC 会抛出错误,防止意外地覆盖已有的词。

  4. 处理词的属性PROC 可以接受额外的参数来定义词的属性,比如是否是立即执行的(immediate)或者是否可编译的(compile-only)。这些属性影响词在 Fift 程序中的使用方式。

  5. 简化错误处理:通过 PROC 定义的词,如果词的定义过程中出现错误,PROC 可以提供更友好的错误信息,帮助开发者快速定位问题。

  6. 支持词的重载:在某些情况下,你可能需要定义多个具有相同名称但不同行为的词。PROC 可以通过管理不同的执行令牌来支持这种重载机制。

  7. 提供默认行为PROC 可以接受默认的执行体,这样即使在没有提供具体行为的情况下,也可以创建一个词的框架,稍后再填充具体的行为。

在 Fift 中定义词(word)时,涉及到的中间值比较和处理,比如 100019、“字典”、“小于零绝对值”等,都是确保词正确定义和存储的关键步骤。下面是这些步骤的详细解释:

1000 的作用

在代码 { 1000 @def-proc } : PROC 中,1000 是一个示例参数,它被传递给 @def-proc 词。这个参数可能表示词的属性或特定的行为,比如词的权限级别、可见性或其他元数据。在不同的上下文中,这个值可以有不同的含义,但通常它用于配置词创建过程中的某些方面。

19 的作用

19 在代码 19 constant @procdictkeylen 中定义了一个常量 @procdictkeylen,它指定了程序字典中的键长度为 19 位。这意味着程序字典的键是一个 19 位的有符号大端整数。这个长度的选择可能基于 Fift 运行时的内部设计,用于确保键的唯一性和足够的存储空间。

字典的作用

在 Fift 中,字典(dictionary)是一种数据结构,用于存储词的定义。每个词都有一个唯一的键(通常是一个整数),和一个值(通常是包含词代码的切片)。字典允许快速查找和执行词,是 Fift 虚拟机的核心组件。

  • 程序字典:存储了所有词的定义,每个词都可以通过其键快速访问。
  • 更新字典:当新词被定义时,idict! 词用于将新词添加到程序字典中。如果键已存在,则表示词已被定义,idict! 将返回失败。

小于零绝对值的作用

在代码 dup 0< { negate } if 中,dup 0< 检查某个值是否小于零,如果是,则 negate 将其转换为正数。这个步骤确保了即使输入的是负数,处理的也是其绝对值。在词定义的上下文中,这可能用于确保键始终是正数,因为字典的键通常不能是负数。

这些中间值比较和处理步骤在 Fift 中定义词时起到了以下作用:

  1. 确保键的正数性:通过将负数转换为正数,确保字典的键始终是正数。
  2. 配置词属性:通过传递参数(如 1000)给 @def-proc,可以配置新词的属性。
  3. 管理字典键长度:通过设置 @procdictkeylen,定义了字典键的长度,确保键的唯一性和存储空间。
  4. 确保词的唯一性:通过检查字典中是否已存在键,防止词的重复定义。

原文地址:https://blog.csdn.net/zhuqiyua/article/details/142861388

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!