diff options
author | 2019-05-21 10:00:15 +0100 | |
---|---|---|
committer | 2023-01-03 12:31:57 +0000 | |
commit | 41de45060710d64b671a0fa001ec187df221359d (patch) | |
tree | acdd772258d4eb671f8aa00876988b3f59cc4d1f | |
parent | 890b19bd625be5d0e4a876e3eb11b8b893fb0c13 (diff) |
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
-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(); } |