RFR: 8240110: Improve NULL

Leo Korinth leo.korinth at oracle.com
Tue Mar 31 19:10:00 UTC 2020


Thanks for taking time to look through this!

On 31/03/2020 12:04, David Holmes wrote:
> Hi Leo,
> 
> I looked at the "combined change". Overall I like the cleanup, but I did 
> find a few questionable items:
> 
> src/hotspot/cpu/arm/interpreterRT_arm.cpp
> 
> -      _toGP[_last_gp++] = (*(intptr_t*)from_addr == 0) ? NULL : from_addr;
> +      _toGP[_last_gp++] = (*(intptr_t*)from_addr == 0) ? NULL_WORD : 
> from_addr;
>       } else {
> -      *_to++ = (*(intptr_t*)from_addr == 0) ? NULL : from_addr;
> +      *_to++ = (*(intptr_t*)from_addr == 0) ? NULL_WORD : from_addr;
> 
> If from_addr is being treated as intptr_t* then shouldn't it be compared 
> against NULL_WORD rather than 0?

I agree that it could be compared against NULL_WORD, do you want me to 
change it? (I have no opinion on this issue)

> 
> ---
> 
> General query: are changes like:
> 
> Foo f = NULL;
> 
> to
> 
> Foo f(NULL);
> 
> just making the implicit constructor selection explicit? (I have to say 
> it would nice if whenever you saw " x = NULL" that you knew x was a 
> pointer type.)

Yes, with my original NULL two conversions would not compile. Assigning 
is adding one extra step from direct construction. The new style is IMO 
easier to read.

> ---
> 
> src/hotspot/cpu/sparc/c1_CodeStubs_sparc.cpp
> 
> -    AddressLiteral addrlit(NULL, metadata_Relocation::spec(_index));
> +    AddressLiteral addrlit(NULL_WORD, metadata_Relocation::spec(_index));
> 
> If the first constructor arg is an "address" which seems to be the 
> pointer type "uchar*" then surely NULL is correct here, not NULL_WORD
The first argument (to the constructor) can be address (u_char*), 
jobject (_jobject*), or intptr_t (not a pointer). Surely if we send a 
pointer (NULL), the compiler would have a hard time choosing between the 
first two pointer constructors, right? Therefore we send a non-pointer!

The reason this worked is because I believe integer /promotion/ has 
precedence over 0-to-pointer /implicit conversion/, but I am not too 
knowledgable about these things... Thus the old NULL prefer to be 
promoted to integral type! Do you agree or am I misunderstanding something?

This is /highly/ non-intuitive and one /strong/ argument to change NULL 
to be of the future c++11 nullptr_t type! (or my failed null_t type).

> 
> Same applies to other AddressLiteral changes.

After above reasoning, I should keep my changes right?

> ---
> 
> src/hotspot/share/code/codeHeapState.cpp
> 
> This doesn't look right:
> 
> -    if (((_termString) != NULL) && (strlen(_termString) > 0)){\
> +    if (((_termString) != 0) && (strlen(_termString) > 0)){   \
> 
> _termString should be a "const char*" so comparison against NULL is 
> correct.

No, _termString is mostly a string literal and not a "const char*" and 
/can not/ in the instances of literal be NULL. Thus the (windows) 
compiler (rightly) will complain if you use a "proper" pointer-type 
NULL. The "technical" reason is that the array will not decay to a 
pointer and my original NULL will thus not convert to the array. Which 
makes sense.

The reason I did not remove the test is that I was afraid that I either 
missed some invocation of the macro where _termString really was of 
pointer type, or that someone in the future would use it as such. The 
real problem here is macros (and c strings).

It is a pity that neither gcc nor clang complains (I am not sure about 
the standard), I am bit unsure about nullptr behaviour in the future in 
this regard, but I hope it will complain as good as my failed NULL!

> ---
> 
> share/compiler/directivesParser.cpp
> 
>  > new NULL value does unfortunately not convert to method pointers
> 
> Is that a temporary limitation? I'm not sure what progress were are 
> making if some pointers can use NULL and others must use 0.

I believe this to be a temporary limitation, my understanding is that 
when we get c++11 nullptr will convert to method pointers as well, I 
could and think I should revert these changes as I did revert my 
implementation of NULL, would you like that? My null_t type could, to my 
knowledge, not auto convert to method pointers at least not without 
template varargs stuff that comes in later versions of the standard.

> 
> ---
> 
> src/hotspot/share/jfr/jni/jfrJavaCall.cpp
> 
> I don't understand why the NULL check has been removed:
> 
> -  assert(_storage != NULL, "invariant");
> +  assert(_storage_index < (int) ARRAY_SIZE(_storage), "invariant");

Because _storage can not be NULL (it is an array and not a pointer). I 
feel the new assert is strictly superior! Also an example of the power 
of static typing and more precise types.

> 
> ---
> 
> src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp
> 
> -    if (value != NULL) { \
> +    if ((value) != 0) { \
> 
> Unclear what type value is - in some cases char* in others I can't 
> determine.

This is the same issue as before with string literals and macros, my 
arguing is that I will not try to prove that value is (or in the future 
always will be) a string literal, instead I will make the code work for 
both string literals and pointers. I hope you do not mind the extra 
parentheses I added around "value".

> 
> ---
> 
> src/hotspot/share/jvmci/jvmciExceptions.hpp
> 
> -#define JVMCI_CHECK_0                                  JVMCI_CHECK_(0)
> -#define JVMCI_CHECK_NULL                               JVMCI_CHECK_(NULL)
> +#define JVMCI_CHECK_0 JVMCI_CHECK_(NULL_WORD)
> +#define JVMCI_CHECK_NULL                               JVMCI_CHECK_(0)
> 
> These just seem plain wrong. CHECK_0 is for returning a integral zero 
> value (e.g. jint, jbtye etc), nothing to do with intptr_t and NULL_WORD.

I feel NULL_WORD act as a more restricted 0 integral literal than the 
zero literal itself (it does not auto converts to pointers). The size of 
the zero constant is of less interest to me.

> Having CHECK_NULL become CHECK_(0) seems completely wrong when you 
> consider that if you were expanding by hand in a function returning a 
> pointer then you would write "return NULL".

I agree it is not as distinct as NULL. Now for example, JVMCIObjectArray 
is not a pointer (strictly at least, it is a class). With my first NULL 
it would need to go through two conversions to become a JVMCIObjectArray 
(NULL converted to any pointer and any pointer converted to 
JVMCIObjectArray), thus it would not compile.

There are several solutions to this including:
1) use 0 literal as I did.
2) change JVMCIObjectArray constructor to take a null(ptr)_t instead of 
a pointer (this is a nice example of how a specific null type can make 
that constructor /total/ and not require the assert(v == NULL, "must be 
NULL");). Although this is a tempting solution, I had to abandon my own 
NULL and this solution is no longer possible until c++11.
3) use NULL as before, and fix it when we get nullptr_t.
4) create another macro in some way?

> Further if this chasnge is 
> necessary/desirable for JVMCI_CHECK_NULL then surely the same is true 
> for the basic CHECK_NULL in exceptions.hpp ?

No, the same is not true for CHECK_NULL. I have mostly changed stuff 
that does not compile. This still compiles.

I could change CHECK_0 to use NULL_WORD though and if I do, I get 
compiler errors. CHECK_0 is used for both pointers and integral zeroes 
and types that implicitly converts from either. What the original 
thought was is unclear to me; is CHECK_0 to be used to return things 
convertible from pointers? If so, what is CHECK_NULL for?

> 
> Aside:
> 
> ./share/jvmci/jvmciCompilerToVM.cpp
> 
> C2V_VMENTRY_0(jboolean, isInternedString, (JNIEnv* env, jobject, jobject 
> object))
>    Handle str = JVMCIENV->asConstant(JVMCIENV->wrap(object), 
> JVMCI_CHECK_0);
>    if (!java_lang_String::is_instance(str())) {
>      return false;
> 
> CHECK_0 should be CHECK_false. Ditto in isInstance, 
> isCurrentThreadAttached,  attachCurrentThread.
> 

Maybe all this should be cleaned up separately? Maybe this macro 
solution could be improved? And preferably not in this change?

> 
> src/hotspot/share/runtime/sweeper.hpp
> 
>  > gcc does unfortunately not know that our NULL is a safe format string
> 
> -  static void log_sweep(const char* msg, const char* format = NULL, 
> ...) ATTRIBUTE_PRINTF(2, 3);
> +  static void log_sweep(const char* msg, const char* format = 0, ...) 
> ATTRIBUTE_PRINTF(2, 3);
> 
> I don't follow. This isn't "our NULL" we're using the toolchain's NULL, 
> and format is a pointer type so this should be fine. Is gcc broken?

Sorry, my comment is unclear, this was from my original failed NULL 
implementation. I will revert this to NULL as I expect this to work with 
nullptr_t in the future.


> ---
> 
> src/hotspot/share/utilities/globalDefinitions_solstudio.hpp
> 
> It isn't clear that the issue of passing 0 as NULL in a varargs 
> situation is now fixed in Sol Studio.
> 

I guess tier1-3 would fail if not? Maybe that is a false assumption of mine?

> 
> src/hotspot/share/utilities/linkedlist.hpp
> 
> -    return equal<E>(_data, t, NULL);
> +    return equal<E>(_data, t, 0);
> 
> Again concerned about the method/function pointer situation.

I am not concerned :-) but I will revert this as it is not necessary now 
or in the future as I reverted my original implementation and c++11 will 
fix this.

> 
> ----
> 
> That's all from me.
> 
> Thanks,
> David
> -----

Before ending this mail, I also want to raise one question. Maybe I 
ought to #ifdef a #define NULL __null for the gcc tool chain to give 
better typing support until the real nullptr? I would hate to wait, but 
maybe changing to c++11 will take longer than I hope for.


Many thanks!
Leo
> 
> 
> 
> On 30/03/2020 10:24 am, Leo Korinth wrote:
>> Hi, could I have a review on this change to improve NULL?
>>
>> I try to:
>> 1) make NULL only convert to pointer types for better type safety, 
>> __null on gcc achieves some of this, but can be casted away.
>> 2) define NULL in one place, in a platform independent way, no need to 
>> define it for every tool chain.
>> 3) make NULL be of a special type (null_t) so that one can template 
>> specialise against it. This is for the future!
>>
>> I fail with this, but continue to read...
>>
>> I try to achieve this by defining NULL as a variable of type null_t in 
>> the header null.hpp using ODR. null_t converts to (almost) any pointer 
>> type.
>>
>> Minor negative issues:
>> 1) No conversion to method pointers, they are only used in three 
>> places though, I use a literal 0 instead.
>> 2) gcc does unfortunately not know that our NULL is a safe format 
>> string, only needed to change one place.
>> 3) No constexpr yet, so I needed to use 0 to initialise a thread 
>> variable.
>>
>> IMO positive issues:
>> 1) I have fixed many bad usages of NULL for integer types and instead 
>> used NULL_WORD or 0.
>> 2) Too many implicit conversions in certain places makes the code not 
>> compile, the changed code does look better and more idiomatic IMO.
>> 3) Possible benign bug found where fields are initialised to NULL (not 
>> in constructor), this should not be possible right?
>> 4) NULL does not convert to array types, on Windows this results in 
>> compile time error as the windows compiler does not decay arrays when 
>> comparing to null_t, this did locate one bug, and possibly some 
>> unnecessary comparisons in xmacros that I kept by changing NULL to 0.
>> 5) Found a reinterpret_cast on NULL that is not allowed by the standard.
>>
>> I also made a new NULL_WORD in the same way as I did NULL, but instead 
>> converting to a intptr_t of value 0. I was about to release NULL_WORD 
>> as a separate patch, but due to the current practice of defining 
>> NULL_WORD as NULL on many platforms, it was easier to merge these two 
>> changes into one. Only small issues in JVMCI code for this change, and 
>> one fix in sparc assembler. I find NULL_WORD questionable, but it is 
>> widely used.
>>
>> Something to notice is the strange comment in the solstudio header 
>> about intptr_t being wider than int on some systems that is to my 
>> understanding false (for systems we support). I have a static assert 
>> to check that intptr_t is indeed of (char) pointer size. I did remove 
>> an assert that checks the size of NULL.
>>
>> Now, the sad thing is that my change does not work fully, I can not 
>> make NULL constexpr before c++ 11, and thus several initialisation 
>> order bugs appear on global variables set to NULL. I could set them to 
>> zero or not set them at all, and they would work, but it feels brittle 
>> and could cause future bugs when people expect NULLs on globals to be 
>> initialised directly. Thus instead, I propose that I revert somewhat 
>> and define NULL as 0 and change NULL_WORD to a constant 0 of type 
>> intptr_t.
>>
>> When we get c++>=11 we can define NULL to nullptr of type nullptr_t, 
>> and hopefully that will be a simple fix after this change.
>>
>> What do you think?
>>
>> Below I have given a short description of the changes:
>> src/hotspot/cpu/aarch64/interpreterRT_aarch64.cpp
>> correct the usage of integer type from NULL to NULL_WORD
>>
>> src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
>> too many implicit conversions
>>
>> src/hotspot/cpu/arm/interpreterRT_arm.cpp
>> correct the usage of integer type from NULL to NULL_WORD
>>
>> src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
>> too many implicit conversions
>>
>> src/hotspot/cpu/sparc/c1_CodeStubs_sparc.cpp
>> correct the usage of integer type from NULL to NULL_WORD
>>
>> src/hotspot/cpu/sparc/c1_LIRAssembler_sparc.cpp
>> correct the usage of integer type from NULL to NULL_WORD, and the
>> opposite way as well!
>>
>> src/hotspot/cpu/sparc/interpreterRT_sparc.cpp
>> correct the usage of integer type from NULL to NULL_WORD
>>
>> src/hotspot/cpu/x86/interp_masm_x86.cpp
>> correct the usage of integer type from NULL to NULL_WORD
>>
>> src/hotspot/cpu/x86/interpreterRT_x86_64.cpp
>> correct the usage of integer type from NULL to NULL_WORD
>>
>> src/hotspot/cpu/x86/stubGenerator_x86_64.cpp
>> correct the usage of integer type from NULL to NULL_WORD
>>
>> src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp
>> correct member initialisation to be in initialise list, gcc bug?
>>
>> src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp
>> correct member initialisation to be in initialise list, gcc bug?
>>
>> src/hotspot/os/solaris/attachListener_solaris.cpp
>> correct the usage of integer type from NULL to 0
>>
>> src/hotspot/os/windows/osThread_windows.cpp
>> correct the usage of integer type from NULL to 0
>>
>> src/hotspot/os/windows/os_windows.cpp
>> correct the usage of integer type from NULL to 0
>>
>> src/hotspot/os/windows/pdh_interface.cpp
>> new NULL value does unfortunately not convert to method pointers
>>
>> src/hotspot/share/classfile/javaClasses.cpp
>> too many implicit conversions
>>
>> src/hotspot/share/classfile/moduleEntry.hpp
>> too many implicit conversions
>>
>> src/hotspot/share/classfile/modules.cpp
>> too many implicit conversions
>>
>> src/hotspot/share/code/codeHeapState.cpp
>> new NULL does not compare with arrays witch is great, maybe (xmacros) 
>> the NULL check can be removed, this change is safe though
>>
>> src/hotspot/share/compiler/directivesParser.cpp
>> new NULL value does unfortunately not convert to method pointers
>>
>> src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp
>> remove unnecessary system include, might redefine NULL
>>
>> src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp
>> remove unnecessary system include, might redefine NULL
>>
>> src/hotspot/share/gc/parallel/psParallelCompact.cpp
>> remove unnecessary system include, might redefine NULL
>>
>> src/hotspot/share/gc/shared/memset_with_concurrent_readers.hpp
>> remove unnecessary system include, might redefine NULL
>>
>> src/hotspot/share/gc/z/zErrno.cpp
>> remove unnecessary system include, might redefine NULL
>>
>> src/hotspot/share/jfr/jni/jfrGetAllEventClasses.cpp
>> fix bad assert found due to new NULL
>>
>> src/hotspot/share/jvmci/jvmciCompilerToVM.cpp
>> too many implicit conversions, and misc changes
>>
>> src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp
>> new NULL does not compare with arrays witch is great, maybe (xmacros) 
>> the NULL check can be removed, this change is safe though
>>
>> src/hotspot/share/jvmci/jvmciExceptions.hpp
>> too many implicit conversions
>>
>> src/hotspot/share/oops/access.hpp reinterpret_cast is not safe on 
>> NULL, rewrite
>>
>> src/hotspot/share/oops/constantPool.cpp
>> too many implicit conversions
>>
>> src/hotspot/share/oops/cpCache.cpp
>> too many implicit conversions
>>
>> src/hotspot/share/oops/klass.cpp
>> too many implicit conversions
>>
>> src/hotspot/share/oops/oopsHierarchy.hpp
>> add null_t overload to oop class
>>
>> src/hotspot/share/prims/jni.cpp
>> too many implicit conversions
>>
>> src/hotspot/share/prims/jvm.cpp
>> too many implicit conversions
>>
>> src/hotspot/share/prims/methodHandles.cpp
>> too many implicit conversions
>>
>> src/hotspot/share/runtime/os.cpp
>> correct the usage of integer type from NULL to 0
>>
>> src/hotspot/share/runtime/os.inline.hpp
>> #undef NULL set by system headers, use the new NULL instead
>>
>> src/hotspot/share/runtime/sweeper.hpp
>> gcc does unfortunately not know that our NULL is a safe format string
>>
>> src/hotspot/share/runtime/thread.cpp
>> our conversion can not be a constexpr before c++11, use 0 instead
>>
>> src/hotspot/share/utilities/debug.hpp
>> stop using system headers for NULL
>>
>> src/hotspot/share/utilities/globalDefinitions.cpp
>> change to static asserts, and remove one sizeof(NULL) assert.
>> I will change the assert on sizeof(uintptr_t) to sizeof(NULL_WORD) in 
>> the next webrev as it is both wrong and not clear (I defined NULL_WORD 
>> to be of type intptr_t and not uintptr_t)
>>
>> src/hotspot/share/utilities/globalDefinitions.hpp
>> include the new definition of NULL *after* system includes, also do 
>> redefinition after include guard end
>>
>> src/hotspot/share/utilities/globalDefinitions_gcc.hpp
>> remove old definition of NULL
>>
>> src/hotspot/share/utilities/globalDefinitions_solstudio.hpp
>> remove old definition of NULL
>>
>> src/hotspot/share/utilities/globalDefinitions_visCPP.hpp
>> remove old definition of NULL
>>
>> src/hotspot/share/utilities/globalDefinitions_xlc.hpp
>> remove old definition of NULL
>>
>> src/hotspot/share/utilities/linkedlist.hpp
>> new NULL value does unfortunately not convert to method pointers
>>
>> src/hotspot/share/utilities/null.hpp
>> new NULL
>>
>> test/hotspot/gtest/gc/z/test_zAddress.cpp
>> correct the usage of integer type from NULL to NULL_WORD
>>
>> test/hotspot/gtest/unittest.hpp
>> do not use system NULL
>>
>> I have a follow up patch that tries to (totally) remove usage of 
>> system header NULL, but that is orthogonal to this patch so I did not 
>> want to mix them if the approach is not liked.
>>
>> Bug:
>> https://bugs.openjdk.java.net/browse/JDK-8240110
>>
>> Webrev:
>> http://cr.openjdk.java.net/~lkorinth/8240110/part1 (NULL)
>> http://cr.openjdk.java.net/~lkorinth/8240110/part2 (NULL_WORD)
>> http://cr.openjdk.java.net/~lkorinth/8240110/part3 (reverse somewhat)
>> http://cr.openjdk.java.net/~lkorinth/8240110/0     (combined change 
>> that I will create incremental changes against)
>>
>> Testing:
>> tier 1-3
>>
>> Thanks,
>> Leo


More information about the hotspot-dev mailing list