1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
use luaur_common::macros::luau_assert::LUAU_ASSERT;
use crate::functions::paged_allocate::page_size;
#[cfg(not(target_os = "windows"))]
use core::ffi::{c_int, c_void};
#[cfg(not(any(target_os = "windows", target_os = "freebsd")))]
extern "C" {
fn munmap(addr: *mut c_void, len: usize) -> c_int;
}
/// Port of `Luau::pagedDeallocate`.
///
/// Frees a block previously returned by `paged_allocate`. `size` is always the
/// block size the matching allocation was made with (`kBlockSizeBytes`), which
/// lets the default heap path reconstruct the same `Layout`.
pub fn paged_deallocate(ptr: *mut core::ffi::c_void, size: usize, freeze: bool) {
// `freeze` is the strategy the matching `paged_allocate` used (captured once by
// the owning TypedAllocator). It must match the allocation — re-reading the
// global DebugLuauFreezeArena flag here was the bug: it is a toggleable
// ScopedFastFlag, so a block allocated under one value could be freed under the
// other, mismatching VirtualFree/operator-delete and corrupting the heap (the
// Windows 0xC0000005 / VirtualFree==0 failures).
if !freeze {
// `::operator delete(ptr)`. Reconstruct the exact `Layout` used by
// `paged_allocate`'s default branch.
if ptr.is_null() || size == 0 {
return;
}
if let Ok(layout) = core::alloc::Layout::from_size_align(size, page_size()) {
unsafe { alloc::alloc::dealloc(ptr as *mut u8, layout) };
}
return;
}
#[cfg(target_os = "windows")]
{
// The matching `paged_allocate` freeze-path used `VirtualAlloc`, so this
// block is OS virtual memory and MUST be released with `VirtualFree(...,
// MEM_RELEASE)`. The previous `_aligned_free` (a CRT-heap function) was
// called on `VirtualAlloc`'d memory — a catastrophic allocator mismatch
// that corrupted the heap and crashed ~every type-checking test on Windows
// with 0xC0000005 (the test fixtures set DebugLuauFreezeArena=true, so all
// their type arenas take this path). Linux/macOS were correct (mmap/munmap),
// which is why the bug was Windows-only and invisible to valgrind on Linux.
// MEM_RELEASE requires the size argument to be 0.
use windows_sys::Win32::System::Memory::{VirtualFree, MEM_RELEASE};
let _ = size;
let rc = unsafe { VirtualFree(ptr, 0, MEM_RELEASE) };
LUAU_ASSERT!(rc != 0);
}
#[cfg(target_os = "freebsd")]
{
extern "C" {
fn free(ptr: *mut c_void);
}
unsafe { free(ptr) };
}
#[cfg(not(any(target_os = "windows", target_os = "freebsd")))]
{
let rc = unsafe { munmap(ptr as *mut c_void, size) };
LUAU_ASSERT!(rc == 0);
}
}