Fix hprof/ahat for string compression.
Test: m test-art-host
Test: m ahat-test
Test: m test-art-host with string compression enabled
Test: m ahat-test with string compression enabled
Bug: 31040547
Change-Id: I660e39c586d23f4a95686d484ca108466e52d249
diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc
index 3d3ad59..133502e 100644
--- a/runtime/hprof/hprof.cc
+++ b/runtime/hprof/hprof.cc
@@ -224,12 +224,6 @@
HandleU1List(values, count);
length_ += count;
}
- void AddU1AsU2List(const uint8_t* values, size_t count) {
- HandleU1AsU2List(values, count);
- // Array of char from compressed String (8-bit) is added as 16-bit blocks
- int ceil_count_to_even = count + ((count & 1) ? 1 : 0);
- length_ += ceil_count_to_even * sizeof(uint8_t);
- }
void AddU2List(const uint16_t* values, size_t count) {
HandleU2List(values, count);
length_ += count * sizeof(uint16_t);
@@ -1277,7 +1271,7 @@
HprofBasicType t = SignatureToBasicTypeAndSize(f->GetTypeDescriptor(), nullptr);
__ AddU1(t);
}
- // Add native value character array for strings.
+ // Add native value character array for strings / byte array for compressed strings.
if (klass->IsStringClass()) {
__ AddStringId(LookupStringId("value"));
__ AddU1(hprof_basic_object);
@@ -1359,8 +1353,16 @@
case hprof_basic_short:
__ AddU2(f->GetShort(obj));
break;
- case hprof_basic_float:
case hprof_basic_int:
+ if (mirror::kUseStringCompression &&
+ klass->IsStringClass() &&
+ f->GetOffset().SizeValue() == mirror::String::CountOffset().SizeValue()) {
+ // Store the string length instead of the raw count field with compression flag.
+ __ AddU4(obj->AsString()->GetLength());
+ break;
+ }
+ FALLTHROUGH_INTENDED;
+ case hprof_basic_float:
case hprof_basic_object:
__ AddU4(f->Get32(obj));
break;
@@ -1397,16 +1399,15 @@
CHECK_EQ(obj->IsString(), string_value != nullptr);
if (string_value != nullptr) {
mirror::String* s = obj->AsString();
- // Compressed string's (8-bit) length is ceil(length/2) in 16-bit blocks
- int length_in_16_bit = (s->IsCompressed()) ? ((s->GetLength() + 1) / 2) : s->GetLength();
__ AddU1(HPROF_PRIMITIVE_ARRAY_DUMP);
__ AddObjectId(string_value);
__ AddStackTraceSerialNumber(LookupStackTraceSerialNumber(obj));
- __ AddU4(length_in_16_bit);
- __ AddU1(hprof_basic_char);
+ __ AddU4(s->GetLength());
if (s->IsCompressed()) {
- __ AddU1AsU2List(s->GetValueCompressed(), s->GetLength());
+ __ AddU1(hprof_basic_byte);
+ __ AddU1List(s->GetValueCompressed(), s->GetLength());
} else {
+ __ AddU1(hprof_basic_char);
__ AddU2List(s->GetValue(), s->GetLength());
}
}
diff --git a/runtime/mirror/string.h b/runtime/mirror/string.h
index 95b6c3e..409c6c2 100644
--- a/runtime/mirror/string.h
+++ b/runtime/mirror/string.h
@@ -241,8 +241,9 @@
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_);
// Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
- // First bit (uppermost/leftmost) is taken out for Compressed/Uncompressed flag
- // [0] Uncompressed: string uses 16-bit memory | [1] Compressed: 8-bit memory
+
+ // If string compression is enabled, count_ holds the StringCompressionFlag in the
+ // least significant bit and the length in the remaining bits, length = count_ >> 1.
int32_t count_;
uint32_t hash_code_;
diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
index 94934a2..a062afd 100644
--- a/tools/ahat/src/InstanceUtils.java
+++ b/tools/ahat/src/InstanceUtils.java
@@ -26,6 +26,7 @@
import com.android.tools.perflib.heap.Type;
import java.awt.image.BufferedImage;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -87,22 +88,27 @@
// is a char[], use that directly as the value, otherwise use the value
// field of the string object. The field accesses for count and offset
// later on will work okay regardless of what type the inst object is.
- Object value = inst;
- if (isInstanceOfClass(inst, "java.lang.String")) {
- value = getField(inst, "value");
- }
+ boolean isString = isInstanceOfClass(inst, "java.lang.String");
+ Object value = isString ? getField(inst, "value") : inst;
if (!(value instanceof ArrayInstance)) {
return null;
}
ArrayInstance chars = (ArrayInstance) value;
+ int numChars = chars.getLength();
+ int offset = getIntField(inst, "offset", 0);
+ int count = getIntField(inst, "count", numChars);
+
+ // With string compression enabled, the array type can be BYTE but in that case
+ // offset must be 0 and count must match numChars.
+ if (isString && (chars.getArrayType() == Type.BYTE) && (offset == 0) && (count == numChars)) {
+ int length = (0 <= maxChars && maxChars < numChars) ? maxChars : numChars;
+ return new String(chars.asRawByteArray(/* offset */ 0, length), StandardCharsets.US_ASCII);
+ }
if (chars.getArrayType() != Type.CHAR) {
return null;
}
-
- int numChars = chars.getLength();
- int count = getIntField(inst, "count", numChars);
if (count == 0) {
return "";
}
@@ -110,7 +116,6 @@
count = maxChars;
}
- int offset = getIntField(inst, "offset", 0);
int end = offset + count - 1;
if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
return new String(chars.asCharArray(offset, count));
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index e08df67..587d9de 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -45,6 +45,8 @@
// class and reading the desired field.
public static class DumpedStuff {
public String basicString = "hello, world";
+ public String nonAscii = "Sigma (\u01a9) is not ASCII";
+ public String embeddedZero = "embedded\0..."; // Non-ASCII for string compression purposes.
public char[] charArray = "char thing".toCharArray();
public String nullString = null;
public Object anObject = new Object();
diff --git a/tools/ahat/test/InstanceUtilsTest.java b/tools/ahat/test/InstanceUtilsTest.java
index ec77e70..fe2706d 100644
--- a/tools/ahat/test/InstanceUtilsTest.java
+++ b/tools/ahat/test/InstanceUtilsTest.java
@@ -37,6 +37,20 @@
}
@Test
+ public void asStringNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("nonAscii");
+ assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str));
+ }
+
+ @Test
+ public void asStringEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("embeddedZero");
+ assertEquals("embedded\0...", InstanceUtils.asString(str));
+ }
+
+ @Test
public void asStringCharArray() throws IOException {
TestDump dump = TestDump.getTestDump();
Instance str = (Instance)dump.getDumpedThing("charArray");
@@ -51,6 +65,20 @@
}
@Test
+ public void asStringTruncatedNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("nonAscii");
+ assertEquals("Sigma (\u01a9)", InstanceUtils.asString(str, 9));
+ }
+
+ @Test
+ public void asStringTruncatedEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("embeddedZero");
+ assertEquals("embed", InstanceUtils.asString(str, 5));
+ }
+
+ @Test
public void asStringCharArrayTruncated() throws IOException {
TestDump dump = TestDump.getTestDump();
Instance str = (Instance)dump.getDumpedThing("charArray");
@@ -65,6 +93,20 @@
}
@Test
+ public void asStringExactMaxNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("nonAscii");
+ assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 22));
+ }
+
+ @Test
+ public void asStringExactMaxEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("embeddedZero");
+ assertEquals("embedded\0...", InstanceUtils.asString(str, 12));
+ }
+
+ @Test
public void asStringCharArrayExactMax() throws IOException {
TestDump dump = TestDump.getTestDump();
Instance str = (Instance)dump.getDumpedThing("charArray");
@@ -79,6 +121,20 @@
}
@Test
+ public void asStringNotTruncatedNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("nonAscii");
+ assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 50));
+ }
+
+ @Test
+ public void asStringNotTruncatedEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("embeddedZero");
+ assertEquals("embedded\0...", InstanceUtils.asString(str, 50));
+ }
+
+ @Test
public void asStringCharArrayNotTruncated() throws IOException {
TestDump dump = TestDump.getTestDump();
Instance str = (Instance)dump.getDumpedThing("charArray");
@@ -93,6 +149,20 @@
}
@Test
+ public void asStringNegativeMaxNonAscii() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("nonAscii");
+ assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, -3));
+ }
+
+ @Test
+ public void asStringNegativeMaxEmbeddedZero() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ Instance str = (Instance)dump.getDumpedThing("embeddedZero");
+ assertEquals("embedded\0...", InstanceUtils.asString(str, -3));
+ }
+
+ @Test
public void asStringCharArrayNegativeMax() throws IOException {
TestDump dump = TestDump.getTestDump();
Instance str = (Instance)dump.getDumpedThing("charArray");