Skip to content

[super! 3/6] Invoke through an erased context instead of the proxy type - #68

Open
mingxwa wants to merge 1 commit into
ngcpp:feature/v5from
mingxwa:user/mingxwa/super-stage3
Open

[super! 3/6] Invoke through an erased context instead of the proxy type#68
mingxwa wants to merge 1 commit into
ngcpp:feature/v5from
mingxwa:user/mingxwa/super-stage3

Conversation

@mingxwa

@mingxwa mingxwa commented Aug 15, 2026

Copy link
Copy Markdown
Member

An invoker's erased function pointer was typed R (*)(proxy<F>&, D, Args...), so its type named the facade it was built for. That is fine while metadata is only ever read by a proxy of exactly that facade, but it prevents one proxy from reusing another's invoker: two proxy types have different layouts, so passing one where the other is expected is not valid. Reusing a base facade's invokers is what super (more PRs coming ahead) needs.

  • Adds erased_context, which carries only a pointer to the storage of the contained value. Invokers become invoker<Ctx, O>.
  • The lifetime dispatches take a void* rather than a target proxy, so their invokers no longer mention F either.
  • Resetting the source metadata after an rvalue-qualified call moves to the call site, where the facade is known.
  • relocate_dispatch becomes a tag with its own erased_context specialization, which absorbs the bitwise-relocation path that used to be selected through internal_dispatch. internal_dispatch is removed.
  • ptr_ is declared ahead of meta_ so that the storage of the contained value lies at offset 0. Every invocation now builds a context from it, and at offset 0 that context is the address of the proxy itself.

Declaring ptr_ first is what keeps the new indirection free at the call site. Where a proxy is invoked more than once, the caller keeps a single value live instead of a separate context pointer. On aarch64 that removes a spill and reload of the context across the first call and shrinks the frame by 16 bytes:

 stp  x29, x30, [sp, #-48]!        stp  x29, x30, [sp, #-32]!
 ...                               ...
 ldr  x1, [x0], #8                 ldr  x1, [x0, #16]
 str  x0, [sp, #40]   <- spill     (none)
 blr  x1                           blr  x1
 ldr  x0, [sp, #40]   <- reload    mov  x0, x19

substitution_dispatch needs the same relocation path, because it relocates a bitwise-relocatable value without requiring it to be move-constructible, so it gets a specialization too. That specialization will be removed together with substitution_dispatch in the last PR of this stack.

Breaking change. Removes the public reinterpret_invoke.

An invoker's erased function pointer was typed R (*)(proxy<F>&, D,
Args...), so its type named the facade it was built for. That is fine
while metadata is only ever read by a proxy of exactly that facade, but
it prevents one proxy from reusing another's invoker: two proxy types
have different layouts, so passing one where the other is expected is
not valid.

Introduce erased_context, which carries only a pointer to the storage of
the contained value, and type invokers as invoker<Ctx, O>. Invocation
now resolves the contained type inside the context rather than through
reinterpret_invoke, and the lifetime dispatches take a void* rather than
a target proxy, so their invokers no longer mention F either. Resetting
the source metadata after an rvalue-qualified call moves to the call
site, where the facade is known.

Declare ptr_ ahead of meta_ so that the storage of the contained value
lies at offset 0. Every invocation now builds a context from it, and at
offset 0 that context is the address of the proxy itself. Where a proxy
is invoked more than once, the caller then keeps a single value live
instead of a separate context pointer: on aarch64 that removes a spill
and reload of the context across the first call, and shrinks the frame
by 16 bytes. Neither sizeof(proxy) nor its alignment changes.

relocate_dispatch becomes a tag with its own erased_context
specialization, which absorbs the bitwise-relocation path that used to
be selected via internal_dispatch, and internal_dispatch is removed.
substitution_dispatch needs the same path, because it relocates a
bitwise-relocatable value without requiring it to be move-constructible,
so it gets a specialization too; both it and the specialization drop out
once `super` replaces substitution.

Removes the public reinterpret_invoke, which had no remaining use: the
invoker macro was its only caller inside the library.
@mingxwa
mingxwa force-pushed the user/mingxwa/super-stage3 branch from 685a990 to 1335a47 Compare August 15, 2026 10:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant