Skip to content

Compiler: a delegate thunk's call skips its sret arg when adding attributes - #2586

Merged
bfiete merged 2 commits into
beefytech:masterfrom
jayrulez:fix/delegate-thunk-sret-attributes
Oct 28, 2026
Merged

bfiete merged 2 commits into
beefytech:masterfrom
jayrulez:fix/delegate-thunk-sret-attributes

Conversation

@jayrulez

Copy link
Copy Markdown
Contributor

A delegate's invoke thunk builds its own argument list for both the bound
and the static call, the sret pointer included, and hands CreateCall a
NULL sret. CreateCall's attribute pass only steps over the sret slot when
sret is non-NULL, so every parameter attribute after it was put on the
argument before its own. With a lowered struct ahead of a ref parameter,
the ref's dereferenceable landed on the struct's second i64:

struct Pos { public uint32 Line; public uint32 Column; }
struct Sel { public Pos A; public Pos B; }

static Pos Each(delegate Pos(Sel s, ref float goal) move) { ... }

LLVM rejected the module ("Attribute 'dereferenceable(4)' applied to
incompatible type!") and BeefBuild aborted. The pass now skips the
thunk's sret slot as well.

…ibutes

A delegate's invoke thunk builds its own argument list for both the bound
and the static call, the sret pointer included, and hands CreateCall a
NULL sret. CreateCall's attribute pass only steps over the sret slot when
sret is non-NULL, so every parameter attribute after it was put on the
argument before its own. With a lowered struct ahead of a ref parameter,
the ref's dereferenceable landed on the struct's second i64:

    struct Pos { public uint32 Line; public uint32 Column; }
    struct Sel { public Pos A; public Pos B; }

    static Pos Each(delegate Pos(Sel s, ref float goal) move) { ... }

LLVM rejected the module ("Attribute 'dereferenceable(4)' applied to
incompatible type!") and BeefBuild aborted. The pass now skips the
thunk's sret slot as well.

Seen in a game engine's code editor, whose per-selection edit and move
delegates take a selection by value beside a ref.
The thunk's call skipped its own sret slot when adding parameter
attributes, but left it unmarked. On x86-64 that passes: sret goes in the
first argument register either way. AArch64 passes sret in x8, apart from
the arguments, and LLVM puts it there only for a parameter marked sret, so
the callee read its return slot from the wrong register and
Delegates.TestSretBeforeLoweredAndRef crashed (SIGSEGV in the lambda) on
the arm64 CI runner. The slot is now marked as a real call's is.
@bfiete
bfiete merged commit ddd71be into beefytech:master Oct 28, 2026
5 checks passed
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.

2 participants