ART: String intrinsics for Mterp interpreter
Adds the most common java.lang.string intrinsics. Includes change
to jvalue handling to zero-exend setting of chars and booleans to
64 bits (aligns with current sign-extension of shorts and ints).
Bug: 30933338
Benchmarks:
2x boost for Caffeinemark String
11% improvement for Dhrystone
Test: ART_TEST_INTERPRETER=true m test-art-host
Test: ART_TEST_INTERPRETER=true m test-art-target (Bullhead)
Note: Added intrinsics have existing test coverage via
082-inline-execute and 123-inline-execute2.
Change-Id: I64b35b1d7bdfe14da0c662594c0edf5cde667277
diff --git a/runtime/interpreter/interpreter_intrinsics.cc b/runtime/interpreter/interpreter_intrinsics.cc
index 5e901cd..ff0c20e 100644
--- a/runtime/interpreter/interpreter_intrinsics.cc
+++ b/runtime/interpreter/interpreter_intrinsics.cc
@@ -79,6 +79,151 @@
// java.lang.Math.atan(D)D
UNARY_SIMPLE_INTRINSIC(MterpMathAtan, std::atan, GetVRegDouble, SetD);
+// java.lang.String.charAt(I)C
+static ALWAYS_INLINE bool MterpStringCharAt(ShadowFrame* shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
+ JValue* result_register)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ uint32_t arg[Instruction::kMaxVarArgRegs] = {};
+ inst->GetVarArgs(arg, inst_data);
+ mirror::String* str = shadow_frame->GetVRegReference(arg[0])->AsString();
+ int length = str->GetLength();
+ int index = shadow_frame->GetVReg(arg[1]);
+ uint16_t res;
+ if (UNLIKELY(index < 0) || (index >= length)) {
+ return false; // Punt and let non-intrinsic version deal with the throw.
+ }
+ if (str->IsCompressed()) {
+ res = str->GetValueCompressed()[index];
+ } else {
+ res = str->GetValue()[index];
+ }
+ result_register->SetC(res);
+ return true;
+}
+
+// java.lang.String.compareTo(Ljava/lang/string)I
+static ALWAYS_INLINE bool MterpStringCompareTo(ShadowFrame* shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
+ JValue* result_register)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ uint32_t arg[Instruction::kMaxVarArgRegs] = {};
+ inst->GetVarArgs(arg, inst_data);
+ mirror::String* str = shadow_frame->GetVRegReference(arg[0])->AsString();
+ mirror::Object* arg1 = shadow_frame->GetVRegReference(arg[1]);
+ if (arg1 == nullptr) {
+ return false;
+ }
+ result_register->SetI(str->CompareTo(arg1->AsString()));
+ return true;
+}
+
+#define STRING_INDEXOF_INTRINSIC(name, starting_pos) \
+static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame, \
+ const Instruction* inst, \
+ uint16_t inst_data, \
+ JValue* result_register) \
+ REQUIRES_SHARED(Locks::mutator_lock_) { \
+ uint32_t arg[Instruction::kMaxVarArgRegs] = {}; \
+ inst->GetVarArgs(arg, inst_data); \
+ mirror::String* str = shadow_frame->GetVRegReference(arg[0])->AsString(); \
+ int ch = shadow_frame->GetVReg(arg[1]); \
+ if (ch >= 0x10000) { \
+ /* Punt if supplementary char. */ \
+ return false; \
+ } \
+ result_register->SetI(str->FastIndexOf(ch, starting_pos)); \
+ return true; \
+}
+
+// java.lang.String.indexOf(I)I
+STRING_INDEXOF_INTRINSIC(StringIndexOf, 0);
+
+// java.lang.String.indexOf(II)I
+STRING_INDEXOF_INTRINSIC(StringIndexOfAfter, shadow_frame->GetVReg(arg[2]));
+
+#define SIMPLE_STRING_INTRINSIC(name, operation) \
+static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame, \
+ const Instruction* inst, \
+ uint16_t inst_data, \
+ JValue* result_register) \
+ REQUIRES_SHARED(Locks::mutator_lock_) { \
+ uint32_t arg[Instruction::kMaxVarArgRegs] = {}; \
+ inst->GetVarArgs(arg, inst_data); \
+ mirror::String* str = shadow_frame->GetVRegReference(arg[0])->AsString(); \
+ result_register->operation; \
+ return true; \
+}
+
+// java.lang.String.isEmpty()Z
+SIMPLE_STRING_INTRINSIC(StringIsEmpty, SetZ(str->GetLength() == 0))
+
+// java.lang.String.length()I
+SIMPLE_STRING_INTRINSIC(StringLength, SetI(str->GetLength()))
+
+// java.lang.String.getCharsNoCheck(II[CI)V
+static ALWAYS_INLINE bool MterpStringGetCharsNoCheck(ShadowFrame* shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
+ JValue* result_register ATTRIBUTE_UNUSED)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Start, end & index already checked by caller - won't throw. Destination is uncompressed.
+ uint32_t arg[Instruction::kMaxVarArgRegs] = {};
+ inst->GetVarArgs(arg, inst_data);
+ mirror::String* str = shadow_frame->GetVRegReference(arg[0])->AsString();
+ int32_t start = shadow_frame->GetVReg(arg[1]);
+ int32_t end = shadow_frame->GetVReg(arg[2]);
+ int32_t index = shadow_frame->GetVReg(arg[4]);
+ mirror::CharArray* array = shadow_frame->GetVRegReference(arg[3])->AsCharArray();
+ uint16_t* dst = array->GetData() + index;
+ int32_t len = (end - start);
+ if (str->IsCompressed()) {
+ const uint8_t* src_8 = str->GetValueCompressed() + start;
+ for (int i = 0; i < len; i++) {
+ dst[i] = src_8[i];
+ }
+ } else {
+ uint16_t* src_16 = str->GetValue() + start;
+ memcpy(dst, src_16, len * sizeof(uint16_t));
+ }
+ return true;
+}
+
+// java.lang.String.equalsLjava/lang/Object;)Z
+static ALWAYS_INLINE bool MterpStringEquals(ShadowFrame* shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
+ JValue* result_register)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ uint32_t arg[Instruction::kMaxVarArgRegs] = {};
+ inst->GetVarArgs(arg, inst_data);
+ mirror::String* str = shadow_frame->GetVRegReference(arg[0])->AsString();
+ mirror::Object* obj = shadow_frame->GetVRegReference(arg[1]);
+ bool res = false; // Assume not equal.
+ if ((obj != nullptr) && obj->IsString()) {
+ mirror::String* str2 = obj->AsString();
+ if (str->GetCount() == str2->GetCount()) {
+ // Length & compression status are same. Can use block compare.
+ void* bytes1;
+ void* bytes2;
+ int len = str->GetLength();
+ if (str->IsCompressed()) {
+ bytes1 = str->GetValueCompressed();
+ bytes2 = str2->GetValueCompressed();
+ } else {
+ len *= sizeof(uint16_t);
+ bytes1 = str->GetValue();
+ bytes2 = str2->GetValue();
+ }
+ res = (memcmp(bytes1, bytes2, len) == 0);
+ }
+ }
+ result_register->SetZ(res);
+ return true;
+}
+
#define INTRINSIC_CASE(name) \
case Intrinsics::k##name: \
res = Mterp##name(shadow_frame, inst, inst_data, result_register); \
@@ -110,6 +255,14 @@
INTRINSIC_CASE(MathAsin)
INTRINSIC_CASE(MathAcos)
INTRINSIC_CASE(MathAtan)
+ INTRINSIC_CASE(StringCharAt)
+ INTRINSIC_CASE(StringCompareTo)
+ INTRINSIC_CASE(StringIndexOf)
+ INTRINSIC_CASE(StringIndexOfAfter)
+ INTRINSIC_CASE(StringEquals)
+ INTRINSIC_CASE(StringGetCharsNoCheck)
+ INTRINSIC_CASE(StringIsEmpty)
+ INTRINSIC_CASE(StringLength)
default:
res = false; // Punt
break;
diff --git a/runtime/jvalue.h b/runtime/jvalue.h
index 398bfbc..f61a07c 100644
--- a/runtime/jvalue.h
+++ b/runtime/jvalue.h
@@ -39,7 +39,9 @@
}
uint16_t GetC() const { return c; }
- void SetC(uint16_t new_c) { c = new_c; }
+ void SetC(uint16_t new_c) {
+ j = static_cast<int64_t>(new_c); // Zero-extend to 64 bits.
+ }
double GetD() const { return d; }
void SetD(double new_d) { d = new_d; }
@@ -66,7 +68,9 @@
}
uint8_t GetZ() const { return z; }
- void SetZ(uint8_t new_z) { z = new_z; }
+ void SetZ(uint8_t new_z) {
+ j = static_cast<int64_t>(new_z); // Zero-extend to 64 bits.
+ }
mirror::Object** GetGCRoot() { return &l; }