我正在为MacOS编写一些使用metal
机箱的GPU代码.为此,我通过调用以下命令来分配Buffer
对象:
let buffer = device.new_buffer(num_bytes, MTLResourceOptions::StorageModeShared)
这是苹果的金属API的FFIS,它分配一个CPU和GPU都可以访问的内存区域,并且Rust包装器返回一个Buffer
对象.然后,我可以通过执行以下操作来获得指向该内存区域的指针:
let data = buffer.contents() as *mut u32
在口语意义上,这一记忆区域是未初始化的.然而,在铁 rust 的意义上,这块内存区域是"未初始化的"吗?
Is this sound?
let num_bytes = num_u32 * std::mem::size_of::<u32>();
let buffer = device.new_buffer(num_bytes, MTLResourceOptions::StorageModeShared);
let data = buffer.contents() as *mut u32;
let as_slice = unsafe { slice::from_raw_parts_mut(data, num_u32) };
for i in as_slice {
*i = 42u32;
}
在这里,我将u32写入FFI返回给我的一个内存区.从nomicon人开始:
...通常,当我们使用=为Rust类型判断器认为已经初始化的值(如x[i])赋值时,存储在左侧的旧值将被丢弃.这将是一场灾难.然而,在本例中,左侧的类型是MaybeUninit<;Box>;,而丢弃它不会做任何事情!有关此丢弃问题的更多讨论,请参见下面的内容.
这from_raw_parts
条规则中没有一条被违反,并且u32没有Drop方法.
- 尽管如此,这是声音吗?
- 在向其写入之前,从该区域(以
u32
为单位)读取数据是否合理(除了无稽之谈)?存储器区域是有效的,并且为所有位模式定义了u32.
Best practices
现在考虑一个确实有Drop方法的类型T
(并且您已经做了所有bindgen
和#[repr(C)]
的胡说八道,以便它可以跨越FFI边界).
在这种情况下,我们应该:
- 通过用指针扫描区域并调用
.write()
来初始化Rust中的缓冲区? - 多
let as_slice = unsafe { slice::from_raw_parts_mut(data as *mut MaybeUninit<T>, num_t) };
for i in as_slice {
*i = unsafe { MaybeUninit::new(T::new()).assume_init() };
}
此外,在初始化区域之后,Rust编译器如何记住该区域在程序后面对.contents()
的后续调用中被初始化?
Thought experiment
在某些情况下,缓冲区是GPU内核的输出,我想要读取结果.所有的写入都发生在Rust控制之外的代码中,当我调用.contents()
时,内存区域的指针包含正确的uint32_t
个值.这个思维实验应该传达我对这一点的担忧.
假设我调用C的malloc
,它返回未初始化数据的已分配缓冲区.是否从该缓冲区读取u32值(指针正确对齐并在范围内),因为任何类型都应该直接进入未定义的行为.
然而,假设我调用calloc
,它在返回缓冲区之前将其置零.如果您不喜欢calloc
,那么假设我有一个FFI函数,它调用Malloc,用C显式地写出0,uint32_t
类型,然后将这个缓冲区返回给Rust.该缓冲器is用有效的u32
位模式初始化.
- 从Rust的Angular 来看,
malloc
返回"未初始化"数据,而calloc
返回初始化数据吗? - 如果情况不同,Rust编译器如何知道两者在可靠性方面的差异?