Skip to content

Commit 7b6e651

Browse files
committed
Implement @mtlprintf using os_log
1 parent 6c82916 commit 7b6e651

File tree

11 files changed

+441
-14
lines changed

11 files changed

+441
-14
lines changed

lib/mtl/MTL.jl

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ include("events.jl")
3434
include("fences.jl")
3535
include("heap.jl")
3636
include("buffer.jl")
37+
include("log_state.jl")
3738
include("command_queue.jl")
3839
include("command_buf.jl")
3940
include("compute_pipeline.jl")

lib/mtl/command_buf.jl

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export MTLCommandBufferDescriptor
2828
@objcproperties MTLCommandBufferDescriptor begin
2929
@autoproperty retainedReferences::Bool setter=setRetainedReferences
3030
@autoproperty errorOptions::MTLCommandBufferErrorOption setter=setErrorOptions
31+
@autoproperty logState::id{MTLLogState} setter=setLogState
3132
end
3233

3334
function MTLCommandBufferDescriptor()

lib/mtl/command_queue.jl

+25
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
export MTLCommandQueueDescriptor
2+
3+
@objcwrapper immutable=false MTLCommandQueueDescriptor <: NSObject
4+
5+
@objcproperties MTLCommandQueueDescriptor begin
6+
@autoproperty maxCommandBufferCount::NSUInteger
7+
@autoproperty logState::id{MTLLogState} setter=setLogState
8+
end
9+
10+
function MTLCommandQueueDescriptor()
11+
handle = @objc [MTLCommandQueueDescriptor alloc]::id{MTLCommandQueueDescriptor}
12+
obj = MTLCommandQueueDescriptor(handle)
13+
finalizer(release, obj)
14+
@objc [obj::id{MTLCommandQueueDescriptor} init]::id{MTLCommandQueueDescriptor}
15+
return obj
16+
end
17+
18+
119
export MTLCommandQueue
220

321
@objcwrapper immutable=false MTLCommandQueue <: NSObject
@@ -13,3 +31,10 @@ function MTLCommandQueue(dev::MTLDevice)
1331
finalizer(release, obj)
1432
return obj
1533
end
34+
35+
function MTLCommandQueue(dev::MTLDevice, descriptor::MTLCommandQueueDescriptor)
36+
handle = @objc [dev::id{MTLDevice} newCommandQueueWithDescriptor:descriptor::id{MTLCommandQueueDescriptor}]::id{MTLCommandQueue}
37+
obj = MTLCommandQueue(handle)
38+
finalizer(release, obj)
39+
return obj
40+
end

lib/mtl/log_state.jl

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
export MTLLogLevel
2+
3+
@cenum MTLLogLevel::NSInteger begin
4+
MTLLogLevelUndefined = 0
5+
MTLLogLevelDebug = 1
6+
MTLLogLevelInfo = 2
7+
MTLLogLevelNotice = 3
8+
MTLLogLevelError = 4
9+
MTLLogLevelFault = 5
10+
end
11+
12+
export MTLLogStateDescriptor
13+
14+
@objcwrapper immutable=false MTLLogStateDescriptor <: NSObject
15+
16+
@objcproperties MTLLogStateDescriptor begin
17+
@autoproperty level::MTLLogLevel setter=setLevel
18+
@autoproperty bufferSize::NSInteger setter=setBufferSize
19+
end
20+
21+
function MTLLogStateDescriptor()
22+
handle = @objc [MTLLogStateDescriptor alloc]::id{MTLLogStateDescriptor}
23+
obj = MTLLogStateDescriptor(handle)
24+
finalizer(release, obj)
25+
@objc [obj::id{MTLLogStateDescriptor} init]::id{MTLLogStateDescriptor}
26+
return obj
27+
end
28+
29+
30+
export MTLLogState
31+
32+
@objcwrapper MTLLogState <: NSObject
33+
34+
function MTLLogState(dev::MTLDevice, descriptor::MTLLogStateDescriptor)
35+
err = Ref{id{NSError}}(nil)
36+
handle = @objc [dev::id{MTLDevice} newLogStateWithDescriptor:descriptor::id{MTLLogStateDescriptor}
37+
error:err::Ptr{id{NSError}}]::id{MTLLogState}
38+
err[] == nil || throw(NSError(err[]))
39+
MTLLogState(handle)
40+
end

src/Metal.jl

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ include("device/intrinsics/memory.jl")
3636
include("device/intrinsics/simd.jl")
3737
include("device/intrinsics/version.jl")
3838
include("device/intrinsics/atomics.jl")
39+
include("device/intrinsics/output.jl")
3940
include("device/quirks.jl")
4041

4142
# array essentials

src/compiler/compilation.jl

+4-4
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ function compile(@nospecialize(job::CompilerJob))
9797

9898
@signpost_interval log=log_compiler() "Generate LLVM IR" begin
9999
# TODO: on 1.9, this actually creates a context. cache those.
100-
ir, entry = JuliaContext() do ctx
100+
ir, entry, loggingEnabled = JuliaContext() do ctx
101101
mod, meta = GPUCompiler.compile(:llvm, job)
102-
string(mod), LLVM.name(meta.entry)
102+
string(mod), LLVM.name(meta.entry), haskey(functions(mod), "air.os_log")
103103
end
104104
end
105105

@@ -160,7 +160,7 @@ function compile(@nospecialize(job::CompilerJob))
160160
end
161161
end
162162

163-
return (; image, entry)
163+
return (; image, entry, loggingEnabled)
164164
end
165165

166166
# link into an executable kernel
@@ -192,5 +192,5 @@ end
192192

193193
# most of the time, we don't need the function object,
194194
# so don't keep it alive unconditionally in GPUCompiler's caches
195-
pipeline_state, return_function ? fun : nothing
195+
pipeline_state, return_function ? fun : nothing, compiled.loggingEnabled
196196
end

src/compiler/execution.jl

+31-3
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ mtlconvert(arg, cce=nothing) = adapt(Adaptor(cce), arg)
161161
struct HostKernel{F,TT}
162162
f::F
163163
pipeline::MTLComputePipelineState
164+
loggingEnabled::Bool
164165
end
165166

166167
const mtlfunction_lock = ReentrantLock()
@@ -182,15 +183,15 @@ function mtlfunction(f::F, tt::TT=Tuple{}; name=nothing, kwargs...) where {F,TT}
182183
cache = compiler_cache(dev)
183184
source = methodinstance(F, tt)
184185
config = compiler_config(dev; name, kwargs...)::MetalCompilerConfig
185-
pipeline, _ = GPUCompiler.cached_compilation(cache, source, config, compile, link)
186+
pipeline, _, loggingEnabled = GPUCompiler.cached_compilation(cache, source, config, compile, link)
186187

187188
# create a callable object that captures the function instance. we don't need to think
188189
# about world age here, as GPUCompiler already does and will return a different object
189190
h = hash(pipeline, hash(f, hash(tt)))
190191
kernel = get(_kernel_instances, h, nothing)
191192
if kernel === nothing
192193
# create the kernel state object
193-
kernel = HostKernel{F,tt}(f, pipeline)
194+
kernel = HostKernel{F,tt}(f, pipeline, loggingEnabled)
194195
_kernel_instances[h] = kernel
195196
end
196197
return kernel::HostKernel{F,tt}
@@ -271,7 +272,34 @@ end
271272
(threads.width * threads.height * threads.depth) > kernel.pipeline.maxTotalThreadsPerThreadgroup &&
272273
throw(ArgumentError("Number of threads in group ($(threads.width * threads.height * threads.depth)) should not exceed $(kernel.pipeline.maxTotalThreadsPerThreadgroup)"))
273274

274-
cmdbuf = MTLCommandBuffer(queue)
275+
cmdbuf = if kernel.loggingEnabled
276+
if macos_version() < v"15"
277+
@error "Logging is only supported on macOS 15 or higher"
278+
end
279+
280+
if MTLCaptureManager().isCapturing
281+
@error "Logging is not supported while GPU frame capturing"
282+
end
283+
284+
log_state_descriptor = MTLLogStateDescriptor()
285+
log_state_descriptor.level = MTL.MTLLogLevelDebug
286+
log_state = MTLLogState(device(), log_state_descriptor)
287+
288+
function log_handler(subSystem, category, logLevel, message)
289+
print(String(NSString(message)))
290+
return nothing
291+
end
292+
293+
block = @objcblock(log_handler, Nothing, (id{NSString}, id{NSString}, NSInteger, id{NSString}))
294+
@objc [log_state::id{MTLLogState} addLogHandler:block::id{NSBlock}]::Nothing
295+
296+
cmdbuf_descriptor = MTLCommandBufferDescriptor()
297+
cmdbuf_descriptor.logState = log_state
298+
MTLCommandBuffer(queue, cmdbuf_descriptor)
299+
else
300+
MTLCommandBuffer(queue)
301+
end
302+
275303
cmdbuf.label = "MTLCommandBuffer($(nameof(kernel.f)))"
276304
cce = MTLComputeCommandEncoder(cmdbuf)
277305
argument_buffers = try

0 commit comments

Comments
 (0)