Metal libraries
Metal libraries (.metallib files) contain AIR modules (LLVM bitcode) and metadata. Metal.jl generates them when compiling kernels and can read, inspect, edit and write libraries produced by either Metal.jl or Apple's toolchain.
julia> lib = read("shaders.metallib", Metal.MetalLib)MetalLib( file: METALLIB_EXECUTE v1.2.9 [macOS] platform: METALLIB_PLATFORM_MACOS v26.0.0 [64-bit] functions: foo [AIR v2.8.0, Metal v4.0.0] bar [AIR v2.8.0, Metal v4.0.0])julia> lib.functions[1].air_module # the function's LLVM bitcode3040-element Vector{UInt8}: ...julia> write("foo.metallib", Metal.MetalLib(lib; functions = lib.functions[1:1]))To load a library on a device, pass its bytes to MTL.MTLLibraryFromData, or the path of a file to MTL.MTLLibraryFromFile. Its functions can then be launched with metalcall:
lib = MTL.MTLLibraryFromFile(device(), "shaders.metallib")pipeline = MTL.MTLComputePipelineState(device(), MTL.MTLFunction(lib, "foo"))metalcall(pipeline, Tuple{Metal.MtlPtr{Float32}}, x; groups=4, threads=256)Metal.MetalLib — Type
MetalLib(; functions, kwargs...)
MetalLib(lib::MetalLib; kwargs...)
read(io_or_path, MetalLib)A Metal library (.metallib file): a container of MetalLibFunctions with their AIR modules and metadata. Libraries can be built from scratch, read from a file or stream, and written with write(io_or_path, lib). The second form copies lib, replacing the fields given as keyword arguments.
Reading and writing a library without changing it reproduces it byte for byte, including parts of the format Metal.jl does not decode: unknown tags are kept as raw bytes and written back in their original position, and the layout of the original file (tag order, group size conventions, shared modules and reflection entries) is remembered. Edits are supported in a bounded way: functions can be added, removed or replaced, and their metadata changed. The writer then recomputes offsets and sizes, the hashes of new modules, and the UUID of the library (unless you set a new one): a library that was read keeps its UUID only as long as it is written back unchanged. Tags Metal.jl does not decode are not updated by edits.
Malformed input throws a MetalLibFormatError.
Metal.MetalLibFunction — Type
MetalLibFunction(; name, air_module, air_version, metal_version, kwargs...)
MetalLibFunction(fun::MetalLibFunction; kwargs...)A function in a MetalLib: its name and type, its AIR module (LLVM bitcode) with the AIR and Metal language versions it targets, and its metadata. The second form copies fun, replacing the fields given as keyword arguments.
Fields:
name,type(aProgramType,METALLIB_COMPUTE_FUNCTIONby default);air_module,air_version,metal_version;constants: the function constants (FunctionConstants) the function declares;debug_info: aDebugInfowith the line and file of the function's definition;dependencies: paths of the files the function was linked from;source_id: the identifier of the embedded source archive the function comes from;reflection_data: an AIRR reflection flatbuffer, as emitted by Apple's toolchain.
Metal.FunctionConstant — Type
FunctionConstant(name, datatype, index, required)A function constant declared by a MetalLibFunction: its name, data type (an MTLDataType), index, and whether a value must be provided for it (constants an MSL function only uses after checking is_function_constant_defined are not required).
Metal.DebugInfo — Type
DebugInfo(line, path)Where a MetalLibFunction was defined: a line number and the path of the source file.
Metal.MetalLibFormatError — Type
MetalLibFormatErrorThrown when reading a Metal library that is malformed or that uses parts of the format Metal.jl does not support. offset is the position in the library where the problem was detected, if known.
Reflection data
Libraries built by Apple's toolchain describe each function with reflection data: its arguments, return values, attributes, function constants and the types involved. Metal.jl decodes it with the schema Apple's tools use. The Metal runtime does not need it, so Metal.jl does not generate it for the kernels it compiles, but MetalLibFunction(mod, name; air_module, reflect=true) derives it from a module's metadata like Apple's linker does, for MTLLibrary's reflectionForFunctionWithName: and Apple's tools.
julia> r = Metal.reflection(lib.functions[1])AIRR reflection v0.4.0 with 3 nodes #0 KernelFunction(name="foo", arguments=[#1, #2]) #1 BufferArg(function_constant=false, location_index=UIntValue(value=0), ..., type_name="float", name="a", unused=false) #2 ThreadPositionInGridArg(function_constant=false, type_name="uint", name="i", unused=false) kernel_functions: [#0]julia> r[r.kernel_functions[1]].name"foo"Metal.reflection_data encodes reflection objects again, e.g. after editing them or building them with the Metal.ReflectionObject constructor.
Metal.reflection — Function
Metal.reflection(mod::LLVM.Module, entry::String)Derive the reflection data of the function entry of an AIR module from the module's metadata, like Apple's linker does. The result can be encoded with Metal.reflection_data.
Only kernels and visible functions are supported, with buffer (incl. threadgroup memory) and built-in arguments. Other functions or arguments (e.g. textures) throw an error, as does malformed metadata.
An argument is unused if the metadata says so (as in modules Apple's linker linked), or if the module does not use it. Arguments that only dead code uses count as used.
The function constants are those of the library function (see MetalLibFunction). For a module that is shared by several functions, that may include constants the function doesn't use, which Apple's linker would have removed.
The module's LLVM context must be active.
Metal.reflection(fun::MetalLibFunction)
Metal.reflection(data::Vector{UInt8})Decode the reflection data of a library function, as stored by Apple's toolchain, or nothing if the function has none (Metal.jl does not generate it). The result is the root ReflectionObject of the data, a :Reflection: its nodes describe the function, its arguments and the types involved, and lists like kernel_functions refer to them by NodeId.
julia> r = Metal.reflection(lib.functions[1]);julia> kernel = r[only(r.kernel_functions)];julia> [r[arg].name for arg in kernel.arguments]2-element Vector{String}: "a" "i"Malformed data throws a FlatBufferError.
Metal.reflection_data — Function
Metal.reflection_data(root::ReflectionObject)Encode reflection data, the inverse of Metal.reflection: root is a :Reflection, e.g. as decoded, or built with the ReflectionObject constructor. Nodes that were decoded as an UnknownReflectionNode cannot be encoded.
Metal.ReflectionObject — Type
Metal.ReflectionObjectA table or struct of the reflection data of a library function (see Metal.reflection). nameof(obj) is its type in Apple's schema, without the namespace (e.g. :BufferArg or :KernelFunction), and its fields are available as properties, in the order of the schema.
Fields that the data does not contain are nothing, except for numbers and booleans, which then have their default value. Structs that only wrap a number (like UIntValue) are used by the schema for optional values, so a nothing there means "not specified" rather than 0.
References between nodes are NodeId objects; index the reflection root with them to get the node they refer to.
Metal.ReflectionEnum — Type
Metal.ReflectionEnumA value of an enumeration in reflection data: type is the enumeration, name the name of the value, or nothing if the schema doesn't know it, and value its number.
Metal.UnknownReflectionNode — Type
Metal.UnknownReflectionNodeA node of a kind the schema doesn't know: tag is its type, offset where its table starts in the reflection data.
Metal.FlatBufferError — Type
FlatBufferError(msg, offset)An error decoding a FlatBuffer: offset is the position in the buffer where it was found.