Open-source project
dougallj/applegpu avatar
dougallj/applegpu

applegpu: reverse engineering the Apple G13 GPU instruction set

Apple G13 GPU architecture docs and tools

723 stars46 forksHTMLBSD-3-Clause

At a glance

What is it?
applegpu is a work in progress on the Apple G13 GPU architecture used in the M1, and it ships the pieces a reverse engineer actually needs: a disassembler, an assembler, an emulator and a shader compiler explorer, all driven by plain Python scripts.
Who is it for?
applegpu fits a developer who wants to read and write Apple GPU instruction bytes without leaving a text editor, since the disassembler, assembler and compiler explorer are ordinary Python scripts you can read and modify, and it fits anyone who wants to check an instruction's behaviour against real hardware through the test harness.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 17 days ago.
What is it written in?
Mainly HTML, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A mess of tools built around one instruction table

applegpu is a project working on reverse engineering the Apple G13 GPU architecture, the one used by the M1, and it describes itself as a somewhat messy work in progress that reflects the author's best understanding. The mess is in the tooling rather than in the goal. At the top level sit separate Python scripts for each job: compiler_explorer.py, disassemble.py, assemble.py, genhtml.py, hwtest.py and hwtestbed.py, alongside applegpu.py, which is the shared core, and smaller modules such as fma.py, srgb.py and metallib_replacer.py. The point of that layout is that everything is a script you can open, edit and step through, with the instruction descriptions living in one place in applegpu.py and the documentation generated from them. The published documentation is served as a single page, docs.html, and the repository also keeps a checked-in copy of it.

The compiler explorer goes from Metal source to instruction listing

The tool aimed at everyday use is compiler_explorer.py, which compiles a Metal shader, extracts the compiled form and disassembles it in one go.

bash
$ python3 compiler_explorer.py msl_code.metal

The output is an instruction listing with the byte encoding in front of each line, so you can read the encoding and the meaning side by side. It also accepts a shader on standard input, which the project describes as a way to be extra lazy.

bash
$ pbpaste | python3 compiler_explorer.py -

If you are not on an M1, the tool also accepts compiled binaries, which you can produce with metal-tt. That path needs a .mtlp-json file describing the pipeline, one shape for a compute pipeline with a compute_function, another for a render pipeline with vertex and fragment functions and colour attachments. The compile and disassemble sequence then looks like this.

bash
$ xcrun metal msl_code.metal -o test.metallib
$ xcrun metal-tt -arch applegpu_g13g -o test.bin test.mtlp-json test.metallib
$ python3 compiler_explorer.py test.bin

Note the architecture argument applegpu_g13g, which is how you point the tool at the G13 target explicitly.

Disassembling raw bytes with no file format around them

The disassembler is the piece for the case where you have bytes and nothing else, that is, instruction data that is not wrapped in any of Apple's file formats. You hand it a binary and it prints the listing.

bash
$ python3 disassemble.py code.bin

The listing format carries three things per instruction: the offset, the raw encoding, and the decoded form with the operands. Seeing device_load next to a destination pair like r0_r1, a uniform pair u2_u3 and a shift amount is the sort of detail that tells you how a buffer read is actually encoded, which is precisely what the documentation page cannot show you. The same format appears in the compiler explorer output, so once you are used to reading it there, the standalone disassembler output is familiar.

The assembler writes bytes back, with caveats

The assembler goes the other way, from text to encoding, and it exists mainly for generating tests. The caveat is stated up front: it is very hacky and missing a lot of error checking, so you are told to check the disassembly output to see if it gave you what you asked for.

bash
$ python3 assemble.py 'fmul $r1, r2.discard, 0.5 ; rcp r1, r1.discard'

The syntax uses a dollar sign for register operands and a .discard suffix, which appears throughout the listings and clearly means the upper part of the register is not part of the operation. The multi-instruction form separated by semicolons makes it easy to script a sequence of encodings for a test, and the fact that the same text is what the disassembler prints makes a round trip easy to check. For a real product you would want a real assembler; for building a test case in a hurry, this is enough.

The emulator is a test harness, not a product

The emulator is where expectations should be lowered, and the project is explicit about it: it is not a standalone tool nor a final API, it has no flow control yet, and so far it has only been used for testing the logic of instructions against hardware. What it looks like is a loop over the instruction bytes where each step asks the core for a descriptor and executes it against a state object.

python
cs = applegpu.CoreState()

# use cs.set_reg32/cs.set_reg16 to initialise core state

remaining = instructions
while remaining:
	n = applegpu.opcode_to_number(remaining)
	desc = applegpu.get_instruction_descriptor(n)
	desc.exec(n, cs)
	size = desc.decode_size(n)
	remaining = remaining[size:]

# use cs.get_reg32/cs.get_reg16 to dump core state

The state object is manipulated with 32-bit and 16-bit setters and getters, and the loop advances by the decoded size of each instruction. That shape is enough to answer questions about one instruction in isolation, which is what the hardware tests need, and not enough to run a shader end to end.

Hardware tests compare real silicon against the emulator

The strongest part of the project is the test approach. Hardware tests run instructions on the actual GPU, so they can only run on Apple Silicon, and they emulate the same instructions, then compare the state afterwards. The trick that makes this possible is overwriting the shader code inside Metal binary archives with the project's own shaders. The entry point is a single Python script, hwtest.py, run from the repository root. There is one wrinkle the project flags: the script compiles its C helpers if they do not exist yet, but not if they exist and are out of date, so you can recompile them by hand. The author is also candid about coverage, saying the tests are a bit scattershot, with some thorough and others only testing what had to be built to implement other tests, and that experimentally breaking things is a useful way to see what is covered. For poking around, hwtestbed.py takes shader code directly and has a --help listing its options, and bash brace expansion is suggested for specifying many input registers at once.

The documentation is generated from the instruction descriptions

The public documentation is a single generated page rather than a hand-written site, and the generation is one command.

bash
$ python3 genhtml.py > docs.html

The generation is partially based on the instruction descriptions in applegpu.py, and most of the prose lives in genhtml.py itself, which means the text and the tables are edited in the same place as the opcode knowledge. A checked-in docs.html sits at the repository root, and the live version is served from the GitHub Pages site at dougallj.github.io/applegpu/docs.html. The project is BSD-3-Clause licensed, has no GitHub releases, and the last push was on 2026-10-06.

Where applegpu is the wrong tool

Two boundaries make this the wrong project for a particular job. The first is the emulator: no flow control means you cannot run a real shader in it, so if your goal is to execute a workload and inspect the output, you need a real device and a debugger rather than this emulator. The second is the scope of the architecture work. The project targets the Apple G13 architecture as used by the M1, and nothing in the overview claims coverage of the generations that came after, so a lookup for a newer Mac is out of scope. There is also a portability limit that is easy to miss: the hardware tests need Apple Silicon, so the part of the toolchain that gives the strongest answers cannot run on the Linux or Windows box you might otherwise use for the rest of it. And the documentation, useful as it is, is generated from the author's current understanding of a reverse engineered architecture, so treat the listings as a working model to check against hardware rather than as a specification.

Editorial conclusion

applegpu fits a developer who wants to read and write Apple GPU instruction bytes without leaving a text editor, since the disassembler, assembler and compiler explorer are ordinary Python scripts you can read and modify, and it fits anyone who wants to check an instruction's behaviour against real hardware through the test harness. It does not fit anyone who wants a finished emulator, because the project states the emulator is not a standalone tool nor a final API, has no flow control yet, and so far has only been used for testing instruction logic against hardware. Before you build on it, read the generated documentation rather than the source comments, run the hardware tests on Apple Silicon only and rebuild the C helpers with make -C hwtestbed -j8 when they are stale, and treat the assembler as experimental, since it is described as very hacky and missing a lot of error checking.

Frequently asked questions

What is applegpu and which GPU does it cover?

It is a work in progress on reverse engineering the Apple G13 GPU architecture as used by the M1, with a disassembler, an assembler, an emulator and a compiler explorer built along the way. The documentation is published as a single generated page.

How do I disassemble Apple GPU instructions?

Run python3 disassemble.py code.bin on raw bytes that are not wrapped in any of Apple's file formats, or use python3 compiler_explorer.py on a Metal source file to compile and disassemble in one step. The output pairs the byte encoding with the decoded operands.

Can I use the applegpu emulator to run a shader?

Not yet. The project states the emulator is not a standalone tool nor a final API, there is no flow control yet, and so far it has only been used for testing the logic of instructions against hardware.

How do the applegpu hardware tests work?

They run instructions on the actual GPU, which means Apple Silicon is required, and emulate the same instructions, then compare the state afterwards. This is done by overwriting the shader code in Metal binary archives with the project's own shaders.

Official sources

  1. dougallj/applegpu on GitHub
  2. Issues
  3. License: BSD-3-Clause
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dougallj-applegpu.svg)](https://hysenlabs.com/projects/dougallj-applegpu)

Was this useful?