diff options
-rw-r--r-- | benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java | 74 | ||||
-rw-r--r-- | compiler/optimizing/instruction_simplifier.cc | 17 | ||||
-rw-r--r-- | compiler/optimizing/nodes.h | 9 | ||||
-rw-r--r-- | runtime/class_linker.cc | 2 | ||||
-rw-r--r-- | runtime/image.cc | 4 | ||||
-rw-r--r-- | runtime/string_builder_append.cc | 162 | ||||
-rw-r--r-- | runtime/well_known_classes.cc | 38 | ||||
-rw-r--r-- | runtime/well_known_classes.h | 6 | ||||
-rw-r--r-- | test/697-checker-string-append/src/Main.java | 156 |
9 files changed, 451 insertions, 17 deletions
diff --git a/benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java b/benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java index 1550e81bf7..d710e3409d 100644 --- a/benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java +++ b/benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java @@ -20,6 +20,10 @@ public class StringBuilderAppendBenchmark { public static String longString1 = "This is a long string 1"; public static String longString2 = "This is a long string 2"; public static int int1 = 42; + public static double double1 = 42.0; + public static double double2 = 1.0E308; + public static float float1 = 42.0f; + public static float float2 = 1.0E38f; public void timeAppendStrings(int count) { String s1 = string1; @@ -59,4 +63,74 @@ public class StringBuilderAppendBenchmark { throw new AssertionError(); } } + + public void timeAppendStringAndDouble(int count) { + String s1 = string1; + double d1 = double1; + int sum = 0; + for (int i = 0; i < count; ++i) { + String result = s1 + d1; + sum += result.length(); // Make sure the append is not optimized away. + } + if (sum != count * (s1.length() + Double.toString(d1).length())) { + throw new AssertionError(); + } + } + + public void timeAppendStringAndHugeDouble(int count) { + String s1 = string1; + double d2 = double2; + int sum = 0; + for (int i = 0; i < count; ++i) { + String result = s1 + d2; + sum += result.length(); // Make sure the append is not optimized away. + } + if (sum != count * (s1.length() + Double.toString(d2).length())) { + throw new AssertionError(); + } + } + + public void timeAppendStringAndFloat(int count) { + String s1 = string1; + float f1 = float1; + int sum = 0; + for (int i = 0; i < count; ++i) { + String result = s1 + f1; + sum += result.length(); // Make sure the append is not optimized away. + } + if (sum != count * (s1.length() + Float.toString(f1).length())) { + throw new AssertionError(); + } + } + + public void timeAppendStringAndHugeFloat(int count) { + String s1 = string1; + float f2 = float2; + int sum = 0; + for (int i = 0; i < count; ++i) { + String result = s1 + f2; + sum += result.length(); // Make sure the append is not optimized away. + } + if (sum != count * (s1.length() + Float.toString(f2).length())) { + throw new AssertionError(); + } + } + + public void timeAppendStringDoubleStringAndFloat(int count) { + String s1 = string1; + String s2 = string2; + double d1 = double1; + float f1 = float1; + int sum = 0; + for (int i = 0; i < count; ++i) { + String result = s1 + d1 + s2 + f1; + sum += result.length(); // Make sure the append is not optimized away. + } + if (sum != count * (s1.length() + + Double.toString(d1).length() + + s2.length() + + Float.toString(f1).length())) { + throw new AssertionError(); + } + } } diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc index 82c1f6d3ff..a2e9f69933 100644 --- a/compiler/optimizing/instruction_simplifier.cc +++ b/compiler/optimizing/instruction_simplifier.cc @@ -2652,6 +2652,7 @@ static bool TryReplaceStringBuilderAppend(HInvoke* invoke) { bool seen_to_string = false; uint32_t format = 0u; uint32_t num_args = 0u; + bool has_fp_args = false; HInstruction* args[StringBuilderAppend::kMaxArgs]; // Added in reverse order. for (HBackwardInstructionIterator iter(block->GetInstructions()); !iter.Done(); iter.Advance()) { HInstruction* user = iter.Current(); @@ -2697,6 +2698,14 @@ static bool TryReplaceStringBuilderAppend(HInvoke* invoke) { case Intrinsics::kStringBuilderAppendLong: arg = StringBuilderAppend::Argument::kLong; break; + case Intrinsics::kStringBuilderAppendFloat: + arg = StringBuilderAppend::Argument::kFloat; + has_fp_args = true; + break; + case Intrinsics::kStringBuilderAppendDouble: + arg = StringBuilderAppend::Argument::kDouble; + has_fp_args = true; + break; case Intrinsics::kStringBuilderAppendCharSequence: { ReferenceTypeInfo rti = user->AsInvokeVirtual()->InputAt(1)->GetReferenceTypeInfo(); if (!rti.IsValid()) { @@ -2716,10 +2725,6 @@ static bool TryReplaceStringBuilderAppend(HInvoke* invoke) { } break; } - case Intrinsics::kStringBuilderAppendFloat: - case Intrinsics::kStringBuilderAppendDouble: - // TODO: Unimplemented, needs to call FloatingDecimal.getBinaryToASCIIConverter(). - return false; default: { return false; } @@ -2772,8 +2777,8 @@ static bool TryReplaceStringBuilderAppend(HInvoke* invoke) { // Create replacement instruction. HIntConstant* fmt = block->GetGraph()->GetIntConstant(static_cast<int32_t>(format)); ArenaAllocator* allocator = block->GetGraph()->GetAllocator(); - HStringBuilderAppend* append = - new (allocator) HStringBuilderAppend(fmt, num_args, allocator, invoke->GetDexPc()); + HStringBuilderAppend* append = new (allocator) HStringBuilderAppend( + fmt, num_args, has_fp_args, allocator, invoke->GetDexPc()); append->SetReferenceTypeInfo(invoke->GetReferenceTypeInfo()); for (size_t i = 0; i != num_args; ++i) { append->SetArgumentAt(i, args[num_args - 1u - i]); diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 591087bae6..cbb55918cf 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -7503,14 +7503,17 @@ class HStringBuilderAppend final : public HVariableInputSizeInstruction { public: HStringBuilderAppend(HIntConstant* format, uint32_t number_of_arguments, + bool has_fp_args, ArenaAllocator* allocator, uint32_t dex_pc) : HVariableInputSizeInstruction( kStringBuilderAppend, DataType::Type::kReference, - // The runtime call may read memory from inputs. It never writes outside - // of the newly allocated result object (or newly allocated helper objects). - SideEffects::AllReads().Union(SideEffects::CanTriggerGC()), + SideEffects::CanTriggerGC().Union( + // The runtime call may read memory from inputs. It never writes outside + // of the newly allocated result object or newly allocated helper objects, + // except for float/double arguments where we reuse thread-local helper objects. + has_fp_args ? SideEffects::AllWritesAndReads() : SideEffects::AllReads()), dex_pc, allocator, number_of_arguments + /* format */ 1u, diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index ed87669760..a2d07a5a7e 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -1163,6 +1163,8 @@ void ClassLinker::RunRootClinits(Thread* self) { WellKnownClasses::java_lang_invoke_MethodHandles_lookup, // Ensure `DirectByteBuffer` class is initialized (avoid check at runtime). WellKnownClasses::java_nio_DirectByteBuffer_init, + // Ensure `FloatingDecimal` class is initialized (avoid check at runtime). + WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D, // Ensure reflection annotation classes are initialized (avoid check at runtime). WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation, WellKnownClasses::libcore_reflect_AnnotationMember_init, diff --git a/runtime/image.cc b/runtime/image.cc index 5030b494c6..133782dc49 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -31,8 +31,8 @@ namespace art { const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -// Last change: Math.fma(double, double, double) intrinsic. -const uint8_t ImageHeader::kImageVersion[] = { '1', '0', '6', '\0' }; +// Last change: StringBuilderAppend for float/double. +const uint8_t ImageHeader::kImageVersion[] = { '1', '0', '7', '\0' }; ImageHeader::ImageHeader(uint32_t image_reservation_size, uint32_t component_count, diff --git a/runtime/string_builder_append.cc b/runtime/string_builder_append.cc index 85b70eb12b..ef6969d4d0 100644 --- a/runtime/string_builder_append.cc +++ b/runtime/string_builder_append.cc @@ -20,9 +20,11 @@ #include "base/logging.h" #include "common_throws.h" #include "gc/heap.h" +#include "mirror/array-inl.h" #include "mirror/string-alloc-inl.h" #include "obj_ptr-inl.h" #include "runtime.h" +#include "well_known_classes.h" namespace art { @@ -60,6 +62,11 @@ class StringBuilderAppend::Builder { return new_string->GetLength() - (data - new_string->GetValue()); } + template <typename CharType> + CharType* AppendFpArg(ObjPtr<mirror::String> new_string, + CharType* data, + size_t fp_arg_index) const REQUIRES_SHARED(Locks::mutator_lock_); + template <typename CharType, size_t size> static CharType* AppendLiteral(ObjPtr<mirror::String> new_string, CharType* data, @@ -75,6 +82,8 @@ class StringBuilderAppend::Builder { CharType* data, int64_t value) REQUIRES_SHARED(Locks::mutator_lock_); + int32_t ConvertFpArgs() REQUIRES_SHARED(Locks::mutator_lock_); + template <typename CharType> void StoreData(ObjPtr<mirror::String> new_string, CharType* data) const REQUIRES_SHARED(Locks::mutator_lock_); @@ -93,6 +102,12 @@ class StringBuilderAppend::Builder { // References are moved to the handle scope during CalculateLengthWithFlag(). StackHandleScope<kMaxArgs> hs_; + // We convert float/double values using jdk.internal.math.FloatingDecimal which uses + // a thread-local converter under the hood. As we may have more than one + // float/double argument, we need to copy the data out of the converter. + uint8_t converted_fp_args_[kMaxArgs][26]; // 26 is the maximum number of characters. + int32_t converted_fp_arg_lengths_[kMaxArgs]; + // The length and flag to store when the AppendBuilder is used as a pre-fence visitor. int32_t length_with_flag_ = 0u; }; @@ -142,6 +157,18 @@ inline size_t StringBuilderAppend::Builder::Uint64Length(uint64_t value) { return log10_value_estimate + adjustment; } +template <typename CharType> +inline CharType* StringBuilderAppend::Builder::AppendFpArg(ObjPtr<mirror::String> new_string, + CharType* data, + size_t fp_arg_index) const { + DCHECK_LE(fp_arg_index, std::size(converted_fp_args_)); + const uint8_t* src = converted_fp_args_[fp_arg_index]; + size_t length = converted_fp_arg_lengths_[fp_arg_index]; + DCHECK_LE(length, std::size(converted_fp_args_[0])); + DCHECK_LE(length, RemainingSpace(new_string, data)); + return std::copy_n(src, length, data); +} + template <typename CharType, size_t size> inline CharType* StringBuilderAppend::Builder::AppendLiteral(ObjPtr<mirror::String> new_string, CharType* data, @@ -204,10 +231,111 @@ inline CharType* StringBuilderAppend::Builder::AppendInt64(ObjPtr<mirror::String return data + length; } +int32_t StringBuilderAppend::Builder::ConvertFpArgs() { + int32_t fp_args_length = 0u; + const uint32_t* current_arg = args_; + size_t fp_arg_index = 0u; + for (uint32_t f = format_; f != 0u; f >>= kBitsPerArg) { + DCHECK_LE(f & kArgMask, static_cast<uint32_t>(Argument::kLast)); + bool fp_arg = false; + ObjPtr<mirror::Object> converter; + switch (static_cast<Argument>(f & kArgMask)) { + case Argument::kString: + case Argument::kBoolean: + case Argument::kChar: + case Argument::kInt: + break; + case Argument::kLong: { + current_arg = AlignUp(current_arg, sizeof(int64_t)); + ++current_arg; // Skip the low word, let the common code skip the high word. + break; + } + case Argument::kFloat: { + fp_arg = true; + float arg = bit_cast<float>(*current_arg); + converter = WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F + ->InvokeStatic<'L', 'F'>(hs_.Self(), arg); + break; + } + case Argument::kDouble: { + fp_arg = true; + current_arg = AlignUp(current_arg, sizeof(int64_t)); + double arg = bit_cast<double>( + static_cast<uint64_t>(current_arg[0]) + (static_cast<uint64_t>(current_arg[1]) << 32)); + converter = WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D + ->InvokeStatic<'L', 'D'>(hs_.Self(), arg); + ++current_arg; // Skip the low word, let the common code skip the high word. + break; + } + case Argument::kStringBuilder: + case Argument::kCharArray: + case Argument::kObject: + LOG(FATAL) << "Unimplemented arg format: 0x" << std::hex + << (f & kArgMask) << " full format: 0x" << std::hex << format_; + UNREACHABLE(); + default: + LOG(FATAL) << "Unexpected arg format: 0x" << std::hex + << (f & kArgMask) << " full format: 0x" << std::hex << format_; + UNREACHABLE(); + } + if (fp_arg) { + // If we see an exception (presumably OOME or SOE), keep it as is, even + // though it may be confusing to see the stack trace for FP argument + // conversion continue at the StringBuilder.toString() invoke location. + DCHECK_EQ(converter == nullptr, hs_.Self()->IsExceptionPending()); + if (UNLIKELY(converter == nullptr)) { + return -1; + } + ArtField* btab_buffer_field = + WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer; + int32_t length; + if (converter->GetClass() == btab_buffer_field->GetDeclaringClass()) { + // Call `converter.getChars(converter.buffer)`. + StackHandleScope<1u> hs2(hs_.Self()); + Handle<mirror::CharArray> buffer = + hs2.NewHandle(btab_buffer_field->GetObj<mirror::CharArray>(converter)); + DCHECK(buffer != nullptr); + length = WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars + ->InvokeInstance<'I', 'L'>(hs_.Self(), converter, buffer.Get()); + if (UNLIKELY(hs_.Self()->IsExceptionPending())) { + return -1; + } + // The converted string is now at the front of the buffer. + DCHECK_GT(length, 0); + DCHECK_LE(length, buffer->GetLength()); + DCHECK_LE(static_cast<size_t>(length), std::size(converted_fp_args_[0])); + DCHECK(mirror::String::AllASCII(buffer->GetData(), length)); + std::copy_n(buffer->GetData(), length, converted_fp_args_[fp_arg_index]); + } else { + ArtField* ebtab_image_field = WellKnownClasses:: + jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image; + DCHECK(converter->GetClass() == ebtab_image_field->GetDeclaringClass()); + ObjPtr<mirror::String> converted = ebtab_image_field->GetObj<mirror::String>(converter); + DCHECK(converted != nullptr); + length = converted->GetLength(); + if (mirror::kUseStringCompression) { + DCHECK(converted->IsCompressed()); + memcpy(converted_fp_args_[fp_arg_index], converted->GetValueCompressed(), length); + } else { + DCHECK(mirror::String::AllASCII(converted->GetValue(), length)); + std::copy_n(converted->GetValue(), length, converted_fp_args_[fp_arg_index]); + } + } + converted_fp_arg_lengths_[fp_arg_index] = length; + fp_args_length += length; + ++fp_arg_index; + } + ++current_arg; + DCHECK_LE(fp_arg_index, kMaxArgs); + } + return fp_args_length; +} + inline int32_t StringBuilderAppend::Builder::CalculateLengthWithFlag() { static_assert(static_cast<size_t>(Argument::kEnd) == 0u, "kEnd must be 0."); bool compressible = mirror::kUseStringCompression; uint64_t length = 0u; + bool has_fp_args = false; const uint32_t* current_arg = args_; for (uint32_t f = format_; f != 0u; f >>= kBitsPerArg) { DCHECK_LE(f & kArgMask, static_cast<uint32_t>(Argument::kLast)); @@ -243,12 +371,19 @@ inline int32_t StringBuilderAppend::Builder::CalculateLengthWithFlag() { ++current_arg; // Skip the low word, let the common code skip the high word. break; } + case Argument::kDouble: + current_arg = AlignUp(current_arg, sizeof(int64_t)); + ++current_arg; // Skip the low word, let the common code skip the high word. + FALLTHROUGH_INTENDED; + case Argument::kFloat: + // Conversion shall be performed in a separate pass because it calls back to + // managed code and we need to convert reference arguments to `Handle<>`s first. + has_fp_args = true; + break; case Argument::kStringBuilder: case Argument::kCharArray: case Argument::kObject: - case Argument::kFloat: - case Argument::kDouble: LOG(FATAL) << "Unimplemented arg format: 0x" << std::hex << (f & kArgMask) << " full format: 0x" << std::hex << format_; UNREACHABLE(); @@ -261,6 +396,16 @@ inline int32_t StringBuilderAppend::Builder::CalculateLengthWithFlag() { DCHECK_LE(hs_.NumberOfReferences(), kMaxArgs); } + if (UNLIKELY(has_fp_args)) { + // Call Java helpers to convert FP args. + int32_t fp_args_length = ConvertFpArgs(); + if (fp_args_length == -1) { + return -1; + } + DCHECK_GT(fp_args_length, 0); + length += fp_args_length; + } + if (length > std::numeric_limits<int32_t>::max()) { // We cannot allocate memory for the entire result. hs_.Self()->ThrowNewException("Ljava/lang/OutOfMemoryError;", @@ -276,6 +421,7 @@ template <typename CharType> inline void StringBuilderAppend::Builder::StoreData(ObjPtr<mirror::String> new_string, CharType* data) const { size_t handle_index = 0u; + size_t fp_arg_index = 0u; const uint32_t* current_arg = args_; for (uint32_t f = format_; f != 0u; f >>= kBitsPerArg) { DCHECK_LE(f & kArgMask, static_cast<uint32_t>(Argument::kLast)); @@ -315,11 +461,18 @@ inline void StringBuilderAppend::Builder::StoreData(ObjPtr<mirror::String> new_s ++current_arg; // Skip the low word, let the common code skip the high word. break; } + case Argument::kDouble: + current_arg = AlignUp(current_arg, sizeof(int64_t)); + ++current_arg; // Skip the low word, let the common code skip the high word. + FALLTHROUGH_INTENDED; + case Argument::kFloat: { + data = AppendFpArg(new_string, data, fp_arg_index); + ++fp_arg_index; + break; + } case Argument::kStringBuilder: case Argument::kCharArray: - case Argument::kFloat: - case Argument::kDouble: LOG(FATAL) << "Unimplemented arg format: 0x" << std::hex << (f & kArgMask) << " full format: 0x" << std::hex << format_; UNREACHABLE(); @@ -330,6 +483,7 @@ inline void StringBuilderAppend::Builder::StoreData(ObjPtr<mirror::String> new_s } ++current_arg; DCHECK_LE(handle_index, hs_.NumberOfReferences()); + DCHECK_LE(fp_arg_index, std::size(converted_fp_args_)); } DCHECK_EQ(RemainingSpace(new_string, data), 0u) << std::hex << format_; } diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc index 397f2bd13e..50f0b615f9 100644 --- a/runtime/well_known_classes.cc +++ b/runtime/well_known_classes.cc @@ -104,6 +104,9 @@ ArtMethod* WellKnownClasses::java_lang_reflect_Proxy_invoke; ArtMethod* WellKnownClasses::java_nio_Buffer_isDirect; ArtMethod* WellKnownClasses::java_nio_DirectByteBuffer_init; ArtMethod* WellKnownClasses::java_util_function_Consumer_accept; +ArtMethod* WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D; +ArtMethod* WellKnownClasses::jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F; +ArtMethod* WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars; ArtMethod* WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation; ArtMethod* WellKnownClasses::libcore_reflect_AnnotationMember_init; ArtMethod* WellKnownClasses::org_apache_harmony_dalvik_ddmc_DdmServer_broadcast; @@ -148,6 +151,8 @@ ArtField* WellKnownClasses::java_nio_ByteBuffer_hb; ArtField* WellKnownClasses::java_nio_ByteBuffer_isReadOnly; ArtField* WellKnownClasses::java_nio_ByteBuffer_offset; ArtField* WellKnownClasses::java_util_Collections_EMPTY_LIST; +ArtField* WellKnownClasses::jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer; +ArtField* WellKnownClasses::jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image; ArtField* WellKnownClasses::libcore_util_EmptyArray_STACK_TRACE_ELEMENT; ArtField* WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_data; ArtField* WellKnownClasses::org_apache_harmony_dalvik_ddmc_Chunk_length; @@ -352,7 +357,7 @@ void WellKnownClasses::InitFieldsAndMethodsOnly(JNIEnv* env) { java_lang_Short_valueOf = CachePrimitiveBoxingMethod(class_linker, self, 'S', "Ljava/lang/Short;"); - StackHandleScope<39u> hs(self); + StackHandleScope<42u> hs(self); Handle<mirror::Class> d_s_bdcl = hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/BaseDexClassLoader;")); Handle<mirror::Class> d_s_dlcl = @@ -421,6 +426,12 @@ void WellKnownClasses::InitFieldsAndMethodsOnly(JNIEnv* env) { hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/util/Collections;")); Handle<mirror::Class> j_u_f_c = hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/util/function/Consumer;")); + Handle<mirror::Class> j_i_m_fd = + hs.NewHandle(FindSystemClass(class_linker, self, "Ljdk/internal/math/FloatingDecimal;")); + Handle<mirror::Class> j_i_m_fd_btab = hs.NewHandle(FindSystemClass( + class_linker, self, "Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;")); + Handle<mirror::Class> j_i_m_fd_ebtab = hs.NewHandle(FindSystemClass( + class_linker, self, "Ljdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer;")); Handle<mirror::Class> l_r_af = hs.NewHandle(FindSystemClass(class_linker, self, "Llibcore/reflect/AnnotationFactory;")); Handle<mirror::Class> l_r_am = @@ -604,6 +615,21 @@ void WellKnownClasses::InitFieldsAndMethodsOnly(JNIEnv* env) { java_util_function_Consumer_accept = CacheMethod( j_u_f_c.Get(), /*is_static=*/ false, "accept", "(Ljava/lang/Object;)V", pointer_size); + jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D = CacheMethod( + j_i_m_fd.Get(), + /*is_static=*/ true, + "getBinaryToASCIIConverter", + "(D)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;", + pointer_size); + jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F = CacheMethod( + j_i_m_fd.Get(), + /*is_static=*/ true, + "getBinaryToASCIIConverter", + "(F)Ljdk/internal/math/FloatingDecimal$BinaryToASCIIConverter;", + pointer_size); + jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars = + CacheMethod(j_i_m_fd_btab.Get(), /*is_static=*/ false, "getChars", "([C)I", pointer_size); + libcore_reflect_AnnotationFactory_createAnnotation = CacheMethod( l_r_af.Get(), /*is_static=*/ true, @@ -712,6 +738,11 @@ void WellKnownClasses::InitFieldsAndMethodsOnly(JNIEnv* env) { java_util_Collections_EMPTY_LIST = CacheField(j_u_c.Get(), /*is_static=*/ true, "EMPTY_LIST", "Ljava/util/List;"); + jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer = + CacheField(j_i_m_fd_btab.Get(), /*is_static=*/ false, "buffer", "[C"); + jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image = CacheField( + j_i_m_fd_ebtab.Get(), /*is_static=*/ false, "image", "Ljava/lang/String;"); + libcore_util_EmptyArray_STACK_TRACE_ELEMENT = CacheField( l_u_ea.Get(), /*is_static=*/ true, "STACK_TRACE_ELEMENT", "[Ljava/lang/StackTraceElement;"); @@ -819,6 +850,9 @@ void WellKnownClasses::Clear() { java_lang_reflect_Proxy_invoke = nullptr; java_nio_Buffer_isDirect = nullptr; java_nio_DirectByteBuffer_init = nullptr; + jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D = nullptr; + jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F = nullptr; + jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars = nullptr; libcore_reflect_AnnotationFactory_createAnnotation = nullptr; libcore_reflect_AnnotationMember_init = nullptr; org_apache_harmony_dalvik_ddmc_DdmServer_broadcast = nullptr; @@ -857,6 +891,8 @@ void WellKnownClasses::Clear() { java_nio_ByteBuffer_isReadOnly = nullptr; java_nio_ByteBuffer_offset = nullptr; java_util_Collections_EMPTY_LIST = nullptr; + jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer = nullptr; + jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image = nullptr; libcore_util_EmptyArray_STACK_TRACE_ELEMENT = nullptr; org_apache_harmony_dalvik_ddmc_Chunk_data = nullptr; org_apache_harmony_dalvik_ddmc_Chunk_length = nullptr; diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h index e8af1c1015..0bca63b8c9 100644 --- a/runtime/well_known_classes.h +++ b/runtime/well_known_classes.h @@ -149,6 +149,9 @@ struct WellKnownClasses { static ArtMethod* java_nio_Buffer_isDirect; static ArtMethod* java_nio_DirectByteBuffer_init; static ArtMethod* java_util_function_Consumer_accept; + static ArtMethod* jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_D; + static ArtMethod* jdk_internal_math_FloatingDecimal_getBinaryToASCIIConverter_F; + static ArtMethod* jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_getChars; static ArtMethod* libcore_reflect_AnnotationFactory_createAnnotation; static ArtMethod* libcore_reflect_AnnotationMember_init; static ArtMethod* org_apache_harmony_dalvik_ddmc_DdmServer_broadcast; @@ -192,8 +195,9 @@ struct WellKnownClasses { static ArtField* java_nio_ByteBuffer_hb; static ArtField* java_nio_ByteBuffer_isReadOnly; static ArtField* java_nio_ByteBuffer_offset; - static ArtField* java_util_Collections_EMPTY_LIST; + static ArtField* jdk_internal_math_FloatingDecimal_BinaryToASCIIBuffer_buffer; + static ArtField* jdk_internal_math_FloatingDecimal_ExceptionalBinaryToASCIIBuffer_image; static ArtField* libcore_util_EmptyArray_STACK_TRACE_ELEMENT; static ArtField* org_apache_harmony_dalvik_ddmc_Chunk_data; static ArtField* org_apache_harmony_dalvik_ddmc_Chunk_length; diff --git a/test/697-checker-string-append/src/Main.java b/test/697-checker-string-append/src/Main.java index c63c328aa7..e35986a075 100644 --- a/test/697-checker-string-append/src/Main.java +++ b/test/697-checker-string-append/src/Main.java @@ -18,6 +18,9 @@ public class Main { public static void main(String[] args) { testAppendStringAndLong(); testAppendStringAndInt(); + testAppendStringAndFloat(); + testAppendStringAndDouble(); + testAppendDoubleAndFloat(); testAppendStringAndString(); testMiscelaneous(); testNoArgs(); @@ -186,6 +189,159 @@ public class Main { } } + private static final String APPEND_FLOAT_PREFIX = "Float/"; + private static final String[] APPEND_FLOAT_TEST_CASES = { + // We're testing only exact values here, i.e. values that do not require rounding. + "Float/1.0", + "Float/9.0", + "Float/10.0", + "Float/99.0", + "Float/100.0", + "Float/999.0", + "Float/1000.0", + "Float/9999.0", + "Float/10000.0", + "Float/99999.0", + "Float/100000.0", + "Float/999999.0", + "Float/1000000.0", + "Float/9999999.0", + "Float/1.0E7", + "Float/1.0E10", + "Float/-1.0", + "Float/-9.0", + "Float/-10.0", + "Float/-99.0", + "Float/-100.0", + "Float/-999.0", + "Float/-1000.0", + "Float/-9999.0", + "Float/-10000.0", + "Float/-99999.0", + "Float/-100000.0", + "Float/-999999.0", + "Float/-1000000.0", + "Float/-9999999.0", + "Float/-1.0E7", + "Float/-1.0E10", + "Float/0.25", + "Float/1.625", + "Float/9.3125", + "Float/-0.25", + "Float/-1.625", + "Float/-9.3125", + }; + + /// CHECK-START: java.lang.String Main.$noinline$appendStringAndFloat(java.lang.String, float) instruction_simplifier (before) + /// CHECK-NOT: StringBuilderAppend + + /// CHECK-START: java.lang.String Main.$noinline$appendStringAndFloat(java.lang.String, float) instruction_simplifier (after) + /// CHECK: StringBuilderAppend + public static String $noinline$appendStringAndFloat(String s, float f) { + return new StringBuilder().append(s).append(f).toString(); + } + + public static void testAppendStringAndFloat() { + for (String expected : APPEND_FLOAT_TEST_CASES) { + float f = Float.valueOf(expected.substring(APPEND_FLOAT_PREFIX.length())); + String result = $noinline$appendStringAndFloat(APPEND_FLOAT_PREFIX, f); + assertEquals(expected, result); + } + // Special values. + assertEquals("Float/NaN", $noinline$appendStringAndFloat(APPEND_FLOAT_PREFIX, Float.NaN)); + assertEquals("Float/Infinity", + $noinline$appendStringAndFloat(APPEND_FLOAT_PREFIX, Float.POSITIVE_INFINITY)); + assertEquals("Float/-Infinity", + $noinline$appendStringAndFloat(APPEND_FLOAT_PREFIX, Float.NEGATIVE_INFINITY)); + } + + private static final String APPEND_DOUBLE_PREFIX = "Double/"; + private static final String[] APPEND_DOUBLE_TEST_CASES = { + // We're testing only exact values here, i.e. values that do not require rounding. + "Double/1.0", + "Double/9.0", + "Double/10.0", + "Double/99.0", + "Double/100.0", + "Double/999.0", + "Double/1000.0", + "Double/9999.0", + "Double/10000.0", + "Double/99999.0", + "Double/100000.0", + "Double/999999.0", + "Double/1000000.0", + "Double/9999999.0", + "Double/1.0E7", + "Double/1.0E24", + "Double/-1.0", + "Double/-9.0", + "Double/-10.0", + "Double/-99.0", + "Double/-100.0", + "Double/-999.0", + "Double/-1000.0", + "Double/-9999.0", + "Double/-10000.0", + "Double/-99999.0", + "Double/-100000.0", + "Double/-999999.0", + "Double/-1000000.0", + "Double/-9999999.0", + "Double/-1.0E7", + "Double/-1.0E24", + "Double/0.25", + "Double/1.625", + "Double/9.3125", + "Double/-0.25", + "Double/-1.625", + "Double/-9.3125", + }; + + /// CHECK-START: java.lang.String Main.$noinline$appendStringAndDouble(java.lang.String, double) instruction_simplifier (before) + /// CHECK-NOT: StringBuilderAppend + + /// CHECK-START: java.lang.String Main.$noinline$appendStringAndDouble(java.lang.String, double) instruction_simplifier (after) + /// CHECK: StringBuilderAppend + public static String $noinline$appendStringAndDouble(String s, double d) { + return new StringBuilder().append(s).append(d).toString(); + } + + public static void testAppendStringAndDouble() { + for (String expected : APPEND_DOUBLE_TEST_CASES) { + double f = Double.valueOf(expected.substring(APPEND_DOUBLE_PREFIX.length())); + String result = $noinline$appendStringAndDouble(APPEND_DOUBLE_PREFIX, f); + assertEquals(expected, result); + } + // Special values. + assertEquals( + "Double/NaN", + $noinline$appendStringAndDouble(APPEND_DOUBLE_PREFIX, Double.NaN)); + assertEquals( + "Double/Infinity", + $noinline$appendStringAndDouble(APPEND_DOUBLE_PREFIX, Double.POSITIVE_INFINITY)); + assertEquals( + "Double/-Infinity", + $noinline$appendStringAndDouble(APPEND_DOUBLE_PREFIX, Double.NEGATIVE_INFINITY)); + } + + /// CHECK-START: java.lang.String Main.$noinline$appendDoubleAndFloat(double, float) instruction_simplifier (before) + /// CHECK-NOT: StringBuilderAppend + + /// CHECK-START: java.lang.String Main.$noinline$appendDoubleAndFloat(double, float) instruction_simplifier (after) + /// CHECK: StringBuilderAppend + public static String $noinline$appendDoubleAndFloat(double d, float f) { + return new StringBuilder().append(d).append(f).toString(); + } + + public static void testAppendDoubleAndFloat() { + assertEquals("1.50.325", $noinline$appendDoubleAndFloat(1.5, 0.325f)); + assertEquals("1.5E170.3125", $noinline$appendDoubleAndFloat(1.5E17, 0.3125f)); + assertEquals("1.0E8NaN", $noinline$appendDoubleAndFloat(1.0E8, Float.NaN)); + assertEquals("Infinity0.5", $noinline$appendDoubleAndFloat(Double.POSITIVE_INFINITY, 0.5f)); + assertEquals("2.5-Infinity", $noinline$appendDoubleAndFloat(2.5, Float.NEGATIVE_INFINITY)); + } + public static String $noinline$appendStringAndString(String s1, String s2) { return new StringBuilder().append(s1).append(s2).toString(); } |