RUST Pointer Dereference LLVM-IR Instruction

Hello!

I have a question about dereferencing raw pointers in Rust, as it relates to LLVM-IR instructions. As I understand it, when a pointer is dereferenced, this is represented as a load instruction in the LLVM-IR language. However, I have noticed that in some cases, I do not see a load instruction, but instead see a store instruction.

For example, the following code creates a raw pointer and then dereferences it, with the corresponding LLVM-IR code appearing as follows:

[Source Code]

    let value = 42;
    let ptr: *const i32 = &value as *const i32;
    unsafe {
        println!("{:?}" , *ptr);
    }

[LLVM-IR]

start:
  %ptr.dbg.spill = alloca i32*, align 8
  %b.dbg.spill = alloca i32, align 4
  %a.dbg.spill = alloca i32, align 4
  %_14 = alloca [1 x { i8*, i64* }], align 8
  %_7 = alloca %"core::fmt::Arguments", align 8
  %value = alloca i32, align 4
  store i32 %a, i32* %a.dbg.spill, align 4
  call void @llvm.dbg.declare(metadata i32* %a.dbg.spill, metadata !388, metadata !DIExpression()), !dbg !395
  store i32 %b, i32* %b.dbg.spill, align 4
  call void @llvm.dbg.declare(metadata i32* %b.dbg.spill, metadata !389, metadata !DIExpression()), !dbg !396
  call void @llvm.dbg.declare(metadata i32* %value, metadata !390, metadata !DIExpression()), !dbg !397
  store i32 42, i32* %value, align 4, !dbg !398
  store i32* %value, i32** %ptr.dbg.spill, align 8, !dbg !399
  call void @llvm.dbg.declare(metadata i32** %ptr.dbg.spill, metadata !392, metadata !DIExpression()), !dbg !400
; call core::fmt::ArgumentV1::new_debug
  %1 = call { i8*, i64* } @_ZN4core3fmt10ArgumentV19new_debug17he33cadefc4e0dc85E(i32* align 4 %value), !dbg !401
  %_15.0 = extractvalue { i8*, i64* } %1, 0, !dbg !401
  %_15.1 = extractvalue { i8*, i64* } %1, 1, !dbg !401
  br label %bb1, !dbg !401

(I expected to see a load instruction, but instead, a store instruction was present.)

Is there a rule or method for handling such exceptions in Rust? Could you explain why this might be the case?

Have a nice day!! :slight_smile:

1 Like

There's no "exception". First of all, because there's no such rule in the first place, but the stores you are observing aren't related to reading from the pointer at all.

The first store just assigns the value 42 to the value, i.e., it's the initialization itself. The second store then merely creates the address of value and stores it in a separate memory location, which seems to have something to do with debug info.

When printing the value using the std::fmt-related formatting mechanism (which println!() delegates to), everything to be formatted is passed by reference to the actual formatting routines; the println!(), format_args!(), write!(), etc. macros implicitly take a reference to all of their arguments.

So if you call println!("{:?}", *ptr), only ptr itself is actually passed to the underlying actual formatting functions. Therefore, no load will be present in the generated code; the formatting mechanism itself will call Debug::fmt() on the passed pointers, and the individual Debug::fmt() impls may or may not read from &self by loading. So if anywhere, you'll be able to find such an instruction in <i32 as Debug>::fmt(), but not in this code itself. (Unless the compiler decides to inline the whole thing, but this apparently hadn't happened in this case.)

2 Likes

Thank you for your comment!!

Then, I have some question about LLVM instruction associated with pointer dereference.

As far as I know, the 'load' instruction is related to pointer dereferencing. There are also other LLVM instructions related to pointer dereferencing, such as 'getelementptr', 'bitcast', and 'extractvalue/insertvalue'.

I'm curious if there are any LLVM instructions among these that are not related to pointer dereferencing. Additionally, I wonder if there are any other LLVM instructions related to pointer dereferencing that are not included in the list above.

Thank you!!

getelementptr doesn't dereference, it performs pointer arithmetic (i.e., offsetting).

The other 3 instructions you listed have absolutely nothing to do with pointers:

  • bitcast is a simple transmute (forces a type change without changing the value)
  • extractvalue and insertvalue set elements of aggregates (structs and arrays), they do not operate on pointers.

But all of this is in the documentation, so you could just have read that.

Thank you for your comment!

Have a nice Day :slight_smile:

That's a very incomplete understanding of what dereferencing does. Notably, dereferencing a pointer creates a place, and what happens from there depends on how you use that place.

*p = x; is a deref that generates a store.
x = *p; is a deref that generates a load.
let _ = *p; is a deref that only mentions the place, but doesn't load it or store it.

What's your goal here? What's the end question you're trying to answer? The only complete answer to "how does rust get translated to LLVM-IR?" is "read the source code for cg_ssa and cg_llvm".

3 Likes

And, to add another case: let q = &*ptr; is a deref that's immediately followed by an address-of, hence, it also doesn't generate any loads – it "copies" the pointer.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.