Skip to content

Commit

Permalink
Add support for parameter reflection proposed for P3096.
Browse files Browse the repository at this point in the history
Closes issue #13.
  • Loading branch information
katzdm committed Mar 22, 2024
1 parent 5895f08 commit 5eaa59f
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 20 deletions.
50 changes: 35 additions & 15 deletions P2996.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The intent of this project is to continue tracking changes to P2996 as it makes
- [Metafunctions](#metafunctions)
- [Tracking source locations](#tracking-source-locations)
- [Name mangling](#name-mangling)
- [Update history](#update-history)


## Rationale
Expand All @@ -41,32 +42,24 @@ The initial implementation of Clang/P2996 was led by Dan Katz, with significant
## Quick start
The upstream LLVM project provides an excellent [Getting Started](https://llvm.org/docs/GettingStarted.html) guide with instructions for building `clang` and `libc++`. After building both of these targets, support for reflection can be enabled in Clang/P2996 by compiling with both the `-std=c++26` and `-freflection` flags.

The following additional experimental features can be enabled on top of `-freflection`:
- Metafunction extensions proposed for [P3096](https://wg21.link/p3096) (`-fparameter-reflection`).


## Implementation status
At present, Clang/P2996 supports:

* Reflection over all entities supported by P2996 (i.e., types, functions, variables, class members, templates, namespaces, constant values)
* Splicing types, expressions, and namespaces
* All metafunctions propopsed by P2996 (except where noted below)
* All metafunctions propopsed by P2996 (except where noted in the [issue tracker](https://github.com/bloomberg/clang-p2996/issues))
* P3096 metafunction extensions for function parameters (enabled with `-fparameter-reflection`)

We have deliberately refrained from implementing any language extensions that are _not_ candidates for inclusion in P2996. Proposed features like expansion statements (i.e., `template for`) and non-transient `constexpr` allocation are not supported by this compiler. Any behaviors divergent from what is described by P2996, unless implemented experimentally for possible adoption by P2996, should be considered bugs.
Any implemented language or library extensions that are _not_ candidates for inclusion in P2996 will be enabled by separate feature flags. Proposed features like expansion statements (i.e., `template for`) and non-transient `constexpr` allocation are not supported at this time. Any behaviors divergent from what is described by P2996, unless implemented experimentally for possible adoption by P2996, should be considered bugs.

One sharp edge worth mentioning is that our current implementation of P2996 metafunctions is regrettably resistant to proper AST serialization. As such, this compiler cannot, in its current state, be safely used to build precompiled headers or C++20 modules that contain reflection features. This is a difficult problem that is discussed in greater detail below. We are very interested in identifying a design that can elegantly address this issue.

### Incomplete features
The following features are known to be missing from Clang/P2996. We aspire to support them, and would happily accept pull requests.

* Language features
* Splicing reflections of templates
* Splicing a namespace from a reflection dependent on a template parameter
* Splices appearing as template arguments should not be assumed to represent an expression
* e.g., `fn<[:R:]>` should be well-formed if `R` reflects a type, even if `R` is a dependent name.
* Library features
* Metafunctions: `qualified_name_of`
* Reflection-based "type trait" implementations (e.g., `std::meta::is_class`)
* Metafunction behaviors
* `reflect_invoke`
* Invoking member functions is not currently supported
A few features and behavior from P2996 are not yet supported (e.g., template splicers). The [issue tracker](https://github.com/bloomberg/clang-p2996/issues) will be kept updated with all known bugs, noncomformant behaviors, and missing features.

Our goal has been to provide a working compiler capable of building executables. It is currently a non-goal to support the full family of features and tools offered by `clang` (e.g., AST output, `clang-tidy`, etc). We have therefore taken several shortcuts when it comes to defining things like the formatting of a reflection in a text dump. We are open to pull requests implementing such things, but are otherwise content to wait until P2996 is further along in the WG21 process.

Expand Down Expand Up @@ -242,7 +235,34 @@ The family of functions used to define a tag type (e.g., `ActOnTag`, `ActOnStart
## Tracking source locations
Clang's ability to report accurate error messages requires it to track the locations of various tokens (e.g., expression locations, `(` and `)` for function calls, `<` and `>` for template specializations). When generating AST nodes from metafunctions or when splicing reflections, answering the question of where certain tokens are becomes awkward since we're synthesizing nodes from token sequences that are "shaped differently" than otherwise expected. While not usually a blocker in practice, the disharmony between the desire of `clang` to individually track the location of many individual tokens, and the desire of metaprogramming frameworks to synthesize a variety of AST nodes that don't easily map to the same tokens expected by `clang`, is worth noting.
## Name mangling
As P2996 proposes mechanisms for _Static_ Reflection, all of the business around reflections and splices and metafunctions has mostly wrapped up by the time CodeGen begins. The most notable exception is the need to mangle the names of templates having a `std::meta::info` value as an argument. As noted previously, this is a rather important use-case: since a reflection can only be spliced if it's a constant expression, a function that wants to splice a reflection that was received as an argument must receive it as a template argument. Some of the more powerful metafunctions (e.g., `substitute`, `reflect_invoke`, `value_of`) reduce the frequency with which reflections must be passed as template arguments, but it remains necessary in some cases.
Given a specialization of `template <std::meta::info R> fn()`, the compiler must be able to mangle a representation of any entity that can be reflected by `R`. This includes the full class of values of literal types - an expansion from the values of _structural_ types that can already appear as non-type template arguments. This raises questions around whether all (or which) such values should be well-formed template arguments - or perhaps, if all such values should be allowed, but some should cause the associated specialization to have internal linkage. These are questions that we expect to see clarified as P2996 advances through WG21.
## Update history
This log will be updated sporadically to indicate which new features and bug fixes that have been added to Clang/P2996.
### 2024-03-22
#### Features
- Allow splices in _using-enum-declarators_ (i.e., `using enum` statements).
- Added `can_substitute` metafunction.
- Splices of types can now appear in the _base-specifier-list_ of a class definition.
- Added metafunctions proposed for [P3096](https://wg21.link/p3096) (i.e., `parameters_of`, `has_unique_name`, and `has_default_argument`). These are only enabled when an additional `-fparameter-reflection` flag is provided. Parameter reflections may be spliced within the body of their associated function; splicing them anywhere else is ill-formed.
- `parameters_of`: Given a reflection of a function, returns a vector of reflections for the parameters. Given a reflection of a function type, returns a vector for the types of the parameters.
- `has_unique_name`: Given a reflection of a function parameter, returns whether all declarations of the function give the same name for the parameter. If this is `false`, it is unspecified which of the declared names is yielded by `name_of(^fn_param)`.
- `has_default_argument`: Given a reflection of a function parameter, returns whether the parameter has a default argument.
#### Bug fixes
- Corrected false diagnoses of meta types that leak into runtime code.
- Fixed crash from calling `members_of` (and related metafunctions) with reflections of primitive types.
- Support use of `define_class` with local classes declared at function scope.
- Ensure that `members_of` returns reflections of all implicit member functions.
- The `is_accessible` metafunction now works with reflections of base classes.
- Fixed crash when calling `define_class` with an instantiation of a class template parameterized by a parameter pack.
### 2024-03-14
- Initial release.
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticFrontendKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ def err_fe_invalid_code_complete_file : Error<
"cannot locate code-completion file %0">, DefaultFatal;
def err_fe_reflection_incompatible_with_blocks : Error<
"cannot specify both '-freflection' and '-fblocks'">, DefaultFatal;
def err_fe_parameter_reflection_without_reflection : Error<
"cannot specify '-fparameter-reflection' without '-freflection'">,
DefaultFatal;
def err_fe_dependency_file_requires_MT : Error<
"-dependency-file requires at least one -MT or -MQ option">;
def err_fe_invalid_plugin_name : Error<
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ EXTENSION(datasizeof, LangOpts.CPlusPlus)
FEATURE(cxx_abi_relative_vtable, LangOpts.CPlusPlus && LangOpts.RelativeCXXABIVTables)

FEATURE(reflection, LangOpts.Reflection)
FEATURE(parameter_reflection, LangOpts.ParameterReflection)

// CUDA/HIP Features
FEATURE(cuda_noinline_keyword, LangOpts.CUDA)
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,8 @@ LANGOPT(HIPStdParInterposeAlloc, 1, 0, "Replace allocations / deallocations with

LANGOPT(OpenACC , 1, 0, "OpenACC Enabled")

LANGOPT(Reflection , 1, 0, "Experimental C++26 Reflection Enabled")
LANGOPT(Reflection , 1, 0, "Experimental C++26 Reflection Enabled")
LANGOPT(ParameterReflection, 1, 0, "Augments C++26 Reflection with function parameter reflection")

LANGOPT(SizedDeallocation , 1, 0, "sized deallocation")
LANGOPT(AlignedAllocation , 1, 0, "aligned allocation")
Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -3337,6 +3337,11 @@ defm reflection : BoolFOption<"reflection",
PosFlag<SetTrue, [], [ClangOption, CC1Option],
"Enable proposed C++26 reflection as described by P2996">,
NegFlag<SetFalse>>;
defm parameter_reflection : BoolFOption<"parameter-reflection",
LangOpts<"ParameterReflection">, DefaultFalse,
PosFlag<SetTrue, [], [ClangOption, CC1Option],
"Enable proposed parameter reflection as described by P3096">,
NegFlag<SetFalse>>;
defm sized_deallocation : BoolFOption<"sized-deallocation",
LangOpts<"SizedDeallocation">, DefaultFalse,
PosFlag<SetTrue, [], [ClangOption, CC1Option],
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7143,6 +7143,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
// -freflection is off by default, as it is experimental.
Args.addOptInFlag(CmdArgs, options::OPT_freflection,
options::OPT_fno_reflection);
// -fparameter-reflection is likewise off by default.
Args.addOptInFlag(CmdArgs, options::OPT_fparameter_reflection,
options::OPT_fno_parameter_reflection);

// -fsized-deallocation is off by default, as it is an ABI-breaking change for
// most platforms.
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,11 @@ static bool FixupInvocation(CompilerInvocation &Invocation,
LangOpts.NewAlignOverride = 0;
}

if (LangOpts.Reflection && Args.hasArg(OPT_fblocks)) {
if (LangOpts.Reflection) {
if (Args.hasArg(OPT_fblocks))
Diags.Report(diag::err_fe_reflection_incompatible_with_blocks);
} else if (LangOpts.ParameterReflection) {
Diags.Report(diag::err_fe_parameter_reflection_without_reflection);
}

// Prevent the user from specifying both -fsycl-is-device and -fsycl-is-host.
Expand Down
147 changes: 146 additions & 1 deletion clang/lib/Sema/Metafunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ namespace clang {
using EvalFn = Metafunction::EvaluateFn;

// -----------------------------------------------------------------------------
// Metafunction declarations
// P2996 Metafunction declarations
// -----------------------------------------------------------------------------

static bool get_begin_enumerator_decl_of(APValue &Result, Sema &S,
Expand Down Expand Up @@ -288,6 +288,21 @@ static bool alignment_of(APValue &Result, Sema &S, EvalFn Evaluator,
QualType ResultTy, SourceRange Range,
ArrayRef<Expr *> Args);

// -----------------------------------------------------------------------------
// P3096 Metafunction declarations
// -----------------------------------------------------------------------------

static bool get_ith_parameter_of(APValue &Result, Sema &S, EvalFn Evaluator,
QualType ResultTy, SourceRange Range,
ArrayRef<Expr *> Args);

static bool has_unique_name(APValue &Result, Sema &S, EvalFn Evaluator,
QualType ResultTy, SourceRange Range,
ArrayRef<Expr *> Args);

static bool has_default_argument(APValue &Result, Sema &S, EvalFn Evaluator,
QualType ResultTy, SourceRange Range,
ArrayRef<Expr *> Args);

// -----------------------------------------------------------------------------
// Metafunction table
Expand Down Expand Up @@ -365,6 +380,11 @@ static constexpr Metafunction Metafunctions[] = {
{ Metafunction::MFRK_sizeT, 1, 1, bit_offset_of },
{ Metafunction::MFRK_sizeT, 1, 1, bit_size_of },
{ Metafunction::MFRK_sizeT, 1, 1, alignment_of },

// P3096 metafunction extensions
{ Metafunction::MFRK_metaInfo, 3, 3, get_ith_parameter_of },
{ Metafunction::MFRK_bool, 1, 1, has_unique_name },
{ Metafunction::MFRK_bool, 1, 1, has_default_argument },
};
constexpr const unsigned NumMetafunctions = sizeof(Metafunctions) /
sizeof(Metafunction);
Expand Down Expand Up @@ -3555,4 +3575,129 @@ bool alignment_of(APValue &Result, Sema &S, EvalFn Evaluator,
llvm_unreachable("unknown reflection kind");
}

bool get_ith_parameter_of(APValue &Result, Sema &S, EvalFn Evaluator,
QualType ResultTy, SourceRange Range,
ArrayRef<Expr *> Args) {
assert(Args[0]->getType()->isReflectionType());
assert(ResultTy == S.Context.MetaInfoTy);

APValue R;
if (!Evaluator(R, Args[0], true))
return true;

APValue Sentinel;
if (!Evaluator(Sentinel, Args[1], true))
return true;
assert(Sentinel.getReflection().getKind() == ReflectionValue::RK_type);

APValue Idx;
if (!Evaluator(Idx, Args[2], true))
return true;
size_t idx = Idx.getInt().getExtValue();

switch (R.getReflection().getKind()) {
case ReflectionValue::RK_type: {
if (auto FT = dyn_cast<FunctionProtoType>(R.getReflectedType())) {
unsigned numParams = FT->getNumParams();
if (idx >= numParams)
return SetAndSucceed(Result, Sentinel);

return SetAndSucceed(Result, makeReflection(FT->getParamType(idx)));
}
return true;
}
case ReflectionValue::RK_declaration: {
if (auto FD = dyn_cast<FunctionDecl>(R.getReflectedDecl())) {
unsigned numParams = FD->getNumParams();
if (idx >= numParams)
return SetAndSucceed(Result, Sentinel);

return SetAndSucceed(Result, makeReflection(FD->getParamDecl(idx)));
}
return true;
}
case ReflectionValue::RK_template:
case ReflectionValue::RK_const_value:
case ReflectionValue::RK_namespace:
case ReflectionValue::RK_base_specifier:
case ReflectionValue::RK_data_member_spec:
return true;
}
llvm_unreachable("unknown reflection kind");
}

bool has_unique_name(APValue &Result, Sema &S, EvalFn Evaluator,
QualType ResultTy, SourceRange Range,
ArrayRef<Expr *> Args) {
assert(Args[0]->getType()->isReflectionType());
assert(ResultTy == S.Context.BoolTy);

APValue R;
if (!Evaluator(R, Args[0], true))
return true;

switch (R.getReflection().getKind()) {
case ReflectionValue::RK_type:
case ReflectionValue::RK_const_value:
case ReflectionValue::RK_template:
case ReflectionValue::RK_namespace:
case ReflectionValue::RK_base_specifier:
case ReflectionValue::RK_data_member_spec:
return true;
case ReflectionValue::RK_declaration: {
if (auto *PVD = dyn_cast<ParmVarDecl>(R.getReflectedDecl())) {
StringRef FirstNameSeen = PVD->getName();
ParmVarDecl *FirstParmSeen = PVD;

bool Unique = true;
while (PVD) {
FunctionDecl *FD = cast<FunctionDecl>(PVD->getDeclContext());
FD = FD->getPreviousDecl();
if (!FD)
break;

PVD = FD->getParamDecl(FirstParmSeen->getFunctionScopeIndex());
assert(PVD);
if (PVD->getName() != FirstNameSeen) {
Unique = false;
break;
}
}
return SetAndSucceed(Result, makeBool(S.Context, Unique));
}
return true;
}
}
llvm_unreachable("unknown reflection kind");
}

bool has_default_argument(APValue &Result, Sema &S, EvalFn Evaluator,
QualType ResultTy, SourceRange Range,
ArrayRef<Expr *> Args) {
assert(Args[0]->getType()->isReflectionType());
assert(ResultTy == S.Context.BoolTy);

APValue R;
if (!Evaluator(R, Args[0], true))
return true;

switch (R.getReflection().getKind()) {
case ReflectionValue::RK_type:
case ReflectionValue::RK_const_value:
case ReflectionValue::RK_template:
case ReflectionValue::RK_namespace:
case ReflectionValue::RK_base_specifier:
case ReflectionValue::RK_data_member_spec:
return true;
case ReflectionValue::RK_declaration: {
if (auto *PVD = dyn_cast<ParmVarDecl>(R.getReflectedDecl())) {
return SetAndSucceed(Result, makeBool(S.Context, PVD->hasDefaultArg()));
}
return true;
}
}
llvm_unreachable("unknown reflection kind");
}


} // end namespace clang
Loading

0 comments on commit 5eaa59f

Please sign in to comment.