在Rust中,Option是否编译为运行时检查或指令跳转? [英] In Rust, is Option compiled to a runtime check or an instruction jump?

查看:95
本文介绍了在Rust中,Option是否编译为运行时检查或指令跳转?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Rust中,选项定义为:

  pub枚举选项< T> {
无,
Some(T),
}

像这样使用:

  fn may_return_none()->选项< i32> {
if is_full_moon {
None
}否则{
Some(1)
}
}

fn main() {
let可选= may_return_none();
match可选{{
None => println!( None),
Some(v)=> println!( Some),
}
}

我不熟悉Rust的内部原理,但是起初我认为它的工作方式类似于.NET中的 Nullable ,所以我上面的Rust代码的编译逻辑如下:

  //占用`sizeof(T)+ 1`的存储空间,可能更多,具体取决于`Bool`对齐方式,因此`Nullable< Int32>`占用5个字节。 
struct Nullable< T> {
Bool hasValue;
T值;
}

Nullable< Int32> MayReturnNone(){
if(isFullMoon)
//作为结构,Nullable< Int32>通过堆栈返回实例
return Nullable< Int32>(){HasValue = false}
否则
return Nullable< Int32>(){HasValue = true,Value = 1}
}

void Test(){
Nullable< Int32>可选= may_return_none();
if(!optional.HasValue)println( None);
else println( Some);
}

但是,由于所需的空间,这并不是零成本的抽象 Bool hasValue 标志-Rust提出了提供零成本抽象的观点。



我意识到 Option 可以由编译器通过直接返回跳转来实现,尽管它需要将确切的跳转值作为参数提供给堆栈-就像您可以推送多个返回地址:



(伪代码)

  mayReturnNone(returnToIfNone,returnToIfHasValue ){

if(isFullMoon){
cleanup-current-stackframe
跳转到returnToIfNone
else {
cleanup-current-stackframe
推入堆栈1
跳转到returnToIfHasValue
}

test(){

mayReturnNone(指令地址(ifHasValue),指令地址(ifNoValue))
ifHasValue:
println( Some)
ifNoVal ue:
println( None)
}

是这样吗实施?这种方法也适用于Rust中的其他 enum 类型-但是,我演示的这个特定应用程序非常脆弱,如果要在调用<之间执行代码,则会中断操作例如,code> mayReturnNone 和 match 语句(如 mayReturnNone 直接跳转到匹配,跳过中间说明)。

解决方案

它完全取决于优化。考虑一下此实现(操场):

 #![feature(asm)] 

外部板条箱兰特;

使用rand :: Rng;

#[inline(never)]
fn is_full_moon()-> bool {
rand :: thread_rng()。gen()
}

fn may_return_none()->选项< i32> {
if is_full_moon(){无}其他{某些(1)}
}

#[inline(never)]
fn use(){
let可选= may_return_none();
匹配可选{{
None =>不安全{asm!( nop)},
Some(v)=>不安全{asm!( nop; nop)},
}
}

fn main(){
usage();
}

在这里,我使用内联汇编而不是打印,因为它没有打印尽可能使结果输出混乱。以下是在发布模式中编译时用法的程序集:

  .section .text._ZN10playground5usage17hc2760d0a512fe6f1E, ax,@ progbits 
.p2align 4,0x90
.type _ZN10playground5usage17hc2760d0a512fe6f1E,@ function
usbcage1startc1c1c0c1h b $ b pushq%rax
.Ltmp6:
.cfi_def_cfa_offset 16
callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
testb%al,%al
je .LBB1_2
#APP b $ b nop
#NO_APP
popq%rax
retq
.LBB1_2:
#APP
nop
nop
# NO_APP
popq%rax
retq
.Lfunc_end1:
.size _ZN10playground5usage17hc2760d0a512fe6f1E,.Lfunc_end1-_ZN10playground5usage17hc2760d0a512fe6f1E
> c / pre>

快速精简如下:


  1. 它调用 is_full _moon 函数( callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E )。

  2. 测试随机值的结果( testb%al,%al

  3. 一个分支转到 nop 转到 nop; nop

其他所有内容均已优化。函数 may_return_none 基本上是不存在的。没有创建选项,从未实现 1 的值。



我确定不同的人有不同的看法,但是我认为我不能对此进行任何优化。






同样,如果我们使用 Some 中的值(为了方便起见,我将其更改为42):

  Some(v)=>不安全{asm!( nop; nop:: r(v))},

然后在使用该值的分支中内联该值:

  .section .text._ZN10playground5usage17hc2760d0a512fe6f1E, ax,@ progbits 
.p2align 4,0x90
.type _ZN10playground5usage17hc2760d0a512fe6f1E,@ function
_ZN10playground5usage17hc2760d0a512fe6f1E:
.cfi_startproc $ b $ mp_L_t_bfa 16
callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
testb%al,%al
je .LBB1_2
#APP
nop
#NO_APP
popq%rax
retq
.LBB1_2:
movl $ 42,%eax ;;这是
#APP
nop
nop
#NO_APP
popq%rax
retq
.Lfunc_end1:
.size _ZN10playground5usage17hc2760d0a512fe6f1E,.Lfunc_end1-_ZN10playground5usage17hc2760d0a512fe6f1E
.cfi_endproc




$ b $ p>但是,没有任何事情可以围绕契约义务进行优化;如果函数必须返回 Option ,则它必须返回 Option

 #[inline(never)] 
pub fn may_return_none()->选项< i32> {
if is_full_moon(){None} else {Some(42)}
}

这将进行一些Deep Magic组装:

  .section .text._ZN10playground15may_return_none17ha1178226d153ece2E, ax,@ progbits 
.p2align 4,0x90
.type _ZN10playground15may_return_none17ha1178226d153ece2E,@ function
_ZN10playground15may_return_none17ha1178226d153ece2E:
.cfi_startproc
pushq%rat
_b_f_b b $ b callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
movabsq $ 180388626432,%rdx
leaq 1(%rdx),%rcx
testb%al,%al
cmovneq%rdx,%rcx $ b b movq%rcx,%rax
popq%rcx
retq
.Lfunc_end1:
.size _ZN10playground15may_return_none17ha1178226d153ece2E,.Lfunc_end1-_ZN10playground15may_return_none17b1e2e code>

希望我做对了...


  1. 将64位值0x2A00000000加载到%rdx。 0x2A是42。这是我们正在构建的 Option ;是变体。

  2. 将%rdx +1加载到%rcx中。这是 Some 的变体。

  3. 我们根据结果测试随机值

  4. 测试中,是否将无效值移至%rcx或不移至

  5. 将%rcx移至%rax-返回寄存器

这里的要点是,不管优化如何,一个要以特定格式返回数据的函数都必须这样做。仅当与其他代码内联时,才可以删除该抽象。


In Rust, Option is defined as:

pub enum Option<T> {
    None,
    Some(T),
}

Used like so:

fn may_return_none() -> Option<i32> {
    if is_full_moon {
        None
    } else {
        Some(1)
    }
}

fn main() {
    let optional = may_return_none();
    match optional {
        None => println!("None"),
        Some(v) => println!("Some"),
    }
}

I'm not familiar with Rust internals, but initially I assumed it might work similar to Nullable in .NET, so the compiled logic of my above Rust code would be like so:

// occupies `sizeof(T) + 1` memory space, possibly more depending on `Bool`'s alignment, so `Nullable<Int32>` consumes 5 bytes.
struct Nullable<T> {
    Bool hasValue;
    T value;
}

Nullable<Int32> MayReturnNone() {
    if( isFullMoon )
        // as a `struct`, the Nullable<Int32> instance is returned via the stack
        return Nullable<Int32>() { HasValue = false }
    else
        return Nullable<Int32>() { HasValue = true, Value = 1 }
}

void Test() {
    Nullable<Int32> optional = may_return_none();
    if( !optional.HasValue ) println("None");
    else                     println("Some");
}

However this isn't a zero-cost abstraction because of the space required for the Bool hasValue flag - and Rust makes a point of providing zero-cost abstractions.

I realise that Option could be implemented via a direct return-jump by the compiler, though it would need the exact jump-to values to be provided as arguments on the stack - as though you can push multiple return addresses:

(Psuedocode)

mayReturnNone(returnToIfNone, returnToIfHasValue) {

    if( isFullMoon ) {
        cleanup-current-stackframe
        jump-to returnToIfNone
    else {
        cleanup-current-stackframe
        push-stack 1
        jump-to returnToIfHasValue
    }

test() {

    mayReturnNone( instructionAddressOf( ifHasValue ), instructionAddressOf( ifNoValue ) )
ifHasValue:
    println("Some")
ifNoValue:
    println("None")
}

Is this how it's implemented? This approach also works for other enum types in Rust - but this specific application I've demonstrated is very brittle and breaks if you want to execute code in-between the call to mayReturnNone and the match statement, for example (as mayReturnNone will jump directly to the match, skipping intermediate instructions).

解决方案

It depends entirely on optimization. Consider this implementation (playground):

#![feature(asm)]

extern crate rand;

use rand::Rng;

#[inline(never)]
fn is_full_moon() -> bool {
    rand::thread_rng().gen()
}

fn may_return_none() -> Option<i32> {
    if is_full_moon() { None } else { Some(1) }
}

#[inline(never)]
fn usage() {
    let optional = may_return_none();
    match optional {
        None => unsafe { asm!("nop") },
        Some(v) => unsafe { asm!("nop; nop") },
    }
}

fn main() {
    usage();
}

Here, I've used inline assembly instead of printing because it doesn't clutter up the resulting output as much. Here's the assembly for usage when compiled in release mode:

    .section    .text._ZN10playground5usage17hc2760d0a512fe6f1E,"ax",@progbits
    .p2align    4, 0x90
    .type   _ZN10playground5usage17hc2760d0a512fe6f1E,@function
_ZN10playground5usage17hc2760d0a512fe6f1E:
    .cfi_startproc
    pushq   %rax
.Ltmp6:
    .cfi_def_cfa_offset 16
    callq   _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
    testb   %al, %al
    je  .LBB1_2
    #APP
    nop
    #NO_APP
    popq    %rax
    retq
.LBB1_2:
    #APP
    nop
    nop
    #NO_APP
    popq    %rax
    retq
.Lfunc_end1:
    .size   _ZN10playground5usage17hc2760d0a512fe6f1E, .Lfunc_end1-_ZN10playground5usage17hc2760d0a512fe6f1E
    .cfi_endproc

The quick rundown is:

  1. It calls the is_full_moon function (callq _ZN10playground12is_full_moon17h78e56c4ffd6b7730E).
  2. The result of the random value is tested (testb %al, %al)
  3. One branch goes to the nop, the other goes to the nop; nop

Everything else has been optimized out. The function may_return_none basically never exists; no Option was ever created, the value of 1 was never materialized.

I'm sure that various people have different opinions, but I don't think I could have written this any more optimized.


Likewise, if we use the value in the Some (which I changed to 42 to find easier):

Some(v) => unsafe { asm!("nop; nop" : : "r"(v)) },

Then the value is inlined in the branch that uses it:

    .section    .text._ZN10playground5usage17hc2760d0a512fe6f1E,"ax",@progbits
    .p2align    4, 0x90
    .type   _ZN10playground5usage17hc2760d0a512fe6f1E,@function
_ZN10playground5usage17hc2760d0a512fe6f1E:
    .cfi_startproc
    pushq   %rax
.Ltmp6:
    .cfi_def_cfa_offset 16
    callq   _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
    testb   %al, %al
    je  .LBB1_2
    #APP
    nop
    #NO_APP
    popq    %rax
    retq
.LBB1_2:
    movl    $42, %eax  ;; Here it is
    #APP
    nop
    nop
    #NO_APP
    popq    %rax
    retq
.Lfunc_end1:
    .size   _ZN10playground5usage17hc2760d0a512fe6f1E, .Lfunc_end1-_ZN10playground5usage17hc2760d0a512fe6f1E
    .cfi_endproc


However, nothing can "optimize" around a contractural obligation; if a function has to return an Option, it has to return an Option:

#[inline(never)]
pub fn may_return_none() -> Option<i32> {
    if is_full_moon() { None } else { Some(42) }
}

This makes some Deep Magic assembly:

    .section    .text._ZN10playground15may_return_none17ha1178226d153ece2E,"ax",@progbits
    .p2align    4, 0x90
    .type   _ZN10playground15may_return_none17ha1178226d153ece2E,@function
_ZN10playground15may_return_none17ha1178226d153ece2E:
    .cfi_startproc
    pushq   %rax
.Ltmp6:
    .cfi_def_cfa_offset 16
    callq   _ZN10playground12is_full_moon17h78e56c4ffd6b7730E
    movabsq $180388626432, %rdx
    leaq    1(%rdx), %rcx
    testb   %al, %al
    cmovneq %rdx, %rcx
    movq    %rcx, %rax
    popq    %rcx
    retq
.Lfunc_end1:
    .size   _ZN10playground15may_return_none17ha1178226d153ece2E, .Lfunc_end1-_ZN10playground15may_return_none17ha1178226d153ece2E
    .cfi_endproc

Let's hope I get this right...

  1. Load the 64-bit value 0x2A00000000 to %rdx. 0x2A is 42. This is our Option being built; it's the None variant.
  2. Load %rdx + 1 into %rcx. This is the Some variant.
  3. We test the random value
  4. Depending on the result of the test, move the invalid value to %rcx or not
  5. Move %rcx to %rax - the return register

The main point here is that regardless of optimization, a function that says it's going to return data in a specific format has to do so. Only when it's inlined with other code is it valid to remove that abstraction.

这篇关于在Rust中,Option是否编译为运行时检查或指令跳转?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆