ART: Better support for arraycopy in unstarted runtime
Extend the System.arraycopy() cutout in the unstarted runtime
to support arrays with differing component types.
Add tests.
Bug: 27805718
Change-Id: Iaacd95a372e9bfa26e9055a06b0d8f0335b8d6d1
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index eaea01d..49db49b 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -353,28 +353,35 @@
jint src_pos = shadow_frame->GetVReg(arg_offset + 1);
jint dst_pos = shadow_frame->GetVReg(arg_offset + 3);
jint length = shadow_frame->GetVReg(arg_offset + 4);
- mirror::Array* src_array = shadow_frame->GetVRegReference(arg_offset)->AsArray();
- mirror::Array* dst_array = shadow_frame->GetVRegReference(arg_offset + 2)->AsArray();
- // Null checking.
- if (src_array == nullptr) {
+ mirror::Object* src_obj = shadow_frame->GetVRegReference(arg_offset);
+ mirror::Object* dst_obj = shadow_frame->GetVRegReference(arg_offset + 2);
+ // Null checking. For simplicity, abort transaction.
+ if (src_obj == nullptr) {
AbortTransactionOrFail(self, "src is null in arraycopy.");
return;
}
- if (dst_array == nullptr) {
+ if (dst_obj == nullptr) {
AbortTransactionOrFail(self, "dst is null in arraycopy.");
return;
}
+ // Test for arrayness. Throw ArrayStoreException.
+ if (!src_obj->IsArrayInstance() || !dst_obj->IsArrayInstance()) {
+ self->ThrowNewException("Ljava/lang/ArrayStoreException;", "src or trg is not an array");
+ return;
+ }
- // Bounds checking.
+ mirror::Array* src_array = src_obj->AsArray();
+ mirror::Array* dst_array = dst_obj->AsArray();
+
+ // Bounds checking. Throw IndexOutOfBoundsException.
if (UNLIKELY(src_pos < 0) || UNLIKELY(dst_pos < 0) || UNLIKELY(length < 0) ||
UNLIKELY(src_pos > src_array->GetLength() - length) ||
UNLIKELY(dst_pos > dst_array->GetLength() - length)) {
- self->ThrowNewExceptionF("Ljava/lang/ArrayIndexOutOfBoundsException;",
+ self->ThrowNewExceptionF("Ljava/lang/IndexOutOfBoundsException;",
"src.length=%d srcPos=%d dst.length=%d dstPos=%d length=%d",
src_array->GetLength(), src_pos, dst_array->GetLength(), dst_pos,
length);
- AbortTransactionOrFail(self, "Index out of bounds.");
return;
}
@@ -393,19 +400,11 @@
return;
}
- // For simplicity only do this if the component types are the same. Otherwise we have to copy
- // even more code from the object-array functions.
- if (src_type != trg_type) {
- AbortTransactionOrFail(self, "Types not the same in arraycopy: %s vs %s",
- PrettyDescriptor(src_array->GetClass()->GetComponentType()).c_str(),
- PrettyDescriptor(dst_array->GetClass()->GetComponentType()).c_str());
- return;
- }
-
mirror::ObjectArray<mirror::Object>* src = src_array->AsObjectArray<mirror::Object>();
mirror::ObjectArray<mirror::Object>* dst = dst_array->AsObjectArray<mirror::Object>();
if (src == dst) {
// Can overlap, but not have type mismatches.
+ // We cannot use ObjectArray::MemMove here, as it doesn't support transactions.
const bool copy_forward = (dst_pos < src_pos) || (dst_pos - src_pos >= length);
if (copy_forward) {
for (int32_t i = 0; i < length; ++i) {
@@ -417,9 +416,15 @@
}
}
} else {
- // Can't overlap. Would need type checks, but we abort above.
- for (int32_t i = 0; i < length; ++i) {
- dst->Set(dst_pos + i, src->Get(src_pos + i));
+ // We're being lazy here. Optimally this could be a memcpy (if component types are
+ // assignable), but the ObjectArray implementation doesn't support transactions. The
+ // checking version, however, does.
+ if (Runtime::Current()->IsActiveTransaction()) {
+ dst->AssignableCheckingMemcpy<true>(
+ dst_pos, src, src_pos, length, true /* throw_exception */);
+ } else {
+ dst->AssignableCheckingMemcpy<false>(
+ dst_pos, src, src_pos, length, true /* throw_exception */);
}
}
} else if (src_type->IsPrimitiveChar()) {
diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc
index a1ae2aa..fb53b1d 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -66,6 +66,94 @@
#undef UNSTARTED_RUNTIME_DIRECT_LIST
#undef UNSTARTED_RUNTIME_JNI_LIST
#undef UNSTARTED_JNI
+
+ // Helpers for ArrayCopy.
+ //
+ // Note: as we have to use handles, we use StackHandleScope to transfer data. Hardcode a size
+ // of three everywhere. That is enough to test all cases.
+
+ static mirror::ObjectArray<mirror::Object>* CreateObjectArray(
+ Thread* self,
+ mirror::Class* component_type,
+ const StackHandleScope<3>& data)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ Runtime* runtime = Runtime::Current();
+ mirror::Class* array_type = runtime->GetClassLinker()->FindArrayClass(self, &component_type);
+ CHECK(array_type != nullptr);
+ mirror::ObjectArray<mirror::Object>* result =
+ mirror::ObjectArray<mirror::Object>::Alloc(self, array_type, 3);
+ CHECK(result != nullptr);
+ for (size_t i = 0; i < 3; ++i) {
+ result->Set(static_cast<int32_t>(i), data.GetReference(i));
+ CHECK(!self->IsExceptionPending());
+ }
+ return result;
+ }
+
+ static void CheckObjectArray(mirror::ObjectArray<mirror::Object>* array,
+ const StackHandleScope<3>& data)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ CHECK_EQ(array->GetLength(), 3);
+ CHECK_EQ(data.NumberOfReferences(), 3U);
+ for (size_t i = 0; i < 3; ++i) {
+ EXPECT_EQ(data.GetReference(i), array->Get(static_cast<int32_t>(i))) << i;
+ }
+ }
+
+ void RunArrayCopy(Thread* self,
+ ShadowFrame* tmp,
+ bool expect_exception,
+ mirror::ObjectArray<mirror::Object>* src,
+ int32_t src_pos,
+ mirror::ObjectArray<mirror::Object>* dst,
+ int32_t dst_pos,
+ int32_t length)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ JValue result;
+ tmp->SetVRegReference(0, src);
+ tmp->SetVReg(1, src_pos);
+ tmp->SetVRegReference(2, dst);
+ tmp->SetVReg(3, dst_pos);
+ tmp->SetVReg(4, length);
+ UnstartedSystemArraycopy(self, tmp, &result, 0);
+ bool exception_pending = self->IsExceptionPending();
+ EXPECT_EQ(exception_pending, expect_exception);
+ if (exception_pending) {
+ self->ClearException();
+ }
+ }
+
+ void RunArrayCopy(Thread* self,
+ ShadowFrame* tmp,
+ bool expect_exception,
+ mirror::Class* src_component_class,
+ mirror::Class* dst_component_class,
+ const StackHandleScope<3>& src_data,
+ int32_t src_pos,
+ const StackHandleScope<3>& dst_data,
+ int32_t dst_pos,
+ int32_t length,
+ const StackHandleScope<3>& expected_result)
+ SHARED_REQUIRES(Locks::mutator_lock_) {
+ StackHandleScope<3> hs_misc(self);
+ Handle<mirror::Class> dst_component_handle(hs_misc.NewHandle(dst_component_class));
+
+ Handle<mirror::ObjectArray<mirror::Object>> src_handle(
+ hs_misc.NewHandle(CreateObjectArray(self, src_component_class, src_data)));
+
+ Handle<mirror::ObjectArray<mirror::Object>> dst_handle(
+ hs_misc.NewHandle(CreateObjectArray(self, dst_component_handle.Get(), dst_data)));
+
+ RunArrayCopy(self,
+ tmp,
+ expect_exception,
+ src_handle.Get(),
+ src_pos,
+ dst_handle.Get(),
+ dst_pos,
+ length);
+ CheckObjectArray(dst_handle.Get(), expected_result);
+ }
};
TEST_F(UnstartedRuntimeTest, MemoryPeekByte) {
@@ -277,5 +365,148 @@
ShadowFrame::DeleteDeoptimizedFrame(shadow_frame);
}
+// Tests the exceptions that should be checked before modifying the destination.
+// (Doesn't check the object vs primitive case ATM.)
+TEST_F(UnstartedRuntimeTest, SystemArrayCopyObjectArrayTestExceptions) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+ JValue result;
+ ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0);
+
+ // Note: all tests are not GC safe. Assume there's no GC running here with the few objects we
+ // allocate.
+ StackHandleScope<2> hs_misc(self);
+ Handle<mirror::Class> object_class(
+ hs_misc.NewHandle(mirror::Class::GetJavaLangClass()->GetSuperClass()));
+
+ StackHandleScope<3> hs_data(self);
+ hs_data.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "1"));
+ hs_data.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "2"));
+ hs_data.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "3"));
+
+ Handle<mirror::ObjectArray<mirror::Object>> array(
+ hs_misc.NewHandle(CreateObjectArray(self, object_class.Get(), hs_data)));
+
+ RunArrayCopy(self, tmp, true, array.Get(), -1, array.Get(), 0, 0);
+ RunArrayCopy(self, tmp, true, array.Get(), 0, array.Get(), -1, 0);
+ RunArrayCopy(self, tmp, true, array.Get(), 0, array.Get(), 0, -1);
+ RunArrayCopy(self, tmp, true, array.Get(), 0, array.Get(), 0, 4);
+ RunArrayCopy(self, tmp, true, array.Get(), 0, array.Get(), 1, 3);
+ RunArrayCopy(self, tmp, true, array.Get(), 1, array.Get(), 0, 3);
+
+ mirror::ObjectArray<mirror::Object>* class_as_array =
+ reinterpret_cast<mirror::ObjectArray<mirror::Object>*>(object_class.Get());
+ RunArrayCopy(self, tmp, true, class_as_array, 0, array.Get(), 0, 0);
+ RunArrayCopy(self, tmp, true, array.Get(), 0, class_as_array, 0, 0);
+
+ ShadowFrame::DeleteDeoptimizedFrame(tmp);
+}
+
+TEST_F(UnstartedRuntimeTest, SystemArrayCopyObjectArrayTest) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+ JValue result;
+ ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0);
+
+ StackHandleScope<1> hs_object(self);
+ Handle<mirror::Class> object_class(
+ hs_object.NewHandle(mirror::Class::GetJavaLangClass()->GetSuperClass()));
+
+ // Simple test:
+ // [1,2,3]{1 @ 2} into [4,5,6] = [4,2,6]
+ {
+ StackHandleScope<3> hs_src(self);
+ hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "1"));
+ hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "2"));
+ hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "3"));
+
+ StackHandleScope<3> hs_dst(self);
+ hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "4"));
+ hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "5"));
+ hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "6"));
+
+ StackHandleScope<3> hs_expected(self);
+ hs_expected.NewHandle(hs_dst.GetReference(0));
+ hs_expected.NewHandle(hs_dst.GetReference(1));
+ hs_expected.NewHandle(hs_src.GetReference(1));
+
+ RunArrayCopy(self,
+ tmp,
+ false,
+ object_class.Get(),
+ object_class.Get(),
+ hs_src,
+ 1,
+ hs_dst,
+ 2,
+ 1,
+ hs_expected);
+ }
+
+ // Simple test:
+ // [1,2,3]{1 @ 1} into [4,5,6] = [4,2,6] (with dst String[])
+ {
+ StackHandleScope<3> hs_src(self);
+ hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "1"));
+ hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "2"));
+ hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "3"));
+
+ StackHandleScope<3> hs_dst(self);
+ hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "4"));
+ hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "5"));
+ hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "6"));
+
+ StackHandleScope<3> hs_expected(self);
+ hs_expected.NewHandle(hs_dst.GetReference(0));
+ hs_expected.NewHandle(hs_src.GetReference(1));
+ hs_expected.NewHandle(hs_dst.GetReference(2));
+
+ RunArrayCopy(self,
+ tmp,
+ false,
+ object_class.Get(),
+ mirror::String::GetJavaLangString(),
+ hs_src,
+ 1,
+ hs_dst,
+ 1,
+ 1,
+ hs_expected);
+ }
+
+ // Simple test:
+ // [1,*,3] into [4,5,6] = [1,5,6] + exc
+ {
+ StackHandleScope<3> hs_src(self);
+ hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "1"));
+ hs_src.NewHandle(mirror::String::GetJavaLangString());
+ hs_src.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "3"));
+
+ StackHandleScope<3> hs_dst(self);
+ hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "4"));
+ hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "5"));
+ hs_dst.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "6"));
+
+ StackHandleScope<3> hs_expected(self);
+ hs_expected.NewHandle(hs_src.GetReference(0));
+ hs_expected.NewHandle(hs_dst.GetReference(1));
+ hs_expected.NewHandle(hs_dst.GetReference(2));
+
+ RunArrayCopy(self,
+ tmp,
+ true,
+ object_class.Get(),
+ mirror::String::GetJavaLangString(),
+ hs_src,
+ 0,
+ hs_dst,
+ 0,
+ 3,
+ hs_expected);
+ }
+
+ ShadowFrame::DeleteDeoptimizedFrame(tmp);
+}
+
} // namespace interpreter
} // namespace art
diff --git a/runtime/mirror/object_array-inl.h b/runtime/mirror/object_array-inl.h
index 6f9d642..c3c5231 100644
--- a/runtime/mirror/object_array-inl.h
+++ b/runtime/mirror/object_array-inl.h
@@ -197,6 +197,7 @@
}
template<class T>
+template<bool kTransactionActive>
inline void ObjectArray<T>::AssignableCheckingMemcpy(int32_t dst_pos, ObjectArray<T>* src,
int32_t src_pos, int32_t count,
bool throw_exception) {
@@ -215,15 +216,15 @@
o = src->GetWithoutChecks(src_pos + i);
if (o == nullptr) {
// Null is always assignable.
- SetWithoutChecks<false>(dst_pos + i, nullptr);
+ SetWithoutChecks<kTransactionActive>(dst_pos + i, nullptr);
} else {
// TODO: use the underlying class reference to avoid uncompression when not necessary.
Class* o_class = o->GetClass();
if (LIKELY(lastAssignableElementClass == o_class)) {
- SetWithoutChecks<false>(dst_pos + i, o);
+ SetWithoutChecks<kTransactionActive>(dst_pos + i, o);
} else if (LIKELY(dst_class->IsAssignableFrom(o_class))) {
lastAssignableElementClass = o_class;
- SetWithoutChecks<false>(dst_pos + i, o);
+ SetWithoutChecks<kTransactionActive>(dst_pos + i, o);
} else {
// Can't put this element into the array, break to perform write-barrier and throw
// exception.
diff --git a/runtime/mirror/object_array.h b/runtime/mirror/object_array.h
index 1b1295c..4257396 100644
--- a/runtime/mirror/object_array.h
+++ b/runtime/mirror/object_array.h
@@ -78,6 +78,7 @@
int32_t count) SHARED_REQUIRES(Locks::mutator_lock_);
// Copy src into this array with assignability checks.
+ template<bool kTransactionActive>
void AssignableCheckingMemcpy(int32_t dst_pos, ObjectArray<T>* src, int32_t src_pos,
int32_t count, bool throw_exception)
SHARED_REQUIRES(Locks::mutator_lock_);
diff --git a/runtime/native/java_lang_System.cc b/runtime/native/java_lang_System.cc
index d9863c5..9e2d68d 100644
--- a/runtime/native/java_lang_System.cc
+++ b/runtime/native/java_lang_System.cc
@@ -149,7 +149,9 @@
dstObjArray->AssignableMemcpy(dstPos, srcObjArray, srcPos, count);
return;
}
- dstObjArray->AssignableCheckingMemcpy(dstPos, srcObjArray, srcPos, count, true);
+ // This code is never run under a transaction.
+ DCHECK(!Runtime::Current()->IsActiveTransaction());
+ dstObjArray->AssignableCheckingMemcpy<false>(dstPos, srcObjArray, srcPos, count, true);
}
// Template to convert general array to that of its specific primitive type.