Skip to content

Commit 8226c63

Browse files
committed
fix: use constants, CI placeholder, detailed comment and better
`mmap_fixed`
1 parent 7895c88 commit 8226c63

File tree

4 files changed

+93
-47
lines changed

4 files changed

+93
-47
lines changed

.github/scripts/ci-setup-x86_64-pc-windows-msvc.sh

Whitespace-only changes.

.github/workflows/minimal-tests-core.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ jobs:
7474

7575
# Setup Environments
7676
- name: Setup Environments
77-
if: runner.os != 'Windows'
7877
run: ./.github/scripts/ci-setup-${{ matrix.target.triple }}.sh
7978

8079
# Build
@@ -131,7 +130,6 @@ jobs:
131130

132131
# Setup Environments
133132
- name: Setup Environments
134-
if: runner.os != 'Windows'
135133
run: ./.github/scripts/ci-setup-${{ matrix.target.triple }}.sh
136134

137135
# Style checks

src/util/malloc/malloc_ms_util.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use crate::util::Address;
44
use crate::vm::VMBinding;
55

66
/// Allocate with alignment. This also guarantees the memory is zero initialized.
7+
/// This uses posix_memalign, which is not available on Windows.
8+
/// This would somehow affect `MallocMarkSweep` performance on Windows.
79
#[cfg(all(
810
not(target_os = "windows"),
911
not(any(feature = "malloc_jemalloc", feature = "malloc_mimalloc"))
@@ -86,6 +88,10 @@ pub fn alloc<VM: VMBinding>(size: usize, align: usize, offset: usize) -> (Addres
8688
not(target_os = "windows"),
8789
not(any(feature = "malloc_jemalloc", feature = "malloc_mimalloc"))
8890
))]
91+
// On non-Windows platforms with posix_memalign, we can use align_alloc for alignments > 16
92+
// However, on Windows, there is no equivalent function.
93+
// The memory alloc by `align_alloc` may not be freed correctly by `free`.
94+
// So we use offset allocation for all alignments > 16 on Windows.
8995
(a, 0) if a > 16 => {
9096
address = align_alloc(size, align);
9197
debug_assert!(

src/util/memory.rs

Lines changed: 87 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ pub(crate) fn result_is_mapped(result: Result<()>) -> bool {
184184
Ok(_) => false,
185185
Err(err) => {
186186
// ERROR_INVALID_ADDRESS may be returned if the address is already mapped or invalid
187-
err.raw_os_error().unwrap() == 487 // ERROR_INVALID_ADDRESS
187+
err.raw_os_error().unwrap()
188+
== windows_sys::Win32::Foundation::ERROR_INVALID_ADDRESS as i32
188189
}
189190
}
190191
}
@@ -339,60 +340,101 @@ fn mmap_fixed(
339340

340341
#[cfg(target_os = "windows")]
341342
{
342-
use windows_sys::Win32::System::Memory::*;
343+
use std::io;
344+
use windows_sys::Win32::System::Memory::{
345+
VirtualAlloc, VirtualQuery, MEMORY_BASIC_INFORMATION, MEM_COMMIT, MEM_FREE, MEM_RESERVE,
346+
};
343347

344-
let ptr = start.to_mut_ptr();
348+
let ptr = start.to_mut_ptr() as *mut u8;
345349
let prot = strategy.prot.into_native_flags();
350+
351+
// Has to COMMIT inmediately if:
352+
// - not MAP_NORESERVE
353+
// - and protection is not NoAccess
346354
let commit =
347355
(flags & MAP_NORESERVE) == 0 && !matches!(strategy.prot, MmapProtection::NoAccess);
348356

349-
// Query the state of the memory region
350-
let mut mbi: MEMORY_BASIC_INFORMATION = unsafe { std::mem::zeroed() };
351-
let query_res = unsafe {
352-
VirtualQuery(
353-
ptr,
354-
&mut mbi,
355-
std::mem::size_of::<MEMORY_BASIC_INFORMATION>(),
356-
)
357-
};
358-
if query_res == 0 {
359-
return Err(std::io::Error::last_os_error());
360-
}
357+
// Scan the region [ptr, ptr + size) to understand its current state
358+
unsafe {
359+
let mut addr = ptr;
360+
let end = ptr.add(size);
361+
362+
let mut saw_free = false;
363+
let mut saw_reserved = false;
364+
let mut saw_committed = false;
365+
366+
while addr < end {
367+
let mut mbi: MEMORY_BASIC_INFORMATION = std::mem::zeroed();
368+
let q = VirtualQuery(
369+
addr as *const _,
370+
&mut mbi,
371+
std::mem::size_of::<MEMORY_BASIC_INFORMATION>(),
372+
);
373+
if q == 0 {
374+
return Err(io::Error::last_os_error().into());
375+
}
361376

362-
let res = unsafe {
363-
match mbi.State {
364-
MEM_FREE => {
365-
let mut allocation_type = MEM_RESERVE;
366-
if commit {
367-
allocation_type |= MEM_COMMIT;
377+
let region_base = mbi.BaseAddress as *mut u8;
378+
let region_size = mbi.RegionSize;
379+
let region_end = region_base.add(region_size);
380+
381+
// Calculate the intersection of [addr, end) and [region_base, region_end)
382+
let _sub_begin = if addr > region_base {
383+
addr
384+
} else {
385+
region_base
386+
};
387+
let _sub_end = if end < region_end { end } else { region_end };
388+
389+
match mbi.State {
390+
MEM_FREE => saw_free = true,
391+
MEM_RESERVE => saw_reserved = true,
392+
MEM_COMMIT => saw_committed = true,
393+
_ => {
394+
return Err(
395+
io::Error::other("Unexpected memory state in mmap_fixed").into()
396+
);
368397
}
369-
VirtualAlloc(ptr, size, allocation_type, prot)
370398
}
371-
MEM_RESERVE => {
372-
if commit {
373-
VirtualAlloc(ptr, size, MEM_COMMIT, prot)
374-
} else {
375-
// Already reserved, nothing to do
376-
ptr
377-
}
399+
400+
// Jump to the next region (VirtualQuery always returns "continuous regions with the same attributes")
401+
addr = region_end;
402+
}
403+
404+
// 1. All FREE: make a new mapping in the region
405+
// 2. All RESERVE/COMMIT: treat as an existing mapping, can just COMMIT or succeed directly
406+
// 3. MIX of FREE + others: not allowed (semantically similar to MAP_FIXED_NOREPLACE)
407+
if saw_free && (saw_reserved || saw_committed) {
408+
return Err(io::Error::other(
409+
"mmap_fixed: region partially free and partially in use on Windows",
410+
));
411+
}
412+
413+
if saw_free && !saw_reserved && !saw_committed {
414+
// All FREE: make a new mapping in the region
415+
let mut allocation_type = MEM_RESERVE;
416+
if commit {
417+
allocation_type |= MEM_COMMIT;
378418
}
379-
MEM_COMMIT => {
380-
// Already committed, just return success.
381-
// We could change protection here if needed, but VirtualAlloc returns success
382-
// if the memory is already committed.
383-
ptr
419+
420+
let res = VirtualAlloc(ptr as *mut _, size, allocation_type, prot);
421+
if res.is_null() {
422+
return Err(io::Error::last_os_error());
384423
}
385-
_ => {
386-
// Should not happen
387-
return Err(std::io::Error::other("Unexpected memory state"));
424+
425+
Ok(())
426+
} else {
427+
// This behavior is similar to mmap with MAP_FIXED on Linux.
428+
// If the region is already mapped, we just ensure the required commitment.
429+
// If commit is not needed, we just return Ok.
430+
if commit {
431+
let res = VirtualAlloc(ptr as *mut _, size, MEM_COMMIT, prot);
432+
if res.is_null() {
433+
return Err(io::Error::last_os_error());
434+
}
388435
}
436+
Ok(())
389437
}
390-
};
391-
392-
if res.is_null() {
393-
Err(std::io::Error::last_os_error())
394-
} else {
395-
Ok(())
396438
}
397439
}
398440
}
@@ -459,7 +501,7 @@ pub fn handle_mmap_error<VM: VMBinding>(
459501
unreachable!()
460502
}
461503
#[cfg(target_os = "windows")]
462-
if os_errno == 8 {
504+
if os_errno == windows_sys::Win32::Foundation::ERROR_NOT_ENOUGH_MEMORY as i32 {
463505
// ERROR_NOT_ENOUGH_MEMORY
464506
trace!("Signal MmapOutOfMemory!");
465507
VM::VMCollection::out_of_memory(tls, AllocationError::MmapOutOfMemory);
@@ -474,7 +516,7 @@ pub fn handle_mmap_error<VM: VMBinding>(
474516
#[cfg(target_os = "windows")]
475517
if let Some(os_errno) = error.raw_os_error() {
476518
// If it is invalid address, we provide a more specific panic message.
477-
if os_errno == 487 {
519+
if os_errno == windows_sys::Win32::Foundation::ERROR_INVALID_ADDRESS as i32 {
478520
// ERROR_INVALID_ADDRESS
479521
trace!("Signal MmapOutOfMemory!");
480522
VM::VMCollection::out_of_memory(tls, AllocationError::MmapOutOfMemory);

0 commit comments

Comments
 (0)