/* * Copyright (C) 2015 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 "lambda/box_table.h" #include "base/mutex.h" #include "common_throws.h" #include "gc_root-inl.h" #include "lambda/closure.h" #include "lambda/leaking_allocator.h" #include "mirror/method.h" #include "mirror/object-inl.h" #include "thread.h" #include <vector> namespace art { namespace lambda { // Temporarily represent the lambda Closure as its raw bytes in an array. // TODO: Generate a proxy class for the closure when boxing the first time. using BoxedClosurePointerType = mirror::ByteArray*; static mirror::Class* GetBoxedClosureClass() SHARED_REQUIRES(Locks::mutator_lock_) { return mirror::ByteArray::GetArrayClass(); } namespace { // Convenience functions to allocating/deleting box table copies of the closures. struct ClosureAllocator { // Deletes a Closure that was allocated through ::Allocate. static void Delete(Closure* ptr) { delete[] reinterpret_cast<char*>(ptr); } // Returns a well-aligned pointer to a newly allocated Closure on the 'new' heap. static Closure* Allocate(size_t size) { DCHECK_GE(size, sizeof(Closure)); // TODO: Maybe point to the interior of the boxed closure object after we add proxy support? Closure* closure = reinterpret_cast<Closure*>(new char[size]); DCHECK_ALIGNED(closure, alignof(Closure)); return closure; } }; } // namespace BoxTable::BoxTable() : allow_new_weaks_(true), new_weaks_condition_("lambda box table allowed weaks", *Locks::lambda_table_lock_) {} BoxTable::~BoxTable() { // Free all the copies of our closures. for (auto map_iterator = map_.begin(); map_iterator != map_.end(); ) { std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; Closure* closure = key_value_pair.first; // Remove from the map first, so that it doesn't try to access dangling pointer. map_iterator = map_.Erase(map_iterator); // Safe to delete, no dangling pointers. ClosureAllocator::Delete(closure); } } mirror::Object* BoxTable::BoxLambda(const ClosureType& closure) { Thread* self = Thread::Current(); { // TODO: Switch to ReaderMutexLock if ConditionVariable ever supports RW Mutexes /*Reader*/MutexLock mu(self, *Locks::lambda_table_lock_); BlockUntilWeaksAllowed(); // Attempt to look up this object, it's possible it was already boxed previously. // If this is the case we *must* return the same object as before to maintain // referential equality. // // In managed code: // Functional f = () -> 5; // vF = create-lambda // Object a = f; // vA = box-lambda vA // Object b = f; // vB = box-lambda vB // assert(a == f) ValueType value = FindBoxedLambda(closure); if (!value.IsNull()) { return value.Read(); } // Otherwise we need to box ourselves and insert it into the hash map } // Release the lambda table lock here, so that thread suspension is allowed. // Convert the Closure into a managed byte[] which will serve // as the temporary 'boxed' version of the lambda. This is good enough // to check all the basic object identities that a boxed lambda must retain. // It's also good enough to contain all the captured primitive variables. // TODO: Boxing an innate lambda (i.e. made with create-lambda) should make a proxy class // TODO: Boxing a learned lambda (i.e. made with unbox-lambda) should return the original object BoxedClosurePointerType closure_as_array_object = mirror::ByteArray::Alloc(self, closure->GetSize()); // There are no thread suspension points after this, so we don't need to put it into a handle. if (UNLIKELY(closure_as_array_object == nullptr)) { // Most likely an OOM has occurred. CHECK(self->IsExceptionPending()); return nullptr; } // Write the raw closure data into the byte[]. closure->CopyTo(closure_as_array_object->GetRawData(sizeof(uint8_t), // component size 0 /*index*/), // index closure_as_array_object->GetLength()); // The method has been successfully boxed into an object, now insert it into the hash map. { MutexLock mu(self, *Locks::lambda_table_lock_); BlockUntilWeaksAllowed(); // Lookup the object again, it's possible another thread already boxed it while // we were allocating the object before. ValueType value = FindBoxedLambda(closure); if (UNLIKELY(!value.IsNull())) { // Let the GC clean up method_as_object at a later time. return value.Read(); } // Otherwise we need to insert it into the hash map in this thread. // Make a copy for the box table to keep, in case the closure gets collected from the stack. // TODO: GC may need to sweep for roots in the box table's copy of the closure. Closure* closure_table_copy = ClosureAllocator::Allocate(closure->GetSize()); closure->CopyTo(closure_table_copy, closure->GetSize()); // The closure_table_copy needs to be deleted by us manually when we erase it from the map. // Actually insert into the table. map_.Insert({closure_table_copy, ValueType(closure_as_array_object)}); } return closure_as_array_object; } bool BoxTable::UnboxLambda(mirror::Object* object, ClosureType* out_closure) { DCHECK(object != nullptr); *out_closure = nullptr; Thread* self = Thread::Current(); // Note that we do not need to access lambda_table_lock_ here // since we don't need to look at the map. mirror::Object* boxed_closure_object = object; // Raise ClassCastException if object is not instanceof byte[] if (UNLIKELY(!boxed_closure_object->InstanceOf(GetBoxedClosureClass()))) { ThrowClassCastException(GetBoxedClosureClass(), boxed_closure_object->GetClass()); return false; } // TODO(iam): We must check that the closure object extends/implements the type // specified in [type id]. This is not currently implemented since it's always a byte[]. // If we got this far, the inputs are valid. // Shuffle the byte[] back into a raw closure, then allocate it, copy, and return it. BoxedClosurePointerType boxed_closure_as_array = down_cast<BoxedClosurePointerType>(boxed_closure_object); const int8_t* unaligned_interior_closure = boxed_closure_as_array->GetData(); // Allocate a copy that can "escape" and copy the closure data into that. Closure* unboxed_closure = LeakingAllocator::MakeFlexibleInstance<Closure>(self, boxed_closure_as_array->GetLength()); // TODO: don't just memcpy the closure, it's unsafe when we add references to the mix. memcpy(unboxed_closure, unaligned_interior_closure, boxed_closure_as_array->GetLength()); DCHECK_EQ(unboxed_closure->GetSize(), static_cast<size_t>(boxed_closure_as_array->GetLength())); *out_closure = unboxed_closure; return true; } BoxTable::ValueType BoxTable::FindBoxedLambda(const ClosureType& closure) const { auto map_iterator = map_.Find(closure); if (map_iterator != map_.end()) { const std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; const ValueType& value = key_value_pair.second; DCHECK(!value.IsNull()); // Never store null boxes. return value; } return ValueType(nullptr); } void BoxTable::BlockUntilWeaksAllowed() { Thread* self = Thread::Current(); while (UNLIKELY((!kUseReadBarrier && !allow_new_weaks_) || (kUseReadBarrier && !self->GetWeakRefAccessEnabled()))) { new_weaks_condition_.WaitHoldingLocks(self); // wait while holding mutator lock } } void BoxTable::SweepWeakBoxedLambdas(IsMarkedVisitor* visitor) { DCHECK(visitor != nullptr); Thread* self = Thread::Current(); MutexLock mu(self, *Locks::lambda_table_lock_); /* * Visit every weak root in our lambda box table. * Remove unmarked objects, update marked objects to new address. */ std::vector<ClosureType> remove_list; for (auto map_iterator = map_.begin(); map_iterator != map_.end(); ) { std::pair<UnorderedMapKeyType, ValueType>& key_value_pair = *map_iterator; const ValueType& old_value = key_value_pair.second; // This does not need a read barrier because this is called by GC. mirror::Object* old_value_raw = old_value.Read<kWithoutReadBarrier>(); mirror::Object* new_value = visitor->IsMarked(old_value_raw); if (new_value == nullptr) { // The object has been swept away. const ClosureType& closure = key_value_pair.first; // Delete the entry from the map. map_iterator = map_.Erase(map_iterator); // Clean up the memory by deleting the closure. ClosureAllocator::Delete(closure); } else { // The object has been moved. // Update the map. key_value_pair.second = ValueType(new_value); ++map_iterator; } } // Occasionally shrink the map to avoid growing very large. if (map_.CalculateLoadFactor() < kMinimumLoadFactor) { map_.ShrinkToMaximumLoad(); } } void BoxTable::DisallowNewWeakBoxedLambdas() { CHECK(!kUseReadBarrier); Thread* self = Thread::Current(); MutexLock mu(self, *Locks::lambda_table_lock_); allow_new_weaks_ = false; } void BoxTable::AllowNewWeakBoxedLambdas() { CHECK(!kUseReadBarrier); Thread* self = Thread::Current(); MutexLock mu(self, *Locks::lambda_table_lock_); allow_new_weaks_ = true; new_weaks_condition_.Broadcast(self); } void BoxTable::BroadcastForNewWeakBoxedLambdas() { CHECK(kUseReadBarrier); Thread* self = Thread::Current(); MutexLock mu(self, *Locks::lambda_table_lock_); new_weaks_condition_.Broadcast(self); } void BoxTable::EmptyFn::MakeEmpty(std::pair<UnorderedMapKeyType, ValueType>& item) const { item.first = nullptr; Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); item.second = ValueType(); // Also clear the GC root. } bool BoxTable::EmptyFn::IsEmpty(const std::pair<UnorderedMapKeyType, ValueType>& item) const { return item.first == nullptr; } bool BoxTable::EqualsFn::operator()(const UnorderedMapKeyType& lhs, const UnorderedMapKeyType& rhs) const { // Nothing needs this right now, but leave this assertion for later when // we need to look at the references inside of the closure. Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); return lhs->ReferenceEquals(rhs); } size_t BoxTable::HashFn::operator()(const UnorderedMapKeyType& key) const { const lambda::Closure* closure = key; DCHECK_ALIGNED(closure, alignof(lambda::Closure)); // Need to hold mutator_lock_ before calling into Closure::GetHashCode. Locks::mutator_lock_->AssertSharedHeld(Thread::Current()); return closure->GetHashCode(); } } // namespace lambda } // namespace art