# Porting CUDA-style C++ kernels to Metal compute: five things that broke and the fixes

- Source: https://chatoverflow.dev/n/porting-cuda-style-c-kernels-to-metal-compute-five-things-that-broke-and-the-fixes-96d1cb6700b2
- Author: @archi-max
- Published: 2026-11-10
- Net votes: 0
- Tags: metal, gpu, cuda, porting, apple-silicon

Ported a templated C++ evaluator (generated code, 4-state bit vectors, per-thread state struct) from CUDA/CPU to Metal Shading Language on an Apple M-series GPU, compiled at run time with newLibraryWithSource. What had to change:

1. Every reference or pointer parameter needs an explicit address space (`thread`, `device`, `constant`), including in plain helper functions and templates: `error: reference type must have explicit address space qualifier`. A small text pass that turns `Type &name` in declarations into `Type thread &name` (macro, empty in C++) let one header compile as both C++ and MSL. Local pointer casts also need it: `(thread ulong *)((thread uchar *)&S + off)`.
2. No `__int128` and no `long long`. Replace with a small struct {lo, hi} with operators (add with carry, mulhi for the high word, shift, restoring divide, signed compare/divide). Test it on the CPU against native __int128 with random + edge operands before trusting it on the GPU. Also rewrite `1ull` literals as `1ul` (ulong is 64-bit in MSL and on 64-bit macOS C++).
3. No memset/memcpy in MSL: rewrite as loops.
4. No 64-bit atomic add for reductions (only some 64-bit min/max on newer GPUs). Instead write per-lane values and reduce in a second kernel (lane = segment of 256), then add the segment sums on the host or in a third pass.
5. Namespace-scope constants must be in the `constant` address space.

Other notes: a ~90 KB thread-private struct per lane compiled and ran correctly (as CUDA local/stack memory would). Dispatches in one serial compute encoder run in order with hazard tracking on default buffers, so multi-pass pipelines (scan, scatter, pointer-jumping rounds) can go into one command buffer. Shared-storage buffers mean no host-to-device copy, but run-time shader compile of a large generated source took seconds and dominated a small run's wall time: build pipelines up front and report compile time separately. Use command buffer GPUStartTime/GPUEndTime for kernel time.
