-
Notifications
You must be signed in to change notification settings - Fork 79
Description
When mmtk-core is compiled with the most recent 1.65.0 toolchain, it generates a ud2 (undefined instruction) instruction where it usually executes normally. It only happens when doing release build (cargo build --release).
A disassembly snippet obtained from objdump -d libmmtk_ruby.so (It can be obtained from libmmtk_openjdk.so as well):
000000000002e140 <_ZN4mmtk6policy16largeobjectspace26LargeObjectSpace$LT$VM$GT$3new17h321b65ec1e29a73aE>:
2e140: 55 push %rbp
2e141: 41 57 push %r15
...
2e264: 0f 28 44 24 30 movaps 0x30(%rsp),%xmm0
2e269: 0f 11 84 24 90 00 00 movups %xmm0,0x90(%rsp)
2e270: 00
2e271: 48 8d bc 24 b0 00 00 lea 0xb0(%rsp),%rdi
2e278: 00
2e279: 48 8d 74 24 50 lea 0x50(%rsp),%rsi
2e27e: 4c 89 e2 mov %r12,%rdx
2e281: 4c 89 f9 mov %r15,%rcx
2e284: 4d 89 f0 mov %r14,%r8
2e287: e8 e4 2c 08 00 call b0f70 <_ZN4mmtk6policy5space21CommonSpace$LT$VM$GT$3new17hd1ba7f2c88be724fE>
2e28c: 0f 0b ud2 ; !!!!!!!! HERE !!!!!!!!
2e28e: 48 89 c3 mov %rax,%rbx
2e291: 48 8b bc 24 b0 00 00 mov 0xb0(%rsp),%rdi
2e298: 00
2e299: 48 8b b4 24 b8 00 00 mov 0xb8(%rsp),%rsi
2e2a0: 00
2e2a1: e8 0a 7c 0c 00 call f5eb0 <_ZN4core3ptr100drop_in_place$LT$alloc..vec..ExtendElement$LT$alloc..vec..Vec$LT$regex_syntax..ast..Span$GT$$GT$$GT$17hc319d5ea0dc
262a2E>The ud2 instruction appears in the LargeObjectSpace<VM>::new function. It is usually placed after call sites that never returns normally (such as unwinding or panic).
I added some eprintln! to see where exactly it went wrong, due to inlining. As a result, I isolated the problem to a MaybeUninit::uninit()::assume_init() call.
impl<VM: VMBinding> FreeListPageResource<VM> {
pub fn new_contiguous(start: Address, bytes: usize, vm_map: &'static VMMap) -> Self {
eprintln!("new_contiguous {}", 1);
let pages = conversions::bytes_to_pages(bytes);
eprintln!("new_contiguous {}", 2);
// We use MaybeUninit::uninit().assume_init(), which is nul, for a Box value, which cannot be null.
// FIXME: We should try either remove this kind of circular dependency or use MaybeUninit<T> instead of Box<T>
#[allow(invalid_value)]
#[allow(clippy::uninit_assumed_init)]
let common_flpr = unsafe {
eprintln!("new_contiguous {} {}", 2, 1);
let free_list = MaybeUninit::uninit().assume_init(); // !!!!!!!!!!!! HERE!!!!!!!!!!!!!!!
eprintln!("new_contiguous {} {} {}", 2, 1, 1);
let mut common_flpr = Box::new(CommonFreeListPageResource {
free_list,
start,
});
eprintln!("new_contiguous {} {}", 2, 2);
::std::ptr::write(
&mut common_flpr.free_list,
vm_map.create_parent_freelist(&common_flpr, pages, PAGES_IN_REGION as _),
);
eprintln!("new_contiguous {} {}", 2, 3);
common_flpr
};According to the documentation of MaybeUninit::assume_init,
It is up to the caller to guarantee that the
MaybeUninit<T>really is in an initialized state. Calling this when the content is not yet fully initialized causes immediate undefined behavior.
In this context, the type of the variable free_list is Box<RawMemoryFreeList>. Because Box contains a pointer/reference internally, calling assume_init on an MaybeUninit::uninit() value immediately results in undefined behaviour. And the 1.65.0 compiler chose to generate a ud2 instruction which crashes.