use metal::foreign_types::ForeignType;
use objc::{sel, sel_impl};
use super::MlxDevice;
use crate::ops::elementwise::{elementwise_add, elementwise_mul};
use crate::{DType, KernelRegistry, MlxBuffer};
const N: usize = 256;
const BYTES: u64 = (N * 4) as u64;
const HEAP_BYTES: u64 = 64 * 1024;
const MAX_HEAP: u64 = 1024 * 1024;
fn unavailable(reason: &'static str) {
eprintln!("Q5_HEAP_GPU_CAPABILITY {{\"status\":\"unavailable_or_unknown\",\"reason\":\"{reason}\",\"runtime_admission\":false}}");
}
fn options() -> metal::MTLResourceOptions {
metal::MTLResourceOptions::StorageModeShared
| metal::MTLResourceOptions::HazardTrackingModeTracked
}
fn create_heap(device: &metal::DeviceRef) -> Option<metal::Heap> {
let descriptor = metal::HeapDescriptor::new();
let supported: bool =
unsafe { objc::msg_send![device, respondsToSelector: sel!(newHeapWithDescriptor:)] };
let tracked: bool =
unsafe { objc::msg_send![&*descriptor, respondsToSelector: sel!(setHazardTrackingMode:)] };
let typed: bool = unsafe { objc::msg_send![&*descriptor, respondsToSelector: sel!(setType:)] };
if !supported || !tracked || !typed {
unavailable("required Shared Automatic Tracked API unavailable");
return None;
}
let layout = device.heap_buffer_size_and_align(BYTES, options());
let packing = layout
.size
.checked_add(layout.align.saturating_sub(1))
.map(|value| value / layout.align.max(1) * layout.align)
.and_then(|value| value.checked_mul(4));
if layout.size == 0
|| layout.align == 0
|| !layout.align.is_power_of_two()
|| !packing.is_some_and(|required| required <= HEAP_BYTES)
{
unavailable("four-child heap layout cannot fit fixed 64 KiB request");
return None;
}
eprintln!("Q5_HEAP_GPU_LAYOUT {{\"requested_heap_bytes\":{HEAP_BYTES},\"child_bytes\":{BYTES},\"child_required_size\":{},\"alignment\":{},\"four_child_packing\":{},\"runtime_admission\":false}}",
layout.size, layout.align, packing.unwrap());
descriptor.set_size(HEAP_BYTES);
descriptor.set_storage_mode(metal::MTLStorageMode::Shared);
descriptor.set_heap_type(metal::MTLHeapType::Automatic);
descriptor.set_hazard_tracking_mode(metal::MTLHazardTrackingMode::Tracked);
let raw: *mut metal::MTLHeap =
unsafe { objc::msg_send![device, newHeapWithDescriptor: &*descriptor] };
if raw.is_null() {
unavailable("Shared Automatic Tracked heap creation returned nil");
return None;
}
let heap = unsafe { metal::Heap::from_ptr(raw) };
if heap.size() == 0
|| heap.size() > MAX_HEAP
|| heap.storage_mode() != metal::MTLStorageMode::Shared
|| heap.heap_type() != metal::MTLHeapType::Automatic
|| heap.hazard_tracking_mode() != metal::MTLHazardTrackingMode::Tracked
{
unavailable("created heap outside size/mode envelope");
return None;
}
eprintln!("Q5_HEAP_GPU_HEAP {{\"size\":{},\"current_allocated_size\":{},\"hazard_mode\":2,\"runtime_admission\":false}}", heap.size(), heap.current_allocated_size());
Some(heap)
}
fn child(device: &MlxDevice, heap: Option<&metal::HeapRef>, index: usize) -> Option<MlxBuffer> {
let buffer = match heap {
Some(heap) => heap.new_buffer(BYTES, options()),
None => {
let raw: *mut metal::MTLBuffer = unsafe {
objc::msg_send![device.metal_device(), newBufferWithLength: BYTES options: options()]
};
if raw.is_null() {
None
} else {
Some(unsafe { metal::Buffer::from_ptr(raw) })
}
}
};
let Some(buffer) = buffer else {
unavailable("bounded child allocation returned nil");
return None;
};
if buffer.length() != BYTES
|| buffer.contents().is_null()
|| buffer.storage_mode() != metal::MTLStorageMode::Shared
|| buffer.hazard_tracking_mode() != metal::MTLHazardTrackingMode::Tracked
{
unavailable("child lacks required length/CPU Shared/Tracked contract");
return None;
}
let parent: *mut metal::MTLHeap = unsafe { objc::msg_send![&*buffer, heap] };
let expected = heap.map_or(std::ptr::null_mut(), |heap| {
heap as *const metal::HeapRef as *mut metal::MTLHeap
});
assert_eq!(parent, expected, "child parent identity mismatch");
assert!(!buffer.is_aliasable());
eprintln!("Q5_HEAP_GPU_RESOURCE {{\"arm\":\"{}\",\"index\":{index},\"length\":{},\"allocated_size\":{},\"hazard_mode\":2,\"heap_matches\":true,\"heap_offset\":{},\"heap_offset_meaning\":\"automatic_nonplacement_zero_sentinel\",\"runtime_admission\":false}}",
if heap.is_some() { "heap" } else { "standalone" }, buffer.length(), buffer.allocated_size(), buffer.heap_offset());
Some(MlxBuffer::from_raw(buffer, DType::F32, vec![N]))
}
fn initial(index: usize, buffer: usize) -> f32 {
match buffer {
0 => ((index % 17) as i32 - 8) as f32,
1 => 2.0,
2 => -99.0,
3 => -77.0,
_ => unreachable!(),
}
}
fn arm(device: &MlxDevice, heap: Option<&metal::HeapRef>) -> Option<[Vec<u32>; 4]> {
let mut buffers = Vec::with_capacity(4);
for index in 0..4 {
buffers.push(child(device, heap, index)?);
}
for left in 0..4 {
for right in left + 1..4 {
assert!(
buffers[left].metal_buffer().as_ptr() != buffers[right].metal_buffer().as_ptr(),
"simultaneously live children must be distinct resource objects"
);
}
}
for (index, buffer) in buffers.iter_mut().enumerate() {
for (element, value) in buffer.as_mut_slice::<f32>().unwrap().iter_mut().enumerate() {
*value = initial(element, index);
}
}
let [a, b, t, o] = buffers.as_slice() else {
unreachable!()
};
let mut registry = KernelRegistry::new();
let mut first = device.command_encoder().expect("first native encoder");
let mut second = device
.command_encoder()
.expect("second native encoder, same queue");
elementwise_add(
&mut first,
&mut registry,
device.metal_device(),
a,
b,
t,
N,
DType::F32,
)
.expect("encode native add before first commit");
elementwise_mul(
&mut second,
&mut registry,
device.metal_device(),
t,
b,
o,
N,
DType::F32,
)
.expect("encode native mul before either command is submitted");
first.commit();
second.commit();
let second_result = second.wait_until_completed();
let first_result = first.wait_until_completed();
eprintln!("Q5_HEAP_GPU_COMMANDS {{\"arm\":\"{}\",\"same_queue\":true,\"queue_identity\":\"single_test_queue\",\"cb1_completed_ok\":{},\"cb2_completed_ok\":{},\"wait_between_commits\":false,\"runtime_admission\":false}}",
if heap.is_some() { "heap" } else { "standalone" }, first_result.is_ok(), second_result.is_ok());
second_result.expect("consumer command completed successfully");
first_result.expect("producer command completed successfully");
let actual: [Vec<u32>; 4] = std::array::from_fn(|buffer| {
buffers[buffer]
.as_slice::<f32>()
.unwrap()
.iter()
.map(|value| value.to_bits())
.collect()
});
for element in 0..N {
let input = initial(element, 0);
let expected = [input, 2.0, input + 2.0, 2.0 * (input + 2.0)];
for buffer in 0..4 {
assert_eq!(
actual[buffer][element],
expected[buffer].to_bits(),
"oracle mismatch arm={} buffer={buffer} element={element}",
if heap.is_some() { "heap" } else { "standalone" }
);
}
}
drop(second);
drop(first);
eprintln!("Q5_HEAP_GPU_ORACLE {{\"arm\":\"{}\",\"elements_per_buffer\":{N},\"buffers_checked\":4,\"exact_bits_equal\":true,\"runtime_admission\":false}}", if heap.is_some() { "heap" } else { "standalone" });
Some(actual)
}
#[test]
#[ignore = "tiny GPU Tracked Shared heap parity; root must externally supervise process timeout"]
fn tracked_shared_heap_two_command_buffers_match_standalone_and_cpu() {
for name in [
"MLX_UNRETAINED_REFS",
"MLX_PROFILE_DISPATCH",
"MLX_PROFILE_CB",
"MLX_ENCODE_TRACE",
"MLX_PROFILE_BARRIERS",
"HF2Q_AUTO_BARRIER",
"HF2Q_FORCE_SERIAL_DISPATCH",
"HF2Q_PIPELINE_TG_MULT_HINT",
] {
assert!(
std::env::var_os(name).is_none(),
"control requires {name} absent"
);
}
objc::rc::autoreleasepool(|| {
let Some(metal_device) = metal::Device::system_default() else {
unavailable("Metal device unavailable");
return;
};
if BYTES > metal_device.max_buffer_length() {
unavailable("tiny buffer exceeds device limit");
return;
}
let queue = metal_device.new_command_queue();
let device = MlxDevice {
device: metal_device,
queue,
residency_set: None,
};
let Some(heap) = create_heap(device.metal_device()) else {
return;
};
eprintln!("Q5_HEAP_GPU_IDENTITY {{\"device_registry_id\":{},\"arm_order\":\"standalone_then_heap\",\"queue_identity\":\"single_test_queue\",\"unretained_refs\":false,\"indirect_bindings\":false,\"runtime_admission\":false}}", device.registry_id());
let standalone = objc::rc::autoreleasepool(|| arm(&device, None));
let Some(standalone) = standalone else {
return;
};
let mapped = objc::rc::autoreleasepool(|| arm(&device, Some(&heap)));
let Some(mapped) = mapped else {
return;
};
assert_eq!(standalone, mapped, "heap versus standalone exact parity");
eprintln!("Q5_HEAP_GPU_CAPABILITY {{\"status\":\"bounded_gpu_probe_completed\",\"exact_cpu_and_standalone_parity\":true,\"heap_size\":{},\"heap_used_size\":{},\"heap_current_allocated_size\":{},\"runtime_admission\":false,\"production_pool_qualified\":false}}", heap.size(), heap.used_size(), heap.current_allocated_size());
});
}