Skip to content

Assemble G17 fma instructions from scratch, registers included - #23

Merged
AndreSlavescu merged 1 commit into
mainfrom
agx-fma-assembler
Jul 30, 2026
Merged

Assemble G17 fma instructions from scratch, registers included#23
AndreSlavescu merged 1 commit into
mainfrom
agx-fma-assembler

Conversation

@AndreSlavescu

Copy link
Copy Markdown
Owner

The register field was the last piece needed to build an instruction rather than edit one.

The index appears twice in the compact form — byte 0's high nibble, and (r << 1) | 1 in byte 1 —
and the two agreed in every instruction examined across three independent fma chains. Confirmed
the only way that counts: redirect an instruction onto another chain's register and predict the
whole kernel's output. Three redirects, three exact matches.

The assembler

With registers, constants and flags all measured, encode_fma assembles the form outright. It
reproduces the compiler's own bytes byte for byte on the cases the compiler emits — the
cheapest available check on an assembler, agreeing with the only other one in existence — and then
goes past it. Four forms no Metal compiler produced, each predicted on four inputs before the
bytes were assembled, each exact:

synthesised predicted = measured
a*3+0.5 87.5, 141.5, 195.5, 303.5
a*1.5-2 0.625, 7.375, 14.125, 27.625
a*7, no addend 1029, 1715, 2401, 3773
-a*2+1 −21, −37, −53, −85

a*1.5-2 is worth noting: the immediate field is unsigned, so a negative addend is not
representable in it at all and the sign has to travel in the control byte's negate bit. The encoder
does that itself.

One prediction failed first, which is the point of writing them down in advance

A synthesised a*7 measured 1536 from x=1 against 1029 predicted — eight-fold growth per step
where seven was asked for — because dropping the addend wrote 0x00 into its immediate slot.
Zero there is not inert: it selects a register operand. Register 0 happened to be the
accumulator, so the kernel computed a*7 + a. The slot now keeps an ordinary encoded constant and
the control bit alone disables it, which is the configuration the flag scan had already verified.
Nothing about that would have been visible from reading the bytes.

The register field also explains an earlier miss. A redirect predicted 976 and measured 980, and
the field was right — chain a had three fmas but only two in the compact form, the one
consuming a freshly loaded value using a longer encoding. The replay now derives the hidden count
per chain, and the baseline check is what licenses that: 488 predicted, 488 measured.

26 ISA tests, 637 in total. The probe re-derives six stages and exits non-zero if any prediction
misses. Lint and vulture clean.

🤖 Generated with Claude Code

The register field was the last piece needed to build an instruction rather than edit one. The
index appears twice in the compact form, as byte 0's high nibble and as (r << 1) | 1 in byte 1,
and the two agreed in every instruction examined across three independent fma chains. Confirmed
the only way that counts: redirect an instruction onto another chain's register and predict the
whole kernel's output. Three redirects, three exact matches.

With registers, constants and flags all measured, `encode_fma` assembles the form outright. It
reproduces the compiler's own bytes byte for byte on the cases the compiler emits, which is the
cheapest available check on an assembler -- agree with the only other one in existence -- and
then goes past it. Four forms no Metal compiler produced, each predicted on four inputs before
the bytes were assembled and each exact:

    a*3+0.5          87.5, 141.5, 195.5, 303.5
    a*1.5-2          0.625, 7.375, 14.125, 27.625
    a*7, no addend   1029, 1715, 2401, 3773
    -a*2+1           -21, -37, -53, -85

`a*1.5-2` is worth noting: the immediate field is unsigned, so a negative addend is not
representable in it at all and the sign has to travel in the control byte's negate bit. The
encoder does that itself.

One prediction failed first, and it is the reason to write predictions down in advance. A
synthesised `a*7` measured 1536 from x=1 against 1029 predicted -- eight-fold growth per step
where seven was asked for -- because dropping the addend wrote 0x00 into its immediate slot.
Zero there is not inert: it selects a register operand, register 0 happened to be the
accumulator, and the kernel computed a*7 + a. The slot now keeps an ordinary encoded constant
and the control bit alone disables it, which is the configuration the flag scan had verified.
Nothing about that would have been visible from reading the bytes.

The register field also explains an earlier miss. A redirect predicted 976 and measured 980, and
the field was right: chain a had three fmas but only two in the compact form, the one consuming a
freshly loaded value using a longer encoding. The replay now derives the hidden count per chain
and the baseline check is what licenses it -- 488 predicted, 488 measured.

26 ISA tests, 637 in total. The probe re-derives six stages and exits non-zero if any prediction
misses. Lint and vulture clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@AndreSlavescu
AndreSlavescu merged commit e49f0fa into main Jul 30, 2026
1 of 2 checks passed
@AndreSlavescu
AndreSlavescu deleted the agx-fma-assembler branch July 30, 2026 02:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant