...
The new design (see here for webrev):
- Builds the checker objects (JVMFlagLimit) at build-time using constexpr.
- JVMFlagLimit objects are indexed by each flag's enum (or NULL if no limit exists), so it's O(1) time.
Implementation of JVMFlagLimit
The range/constraint information for a flag of type T is described by a JVMTypedFlagLimit<T>:
Code Block |
---|
class JVMFlagLimit { enum { HAS_RANGE = 1, HAS_CONSTRAINT = 2 }; short _constraint_func; char _phase; char _kind; ...}; template <typename T> class JVMTypedFlagLimit : public JVMFlagLimit { const T _min; const T _max; ...}; |
Each flag is given a unique enum that starts from 0 to NUM_JVMFlagsEnum-1. We use this enum to find the JVMTypedFlagLimit<T> of this flag from an array:
Code Block |
---|
static constexpr const JVMFlagLimit* const flagLimitTable[1 + NUM_JVMFlagsEnum] = { .... } const JVMFlagLimit* const* JVMFlagLimit::flagLimits = &flagLimitTable[1]; // excludes dummy /* E.g., to get the limit of this flag: product(intx, ContendedPaddingWidth, 128, \ "How many bytes to pad the fields/classes marked @Contended with")\ range(0, 8192) \ constraint(ContendedPaddingWidthConstraintFunc,AfterErgo) \ */ const JVMTypedFlagLimit<intx>* limit = JVMFlagLimit::flagLimits[Flag_ContendedPaddingWidth_Enum]; // We have will see these fields: // limit->_constraint_func ==> constraint_enum_ContendedPaddingWidthConstraintFunc (more on this below) // limit->_phase ==> AfterErgo // limit->_kind ==> HAS_RANGE | HAS_CONSTRAINT // limit->_min ==> 0 // limit->_max ==> 8192 |
...
To do this, we first define a JVMTypedFlagLimit<T> variable for each flag (including the ones that don't have range/constraint). It's done by this macro:
Code Block |
---|
// macro body starts here -------------------+ // | // v #define FLAG_LIMIT_DEFINE( type, name, ...) ); const JVMTypedFlagLimit<type> limit_##name(0 #define FLAG_LIMIT_DEFINE_DUMMY(type, name, ...) ); const DummyLimit nolimit_##name(0 #define FLAG_LIMIT_PTR( type, name, ...) ), LimitGetter<type>::get_limit(&limit_##name, 0 #define FLAG_LIMIT_PTR_NONE( type, name, ...) ), LimitGetter<type>::no_limit(0 #define APPLY_FLAG_RANGE(...) , __VA_ARGS__ #define APPLY_FLAG_CONSTRAINT(func, phase) , next_two_args_are_constraint, (short)CONSTRAINT_ENUM(func), int(JVMFlagConstraint::phase) constconstexpr JVMTypedFlagLimit<int> limit_dummy ( #ifdef PRODUCT ALL_FLAGS(FLAG_LIMIT_DEFINE_DUMMY, FLAG_LIMIT_DEFINE_DUMMY, FLAG_LIMIT_DEFINE, FLAG_LIMIT_DEFINE, FLAG_LIMIT_DEFINE_DUMMY, APPLY_FLAG_RANGE, APPLY_FLAG_CONSTRAINT) #else ALL_FLAGS(FLAG_LIMIT_DEFINE, FLAG_LIMIT_DEFINE, FLAG_LIMIT_DEFINE, FLAG_LIMIT_DEFINE, FLAG_LIMIT_DEFINE, APPLY_FLAG_RANGE, APPLY_FLAG_CONSTRAINT) #endif ); |
To understand how this worksthe macros work, it's best to compile jvmFlagLimit.c with gcc -save-temps. and look at the generated jvmFlagLimit.ii with the macros expanded. Here's an example:
...
(prettified manually):
Code Block |
---|
constexpr JVMTypedFlagLimit<int> limit_dummy();
constexpr JVMTypedFlagLimit<bool> limit_UseCompressedOops(0);
....
constexpr JVMTypedFlagLimit<intx> limit_ObjectAlignmentInBytes(0, 8, 256,
next_two_args_are_constraint,
(short)constraint_enum_ObjectAlignmentInBytesConstraintFunc, int(JVMFlagConstraint::AtParse));
....
constexpr JVMTypedFlagLimit<intx> limit_JVMCIThreads(0, 1, max_jint); |
We use overloaded constructors to fill out the necessarily fields of the JVMTypedFlagLimit<T> variables. Note that the min/max parameters, as well as the constraint_func/phase parameters, can both be integer values. For disambiguation, we pass in a dummy next_two_args_are_constraint for the constraint_func/phase.
We also need to always pass in an initial dummy 0 parameter so that the macros can safely add a comma before passing the min/max or constraint_func/phase.
These dummy parameters are evaluated at compile time so they can be safely optimized away.
The next step is to fill out the flagLimitTable[] array:
Code Block |
---|
static constexpr const JVMFlagLimit* const flagLimitTable[1 + NUM_JVMFlagsEnum] = {
// Because FLAG_LIMIT_PTR must start with an "),", we have to place a dummy element here.
LimitGetter<int>::get_limit(NULL, 0
#ifdef PRODUCT
ALL_FLAGS(FLAG_LIMIT_PTR_NONE,
FLAG_LIMIT_PTR_NONE,
FLAG_LIMIT_PTR,
FLAG_LIMIT_PTR,
FLAG_LIMIT_PTR_NONE,
APPLY_FLAG_RANGE,
APPLY_FLAG_CONSTRAINT)
#else
ALL_FLAGS(FLAG_LIMIT_PTR,
FLAG_LIMIT_PTR,
FLAG_LIMIT_PTR,
FLAG_LIMIT_PTR,
FLAG_LIMIT_PTR,
APPLY_FLAG_RANGE,
APPLY_FLAG_CONSTRAINT)
#endif
)
}; |
For the flags shown in the example above, the following code is generated by the macros:
Code Block |
---|
static constexpr const JVMFlagLimit* const flagLimitTable[1 + NUM_JVMFlagsEnum] = {
LimitGetter<int>::get_limit(NULL, 0),
LimitGetter<bool>::get_limit(&limit_UseCompressedOops, 0),
....
LimitGetter<intx>::get_limit(&limit_ObjectAlignmentInBytes, 0,
8, 256,
next_two_args_are_constraint,
(short)constraint_enum_ObjectAlignmentInBytesConstraintFunc, int(JVMFlagConstraint::AtParse)),
....
LimitGetter<intx>::get_limit(&limit_JVMCIThreads, 0, 1, max_jint), |
- If a flag has neither range nor constraint, we will call the LimitGetter<T>::get_limit() function with two parameters, which returns NULL.
- If a flag has range and/or constraint, we will call a LimitGetter<T>::get_limit() function with more than two parameters. These functions would return the same JVMTypedFlagLimit<T> as passed in.
As a result, we will end up with this in the final output of the C++ compiler:
Code Block |
---|
static constexpr const JVMFlagLimit* const flagLimitTable[1 + NUM_JVMFlagsEnum] = {
NULL, // dummy
NULL, // UseCompressedOops has no range/constraint
....
&limit_ObjectAlignmentInBytes
....
&limit_JVMCIThreads |
What happens to unreferenced flag limits
All the flag limits are defined with the constexpr keyword, which has internal linkage by default. If a flag has no range/constraint, its flag limit (e.g., limit_UseCompressedOops in the example above) will be unused, and will be eliminated by the C++ compiler from the object file. So we don't waste any space.
Why use enums for constraint_func
This is a small optimization: There are 120 flags that use a constraint function, but there are only 65 total constraint functions. By using a short index, we can:
- Reduce the size of the JVMFlagLimit object (the 2 bytes fits in unused space)
- Reduce the number of pointers relocated when libjvm.so is dynamically loaded (from 120 to 65).
The savings are not a big deal, but since we can do it, why not?
...
Is constexpr really working?
...