rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.67k stars 12.63k forks source link

Option::unwrap, panic!, print!, maybe others induce string relocations #48254

Open glandium opened 6 years ago

glandium commented 6 years ago

Consider the following code:

pub fn foo(a: Option<usize>) -> usize {
    a.unwrap()
}

This generates the following:

example::foo:
  cmp qword ptr [rdi], 1
  jne .LBB0_1
  mov rax, qword ptr [rdi + 8]
  ret
.LBB0_1:
  push rbp
  mov rbp, rsp
  lea rdi, [rip + .Lref.2]
  call core::panicking::panic@PLT
  ud2

str.0:
  .ascii "called `Option::unwrap()` on a `None` value"

str.1:
  .ascii "/checkout/src/libcore/option.rs"

.Lref.2:
  .quad str.0
  .quad 43
  .quad str.1
  .quad 31
  .long 335
  .long 21

The quads under .Lref.2 correspond to (pointer, length) pairs for both strings emitted by unwrap(). The way they are stored looks related to the call convention for core::panicking::panic, which looks like it just wants a pointer to a buffer containing 2 &str and 2 integers. The problem with this is that each of those pointers need relocations in the final position independent binary. And in ELF, those are large: each relocation is 2 (on 32-bits) or 3 (on 64-bits) words, so 24 bytes on 64-bits. Per string.

The same kind of thing happens with panic!:

pub fn foo() {
    panic!("foo")
}

I'll skip the assembly for the code itself, but the data looks like:

str.1:
  .ascii "/tmp/compiler-explorer-compiler118116-63-xzcrje.hzu9d/example.rs"

.Lref.2:
  .quad str.1
  .quad 64
  .long 2
  .long 5

str.4:
  .ascii "foo"

Do note that there is no reference to str.4, the corresponding (pointer, length) is actually created from code in that case:

  lea rcx, [rip + str.4]
  mov qword ptr [rax], rcx
  mov qword ptr [rax + 8], 3

And the same again for print!:

pub fn foo() {
    print!("foo")
}

generating this data:

str.0:
  .ascii "foo"

.Lref.1:
  .quad str.0
  .quad 3

A counter example is Option::expect:

pub fn foo(a: Option<usize>) -> usize {
    a.expect("bar")
}

which generates:

example::foo:
  cmp qword ptr [rdi], 1
  jne .LBB0_1
  mov rax, qword ptr [rdi + 8]
  ret
.LBB0_1:
  push rbp
  mov rbp, rsp
  lea rdi, [rip + str.0]
  mov esi, 3
  call core::option::expect_failed@PLT
  ud2

str.0:
  .ascii "bar"
nox commented 6 years ago

I have no clue how this can be improved, but surely one of our enlightened people have.

Cc @rust-lang/wg-codegen

workingjubilee commented 2 years ago

I am not sure what the proposed improvement is here, either. On recent versions, everything seems much different, see this Godbolt:

.L__unnamed_6:

.L__unnamed_1:
        .ascii  "called `Option::unwrap()` on a `None` value"

.L__unnamed_7:
        .ascii  "/app/example.rs"

.L__unnamed_2:
        .quad   .L__unnamed_7
        .asciz  "\017\000\000\000\000\000\000\000\002\000\000\000\005\000\000"

.L__unnamed_3:
        .ascii  "an expectation"

.L__unnamed_4:
        .quad   .L__unnamed_7
        .asciz  "\017\000\000\000\000\000\000\000\006\000\000\000\005\000\000"

.L__unnamed_8:
        .ascii  "a print"

.L__unnamed_5:
        .quad   .L__unnamed_8
        .asciz  "\007\000\000\000\000\000\000"

Nonzero number of quads, but less. Everything in the actual code seems to be using rip-relative addressing with lea, and it loads both things at a time before proceeding.

However, the dizzying array of stuff with the full-blown panic! sections remains quite significant in this one, but that may be because of trying to minimize the impact of the panic! call itself:

example::panics:
        push    rax
        call    std::panicking::begin_panic
        ud2

.L__unnamed_3:
        .quad   core::ptr::drop_in_place<&str>
        .asciz  "\020\000\000\000\000\000\000\000\b\000\000\000\000\000\000"
        .quad   <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::take_box
        .quad   <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::get

.L__unnamed_4:
        .quad   core::ptr::drop_in_place<&str>
        .asciz  "\020\000\000\000\000\000\000\000\b\000\000\000\000\000\000"
        .quad   <T as core::any::Any>::type_id

.L__unnamed_1:
        .ascii  "a panic"

.L__unnamed_5:
        .ascii  "/app/example.rs"

.L__unnamed_2:
        .quad   .L__unnamed_5
        .asciz  "\017\000\000\000\000\000\000\000\002\000\000\000\005\000\000"

I hesitate to pronounce things as better or worse now, or to assert the string relocations are necessarily bad or good.