[翻译]Rust借用模型-基于栈的借用模型,第二部分

Stacked Borrows: An Aliasing Model for Rust笔记

到目前为止,我们已经看到了两种优化方法,它们利用了即使不安全的代码也必须符合Stacked Borrows的假设。动态分析,反映了Rust借阅检查器执行的静态分析。两者都遵循类似的模式:重新引用了一些参考文献
(证明草图中的步骤(1)),然后我们进行了一些更改/观察(我们写了mutablereference /读取了共享引用),然后执行了一些未知代码(2),然后再次使用了原始引用( 3)。重新标记使我们的引用成为堆栈中的“最高”引用,最终使用断言它仍在堆栈中。这对于在未知代码中对指令进行重新排序非常有效,实际上,到目前为止我们考虑的两种转换都可以简化为这种重新排序。例如,我们基本上执行了以下转换(将未知代码作为函数g和f提取出来):

这是“正好”重新排列f()上的读取xx,交换第4和5行。现在,对x的写入和对x的读取在第3和4行中直接相邻,因此转发42是一个不重要的正确转换。
类似地,example2“ just”要求将读取xx在f(x)上移动。然后我们有两个相邻的读物;重复数据删除很容易辩解。与example1的区别在于,这是读取共享引用,并且f中的未知代码实际上可以访问x。
但是,在某些情况下,能够在未知代码之间向下移动内存访问也很有趣。这比较困难,因为它扩展了参考的有效范围!例如。
考虑以下功能:

我们可能希望在对f的调用中将x的读数从第3行向下移动到第5行。这减少了寄存器压力,因为我们不需要记住对f的调用周围的val。
对于可变引用,我们可以类似地在不需要它们的代码之间向下移动读取,但更有趣的情况是向下移动写访问权限:

删除此处的冗余写入归结为将写入到x的内容从第3行移动到第5行。
然后,我们将对同一引用进行两次相邻的写操作,然后可以删除第一个写操作。
但是,这意味着与我们考虑的任何先前优化不同(事实上,与通常执行的绝大多数优化不同),f将使用存储在x中的值与原始程序中的值不同来调用f!我们将在后面看到为什么这行得通。

 

保护器

事实证明,到目前为止,我们已经介绍了Stacked Borrows,不允许这些优化使操作下降。这是example2_down的反例:

 

我们传递给f的闭包通过写入x的别名raw_pointer来更改x中存储的值。Stacked Borrows允许这样做:在写操作之前,;;location的Stacked Borrows看起来像[Unique(0),Unique(1),SharedRW(L),SharedRO(2),SharedRO(3),其中1是标记在第3行中创建的临时可变引用的2。是在第5行中创建的共享引用的标签。
3是example2_down内部的x标记(重新标记会在此处分配一个新标记)。在这种情况下,由于堆栈中的SharedRW(L),我们可以使用原始指针执行写操作,并且结果堆栈为[Unique(0),Unique(1),SharedRW(L)。这将从堆栈中删除x的标记。但是由于x不再被使用,所以这并不违反我们到目前为止设置的规则,但是,在Rust中,传递给函数的引用必须比函数call-i长寿。其生存期至少要持续到通话期间。直观地,由于引用的生存期与该引用标签的项目在堆栈中的时间有关,因此上述反例中的问题是,当SharedRO(2)弹出时,x的生存期在example2_down仍在运行时结束堆栈。为了实现所需的优化,因此,我们希望通过将Rust的“生存”规则反映到堆积的借项中来将反例视为未定义的行为。为防止在example2_down仍运行时弹出SharedRO(2),我们引入了保护器的概念:可以通过函数调用来保护堆栈中的某个项目,这意味着在该函数调用仍在进行中时,如果该项目从堆栈中弹出,则表示违反了堆栈借用,因此行为不确定。

上述描述的是,在子函数调用消耗了外部函数的引用,导致外部函数后续的栈找不到引用。
正式地,我们使用可选的Call ID扩展Stacked Borrows中的item

 

 

 

c是

可选元素,每个call Id代表一个函数调用(我们能想想它关联栈帧),想象它与堆栈框架相关联)。

我们还假设语义跟踪与尚未返回的函数调用相对应的一组调用ID。

我们将Unique(t)用作Unique(t,⊥)的表示法,类似地,将其用于其他种类的项目。

那么,新入栈的item何时会得到保护?请记住,每次将引用作为参数传递,通过指针读取或从另一个函数返回时,我们都会对操作进行重新标记操作。我们的想法是,我们将第一种情况(即参数的重新标记)特殊化,即重新标记将保护使用当前函数调用的调用ID添加的所有新ierm。 这确保在所述函数调用期间不会弹出此类项目。语法上,我们将写retag [fn] x来指示x在函数的序言中被重新标记,因此它的item得到保护。
现在,我们将以下内容添加到Stacked Borrows的规则中:

规则(RETAG-FN),当将新项目作为retag[fn](在NEW-MUTABLE-REF或NEW-SHARED-REF-1中)的一部分推入位置的借用堆栈时,这些item的保护器设置为呼叫ID当前函数的调用。

规则(PROTECTOR)。任何时候弹出item(通过USE-2或READ-1),请检查其是否具有保护器
(c ,不等于⊥).。如果是这样,并且该调用ID对应于仍在进行的函数调用(即,相应的堆栈框架仍在调用堆栈中),则说保护器是“活动的”,弹出活动的保护器是未定义的行为。
example2_down的代码有些变化,以反映出我们需要x的新型重新标记:

如果我们现在再次考虑前面的反例,那么这次在执行闭包开始时的借入stack location是:

在这里,c是对example2_down的调用的调用ID。但这意味着当闭包执行并执行其原始指针写入时,它将击中PROTECTOR:原始指针写入必须使SharedRW(L)成为堆栈的顶部,这需要弹出SharedRO(3,c),但这就是不允许,因为保护器c仍处于活动状态。

 

优化证明草图

我们已经看到,保护者成功地排除了曾经是优化example2_down的反例。实际上,保护器使我们能够验证转换:
(1)假设在第2行的重新标记[fn]之后,x的值为Pointer(location,t)。我们知道,用于location的借用堆栈的第一项是SharedRO(t,c)。这里,c是example2_down的call-ID。我们记得存储在location的值是s
(2)在执行f的同时,我们知道对t的任何写访问都会将所有SharedRO(,)从堆栈中弹出。因为SharedRO(t.c)具有有效的保护者c,所以这将立即成为Stacked Borrows违规行为。因此,不会发生写访问。
(3)结果,当f返回时,我仍然存储s。这证明了转换是正确的。example3_down几乎可以使用相同的参数,从而可以在函数调用之间向下写入。同样,当f被调用时,x的标签位于借用堆栈的顶部,并且它受到保护。f尝试访问x(读或写)的任何尝试都会弹出该项目,由于保护者的原因,这是不允许的。

因此f不能观察x中存储的值(观察需要弹出其他item,把x的写入指针处于栈顶),因此我们可以自由地稍后进行写操作。这种转换的显着之处在于,编译器最终以存储在x中的另一个值调用f,但是我们可以证明这不会影响行为。即使f陷入无限循环并且我们从不执行第(3)步,也符合堆栈借用程序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值