Implemented Nested Transaction
Transaction can be created recursively and every transaction knows which
class it is created for.
The goal of implementing nested transaction is to let every class
initialization (especially execution of class initializer) associates
with one transaction which provides the ability to track whether field
accesses are valid. To achieve this goal, we implemented a subclass for
ClassLinker called AotClassLinker, which will be instantiated instead of
ClassLinker under AOT compiler.
All invocations of InitializeClass happens at AOT compiler will be
wrapped with creating and destorying transactions.
Transactions will be paused when rollbacking, otherwise the memory will
not be rollbacked properly.
The use the transaction is changed and all usage are updated,
corresponding with native tests mentioning transaction, including
transaction_test and unstarted_runtime_test.
The validation rules will be implemented and explained in another CL.
Test: make test-art-host -j64
Change-Id: If53d3ee3231c337a9ea917f5b885c173917765de
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index a932e38..7e31ff0 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -2359,7 +2359,7 @@
*file_log << exception->Dump() << "\n";
}
soa.Self()->ClearException();
- runtime->RollbackAndExitTransactionMode();
+ runtime->RollbackAllTransactions();
CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored";
} else if (is_boot_image) {
// For boot image, we want to put the updated status in the oat class since we can't
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 8d15c34..96197a5 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -25,6 +25,7 @@
defaults: ["art_defaults"],
host_supported: true,
srcs: [
+ "aot_class_linker.cc",
"art_field.cc",
"art_method.cc",
"atomic.cc",
diff --git a/runtime/aot_class_linker.cc b/runtime/aot_class_linker.cc
new file mode 100644
index 0000000..871604b
--- /dev/null
+++ b/runtime/aot_class_linker.cc
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "aot_class_linker.h"
+
+#include "handle_scope-inl.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "runtime.h"
+
+namespace art {
+
+AotClassLinker::AotClassLinker(InternTable *intern_table) : ClassLinker(intern_table) {}
+
+AotClassLinker::~AotClassLinker() {}
+
+// Wrap the original InitializeClass with creation of transaction when in strict mode.
+bool AotClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass,
+ bool can_init_statics, bool can_init_parents) {
+ Runtime* const runtime = Runtime::Current();
+
+ DCHECK(klass != nullptr);
+ if (klass->IsInitialized() || klass->IsInitializing()) {
+ return ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
+ }
+
+ if (runtime->IsActiveStrictTransactionMode()) {
+ runtime->EnterTransactionMode(true, klass.Get()->AsClass());
+ }
+ bool success = ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
+
+ if (runtime->IsActiveStrictTransactionMode()) {
+ if (success) {
+ // Exit Transaction if success.
+ runtime->ExitTransactionMode();
+ } else {
+ // If not successfully initialized, the last transaction must abort. Don't rollback
+ // immediately, leave the cleanup to compiler driver which needs abort message and exception.
+ DCHECK(runtime->IsTransactionAborted());
+ DCHECK(self->IsExceptionPending());
+ }
+ }
+ return success;
+}
+} // namespace art
diff --git a/runtime/aot_class_linker.h b/runtime/aot_class_linker.h
new file mode 100644
index 0000000..11bea86
--- /dev/null
+++ b/runtime/aot_class_linker.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_AOT_CLASS_LINKER_H_
+#define ART_RUNTIME_AOT_CLASS_LINKER_H_
+
+#include "class_linker.h"
+
+namespace art {
+// AotClassLinker is only used for AOT compiler, which includes some logic for class initialization
+// which will only be used in pre-compilation.
+class AotClassLinker : public ClassLinker {
+ public:
+ explicit AotClassLinker(InternTable *intern_table);
+ ~AotClassLinker();
+
+ bool InitializeClass(Thread *self,
+ Handle<mirror::Class> klass,
+ bool can_run_clinit,
+ bool can_init_parents)
+ OVERRIDE
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(!Locks::dex_lock_);
+};
+} // namespace art
+
+#endif // ART_RUNTIME_AOT_CLASS_LINKER_H_
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 62fb45b..324ed0c 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -141,7 +141,7 @@
};
explicit ClassLinker(InternTable* intern_table);
- ~ClassLinker();
+ virtual ~ClassLinker();
// Initialize class linker by bootstraping from dex files.
bool InitWithoutImage(std::vector<std::unique_ptr<const DexFile>> boot_class_path,
@@ -702,6 +702,14 @@
ClassTable* class_table;
};
+ protected:
+ virtual bool InitializeClass(Thread* self,
+ Handle<mirror::Class> klass,
+ bool can_run_clinit,
+ bool can_init_parents)
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(!Locks::dex_lock_);
+
private:
class LinkInterfaceMethodsHelper;
@@ -891,12 +899,6 @@
REQUIRES(!Locks::dex_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
- bool InitializeClass(Thread* self,
- Handle<mirror::Class> klass,
- bool can_run_clinit,
- bool can_init_parents)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!Locks::dex_lock_);
bool InitializeDefaultInterfaceRecursive(Thread* self,
Handle<mirror::Class> klass,
bool can_run_clinit,
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 5018eae..a8ccf89 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -38,6 +38,7 @@
#include "android-base/strings.h"
+#include "aot_class_linker.h"
#include "arch/arm/quick_method_frame_info_arm.h"
#include "arch/arm/registers_arm.h"
#include "arch/arm64/quick_method_frame_info_arm64.h"
@@ -237,7 +238,7 @@
system_thread_group_(nullptr),
system_class_loader_(nullptr),
dump_gc_performance_on_shutdown_(false),
- preinitialization_transaction_(nullptr),
+ preinitialization_transactions_(),
verify_(verifier::VerifyMode::kNone),
allow_dex_file_fallback_(true),
target_sdk_version_(0),
@@ -1283,7 +1284,11 @@
GetHeap()->EnableObjectValidation();
CHECK_GE(GetHeap()->GetContinuousSpaces().size(), 1U);
- class_linker_ = new ClassLinker(intern_table_);
+ if (UNLIKELY(IsAotCompiler())) {
+ class_linker_ = new AotClassLinker(intern_table_);
+ } else {
+ class_linker_ = new ClassLinker(intern_table_);
+ }
if (GetHeap()->HasBootImageSpace()) {
bool result = class_linker_->InitFromBootImage(&error_msg);
if (!result) {
@@ -1829,8 +1834,8 @@
}
void Runtime::VisitTransactionRoots(RootVisitor* visitor) {
- if (preinitialization_transaction_ != nullptr) {
- preinitialization_transaction_->VisitRoots(visitor);
+ for (auto& transaction : preinitialization_transactions_) {
+ transaction->VisitRoots(visitor);
}
}
@@ -2062,28 +2067,32 @@
}
// Transaction support.
+bool Runtime::IsActiveTransaction() const {
+ return !preinitialization_transactions_.empty() && !GetTransaction()->IsRollingBack();
+}
+
void Runtime::EnterTransactionMode() {
DCHECK(IsAotCompiler());
DCHECK(!IsActiveTransaction());
- preinitialization_transaction_ = std::make_unique<Transaction>();
+ preinitialization_transactions_.push_back(std::make_unique<Transaction>());
}
void Runtime::EnterTransactionMode(bool strict, mirror::Class* root) {
DCHECK(IsAotCompiler());
- preinitialization_transaction_ = std::make_unique<Transaction>(strict, root);
+ preinitialization_transactions_.push_back(std::make_unique<Transaction>(strict, root));
}
void Runtime::ExitTransactionMode() {
DCHECK(IsAotCompiler());
- preinitialization_transaction_ = nullptr;
+ DCHECK(IsActiveTransaction());
+ preinitialization_transactions_.pop_back();
}
void Runtime::RollbackAndExitTransactionMode() {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- std::unique_ptr<Transaction> rollback_transaction_= std::move(preinitialization_transaction_);
- ExitTransactionMode();
- rollback_transaction_->Rollback();
+ preinitialization_transactions_.back()->Rollback();
+ preinitialization_transactions_.pop_back();
}
bool Runtime::IsTransactionAborted() const {
@@ -2091,12 +2100,25 @@
return false;
} else {
DCHECK(IsAotCompiler());
- return preinitialization_transaction_->IsAborted();
+ return GetTransaction()->IsAborted();
+ }
+}
+
+void Runtime::RollbackAllTransactions() {
+ // If transaction is aborted, all transactions will be kept in the list.
+ // Rollback and exit all of them.
+ while (IsActiveTransaction()) {
+ RollbackAndExitTransactionMode();
}
}
bool Runtime::IsActiveStrictTransactionMode() const {
- return IsActiveTransaction() && preinitialization_transaction_->IsStrict();
+ return IsActiveTransaction() && GetTransaction()->IsStrict();
+}
+
+const std::unique_ptr<Transaction>& Runtime::GetTransaction() const {
+ DCHECK(!preinitialization_transactions_.empty());
+ return preinitialization_transactions_.back();
}
void Runtime::AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message) {
@@ -2105,57 +2127,59 @@
// Throwing an exception may cause its class initialization. If we mark the transaction
// aborted before that, we may warn with a false alarm. Throwing the exception before
// marking the transaction aborted avoids that.
- preinitialization_transaction_->ThrowAbortError(self, &abort_message);
- preinitialization_transaction_->Abort(abort_message);
+ // But now the transaction can be nested, and abort the transaction will relax the constraints
+ // for constructing stack trace.
+ GetTransaction()->Abort(abort_message);
+ GetTransaction()->ThrowAbortError(self, &abort_message);
}
void Runtime::ThrowTransactionAbortError(Thread* self) {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
// Passing nullptr means we rethrow an exception with the earlier transaction abort message.
- preinitialization_transaction_->ThrowAbortError(self, nullptr);
+ GetTransaction()->ThrowAbortError(self, nullptr);
}
void Runtime::RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset,
uint8_t value, bool is_volatile) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWriteFieldBoolean(obj, field_offset, value, is_volatile);
+ GetTransaction()->RecordWriteFieldBoolean(obj, field_offset, value, is_volatile);
}
void Runtime::RecordWriteFieldByte(mirror::Object* obj, MemberOffset field_offset,
int8_t value, bool is_volatile) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWriteFieldByte(obj, field_offset, value, is_volatile);
+ GetTransaction()->RecordWriteFieldByte(obj, field_offset, value, is_volatile);
}
void Runtime::RecordWriteFieldChar(mirror::Object* obj, MemberOffset field_offset,
uint16_t value, bool is_volatile) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWriteFieldChar(obj, field_offset, value, is_volatile);
+ GetTransaction()->RecordWriteFieldChar(obj, field_offset, value, is_volatile);
}
void Runtime::RecordWriteFieldShort(mirror::Object* obj, MemberOffset field_offset,
int16_t value, bool is_volatile) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWriteFieldShort(obj, field_offset, value, is_volatile);
+ GetTransaction()->RecordWriteFieldShort(obj, field_offset, value, is_volatile);
}
void Runtime::RecordWriteField32(mirror::Object* obj, MemberOffset field_offset,
uint32_t value, bool is_volatile) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWriteField32(obj, field_offset, value, is_volatile);
+ GetTransaction()->RecordWriteField32(obj, field_offset, value, is_volatile);
}
void Runtime::RecordWriteField64(mirror::Object* obj, MemberOffset field_offset,
uint64_t value, bool is_volatile) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWriteField64(obj, field_offset, value, is_volatile);
+ GetTransaction()->RecordWriteField64(obj, field_offset, value, is_volatile);
}
void Runtime::RecordWriteFieldReference(mirror::Object* obj,
@@ -2164,7 +2188,7 @@
bool is_volatile) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWriteFieldReference(obj,
+ GetTransaction()->RecordWriteFieldReference(obj,
field_offset,
value.Ptr(),
is_volatile);
@@ -2173,38 +2197,38 @@
void Runtime::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWriteArray(array, index, value);
+ GetTransaction()->RecordWriteArray(array, index, value);
}
void Runtime::RecordStrongStringInsertion(ObjPtr<mirror::String> s) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordStrongStringInsertion(s);
+ GetTransaction()->RecordStrongStringInsertion(s);
}
void Runtime::RecordWeakStringInsertion(ObjPtr<mirror::String> s) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWeakStringInsertion(s);
+ GetTransaction()->RecordWeakStringInsertion(s);
}
void Runtime::RecordStrongStringRemoval(ObjPtr<mirror::String> s) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordStrongStringRemoval(s);
+ GetTransaction()->RecordStrongStringRemoval(s);
}
void Runtime::RecordWeakStringRemoval(ObjPtr<mirror::String> s) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordWeakStringRemoval(s);
+ GetTransaction()->RecordWeakStringRemoval(s);
}
void Runtime::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache,
dex::StringIndex string_idx) const {
DCHECK(IsAotCompiler());
DCHECK(IsActiveTransaction());
- preinitialization_transaction_->RecordResolveString(dex_cache, string_idx);
+ GetTransaction()->RecordResolveString(dex_cache, string_idx);
}
void Runtime::SetFaultMessage(const std::string& message) {
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 7ee73e8..0c1344e 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -454,16 +454,16 @@
const std::string& profile_output_filename);
// Transaction support.
- bool IsActiveTransaction() const {
- return preinitialization_transaction_ != nullptr;
- }
+ bool IsActiveTransaction() const;
void EnterTransactionMode();
void EnterTransactionMode(bool strict, mirror::Class* root);
void ExitTransactionMode();
+ void RollbackAllTransactions() REQUIRES_SHARED(Locks::mutator_lock_);
// Transaction rollback and exit transaction are always done together, it's convenience to
// do them in one function.
void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
bool IsTransactionAborted() const;
+ const std::unique_ptr<Transaction>& GetTransaction() const;
bool IsActiveStrictTransactionMode() const;
void AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message)
@@ -842,8 +842,11 @@
// If true, then we dump the GC cumulative timings on shutdown.
bool dump_gc_performance_on_shutdown_;
- // Transaction used for pre-initializing classes at compilation time.
- std::unique_ptr<Transaction> preinitialization_transaction_;
+ // Transactions used for pre-initializing classes at compilation time.
+ // Support nested transactions, maintain a list containing all transactions. Transactions are
+ // handled under a stack discipline. Because GC needs to go over all transactions, we choose list
+ // as substantial data structure instead of stack.
+ std::list<std::unique_ptr<Transaction>> preinitialization_transactions_;
// If kNone, verification is disabled. kEnable by default.
verifier::VerifyMode verify_;
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index b4e35cd..50deb1f 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -36,6 +36,7 @@
Transaction::Transaction()
: log_lock_("transaction log lock", kTransactionLogLock),
aborted_(false),
+ rolling_back_(false),
strict_(false) {
CHECK(Runtime::Current()->IsAotCompiler());
}
@@ -104,6 +105,10 @@
return aborted_;
}
+bool Transaction::IsRollingBack() {
+ return rolling_back_;
+}
+
bool Transaction::IsStrict() {
MutexLock mu(Thread::Current(), log_lock_);
return strict_;
@@ -234,15 +239,17 @@
}
void Transaction::Rollback() {
- CHECK(!Runtime::Current()->IsActiveTransaction());
Thread* self = Thread::Current();
self->AssertNoPendingException();
MutexLock mu1(self, *Locks::intern_table_lock_);
MutexLock mu2(self, log_lock_);
+ rolling_back_ = true;
+ CHECK(!Runtime::Current()->IsActiveTransaction());
UndoObjectModifications();
UndoArrayModifications();
UndoInternStringTableModifications();
UndoResolveStringModifications();
+ rolling_back_ = false;
}
void Transaction::UndoObjectModifications() {
@@ -424,7 +431,7 @@
const FieldValue& field_value) const {
// TODO We may want to abort a transaction while still being in transaction mode. In this case,
// we'd need to disable the check.
- constexpr bool kCheckTransaction = true;
+ constexpr bool kCheckTransaction = false;
switch (field_value.kind) {
case kBoolean:
if (UNLIKELY(field_value.is_volatile)) {
@@ -603,30 +610,39 @@
uint64_t value) const {
// TODO We may want to abort a transaction while still being in transaction mode. In this case,
// we'd need to disable the check.
+ constexpr bool kCheckTransaction = false;
switch (array_type) {
case Primitive::kPrimBoolean:
- array->AsBooleanArray()->SetWithoutChecks<false>(index, static_cast<uint8_t>(value));
+ array->AsBooleanArray()->SetWithoutChecks<false, kCheckTransaction>(
+ index, static_cast<uint8_t>(value));
break;
case Primitive::kPrimByte:
- array->AsByteArray()->SetWithoutChecks<false>(index, static_cast<int8_t>(value));
+ array->AsByteArray()->SetWithoutChecks<false, kCheckTransaction>(
+ index, static_cast<int8_t>(value));
break;
case Primitive::kPrimChar:
- array->AsCharArray()->SetWithoutChecks<false>(index, static_cast<uint16_t>(value));
+ array->AsCharArray()->SetWithoutChecks<false, kCheckTransaction>(
+ index, static_cast<uint16_t>(value));
break;
case Primitive::kPrimShort:
- array->AsShortArray()->SetWithoutChecks<false>(index, static_cast<int16_t>(value));
+ array->AsShortArray()->SetWithoutChecks<false, kCheckTransaction>(
+ index, static_cast<int16_t>(value));
break;
case Primitive::kPrimInt:
- array->AsIntArray()->SetWithoutChecks<false>(index, static_cast<int32_t>(value));
+ array->AsIntArray()->SetWithoutChecks<false, kCheckTransaction>(
+ index, static_cast<int32_t>(value));
break;
case Primitive::kPrimFloat:
- array->AsFloatArray()->SetWithoutChecks<false>(index, static_cast<float>(value));
+ array->AsFloatArray()->SetWithoutChecks<false, kCheckTransaction>(
+ index, static_cast<float>(value));
break;
case Primitive::kPrimLong:
- array->AsLongArray()->SetWithoutChecks<false>(index, static_cast<int64_t>(value));
+ array->AsLongArray()->SetWithoutChecks<false, kCheckTransaction>(
+ index, static_cast<int64_t>(value));
break;
case Primitive::kPrimDouble:
- array->AsDoubleArray()->SetWithoutChecks<false>(index, static_cast<double>(value));
+ array->AsDoubleArray()->SetWithoutChecks<false, kCheckTransaction>(
+ index, static_cast<double>(value));
break;
case Primitive::kPrimNot:
LOG(FATAL) << "ObjectArray should be treated as Object";
diff --git a/runtime/transaction.h b/runtime/transaction.h
index fb0d1ec..64349de 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -56,6 +56,10 @@
REQUIRES_SHARED(Locks::mutator_lock_);
bool IsAborted() REQUIRES(!log_lock_);
+ // If the transaction is rollbacking. Transactions will set this flag when they start rollbacking,
+ // because the nested transaction should be disabled when rollbacking to restore the memory.
+ bool IsRollingBack();
+
// If the transaction is in strict mode, then all access of static fields will be constrained,
// one class's clinit will not be allowed to read or modify another class's static fields, unless
// the transaction is aborted.
@@ -294,6 +298,7 @@
std::list<InternStringLog> intern_string_logs_ GUARDED_BY(log_lock_);
std::list<ResolveStringLog> resolve_string_logs_ GUARDED_BY(log_lock_);
bool aborted_ GUARDED_BY(log_lock_);
+ bool rolling_back_; // Single thread, no race.
bool strict_ GUARDED_BY(log_lock_);
std::string abort_message_ GUARDED_BY(log_lock_);
mirror::Class* root_ GUARDED_BY(log_lock_);