# 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.

