Open Coekjan opened 8 months ago
This is an allowed optimization for Global
: the compiler can see that the allocation is unused and optimizes it away. This is documented here: https://doc.rust-lang.org/nightly/alloc/alloc/trait.GlobalAlloc.html#safety
However if you call your allocator directly without going through #[global_allocator]
then this optimization doesn't apply.
The docs here state: https://doc.rust-lang.org/beta/std/alloc/trait.GlobalAlloc.html#safety
You must not rely on allocations actually happening, even if there are explicit heap allocations in the source. The optimizer may detect unused allocations that it can either eliminate entirely or move to the stack and thus never invoke the allocator. The optimizer may further assume that allocation is infallible, so code that used to fail due to allocator failures may now suddenly work because the optimizer worked around the need for an allocation.
I think removing unnecessary heap allocations is an important enough optimization to put these kind of restrictions on GlobalAlloc
If you want to test your allocator, passing the object through core::hint::black_box
before forgetting should prevent the optimizer from removing the allocation
This is an allowed optimization for
Global
: the compiler can see that the allocation is unused and optimizes it away. This is documented here: https://doc.rust-lang.org/nightly/alloc/alloc/trait.GlobalAlloc.html#safetyHowever if you call your allocator directly without going through
#[global_allocator]
then this optimization doesn't apply.
Thanks for your explanation!
But when I define the #[global_allocator]
in an other crate (as one of the dependencies), this optimization still happens.
The docs here state: https://doc.rust-lang.org/beta/std/alloc/trait.GlobalAlloc.html#safety
You must not rely on allocations actually happening, even if there are explicit heap allocations in the source. The optimizer may detect unused allocations that it can either eliminate entirely or move to the stack and thus never invoke the allocator. The optimizer may further assume that allocation is infallible, so code that used to fail due to allocator failures may now suddenly work because the optimizer worked around the need for an allocation.
I think removing unnecessary heap allocations is an important enough optimization to put these kind of restrictions on
GlobalAlloc
If you want to test your allocator, passing the object throughcore::hint::black_box
before forgetting should prevent the optimizer from removing the allocation
Thanks for your advice! I will try the black_box
later.
core::hint::black_box
helps to prevent the optimization.
However, I am still confused by the optimizer behavior. When I mark my allocator with #[global_allocator]
inside my binary crate, the optimization doesn't apply (my test function reaches its endpoint). Whereas, when I move the allocator outside of my crate as a library, this optimization applies (my test function is optimized into a dead-loop).
Recently, I tried to write a unit-test about memory allocation & deallocation for my embedded system, with usage of
Global.allocate_zeroed
(in factGlobal.allocate
also reproduces this bug) and#[global_allocator]
. When I wanted to test the behavior of memory-exhaustion, I found that rustc seems to "optimized" my loop into dead-loop. Details are as follows.I tried this code (as a sample that can reproduce the bug) in current nightly:
I expected to see this happen:
main
function reaches its end (not dead loop)Instead, this happened:
main
function never returnsI used
RUSTFLAGS='--emit=llvm-ir'
to check its LLVM-IR, which seemed to show that the allocating-loop was optimized wrongly into a dead-loop.