summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--benchmark/stringbuilder-append/src/StringBuilderAppendBenchmark.java74
-rw-r--r--compiler/optimizing/instruction_simplifier.cc17
-rw-r--r--compiler/optimizing/nodes.h9
-rw-r--r--runtime/class_linker.cc2
-rw-r--r--runtime/image.cc4
-rw-r--r--runtime/string_builder_append.cc162
-rw-r--r--runtime/well_known_classes.cc38
-rw-r--r--runtime/well_known_classes.h6
-rw-r--r--test/697-checker-string-append/src/Main.java156
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();
}