The lifetime of the borrow &mut t lasts until the borrow's last use, which is inside some_ffi_call. The fact you convert it to an integer first is irrelevant; there is still a way to assign lifetimes to borrows in a way that satisfies the stacked borrows model. So this is fine.
If you switched read from t and some_ffi_call(ptr) that would definitely be UB.
Sorry, either I failed to explain why I think it's UB or I failed to understand your reasoning. My fear is the follows:
let mut t = T::new(); // t.idx = 0
let ptr = &mut t as *mut T as u64;
some_ffi_call(ptr); // sets t.idx = 1`
read from t // what if there is some cached value of t.idx == 0? (because Rust doesn't see any 'modification')
To state this question yet another way: ptr doesn't mutably borrow t. So how does rust know that t may be modified by the ffi call and make sure to read t only after the ffi call instead of, say, reordering the read before the ffi call during optimization?
I think if you pass something which behaves like a mutable pointer to a function, the optimiser will assume it could have been mutated and won't be able to rely on cached values.
Not quite. Integers do not have provenance, so LLVM doesn't know that the integer was derived from the pointer. However the moment you turned the pointer into an integer, LLVM has to assume that the pointer escaped and that any function call can create a pointer out of thin air until t ceases to exist (eg the function defining t returns.) or the original pointer is invalidated in another way.