GizStack
View .mdOpen in ChatGPTOpen in Claude

gizhw:metal

an Apple GPU through Metal, driven with Objective-C's runtime and gizlang:native (nothing links at build time). gizhw:gpu opens it as kind "metal" and runs the MSL that Kernel.msl prints.

Memory is unified: one MTLBuffer in shared storage is its contents on the CPU and its gpuAddress on the GPU, so a copy to or from the device is a plain copy. The GPU address is a buffer's address everywhere (a view is that address plus an offset, as with CUDA and HIP), and a kernel gets its arguments as one struct of 8-byte words, so no buffer is bound by index. Every live buffer is made resident (useResources) for the commands that follow.

The driver does not know any program: it calls what Objc gives. system gives the libraries' own; a test gives a fake that records the calls.

gizlang
import * as Metal from "gizhw:metal";

Types

Objc

gizlang
type Objc = { send: (words: Vec<u64>) => u64 effects { io, fail, read, write }; selector: (name: string) => u64 effects { io, fail, read, write }; find: (name: string) => u64 effects { io, fail, read, write }; push: () => u64 effects { io, fail, read, write }; pop: (pool: u64) => void effects { io, fail, read, write }; device: () => u64 effects { io, fail, read, write }; text: (address: u64) => string effects { io, fail, read, write }; sendAddress: u64; freeAddress: u64; }

What the driver needs from Objective-C's runtime and Metal. send is objc_msgSend (words: target, selector, integer arguments; a struct larger than 16 bytes is passed as the address of a copy); device is MTLCreateSystemDefaultDevice; text is the C string at an address.

  • send: (words: Vec<u64>) => u64 effects { fail, read, write, io }
  • selector: (name: string) => u64 effects { fail, read, write, io }
  • find: (name: string) => u64 effects { fail, read, write, io }
  • push: () => u64 effects { fail, read, write, io }
  • pop: (pool: u64) => void effects { fail, read, write, io }
  • device: () => u64 effects { fail, read, write, io }
  • text: (address: u64) => string effects { fail, read, write, io }
  • sendAddress: u64
  • freeAddress: u64

Alloc

gizlang
type Alloc = { gpu: u64; cpu: u64; size: i64; handle: u64; }

One MTLBuffer: its GPU address (what a kernel gets), CPU address, size and object.

  • gpu: u64
  • cpu: u64
  • size: i64
  • handle: u64

Work

gizlang
type Work = { buffer: u64; encoder: u64; launches: i64; last: u64; inflight: Vec<u64>; }

The command buffer being encoded and its compute encoder (0 when none), the launches in it, the pipeline set last, and the command buffers committed and not yet waited for.

  • buffer: u64
  • encoder: u64
  • launches: i64
  • last: u64
  • inflight: Vec<u64>

Resident

gizlang
type Resident = { array: u64; count: i64; dirty: bool; }

The array of every buffer's object that useResources reads: dirty when a buffer came or went since it was made.

  • array: u64
  • count: i64
  • dirty: bool

Launch

gizlang
type Launch = { pipeline: u64; blocks: i64; threads: i64; arguments: Vec<u64>; }

A launch kept for Gpu.replay.

  • pipeline: u64
  • blocks: i64
  • threads: i64
  • arguments: Vec<u64>

Selectors

gizlang
type Selectors = { retain: u64; release: u64; setPipeline: u64; setBytes: u64; dispatch: u64; }

The selectors of the calls made for every launch, found once.

  • retain: u64
  • release: u64
  • setPipeline: u64
  • setBytes: u64
  • dispatch: u64

Metal

gizlang
type Metal

An opened Apple GPU: arch is "apple7" (M1) to "apple10", the instruction set Kernel.msl prints for.

  • arch: string
  • name: string

Functions

system

gizlang
function system(nat: Native): Option<Objc>

Objective-C's runtime and Metal as this process has them, or none (not macOS, or no Metal).

Effects: fail, io.

open

gizlang
function open(nat: Native, objc: Objc): Option<Metal>

The Apple GPU objc finds, or none (no GPU, or one before the Apple7 family: Intel Macs, A-series).

Effects: fail, read, write, io.

Methods

Metal.alloc

gizlang
function Metal.alloc(self: Metal, size: i64): u64

A new buffer of size bytes (shared storage), contents undefined: its GPU address, or 0 when there is no room.

Effects: fail, read, write, io.

Metal.free

gizlang
function Metal.free(self: Metal, address: u64): void

Frees the buffer at GPU address address (after the work before: the GPU may still read it).

Effects: fail, read, write, io.

Metal.host

gizlang
function Metal.host(self: Metal, address: u64): u64

The CPU address of the byte at GPU address address (inside a buffer).

Effects: fail, read.

Metal.sizeOf

gizlang
function Metal.sizeOf(self: Metal, address: u64): i64

The size of the buffer at GPU address address.

Effects: fail, read.

Metal.workingSet

gizlang
function Metal.workingSet(self: Metal): i64

The bytes the GPU lets this process use (its recommended working set), and those its buffers hold now.

Effects: fail, read, write, io.

Metal.allocated

gizlang
function Metal.allocated(self: Metal): i64

The bytes that the buffers of the device hold now.

Effects: fail, read, write, io.

Metal.maxBuffer

gizlang
function Metal.maxBuffer(self: Metal): i64

The size of the largest buffer.

Effects: fail, read, write, io.

Metal.releaseFunction

gizlang
function Metal.releaseFunction(self: Metal): u64

The function that a Gizlang value that owns a buffer is given (Native.own).

Metal.hostFree

gizlang
function Metal.hostFree(self: Metal): u64

The address of the function that frees host memory.

Metal.failure

gizlang
function Metal.failure(self: Metal): string

The text of the last failure of a command buffer or a compile.

Effects: fail, read.

Metal.compile

gizlang
function Metal.compile(self: Metal, source: string, name: string): u64 throws string

The compute pipeline of the function name of the MSL source, compiled by the system compiler (no Xcode).

Effects: fail, read, write, io.

Metal.sync

gizlang
function Metal.sync(self: Metal): u64

Waits for every launched kernel: 0, or 1 when a command buffer failed (Metal.failure says why).

Effects: fail, read, write, io.

Metal.launch

gizlang
function Metal.launch(self: Metal, pipeline: u64, blocks: i64, threads: i64, arguments: Vec<u64>): void

Runs pipeline on blocks workgroups of threads threads with arguments (8-byte words): returns at once.

Effects: fail, read, write, io.

Metal.startRecording

gizlang
function Metal.startRecording(self: Metal): void

From now on launches run nothing: they are kept for finishRecording.

Effects: fail, write.

Metal.finishRecording

gizlang
function Metal.finishRecording(self: Metal): i64

The launches kept since startRecording, as a recording to replay (a number); launches run again from now on.

Effects: fail, read, write.

Metal.replay

gizlang
function Metal.replay(self: Metal, id: i64): void

Runs the launches of recording id again, in order.

Effects: fail, read, write, io.

Metal.forget

gizlang
function Metal.forget(self: Metal, id: i64): void

Forgets recording id.

Effects: fail, read, write.