How to generate MIR into a basic block to read-increment-write to a constant address?

I am trying to interpose the drop shim code so that my runtime can know if a thread is currently executing inside a drop handler. My plan is to let each drop handler perform a read-increment-write to a fixed memory address before executing the drop handler body, and similarly a read-decrement-write after the body finishes execution. A thread is inside a drop handler if the counter is positive.

I believe the build_drop_shim function in the MIR stage is where I should make the modification. I am experimenting with adding additional basic blocks and generating MIR instructions. The following is my attempt to generate the read-increment-write operation. However, I encountered errors when trying to make a fixed address (0x2000_0000) a Place that I can load from and store to. My target is a 32-bit ARM microcontroller.

let addr = 0x2000_0000u32;
let addr_constant = ConstOperand {
    span,
    user_ty: None,
    const_: Const::Val(ConstValue::Scalar(Scalar::from_u32(0x2000_0000u32)), tcx.types.u32),
};
let addr_operand = Operand::Constant(Box::new(addr_constant));
let addr_place = Place::from(addr_operand); // <- Error

// Make a local variable.
let local_place = Place::from(Local::from_u32(1));

// Read from 0x2000_0000.
let load_rvalue = Rvalue::Use(Operand::Copy(addr_place));
body[prologue_block].statements.push(Statement {
    source_info,
    kind: StatementKind::Assign(Box::new((local_place, load_rvalue))),
});

// Increment the local variable.
let increment = Rvalue::BinaryOp(
    BinOp::Add,
    Box::new((
        Operand::Copy(local_place),
        Operand::Constant(Box::new(ConstOperand {
            span,
            user_ty: None,
            const_: Const::Val(ConstValue::Scalar(Scalar::from_u32(1)), tcx.types.u32),
        })),
    )),
);
body[prologue_block].statements.push(Statement {
    source_info,
    kind: StatementKind::Assign(Box::new((local_place, increment))),
});

// Write back to 0x2000_0000.
let store = Rvalue::Use(Operand::Copy(local_place));
body[prologue_block].statements.push(Statement {
    source_info,
    kind: StatementKind::Assign(Box::new((addr_place, store))),
});

I hope someone could enlighten me about how to represent the fixed address 0x2000_0000 as a Place.

The best way to structure this is probably to have a #[lang] item static which you can utilize and use a linker script to ensure the static's address is what you intend. Getting the place should then just be a place mention of the lang item static.

Otherwise, you probably want to look at PlaceBuilder.

Also - I don't know MIR but you probably want this to be handled by volatile memory operations.

if it's static but not thread-local, you'd need atomic operations, not just volatile. too bad rust doesn't yet have atomic volatile operations...

This sounds like a single-threaded case to me

Thank you for your suggestion. I am working with the compiler version 1.75.0, and it looks like #[lang = "static"] is not available.

Nevertheless, now it seems like declaring an external global variable __VAR and perform the read-increment-write from and to __VAR would be the easiest.

However, I am still struggling to create a Place that represents the extern global variable. I hope you could shed some lights. Many thanks!

Thanks and I should definitely use volatile operations. But just to get a working (non-crashing) version I will start with normal read/write first. Seems like I need to call intrinsic functions to perform volatile read/write which will complicate the code.

Theoretically the counter is thread-local. When the runtime system performs context switch, it will also switch the counter value. There is also only one core on the microcontroller. For my use case it is fine to not use atomic load/store.

To be clear, I'm suggesting adding a new language item[1]. You're writing a bespoke MIR transform, so you're modifying the compiler toolchain already, and adding the language item is the cleanest way to get a known-to-the-compiler location.

If that's impossible for some reason, a "pretend" lang item which is used by unmangled name instead of the #[lang] glue is also doable, but at that point isn't all that different from a hardcoded magic address.

Thinking about it once again, though, it actually might be simpler to emit calls to two functions e.g. rust_enter_drop_glue and rust_exit_drop_glue instead of codegenning the prolog/epilog directly. Then any tracking is just (almost[1:1]) straightforward Rust code, and you can set up a count in the same way manner that the panic count is tracked.


  1. It would need to be careful to be free of any nontrivial destructors to avoid endless recursion. ↩︎ ↩︎

3 Likes

Emitting two functions calls looks like a simpler way to go. But I have a follow up question: How can I let the compiler assume that there are two externally defined function available? To make a function call, the compiler requires a DefId which I don't know where to get.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.