llvm / llvm/llvm-project

[clang] parameter attributes ([[clang::noescape]], __restrict on references) are silently dropped from function(-pointer) types built through templates — no generic-programming path to attributed thunk-pointer types

Open
#210,306 1 comment 0 reactions 0 assignees View on GitHub
clang
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

### Summary

There is no way to construct a function-pointer type carrying parameter-level attributes (`[[clang::noescape]]`, and `__restrict` on a reference parameter) through a template — both are **silently dropped** when the signature is passed as a template argument, with no diagnostic. Since these attributes are **caller-side** facts (they must be on the function-pointer *type* to affect the indirect callsite), this makes it impossible to write a generic "attributed fn-ptr factory" for type-erasure thunk tables; every dispatch table must hand-spell the full attributed pointer type (or resort to a token-pasting macro).

### Repro (clang 22.1.3, `-S -emit-llvm`, x86_64)

```c++
struct Header { void * payload; int const * coord; };

// direct spelling — attributes written on the fn-ptr parameter
using DirectFn = int const * (*)( [[clang::noescape]] Header & __restrict ) noexcept;

// generic spelling — signature passed through a template
template struct fn_ptr;
template
struct fn_ptr { using type = Ret (*)( Args... ) noexcept; };
using FactoryFn = fn_ptr::type;

int callDirect ( DirectFn f, Header & h ) { return *f( h ); }
int callFactory( FactoryFn f, Header & h ) { return *f( h ); }
```

Emitted IR:

```llvm
; callDirect's indirect call — captures(none) present, restrict in the mangled type (AEIAU…)
%7 = call noundef ptr %5(ptr noundef nonnull align 8 captures(none) dereferenceable(16) %6)
; callFactory's indirect call — BOTH silently dropped (mangling shows plain AEAU…)
%7 = call noundef ptr %5(ptr noundef nonnull align 8 dereferenceable(16) %6)
```

Note the callee *definitions* keeping the attributes does not help: at an indirect call through the erased pointer, only the pointer type's attributes reach the callsite.

### Why this matters

Function-pointer-based type erasure (thunk tables instead of vtables) is a common size/perf technique; the attribute set (`noescape`, `__restrict`, calling convention) is boilerplate one wants to stamp once via a metafunction like `erased_fn_t`. Today:

- `__attribute__((sysv_abi))` etc. CAN be applied at the alias level (works),
- parameter-level attributes CANNOT ride through the template argument, and are dropped **without any `-Wignored-attributes` style diagnostic**, so the pessimization is invisible.

### Ask

1. At minimum: diagnose the silent drop.
2. Ideally: a generic-programming path — either make declaration-level parameter attributes on function *types* participate in template argument deduction/substitution, or provide compiler builtins to conditionally apply `noescape`/`restrict` to reference/pointer parameters in generic contexts (analogous to how `__restrict` already participates in the type system for member functions and mangling when spelled directly).

Contributor guide

Open the contributing guide

Research direction

Start by compiling the provided reproducer with clang 22.1.3 using -S -emit-llvm and compare the indirect calls for DirectFn and FactoryFn. Investigate how parameter-level attributes are represented when the function signature becomes a template argument. Done means either diagnosing the silent drop or establishing a generic path that preserves these attributes at the indirect callsite.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.