Skip to content

Commit 06b9c59

Browse files
committed
Mark MTLResource and subprotocols as unsafe
These need to be synchronized.
1 parent 715ede2 commit 06b9c59

File tree

5 files changed

+108
-12
lines changed

5 files changed

+108
-12
lines changed

crates/header-translator/src/protocol.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,16 @@ impl ProtocolRef {
6969
ItemTree::objc("__macros__"),
7070
]
7171
}
72+
73+
pub(crate) fn is_subprotocol_of(&self, protocol_name: &str) -> bool {
74+
if self.id.name == protocol_name {
75+
return true;
76+
}
77+
for p in &self.super_protocols {
78+
if p.is_subprotocol_of(protocol_name) {
79+
return true;
80+
}
81+
}
82+
false
83+
}
7284
}

crates/header-translator/src/rust_type.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1141,14 +1141,33 @@ impl PointeeTy {
11411141
// }
11421142
// ```
11431143
[(protocol, _)]
1144-
if matches!(&*protocol.id.name, "MTLFunction" | "MTLFunctionHandle") =>
1144+
if protocol.is_subprotocol_of("MTLFunction")
1145+
|| protocol.is_subprotocol_of("MTLFunctionHandle") =>
11451146
{
11461147
TypeSafety::unknown_in_argument("must be safe to call").merge(
11471148
TypeSafety::unknown_in_argument(
11481149
"must have the correct argument and return types",
11491150
),
11501151
)
11511152
}
1153+
// Access to the contents of a resource has to be manually
1154+
// synchronized using things like `didModifyRange:` (CPU side)
1155+
// or `synchronizeResource:`, `useResource:usage:` and
1156+
// `MTLFence` (GPU side).
1157+
[(protocol, _)] if protocol.is_subprotocol_of("MTLResource") => {
1158+
let safety = TypeSafety::unknown_in_argument("may need to be synchronized");
1159+
// `MTLBuffer` is effectively a `Box<[u8]>` stored on the
1160+
// GPU (and depending on the storage mode, optionally also
1161+
// on the CPU). Type-safety of the contents is left
1162+
// completely up to the user.
1163+
if protocol.id.name == "MTLBuffer" {
1164+
safety.merge(TypeSafety::unknown_in_argument(
1165+
"contents should be of the correct type",
1166+
))
1167+
} else {
1168+
safety
1169+
}
1170+
}
11521171
// Other `ProtocolObject<dyn MyProtocol>`s are treated as
11531172
// proper types. (An example here is delegate protocols).
11541173
[_] => TypeSafety::SAFE,

framework-crates/objc2-metal/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,11 @@
5454
//! It is yet unclear whether Metal APIs are bounds-checked on the CPU side or
5555
//! not, so APIs that take offsets / lengths are often unsafe.
5656
//!
57-
//! ## Threading
57+
//! ## Synchronization
5858
//!
59-
//! TODO.
59+
//! `MTLResource` subclasses such as `MTLBuffer` require synchronization
60+
//! between the CPU and the GPU, or between different threads on the GPU
61+
//! itself, so APIs taking these are often unsafe.
6062
//!
6163
//! ## Resource allocation and memory management
6264
//!

framework-crates/objc2-metal/translation-config.toml

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,22 +99,85 @@ class.MTLLinkedFunctions.methods."setPrivateFunctions:".unsafe = false
9999
# nothing. Metal enables this by default, so we must assume that their
100100
# configuration of it is sound (?), and that we don't need to mark compilation
101101
# as unsafe.
102+
#
102103
# class.MTLCompileOptions.methods."setMathMode:".unsafe = false
103104
# class.MTLCompileOptions.methods."setFastMathEnabled:".unsafe = false
104105

106+
# SAFETY: Mark any accesses to the contents of `MTLResource`s as unsafe.
107+
#
108+
# Reading or writing to resources is unsynchronized and effectively equivalent
109+
# to (non-atomically?) sharing these between threads. The user needs to
110+
# explicitly synchronize accesses to these. (Explicit synchronization could
111+
# maybe in some cases be avoided with `MTLStorageModeShared` and
112+
# `MTLHazardTrackingModeTracked`, but that's dangerous to solely rely on).
113+
#
114+
# In `header-translator`, subprotocols of `MTLResource` is marked as unsafe in
115+
# argument position to ensure that any accesses to these on the GPU side are
116+
# properly synchronized.
117+
#
118+
# Here, we mark all inherent methods that read/write on these as `unsafe` to
119+
# also make things unsafe on the CPU side.
120+
protocol.MTLBuffer.methods.contents.unsafe = false # Raw pointer, unsafe in itself.
121+
protocol.MTLTensor.methods."getBytes:strides:fromSliceOrigin:sliceDimensions:".unsafe = true
122+
protocol.MTLTensor.methods."replaceSliceOrigin:sliceDimensions:withBytes:strides:".unsafe = true
123+
protocol.MTLTexture.methods."getBytes:bytesPerRow:bytesPerImage:fromRegion:mipmapLevel:slice:".unsafe = true
124+
protocol.MTLTexture.methods."getBytes:bytesPerRow:fromRegion:mipmapLevel:".unsafe = true
125+
protocol.MTLTexture.methods."replaceRegion:mipmapLevel:slice:withBytes:bytesPerRow:bytesPerImage:".unsafe = true
126+
protocol.MTLTexture.methods."replaceRegion:mipmapLevel:withBytes:bytesPerRow:".unsafe = true
127+
protocol.MTLAccelerationStructure.unsafe = true
128+
protocol.MTLAccelerationStructure.methods.gpuResourceID.unsafe = false
129+
protocol.MTLAccelerationStructure.methods.size.unsafe = false
130+
protocol.MTLIndirectCommandBuffer.unsafe = true
131+
protocol.MTLIndirectCommandBuffer.methods.gpuResourceID.unsafe = false
132+
protocol.MTLIndirectCommandBuffer.methods.size.unsafe = false
133+
protocol.MTLIntersectionFunctionTable.unsafe = true
134+
protocol.MTLIntersectionFunctionTable.methods.gpuResourceID.unsafe = false
135+
protocol.MTLVisibleFunctionTable.unsafe = true
136+
protocol.MTLVisibleFunctionTable.methods.gpuResourceID.unsafe = false
137+
138+
# TODO(breaking): Mark these as unsafe, they probably require synchronization.
139+
class.MTLRenderPassAttachmentDescriptor.methods."setTexture:".unsafe = false
140+
class.MTLRenderPassAttachmentDescriptor.methods."setResolveTexture:".unsafe = false
141+
class.MTLAccelerationStructureBoundingBoxGeometryDescriptor.methods."setBoundingBoxBuffer:".unsafe = false
142+
class.MTLRenderPassDescriptor.methods."setVisibilityResultBuffer:".unsafe = false
143+
class.MTLInstanceAccelerationStructureDescriptor.methods."setInstanceDescriptorBuffer:".unsafe = false
144+
class.MTLInstanceAccelerationStructureDescriptor.methods."setInstancedAccelerationStructures:".unsafe = false
145+
class.MTLAccelerationStructureGeometryDescriptor.methods."setPrimitiveDataBuffer:".unsafe = false
146+
class.MTLAccelerationStructureTriangleGeometryDescriptor.methods."setVertexBuffer:".unsafe = false
147+
protocol.MTLRenderCommandEncoder.methods."useResource:usage:".unsafe = false
148+
protocol.MTLRenderCommandEncoder.methods."useResource:usage:stages:".unsafe = false
149+
protocol.MTLComputeCommandEncoder.methods."useResource:usage:".unsafe = false
150+
protocol.MTLBlitCommandEncoder.methods."synchronizeResource:".unsafe = false
151+
protocol.MTLBlitCommandEncoder.methods."generateMipmapsForTexture:".unsafe = false
152+
protocol.MTLBlitCommandEncoder.methods."optimizeContentsForGPUAccess:".unsafe = false
153+
protocol.MTLAccelerationStructureCommandEncoder.methods."copyAndCompactAccelerationStructure:toAccelerationStructure:".unsafe = false
154+
155+
# SAFETY: Resource options are safe to specify:
156+
# - Hazard tracking and storage modes change the required synchronization, but
157+
# we handle that above. Also, we wouldn't really be able to prevent
158+
# untracked resources, these are the only option in Metal 4.
159+
# - The CPU cache mode is safe, it should only affect performance, not
160+
# correctness.
161+
#
162+
# class.*.methods."setResourceOptions:".unsafe = false
163+
164+
# TODO(breaking): Mark these as unsafe, setting `MTLPurgeableState::Volatile)`
165+
# is probably not safe, as you have to lock resources to prevent them from
166+
# being purged while in use.
167+
# TODO: How would you do such locking?
168+
protocol.MTLResource.methods."setPurgeableState:".unsafe = false
169+
protocol.MTLHeap.methods."setPurgeableState:".unsafe = false
170+
105171
# Using the resource's contents in a memory-safe manner is very difficult
106172
# after this is called.
107173
protocol.MTLResource.methods.makeAliasable.unsafe = true
108174

109-
# Using `MTLHazardTrackingModeUntracked` requires extra synchronization.
110-
# TODO(breaking): Mark all of these as unsafe.
111-
class.MTLTensorDescriptor.methods."setHazardTrackingMode:".unsafe = true
112-
class.MTLTextureDescriptor.methods."setHazardTrackingMode:".unsafe = false
113-
# TODO: MTLHeap should maybe be unsafe by default, since it has untracked
114-
# by default?
115-
class.MTLHeapDescriptor.methods."setHazardTrackingMode:".unsafe = false
175+
# SAFETY: Modifying residency is safe, it's effectively the same as
176+
# controlling what's in the L1/L2/L3 cache on the CPU.
177+
# protocol.MTLResidencySet.methods.requestResidency.unsafe = false
178+
# protocol.MTLResidencySet.methods.endResidency.unsafe = false
116179

117-
# TODO(breaking): Mark this as unsafe.
180+
# TODO(breaking): Mark this as unsafe?
118181
class.MTLHeapDescriptor.methods."setType:".unsafe = false
119182

120183
# These affect lifetime safety, and can cause use-after-free if used incorrectly.

0 commit comments

Comments
 (0)