From 41de45060710d64b671a0fa001ec187df221359d Mon Sep 17 00:00:00 2001 From: Vladimir Marko Date: Tue, 21 May 2019 10:00:15 +0100 Subject: [PATCH] StringBuilder append pattern for float/double. Results for added benchmarks on blueline-userdebug with cpu frequencies fxed at 1420800 (cpus 0-3; little) and 1459200 (cpus 4-7; big): 32-bit little (--variant=X32 --invoke-with 'taskset 0f') timeAppendStringAndDouble: ~1260ns -> ~970ns timeAppendStringAndFloat: ~1250ns -> ~940ns timeAppendStringAndHugeDouble: ~4700ns -> ~4690ns (noise) timeAppendStringAndHugeFloat: ~3400ns -> ~3300ns (noise) timeAppendStringDoubleStringAndFloat: ~1980ns -> ~1550ns 64-bit little (--variant=X64 --invoke-with 'taskset 0f') timeAppendStringAndDouble: ~1260ns -> ~970ns timeAppendStringAndFloat: ~1260ns -> ~940ns timeAppendStringAndHugeDouble: ~4700ns -> ~4800ns (noise) timeAppendStringAndHugeFloat: ~3300ns -> ~3400ns (noise) timeAppendStringDoubleStringAndFloat: ~1970ns -> ~1550ns 32-bit big (--variant=X32 --invoke-with 'taskset f0') timeAppendStringAndDouble: ~580ns -> ~450ns timeAppendStringAndFloat: ~590ns -> ~430ns timeAppendStringAndHugeDouble: ~2500ns -> ~2100ns (noise) timeAppendStringAndHugeFloat: ~1500ns -> ~1300ns (noise) timeAppendStringDoubleStringAndFloat: ~880ns -> ~730ns 64-bit big (--variant=X64 --invoke-with 'taskset f0') timeAppendStringAndDouble: ~590ns -> ~450ns timeAppendStringAndFloat: ~590ns -> ~430ns timeAppendStringAndHugeDouble: ~2300ns -> ~2300ns (noise) timeAppendStringAndHugeFloat: ~1500ns -> ~1300ns (noise) timeAppendStringDoubleStringAndFloat: ~870ns -> ~730ns The `timeAppendStringAnd{Double,Float)` benchmarks show very nice improvements, roughly 25% on both little and big cores. The `timeAppendStringDoubleStringAndFloat` also shows decent improvements, over 20% on little and over 15% on big cores. (These benchmarks test the best-case scenario for "before" as the StringBuilder's internal buffer is not reallocated.) The `testAppendStringAndHuge{Double,Float}` results are too noisy to draw any conclusions (especially on little cores but there is still too much noise on big cores as well). There are also small regressions for existing benchmarks `timeAppend{LongStrings,StringAndInt,Strings}` but these non-FP regressions may be mitigated after updating the ThinLTO profile. There is also an opportunity to optimize the calls back to managed code for known shorty (in this change we use "LD" and "LF") by using a dedicated stub instead of going through the generic invoke stub. Boot image size changes are insignificant (few matches). Test: Added tests to 697-checker-string-append Test: m test-art-host-gtest Test: testrunner.py --host --optimizing Test: testrunner.py --target --optimizing Bug: 19575890 Change-Id: I9cf38c2d615a0a2b14255d18588a694d8870aae5 --- .../src/StringBuilderAppendBenchmark.java | 74 ++++++++ compiler/optimizing/instruction_simplifier.cc | 17 +- compiler/optimizing/nodes.h | 9 +- runtime/class_linker.cc | 2 + runtime/image.cc | 4 +- runtime/string_builder_append.cc | 162 +++++++++++++++++- runtime/well_known_classes.cc | 38 +++- runtime/well_known_classes.h | 6 +- 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 void timeAppendStringAndInt(int count) { 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(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 + CharType* AppendFpArg(ObjPtr new_string, + CharType* data, + size_t fp_arg_index) const REQUIRES_SHARED(Locks::mutator_lock_); + template static CharType* AppendLiteral(ObjPtr 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 void StoreData(ObjPtr 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 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 +inline CharType* StringBuilderAppend::Builder::AppendFpArg(ObjPtr 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 inline CharType* StringBuilderAppend::Builder::AppendLiteral(ObjPtr new_string, CharType* data, @@ -204,10 +231,111 @@ inline CharType* StringBuilderAppend::Builder::AppendInt64(ObjPtr>= kBitsPerArg) { + DCHECK_LE(f & kArgMask, static_cast(Argument::kLast)); + bool fp_arg = false; + ObjPtr converter; + switch (static_cast(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(*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( + static_cast(current_arg[0]) + (static_cast(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 buffer = + hs2.NewHandle(btab_buffer_field->GetObj(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(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 converted = ebtab_image_field->GetObj(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(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(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::max()) { // We cannot allocate memory for the entire result. hs_.Self()->ThrowNewException("Ljava/lang/OutOfMemoryError;", @@ -276,6 +421,7 @@ template inline void StringBuilderAppend::Builder::StoreData(ObjPtr 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(Argument::kLast)); @@ -315,11 +461,18 @@ inline void StringBuilderAppend::Builder::StoreData(ObjPtr 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 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 d_s_bdcl = hs.NewHandle(FindSystemClass(class_linker, self, "Ldalvik/system/BaseDexClassLoader;")); Handle d_s_dlcl = @@ -421,6 +426,12 @@ void WellKnownClasses::InitFieldsAndMethodsOnly(JNIEnv* env) { hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/util/Collections;")); Handle j_u_f_c = hs.NewHandle(FindSystemClass(class_linker, self, "Ljava/util/function/Consumer;")); + Handle j_i_m_fd = + hs.NewHandle(FindSystemClass(class_linker, self, "Ljdk/internal/math/FloatingDecimal;")); + Handle j_i_m_fd_btab = hs.NewHandle(FindSystemClass( + class_linker, self, "Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;")); + Handle j_i_m_fd_ebtab = hs.NewHandle(FindSystemClass( + class_linker, self, "Ljdk/internal/math/FloatingDecimal$ExceptionalBinaryToASCIIBuffer;")); Handle l_r_af = hs.NewHandle(FindSystemClass(class_linker, self, "Llibcore/reflect/AnnotationFactory;")); Handle 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 static void testAppendStringAndInt() { } } + 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(); }