Skip to content

bench: add five synthetic union probes - #18

Merged
macabeus merged 2 commits into
mainfrom
bench/synthetic-unions
Aug 4, 2026
Merged

bench: add five synthetic union probes#18
macabeus merged 2 commits into
mainfrom
bench/synthetic-unions

Conversation

@macabeus

@macabeus macabeus commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Stacked on #16. Adds five synthetic union probes — 20 rows, 723 → 743.

Why

The synthetic tier had no union spec at all, and the real tier only reaches a union in two rows — while unions not modelled is one of asmlift's most common decline reasons, and the audits behind #16 found two more real functions (sa3:OamMalloc, sa3:ProcessOamBuffers) that hit a union without anyone noticing. The construct was effectively unmeasured.

The probes

spec source what it forces
uhalf u->w = v; return u->h[0] + u->h[1]; a 32-bit store, then two 16-bit reads of the same slot
ubyte u->w = v; return u->b[i]; a 32-bit store, then a variable-indexed byte read of the same slot
upun t.f = x; return t.u; a float punned to u32 through a union
utag switch (t->kind) { … t->v.i / t->v.h / t->v.b } a tagged union in a struct, read at three widths
ustore e[i].a = v; e[i].b[0] = 0; an array of unions: s32 write, then a u16 write to the same element

A union is only measurable when the same bytes are reached through members of different width or domain. A union whose members are never aliased compiles identically to a struct — that is the sfield problem the audit flagged, where the tag marks a property the bytes cannot falsify. Every probe here aliases.

What they measure

agbcc ido7.1 gcc2.7.2kmc mwcc_242_81
uhalf declined declined declined declined
ubyte diff:3 diff:3 diff:4 diff:2
upun match declined declined declined
utag declined declined declined declined
ustore match diff:5 match match
  • uhalf declines on all four with the union messageoverlapping fields at offset 0 (widths 4 and 2) — unions not modelled. The cleanest isolate of the gap in the dataset.
  • utag gives the same message on three; mwcc declines earlier, on an unrelated cr0 compare.
  • upun is the sharpest head-to-head: m2c emits its M2C bitwise cast marker on all four — its dedicated "cannot express this reinterpret in C" signal — while asmlift matches on agbcc, where soft-float turns the pun into a register move. On the FPU targets asmlift declines on the swc1/stfs/mfc1 store-load.
  • ubyte and ustore compile and score on both sides, so the set is not all-or-nothing: it moves gap size as well as exposing declines.

Net: asmlift 350 → 354 (upun on agbcc, ustore on agbcc/kmc/mwcc), m2c unchanged at 342. No pre-existing row moved.

A probe-design trap worth recording

The first version of uhalf and ubyte declared the union as a local. On the register-poor targets it landed in a stack slot, and asmlift declined on the stack-frame gap before ever reaching the overlap:

ubyte:agbcc   declined — stack pointer used as data (address-taken local / sp-relative slot…)
ubyte:ido7.1  declined — stack pointer used as data …
ubyte:kmc     declined — stack pointer used as data …
ubyte:mwcc    declined — stack pointer r1 used as data …

Four rows that looked like union coverage and measured frames. Taking the union through a pointer removed the masking: ubyte now compiles and scores on all four, and uhalf fails with the union message instead. upun is the deliberate exception — punning a float through memory is the construct, so its FP store/load is the point, not an artifact.

This is the same standard the #16 audits applied to the existing tier (ll2i compiling to a bare bx lr, sfield being byte-identical to loadoff): a probe has to isolate what it claims, and the only way to know is to look at what the compiler actually emitted.

Verification

pnpm bench run + merge → 743 rows, dirty: false, stale-check green. 155 benchmark tests and 34 web tests pass; typecheck, format:check, lint clean. All 20 new rows build on all four toolchains.

🤖 Generated with Claude Code

@macabeus
macabeus force-pushed the bench/synthetic-unions branch from bda1c7d to 6039d35 Compare August 4, 2026 11:58
@macabeus
macabeus force-pushed the bench/synthetic-unions branch from 6039d35 to c89a050 Compare August 4, 2026 13:35
@macabeus
macabeus force-pushed the bench/synthetic-unions branch from c89a050 to 66929ee Compare August 4, 2026 13:45
Base automatically changed from bench/feature-tag-audit to main August 4, 2026 13:51
macabeus and others added 2 commits August 4, 2026 14:52
The tier had no union spec at all, and the real tier only reaches a union in
two rows — while "unions not modelled" is one of asmlift's most common decline
reasons. Five probes, 20 rows:

  uhalf   32-bit store, two 16-bit reads of the same slot
  ubyte   32-bit store, variable-indexed byte read of the same slot
  upun    float punned to u32 through a union
  utag    tagged union in a struct, switched on the tag, read at three widths
  ustore  array of unions: s32 write then a u16 write to the same element

A union is only measurable when the SAME bytes are reached through members of
different width or domain. A union whose members are never aliased compiles
identically to a struct — that is the `sfield` problem, where the tag marks a
property the bytes cannot falsify — so every probe here aliases.

The aliasing probes take the union through a POINTER on purpose. Written as a
local, `uhalf` and `ubyte` landed in a stack slot on the register-poor targets
and asmlift declined on the stack-frame gap BEFORE reaching the overlap: the
first version of `ubyte` declined on all four toolchains for sp-as-data and
measured nothing about unions. `upun` is the deliberate exception — punning a
float through memory IS the construct, so its FP store/load is the point.

What they measure now: `uhalf` declines on all four with the union message;
`utag` on three (mwcc declines earlier, on a cr0 compare); `upun` has m2c
emitting its `M2C bitwise cast` marker on all four while asmlift matches on
agbcc, where soft-float makes the pun a register move; `ubyte` and `ustore`
both compile and score on both sides.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
asmlift 354 match (+4: upun on agbcc, ustore on agbcc/kmc/mwcc), m2c 342.
No pre-existing row moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@macabeus
macabeus force-pushed the bench/synthetic-unions branch from 66929ee to f23df28 Compare August 4, 2026 13:54
@macabeus
macabeus merged commit ec082bb into main Aug 4, 2026
2 checks passed
@macabeus
macabeus deleted the bench/synthetic-unions branch August 4, 2026 15:35
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