Change string compression encoding.
Encode the string compression flag as the least significant
bit of the "count" field, with 0 meaning compressed and 1
meaning uncompressed.
The main vdex file is a tiny bit larger (+28B for prebuilt
boot images, +32 for on-device built images) and the oat
file sizes change. Measured on Nexus 9, AOSP ToT, these
changes are insignificant when string compression is
disabled (-200B for the 32-bit boot*.oat for prebuilt boot
image, -4KiB when built on the device attributable to
rounding, -16B for 64-bit boot*.oat for prebuilt boot image,
no change when built on device) but with string compression
enabled we get significant differences:
prebuilt multi-part boot image:
- 32-bit boot*.oat: -28KiB
- 64-bit boot*.oat: -24KiB
on-device built single boot image:
- 32-bit boot.oat: -32KiB
- 64-bit boot.oat: -28KiB
The boot image oat file overhead for string compression:
prebuilt multi-part boot image:
- 32-bit boot*.oat: before: ~80KiB after: ~52KiB
- 64-bit boot*.oat: before: ~116KiB after: ~92KiB
on-device built single boot image:
- 32-bit boot.oat: before: 92KiB after: 60KiB
- 64-bit boot.oat: before: 116KiB after: 92KiB
The differences in the SplitStringBenchmark seem to be lost
in the noise.
Test: Run ART test suite on host and Nexus 9 with Optimizing.
Test: Run ART test suite on host and Nexus 9 with interpreter.
Test: All of the above with string compression enabled.
Bug: 31040547
Change-Id: I7570c2b700f1a31004a2d3c18b1cc30046d35a74
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index 43682c5..80b4df6 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -1408,21 +1408,22 @@
// compression style is decided on alloc.
__ cmpl(ecx, Address(arg, count_offset));
__ j(kNotEqual, &return_false);
+ // Return true if strings are empty. Even with string compression `count == 0` means empty.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ jecxz(&return_true);
if (mirror::kUseStringCompression) {
NearLabel string_uncompressed;
- // Differ cases into both compressed or both uncompressed. Different compression style
- // is cut above.
- __ cmpl(ecx, Immediate(0));
- __ j(kGreaterEqual, &string_uncompressed);
+ // Extract length and differentiate between both compressed or both uncompressed.
+ // Different compression style is cut above.
+ __ shrl(ecx, Immediate(1));
+ __ j(kCarrySet, &string_uncompressed);
// Divide string length by 2, rounding up, and continue as if uncompressed.
- // Merge clearing the compression flag (+0x80000000) with +1 for rounding.
- __ addl(ecx, Immediate(0x80000001));
+ __ addl(ecx, Immediate(1));
__ shrl(ecx, Immediate(1));
__ Bind(&string_uncompressed);
}
- // Return true if strings are empty.
- __ jecxz(&return_true);
// Load starting addresses of string values into ESI/EDI as required for repe_cmpsl instruction.
__ leal(esi, Address(str, value_offset));
__ leal(edi, Address(arg, value_offset));
@@ -1535,21 +1536,24 @@
// Location of count within the String object.
int32_t count_offset = mirror::String::CountOffset().Int32Value();
- // Load string length, i.e., the count field of the string.
+ // Load the count field of the string containing the length and compression flag.
__ movl(string_length, Address(string_obj, count_offset));
- if (mirror::kUseStringCompression) {
- string_length_flagged = locations->GetTemp(2).AsRegister<Register>();
- __ movl(string_length_flagged, string_length);
- // Mask out first bit used as compression flag.
- __ andl(string_length, Immediate(INT32_MAX));
- }
- // Do a zero-length check.
+ // Do a zero-length check. Even with string compression `count == 0` means empty.
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
// TODO: Support jecxz.
NearLabel not_found_label;
__ testl(string_length, string_length);
__ j(kEqual, ¬_found_label);
+ if (mirror::kUseStringCompression) {
+ string_length_flagged = locations->GetTemp(2).AsRegister<Register>();
+ __ movl(string_length_flagged, string_length);
+ // Extract the length and shift out the least significant bit used as compression flag.
+ __ shrl(string_length, Immediate(1));
+ }
+
if (start_at_zero) {
// Number of chars to scan is the same as the string length.
__ movl(counter, string_length);
@@ -1570,8 +1574,8 @@
if (mirror::kUseStringCompression) {
NearLabel modify_counter, offset_uncompressed_label;
- __ cmpl(string_length_flagged, Immediate(0));
- __ j(kGreaterEqual, &offset_uncompressed_label);
+ __ testl(string_length_flagged, Immediate(1));
+ __ j(kNotZero, &offset_uncompressed_label);
// Move to the start of the string: string_obj + value_offset + start_index.
__ leal(string_obj, Address(string_obj, counter, ScaleFactor::TIMES_1, value_offset));
__ jmp(&modify_counter);
@@ -1593,8 +1597,8 @@
if (mirror::kUseStringCompression) {
NearLabel uncompressed_string_comparison;
NearLabel comparison_done;
- __ cmpl(string_length_flagged, Immediate(0));
- __ j(kGreater, &uncompressed_string_comparison);
+ __ testl(string_length_flagged, Immediate(1));
+ __ j(kNotZero, &uncompressed_string_comparison);
// Check if EAX (search_value) is ASCII.
__ cmpl(search_value, Immediate(127));
@@ -1787,8 +1791,10 @@
__ cfi().AdjustCFAOffset(stack_adjust);
NearLabel copy_loop, copy_uncompressed;
- __ cmpl(Address(obj, count_offset), Immediate(0));
- __ j(kGreaterEqual, ©_uncompressed);
+ __ testl(Address(obj, count_offset), Immediate(1));
+ static_assert(static_cast<uint32_t>(mirror::StringCompressionFlag::kCompressed) == 0u,
+ "Expecting 0=compressed, 1=uncompressed");
+ __ j(kNotZero, ©_uncompressed);
// Compute the address of the source string by adding the number of chars from
// the source beginning to the value offset of a string.
__ leal(ESI, CodeGeneratorX86::ArrayAddress(obj, srcBegin, TIMES_1, value_offset));