- Loading...
This document describes ....Latest Webrev: http://cr.openjdk.java.net/~iklam/jdk16/8243208-cleanup-jvmflag-impl.v00/
...
Code Block |
---|
#define RUNTIME_FLAGS(develop, \ develop_pd, \ product, \ product_pd, \ /* REMOVED diagnostic, */\ /* REMOVED diagnostic_pd, */ \ /* REMOVED experimental, */\ notproduct, \ /* REMOVED manageable, */ \ /* REMOVED product_rw, */\ /* REMOVED lp64_product, */\ range, \ constraint) \ ..... develop(bool, CleanChunkPoolAsync, true, /* no attr */ \ "Clean the chunk pool asynchronously") \ \ product(uint, HandshakeTimeout, 0, /* attr= */DIAGNOSTIC, \ "If nonzero set a timeout in milliseconds for handshakes") \ \ product(bool, AlwaysSafeConstructors, false, /* attr= */ EXPERIMENTAL, \ "Force safe construction, as if all fields are final.") .... |
The flags metadata is defined using overloaded constructors (around lin 679 of jvmFlag.cpp)
Code Block |
---|
constexpr JVMFlag::JVMFlag(int flag_enum, const char* type, const char* name, size_t name_len, void* void* addr, int extra_flags, int flagsattrs, const char* doc) : _type(type), _name(name), _addr(addr), NOT_PRODUCT_ARG(COMMA _doc(doc)) {} constexpr JVMFlag::JVMFlag(int _flags(Flags(flags | extra_flags | DEFAULT | flag_group(flag_enum))), _name_len(name_len) {} constexpr JVMFlag::JVMFlag(int flag_enum, const flag_enum, const char* type, const char* name, size_t name_len, void* addr, int extra_flags, const char* doc) : JVMFlag(flag_enum, type, name, name_lenconst char* doc) : JVMFlag(flag_enum, type, name, addr, extra_flags, /*flagsattrs*/0, doc) {} .... #define DEVELOP_FLAG_INIT( type, name, value, ...) JVMFlag(FLAG_MEMBER_ENUM(name), ...snip..., __VA_ARGS__), #define DEVELOP_FLAG_INIT_PD(type, name, ...) JVMFlag(FLAG_MEMBER_ENUM(name), ...snip..., __VA_ARGS__), ...snip.... static JVMFlag flagTable[1 + NUM_JVMFlagsEnum + 1] = { ...... ALL_FLAGS(DEVELOP_FLAG_INIT, DEVELOP_FLAG_INIT_PD, PRODUCT_FLAG_INIT, PRODUCT_FLAG_INIT_PD, NOTPROD_FLAG_INIT, IGNORE_RANGE, IGNORE_CONSTRAINT) .... // NOTE: ALL_FLAGS() calls RUNTIME_FLAGS() |
...
The new code design (see here for webrev):
If your constexpr's are too complex, the C compiler may decide to generate runtime code (instead of generating the data that your constexpr's compute). A good way to check is to build the .o with something like "gcc -save-temps" and look at the .s file. Here's an example of jvmFlagLimit.s. The following is the C++ global constructor section. It's very small, so it's good.
Code Block |
---|
_GLOBAL__sub_I_jvmFlagLimit.cpp:
.LFB6450:
.cfi_startproc
pxor %xmm0, %xmm0
movaps %xmm0, 1776+_ZL14flagLimitTable(%rip)
movaps %xmm0, 1792+_ZL14flagLimitTable(%rip)
movaps %xmm0, 1808+_ZL14flagLimitTable(%rip)
movaps %xmm0, 1824+_ZL14flagLimitTable(%rip)
ret
.cfi_endproc
.LFE6450:
.size _GLOBAL__sub_I_jvmFlagLimit.cpp, .-_GLOBAL__sub_I_jvmFlagLimit.cpp
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_jvmFlagLimit.cpp |
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 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 |
Most flags have neither range nor constraint. For those flags, we want its flagLimits[Flag_flagname_Enum] to be NULL.
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, ...) ); constexpr JVMTypedFlagLimit<type> limit_##name(0
#define FLAG_LIMIT_DEFINE_DUMMY(type, name, ...) ); constexpr 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)
constexpr 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 the 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:
Code Block |
---|
// code excerpt prettified manually
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), |
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 |
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.
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:
The savings are not a big deal, but since we can do it, why not?
A good way to check is to build the .o with something like "gcc -save-temps" and look at the .s file. Here's an example of jvmFlagLimit.s. You can see that the content of the flagLimitTable is also completely determined at build time (it's in the "ro" section):The content of the flagLimitTable is completely determined at build time (except for the 4 slots above which are null'ed at runtime. I don't know why.
Code Block |
---|
.section .data.rel.localro.local,"aw" .align 32 .type _ZL14flagLimitTable, @object .size _ZL14flagLimitTable, 9728 _ZL14flagLimitTable: .quad 0 .quad 0 .quad 0 .quad _ZL28limit_ObjectAlignmentInBytes .quad 0 .quad 0 .quad 0 .quad 0 .quad 0 .quad 0 .quad 0 .quad _ZL18limit_JVMCIThreads .quad _ZL22limit_JVMCIHostThreads .quad _ZL24limit_JVMCIEventLogLevel .quad _ZL21limit_JVMCITraceLevel .quad _ZL22limit_JVMCICounterSize .quad 0 .quad 0 .quad _ZL27limit_JVMCINMethodSizeLimit .quad 0 .quad 0 .quad 0 .quad 0 .... |