blob: 179e4ee7f2db24ed59493503938aa8d1b9051e97 [file] [log] [blame]
/*
* 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/closure.h"
#include "base/logging.h"
#include "lambda/art_lambda_method.h"
#include "runtime/mirror/object_reference.h"
static constexpr const bool kClosureSupportsReferences = false;
static constexpr const bool kClosureSupportsGarbageCollection = false;
namespace art {
namespace lambda {
template <typename T>
// TODO: can I return T __attribute__((__aligned__(1)))* here instead?
const uint8_t* Closure::GetUnsafeAtOffset(size_t offset) const {
// Do not DCHECK here with existing helpers since most of them will call into this function.
return reinterpret_cast<const uint8_t*>(captured_) + offset;
}
size_t Closure::GetCapturedVariableSize(ShortyFieldType variable_type, size_t offset) const {
switch (variable_type) {
case ShortyFieldType::kLambda:
{
return GetClosureSize(GetUnsafeAtOffset<Closure>(offset));
}
default:
DCHECK(variable_type.IsStaticSize());
return variable_type.GetStaticSize();
}
}
// Templatize the flags to give the compiler a fighting chance to eliminate
// any unnecessary code through different uses of this function.
template <Closure::VariableInfo::Flags flags>
inline Closure::VariableInfo Closure::ParseTypeDescriptor(const char* type_descriptor,
size_t upto_index) const {
DCHECK(type_descriptor != nullptr);
VariableInfo result;
ShortyFieldType last_type;
size_t offset = (flags & VariableInfo::kOffset) ? GetStartingOffset() : 0;
size_t prev_offset = 0;
size_t count = 0;
while ((type_descriptor =
ShortyFieldType::ParseFromFieldTypeDescriptor(type_descriptor, &last_type)) != nullptr) {
count++;
if (flags & VariableInfo::kOffset) {
// Accumulate the sizes of all preceding captured variables as the current offset only.
offset += prev_offset;
prev_offset = GetCapturedVariableSize(last_type, offset);
}
if ((count > upto_index)) {
break;
}
}
if (flags & VariableInfo::kVariableType) {
result.variable_type_ = last_type;
}
if (flags & VariableInfo::kIndex) {
result.index_ = count;
}
if (flags & VariableInfo::kCount) {
result.count_ = count;
}
if (flags & VariableInfo::kOffset) {
result.offset_ = offset;
}
// TODO: We should probably store the result of this in the ArtLambdaMethod,
// to avoid re-computing the data every single time for static closures.
return result;
}
size_t Closure::GetCapturedVariablesSize() const {
const size_t captured_variable_offset = offsetof(Closure, captured_);
DCHECK_GE(GetSize(), captured_variable_offset); // Prevent underflows.
return GetSize() - captured_variable_offset;
}
size_t Closure::GetSize() const {
const size_t static_closure_size = lambda_info_->GetStaticClosureSize();
if (LIKELY(lambda_info_->IsStaticSize())) {
return static_closure_size;
}
DCHECK_GE(static_closure_size, sizeof(captured_[0].dynamic_.size_));
const size_t dynamic_closure_size = captured_[0].dynamic_.size_;
// The dynamic size better be at least as big as the static size.
DCHECK_GE(dynamic_closure_size, static_closure_size);
return dynamic_closure_size;
}
void Closure::CopyTo(void* target, size_t target_size) const {
DCHECK_GE(target_size, GetSize());
// TODO: using memcpy is unsafe with read barriers, fix this once we add reference support
static_assert(kClosureSupportsReferences == false,
"Do not use memcpy with readbarrier references");
memcpy(target, this, GetSize());
}
ArtMethod* Closure::GetTargetMethod() const {
return const_cast<ArtMethod*>(lambda_info_->GetArtMethod());
}
uint32_t Closure::GetHashCode() const {
// Start with a non-zero constant, a prime number.
uint32_t result = 17;
// Include the hash with the ArtMethod.
{
uintptr_t method = reinterpret_cast<uintptr_t>(GetTargetMethod());
result = 31 * result + Low32Bits(method);
if (sizeof(method) == sizeof(uint64_t)) {
result = 31 * result + High32Bits(method);
}
}
// Include a hash for each captured variable.
for (size_t i = 0; i < GetCapturedVariablesSize(); ++i) {
// TODO: not safe for GC-able values since the address can move and the hash code would change.
uint8_t captured_variable_raw_value;
CopyUnsafeAtOffset<uint8_t>(i, /*out*/&captured_variable_raw_value); // NOLINT: [whitespace/comma] [3]
result = 31 * result + captured_variable_raw_value;
}
// TODO: Fix above loop to work for objects and lambdas.
static_assert(kClosureSupportsGarbageCollection == false,
"Need to update above loop to read the hash code from the "
"objects and lambdas recursively");
return result;
}
bool Closure::ReferenceEquals(const Closure* other) const {
DCHECK(other != nullptr);
// TODO: Need rework to use read barriers once closures have references inside of them that can
// move. Until then, it's safe to just compare the data inside of it directly.
static_assert(kClosureSupportsReferences == false,
"Unsafe to use memcmp in read barrier collector");
if (GetSize() != other->GetSize()) {
return false;
}
return memcmp(this, other, GetSize());
}
size_t Closure::GetNumberOfCapturedVariables() const {
// TODO: refactor into art_lambda_method.h. Parsing should only be required here as a DCHECK.
VariableInfo variable_info =
ParseTypeDescriptor<VariableInfo::kCount>(GetCapturedVariablesTypeDescriptor(),
VariableInfo::kUpToIndexMax);
size_t count = variable_info.count_;
// Assuming each variable was 1 byte, the size should always be greater or equal than the count.
DCHECK_LE(count, GetCapturedVariablesSize());
return count;
}
const char* Closure::GetCapturedVariablesTypeDescriptor() const {
return lambda_info_->GetCapturedVariablesTypeDescriptor();
}
ShortyFieldType Closure::GetCapturedShortyType(size_t index) const {
DCHECK_LT(index, GetNumberOfCapturedVariables());
VariableInfo variable_info =
ParseTypeDescriptor<VariableInfo::kVariableType>(GetCapturedVariablesTypeDescriptor(),
index);
return variable_info.variable_type_;
}
uint32_t Closure::GetCapturedPrimitiveNarrow(size_t index) const {
DCHECK(GetCapturedShortyType(index).IsPrimitiveNarrow());
ShortyFieldType variable_type;
size_t offset;
GetCapturedVariableTypeAndOffset(index, &variable_type, &offset);
// TODO: Restructure to use template specialization, e.g. GetCapturedPrimitive<T>
// so that we can avoid this nonsense regarding memcpy always overflowing.
// Plus, this additional switching seems redundant since the interpreter
// would've done it already, and knows the exact type.
uint32_t result = 0;
static_assert(ShortyFieldTypeTraits::IsPrimitiveNarrowType<decltype(result)>(),
"result must be a primitive narrow type");
switch (variable_type) {
case ShortyFieldType::kBoolean:
CopyUnsafeAtOffset<bool>(offset, &result);
break;
case ShortyFieldType::kByte:
CopyUnsafeAtOffset<uint8_t>(offset, &result);
break;
case ShortyFieldType::kChar:
CopyUnsafeAtOffset<uint16_t>(offset, &result);
break;
case ShortyFieldType::kShort:
CopyUnsafeAtOffset<int16_t>(offset, &result);
break;
case ShortyFieldType::kInt:
CopyUnsafeAtOffset<int32_t>(offset, &result);
break;
case ShortyFieldType::kFloat:
// XX: Maybe there should just be a GetCapturedPrimitive<T> to avoid this shuffle?
// The interpreter's invoke seems to only special case references and wides,
// everything else is treated as a generic 32-bit pattern.
CopyUnsafeAtOffset<float>(offset, &result);
break;
default:
LOG(FATAL)
<< "expected a valid narrow primitive shorty type but got "
<< static_cast<char>(variable_type);
UNREACHABLE();
}
return result;
}
uint64_t Closure::GetCapturedPrimitiveWide(size_t index) const {
DCHECK(GetCapturedShortyType(index).IsPrimitiveWide());
ShortyFieldType variable_type;
size_t offset;
GetCapturedVariableTypeAndOffset(index, &variable_type, &offset);
// TODO: Restructure to use template specialization, e.g. GetCapturedPrimitive<T>
// so that we can avoid this nonsense regarding memcpy always overflowing.
// Plus, this additional switching seems redundant since the interpreter
// would've done it already, and knows the exact type.
uint64_t result = 0;
static_assert(ShortyFieldTypeTraits::IsPrimitiveWideType<decltype(result)>(),
"result must be a primitive wide type");
switch (variable_type) {
case ShortyFieldType::kLong:
CopyUnsafeAtOffset<int64_t>(offset, &result);
break;
case ShortyFieldType::kDouble:
CopyUnsafeAtOffset<double>(offset, &result);
break;
default:
LOG(FATAL)
<< "expected a valid primitive wide shorty type but got "
<< static_cast<char>(variable_type);
UNREACHABLE();
}
return result;
}
mirror::Object* Closure::GetCapturedObject(size_t index) const {
DCHECK(GetCapturedShortyType(index).IsObject());
ShortyFieldType variable_type;
size_t offset;
GetCapturedVariableTypeAndOffset(index, &variable_type, &offset);
// TODO: Restructure to use template specialization, e.g. GetCapturedPrimitive<T>
// so that we can avoid this nonsense regarding memcpy always overflowing.
// Plus, this additional switching seems redundant since the interpreter
// would've done it already, and knows the exact type.
mirror::Object* result = nullptr;
static_assert(ShortyFieldTypeTraits::IsObjectType<decltype(result)>(),
"result must be an object type");
switch (variable_type) {
case ShortyFieldType::kObject:
// TODO: This seems unsafe. This may need to use gcroots.
static_assert(kClosureSupportsGarbageCollection == false,
"May need GcRoots and definitely need mutator locks");
{
mirror::CompressedReference<mirror::Object> compressed_result;
CopyUnsafeAtOffset<uint32_t>(offset, &compressed_result);
result = compressed_result.AsMirrorPtr();
}
break;
default:
CHECK(false)
<< "expected a valid shorty type but got " << static_cast<char>(variable_type);
UNREACHABLE();
}
return result;
}
size_t Closure::GetCapturedClosureSize(size_t index) const {
DCHECK(GetCapturedShortyType(index).IsLambda());
size_t offset = GetCapturedVariableOffset(index);
auto* captured_ptr = reinterpret_cast<const uint8_t*>(&captured_);
size_t closure_size = GetClosureSize(captured_ptr + offset);
return closure_size;
}
void Closure::CopyCapturedClosure(size_t index, void* destination, size_t destination_room) const {
DCHECK(GetCapturedShortyType(index).IsLambda());
size_t offset = GetCapturedVariableOffset(index);
auto* captured_ptr = reinterpret_cast<const uint8_t*>(&captured_);
size_t closure_size = GetClosureSize(captured_ptr + offset);
static_assert(ShortyFieldTypeTraits::IsLambdaType<Closure*>(),
"result must be a lambda type");
CopyUnsafeAtOffset<Closure>(offset, destination, closure_size, destination_room);
}
size_t Closure::GetCapturedVariableOffset(size_t index) const {
VariableInfo variable_info =
ParseTypeDescriptor<VariableInfo::kOffset>(GetCapturedVariablesTypeDescriptor(),
index);
size_t offset = variable_info.offset_;
return offset;
}
void Closure::GetCapturedVariableTypeAndOffset(size_t index,
ShortyFieldType* out_type,
size_t* out_offset) const {
DCHECK(out_type != nullptr);
DCHECK(out_offset != nullptr);
static constexpr const VariableInfo::Flags kVariableTypeAndOffset =
static_cast<VariableInfo::Flags>(VariableInfo::kVariableType | VariableInfo::kOffset);
VariableInfo variable_info =
ParseTypeDescriptor<kVariableTypeAndOffset>(GetCapturedVariablesTypeDescriptor(),
index);
ShortyFieldType variable_type = variable_info.variable_type_;
size_t offset = variable_info.offset_;
*out_type = variable_type;
*out_offset = offset;
}
template <typename T>
void Closure::CopyUnsafeAtOffset(size_t offset,
void* destination,
size_t src_size,
size_t destination_room) const {
DCHECK_GE(destination_room, src_size);
const uint8_t* data_ptr = GetUnsafeAtOffset<T>(offset);
memcpy(destination, data_ptr, sizeof(T));
}
// TODO: This is kind of ugly. I would prefer an unaligned_ptr<Closure> here.
// Unfortunately C++ doesn't let you lower the alignment (i.e. alignas(1) Closure*) is not legal.
size_t Closure::GetClosureSize(const uint8_t* closure) {
DCHECK(closure != nullptr);
static_assert(!std::is_base_of<mirror::Object, Closure>::value,
"It might be unsafe to call memcpy on a managed object");
// Safe as long as it's not a mirror Object.
// TODO: Should probably wrap this in like MemCpyNative or some such which statically asserts
// we aren't trying to copy mirror::Object data around.
ArtLambdaMethod* closure_info;
memcpy(&closure_info, closure + offsetof(Closure, lambda_info_), sizeof(closure_info));
if (LIKELY(closure_info->IsStaticSize())) {
return closure_info->GetStaticClosureSize();
}
// The size is dynamic, so we need to read it from captured_variables_ portion.
size_t dynamic_size;
memcpy(&dynamic_size,
closure + offsetof(Closure, captured_[0].dynamic_.size_),
sizeof(dynamic_size));
static_assert(sizeof(dynamic_size) == sizeof(captured_[0].dynamic_.size_),
"Dynamic size type must match the structural type of the size");
DCHECK_GE(dynamic_size, closure_info->GetStaticClosureSize());
return dynamic_size;
}
size_t Closure::GetStartingOffset() const {
static constexpr const size_t captured_offset = offsetof(Closure, captured_);
if (LIKELY(lambda_info_->IsStaticSize())) {
return offsetof(Closure, captured_[0].static_variables_) - captured_offset;
} else {
return offsetof(Closure, captured_[0].dynamic_.variables_) - captured_offset;
}
}
} // namespace lambda
} // namespace art