diff options
author | 2015-07-30 15:11:09 -0700 | |
---|---|---|
committer | 2015-09-03 17:28:37 -0700 | |
commit | fc1ccd740b7c8e96dfac675cfc580122cd1b40a6 (patch) | |
tree | 95eb97f3cfebe13fa4c5928089ff9b7ee50c4793 | |
parent | ea33c3041e8db74d79a188703c9ec9c3879f9c1b (diff) |
lambda: Infrastructure to support capture/liberate-variable dex opcodes
* ArtLambdaMethod - wrap an ArtMethod with extra runtime lambda info
* Closure - data representation for a runtime lambda closure (read-only)
* ClosureBuilder - writer for creating a Closure at runtime
* ShortyFieldType - char/enum wrapper for shorty_field_type in dex
Tests:
* Closure, ClosureBuilder, ShortyFieldType have full unit test coverage.
* ArtLambdaMethod does not, but it is tested indirectly and is otherwise
trivial getters.
Future CLs will include interpreter integration with minimal changes to
this Closure infrastructure.
Change-Id: I38a7aea8df1da7b154fd6623258c6c228c8e51df
-rw-r--r-- | build/Android.gtest.mk | 2 | ||||
-rw-r--r-- | runtime/Android.mk | 3 | ||||
-rw-r--r-- | runtime/lambda/art_lambda_method.cc | 77 | ||||
-rw-r--r-- | runtime/lambda/art_lambda_method.h | 116 | ||||
-rw-r--r-- | runtime/lambda/closure.cc | 365 | ||||
-rw-r--r-- | runtime/lambda/closure.h | 171 | ||||
-rw-r--r-- | runtime/lambda/closure_builder-inl.h | 43 | ||||
-rw-r--r-- | runtime/lambda/closure_builder.cc | 198 | ||||
-rw-r--r-- | runtime/lambda/closure_builder.h | 101 | ||||
-rw-r--r-- | runtime/lambda/closure_test.cc | 356 | ||||
-rw-r--r-- | runtime/lambda/shorty_field_type.h | 475 | ||||
-rw-r--r-- | runtime/lambda/shorty_field_type_test.cc | 354 |
12 files changed, 2261 insertions, 0 deletions
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index c88d677738..326a92bc20 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -204,6 +204,8 @@ RUNTIME_GTEST_COMMON_SRC_FILES := \ runtime/interpreter/unstarted_runtime_test.cc \ runtime/java_vm_ext_test.cc \ runtime/jit/jit_code_cache_test.cc \ + runtime/lambda/closure_test.cc \ + runtime/lambda/shorty_field_type_test.cc \ runtime/leb128_test.cc \ runtime/mem_map_test.cc \ runtime/memory_region_test.cc \ diff --git a/runtime/Android.mk b/runtime/Android.mk index 8f70d30894..963eecb84a 100644 --- a/runtime/Android.mk +++ b/runtime/Android.mk @@ -99,7 +99,10 @@ LIBART_COMMON_SRC_FILES := \ jit/jit.cc \ jit/jit_code_cache.cc \ jit/jit_instrumentation.cc \ + lambda/art_lambda_method.cc \ lambda/box_table.cc \ + lambda/closure.cc \ + lambda/closure_builder.cc \ jni_internal.cc \ jobject_comparator.cc \ linear_alloc.cc \ diff --git a/runtime/lambda/art_lambda_method.cc b/runtime/lambda/art_lambda_method.cc new file mode 100644 index 0000000000..6f9f8bbb59 --- /dev/null +++ b/runtime/lambda/art_lambda_method.cc @@ -0,0 +1,77 @@ +/* + * 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/art_lambda_method.h" + +#include "base/logging.h" +#include "lambda/shorty_field_type.h" + +namespace art { +namespace lambda { + +ArtLambdaMethod::ArtLambdaMethod(ArtMethod* target_method, + const char* captured_variables_type_descriptor, + const char* captured_variables_shorty, + bool innate_lambda) + : method_(target_method), + captured_variables_type_descriptor_(captured_variables_type_descriptor), + captured_variables_shorty_(captured_variables_shorty), + innate_lambda_(innate_lambda) { + DCHECK(target_method != nullptr); + DCHECK(captured_variables_type_descriptor != nullptr); + DCHECK(captured_variables_shorty != nullptr); + + // Calculate the static closure size from the captured variables. + size_t size = sizeof(ArtLambdaMethod*); // Initial size is just this method. + bool static_size = true; + const char* shorty = captured_variables_shorty_; + while (shorty != nullptr && *shorty != '\0') { + // Each captured variable also appends to the size. + ShortyFieldType shorty_field{*shorty}; // NOLINT [readability/braces] [4] + size += shorty_field.GetStaticSize(); + static_size &= shorty_field.IsStaticSize(); + ++shorty; + } + closure_size_ = size; + + // We determine whether or not the size is dynamic by checking for nested lambdas. + // + // This is conservative, since in theory an optimization could determine the size + // of the nested lambdas recursively. In practice it's probably better to flatten out + // nested lambdas and inline all their code if they are known statically. + dynamic_size_ = !static_size; + + if (kIsDebugBuild) { + // Double check that the number of captured variables match in both strings. + size_t shorty_count = strlen(captured_variables_shorty); + + size_t long_count = 0; + const char* long_type = captured_variables_type_descriptor; + ShortyFieldType out; + while ((long_type = ShortyFieldType::ParseFromFieldTypeDescriptor(long_type, &out)) + != nullptr) { + ++long_count; + } + + DCHECK_EQ(shorty_count, long_count) + << "number of captured variables in long type '" << captured_variables_type_descriptor + << "' (" << long_count << ")" << " did not match short type '" + << captured_variables_shorty << "' (" << shorty_count << ")"; + } +} + +} // namespace lambda +} // namespace art diff --git a/runtime/lambda/art_lambda_method.h b/runtime/lambda/art_lambda_method.h new file mode 100644 index 0000000000..892d8c6f6b --- /dev/null +++ b/runtime/lambda/art_lambda_method.h @@ -0,0 +1,116 @@ +/* + * 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. + */ +#ifndef ART_RUNTIME_LAMBDA_ART_LAMBDA_METHOD_H_ +#define ART_RUNTIME_LAMBDA_ART_LAMBDA_METHOD_H_ + +#include "base/macros.h" +#include "art_method.h" + +#include <stdint.h> + +namespace art { +namespace lambda { + +class ArtLambdaMethod { + public: + // Construct an art lambda method. + // The target method is the one invoked by invoke-lambda. + // The type descriptor describes the types of variables captured, e.g. "ZFLObject;\FI;[Z" + // The shorty drops the object name and treats arrays as objects, e.g. "ZFL\L" + // Innate lambda means that the lambda was originally created via invoke-lambda. + // -- Non-innate lambdas (learned lambdas) come from a regular class that was boxed to lambda. + // (Ownership of strings is retained by the caller and the lifetime should exceed this class). + ArtLambdaMethod(ArtMethod* target_method, + const char* captured_variables_type_descriptor, + const char* captured_variables_shorty_, + bool innate_lambda = true); + + // Get the target method for this lambda that would be used by the invoke-lambda dex instruction. + ArtMethod* GetArtMethod() const { + return method_; + } + + // Get the compile-time size of lambda closures for this method in bytes. + // This is circular (that is, it includes the size of the ArtLambdaMethod pointer). + // One should also check if the size is dynamic since nested lambdas have a runtime size. + size_t GetStaticClosureSize() const { + return closure_size_; + } + + // Get the type descriptor for the list of captured variables. + // e.g. "ZFLObject;\FI;[Z" means a captured int, float, class Object, lambda FI, array of ints + const char* GetCapturedVariablesTypeDescriptor() const { + return captured_variables_type_descriptor_; + } + + // Get the shorty 'field' type descriptor list of captured variables. + // This follows the same rules as a string of ShortyFieldType in the dex specification. + // Every captured variable is represented by exactly one character. + // - Objects become 'L'. + // - Arrays become 'L'. + // - Lambdas become '\'. + const char* GetCapturedVariablesShortyTypeDescriptor() const { + return captured_variables_shorty_; + } + + // Will the size of this lambda change at runtime? + // Only returns true if there is a nested lambda that we can't determine statically the size of. + bool IsDynamicSize() const { + return dynamic_size_; + } + + // Will the size of this lambda always be constant at runtime? + // This generally means there's no nested lambdas, or we were able to successfully determine + // their size statically at compile time. + bool IsStaticSize() const { + return !IsDynamicSize(); + } + // Is this a lambda that was originally created via invoke-lambda? + // -- Non-innate lambdas (learned lambdas) come from a regular class that was boxed to lambda. + bool IsInnateLambda() const { + return innate_lambda_; + } + + // How many variables were captured? + // (Each nested lambda counts as 1 captured var regardless of how many captures it itself has). + size_t GetNumberOfCapturedVariables() const { + return strlen(captured_variables_shorty_); + } + + private: + // TODO: ArtMethod, or at least the entry points should be inlined into this struct + // to avoid an extra indirect load when doing invokes. + // Target method that invoke-lambda will jump to. + ArtMethod* method_; + // How big the closure is (in bytes). Only includes the constant size. + size_t closure_size_; + // The type descriptor for the captured variables, e.g. "IS" for [int, short] + const char* captured_variables_type_descriptor_; + // The shorty type descriptor for captured vars, (e.g. using 'L' instead of 'LObject;') + const char* captured_variables_shorty_; + // Whether or not the size is dynamic. If it is, copiers need to read the Closure size at runtime. + bool dynamic_size_; + // True if this lambda was originally made with create-lambda, + // false if it came from a class instance (through new-instance and then unbox-lambda). + bool innate_lambda_; + + DISALLOW_COPY_AND_ASSIGN(ArtLambdaMethod); +}; + +} // namespace lambda +} // namespace art + +#endif // ART_RUNTIME_LAMBDA_ART_LAMBDA_METHOD_H_ diff --git a/runtime/lambda/closure.cc b/runtime/lambda/closure.cc new file mode 100644 index 0000000000..95a17c660c --- /dev/null +++ b/runtime/lambda/closure.cc @@ -0,0 +1,365 @@ +/* + * 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()); +} + +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 diff --git a/runtime/lambda/closure.h b/runtime/lambda/closure.h new file mode 100644 index 0000000000..60d117e9e2 --- /dev/null +++ b/runtime/lambda/closure.h @@ -0,0 +1,171 @@ +/* + * 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. + */ +#ifndef ART_RUNTIME_LAMBDA_CLOSURE_H_ +#define ART_RUNTIME_LAMBDA_CLOSURE_H_ + +#include "base/macros.h" +#include "base/mutex.h" // For Locks::mutator_lock_. +#include "lambda/shorty_field_type.h" + +#include <stdint.h> + +namespace art { +class ArtMethod; // forward declaration + +namespace mirror { +class Object; // forward declaration +} // namespace mirror + +namespace lambda { +class ArtLambdaMethod; // forward declaration +class ClosureBuilder; // forward declaration + +// Inline representation of a lambda closure. +// Contains the target method and the set of packed captured variables as a copy. +// +// The closure itself is logically immutable, although in practice any object references +// it (recursively) contains can be moved and updated by the GC. +struct PACKED(sizeof(ArtLambdaMethod*)) Closure { + // Get the size of the Closure in bytes. + // This is necessary in order to allocate a large enough area to copy the Closure into. + // Do *not* copy the closure with memcpy, since references also need to get moved. + size_t GetSize() const; + + // Copy this closure into the target, whose memory size is specified by target_size. + // Any object references are fixed up during the copy (if there was a read barrier). + // The target_size must be at least as large as GetSize(). + void CopyTo(void* target, size_t target_size) const; + + // How many variables were captured? + size_t GetNumberOfCapturedVariables() const; + + // Returns a type descriptor string that represents each captured variable. + // e.g. "Ljava/lang/Object;ZB" would mean a capture tuple of (Object, boolean, byte) + const char* GetCapturedVariablesTypeDescriptor() const; + + // Returns the short type for the captured variable at index. + // Index must be less than the number of captured variables. + ShortyFieldType GetCapturedShortyType(size_t index) const; + + // Returns the 32-bit representation of a non-wide primitive at the captured variable index. + // Smaller types are zero extended. + // Index must be less than the number of captured variables. + uint32_t GetCapturedPrimitiveNarrow(size_t index) const; + // Returns the 64-bit representation of a wide primitive at the captured variable index. + // Smaller types are zero extended. + // Index must be less than the number of captured variables. + uint64_t GetCapturedPrimitiveWide(size_t index) const; + // Returns the object reference at the captured variable index. + // The type at the index *must* be an object reference or a CHECK failure will occur. + // Index must be less than the number of captured variables. + mirror::Object* GetCapturedObject(size_t index) const SHARED_REQUIRES(Locks::mutator_lock_); + + // Gets the size of a nested capture closure in bytes, at the captured variable index. + // The type at the index *must* be a lambda closure or a CHECK failure will occur. + size_t GetCapturedClosureSize(size_t index) const; + + // Copies a nested lambda closure at the captured variable index. + // The destination must have enough room for the closure (see GetCapturedClosureSize). + void CopyCapturedClosure(size_t index, void* destination, size_t destination_room) const; + + private: + // Read out any non-lambda value as a copy. + template <typename T> + T GetCapturedVariable(size_t index) const; + + // Reconstruct the closure's captured variable info at runtime. + struct VariableInfo { + size_t index_; + ShortyFieldType variable_type_; + size_t offset_; + size_t count_; + + enum Flags { + kIndex = 0x1, + kVariableType = 0x2, + kOffset = 0x4, + kCount = 0x8, + }; + + // Traverse to the end of the type descriptor list instead of stopping at some particular index. + static constexpr size_t kUpToIndexMax = static_cast<size_t>(-1); + }; + + // Parse a type descriptor, stopping at index "upto_index". + // Returns only the information requested in flags. All other fields are indeterminate. + template <VariableInfo::Flags flags> + inline VariableInfo ALWAYS_INLINE ParseTypeDescriptor(const char* type_descriptor, + size_t upto_index) const; + + // Convenience function to call ParseTypeDescriptor with just the type and offset. + void GetCapturedVariableTypeAndOffset(size_t index, + ShortyFieldType* out_type, + size_t* out_offset) const; + + // How many bytes do the captured variables take up? Runtime sizeof(captured_variables). + size_t GetCapturedVariablesSize() const; + // Get the size in bytes of the variable_type which is potentially stored at offset. + size_t GetCapturedVariableSize(ShortyFieldType variable_type, size_t offset) const; + // Get the starting offset (in bytes) for the 0th captured variable. + // All offsets are relative to 'captured_'. + size_t GetStartingOffset() const; + // Get the offset for this index. + // All offsets are relative to 'captuerd_'. + size_t GetCapturedVariableOffset(size_t index) const; + + // Cast the data at '(char*)captured_[offset]' into T, returning its address. + // This value should not be de-referenced directly since its unaligned. + template <typename T> + inline const uint8_t* GetUnsafeAtOffset(size_t offset) const; + + // Copy the data at the offset into the destination. DCHECKs that + // the destination_room is large enough (in bytes) to fit the data. + template <typename T> + inline void CopyUnsafeAtOffset(size_t offset, + void* destination, + size_t src_size = sizeof(T), + size_t destination_room = sizeof(T)) const; + + // Get the closure size from an unaligned (i.e. interior) closure pointer. + static size_t GetClosureSize(const uint8_t* closure); + + /////////////////////////////////////////////////////////////////////////////////// + + // Compile-time known lambda information such as the type descriptor and size. + ArtLambdaMethod* lambda_info_; + + // A contiguous list of captured variables, and possibly the closure size. + // The runtime size can always be determined through GetSize(). + union { + // Read from here if the closure size is static (ArtLambdaMethod::IsStatic) + uint8_t static_variables_[0]; + struct { + // Read from here if the closure size is dynamic (ArtLambdaMethod::IsDynamic) + size_t size_; // The lambda_info_ and the size_ itself is also included as part of the size. + uint8_t variables_[0]; + } dynamic_; + } captured_[0]; + // captured_ will always consist of one array element at runtime. + // Set to [0] so that 'size_' is not counted in sizeof(Closure). + + friend class ClosureBuilder; + friend class ClosureTest; +}; + +} // namespace lambda +} // namespace art + +#endif // ART_RUNTIME_LAMBDA_CLOSURE_H_ diff --git a/runtime/lambda/closure_builder-inl.h b/runtime/lambda/closure_builder-inl.h new file mode 100644 index 0000000000..41a803baf2 --- /dev/null +++ b/runtime/lambda/closure_builder-inl.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#ifndef ART_RUNTIME_LAMBDA_CLOSURE_BUILDER_INL_H_ +#define ART_RUNTIME_LAMBDA_CLOSURE_BUILDER_INL_H_ + +#include "lambda/closure_builder.h" +#include <string.h> + +namespace art { +namespace lambda { + +template <typename T, ClosureBuilder::ShortyTypeEnum kShortyType> +void ClosureBuilder::CaptureVariablePrimitive(T value) { + static_assert(ShortyFieldTypeTraits::IsPrimitiveType<T>(), "T must be a primitive type"); + const size_t type_size = ShortyFieldType(kShortyType).GetStaticSize(); + DCHECK_EQ(type_size, sizeof(T)); + + // Copy the data while retaining the bit pattern. Strict-aliasing safe. + ShortyFieldTypeTraits::MaxType value_storage = 0; + memcpy(&value_storage, &value, sizeof(T)); + + values_.push_back(value_storage); + size_ += sizeof(T); +} + +} // namespace lambda +} // namespace art + +#endif // ART_RUNTIME_LAMBDA_CLOSURE_BUILDER_INL_H_ diff --git a/runtime/lambda/closure_builder.cc b/runtime/lambda/closure_builder.cc new file mode 100644 index 0000000000..56bb9fb805 --- /dev/null +++ b/runtime/lambda/closure_builder.cc @@ -0,0 +1,198 @@ +/* + * 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_builder.h" + +#include "base/macros.h" +#include "base/value_object.h" +#include "lambda/art_lambda_method.h" +#include "lambda/closure.h" +#include "lambda/shorty_field_type.h" +#include "runtime/mirror/object_reference.h" + +#include <stdint.h> +#include <vector> + +namespace art { +namespace lambda { + +/* + * GC support TODOs: + * (Although there's some code for storing objects, it is UNIMPLEMENTED(FATAL) because it is + * incomplete). + * + * 1) GC needs to be able to traverse the Closure and visit any references. + * It might be possible to get away with global roots in the short term. + * + * 2) Add brooks read barrier support. We can store the black/gray/white bits + * in the lower 2 bits of the lambda art method pointer. Whenever a closure is copied + * [to the stack] we'd need to add a cold path to turn it black. + * (since there's only 3 colors, I can use the 4th value to indicate no-refs). + * e.g. 0x0 = gray, 0x1 = white, 0x2 = black, 0x3 = no-nested-references + * - Alternatively the GC can mark reference-less closures as always-black, + * although it would need extra work to check for references. + */ + +void ClosureBuilder::CaptureVariableObject(mirror::Object* object) { + auto compressed_reference = mirror::CompressedReference<mirror::Object>::FromMirrorPtr(object); + ShortyFieldTypeTraits::MaxType storage = 0; + + static_assert(sizeof(storage) >= sizeof(compressed_reference), + "not enough room to store a compressed reference"); + memcpy(&storage, &compressed_reference, sizeof(compressed_reference)); + + values_.push_back(storage); + size_ += kObjectReferenceSize; + + static_assert(kObjectReferenceSize == sizeof(compressed_reference), "reference size mismatch"); + + // TODO: needs more work to support concurrent GC + if (kIsDebugBuild) { + if (kUseReadBarrier) { + UNIMPLEMENTED(FATAL) << "can't yet safely capture objects with read barrier"; + UNREACHABLE(); + } + } +} + +void ClosureBuilder::CaptureVariableLambda(Closure* closure) { + DCHECK(closure != nullptr); // null closures not allowed, target method must be null instead. + values_.push_back(reinterpret_cast<ShortyFieldTypeTraits::MaxType>(closure)); + + if (LIKELY(is_dynamic_size_ == false)) { + // Write in the extra bytes to store the dynamic size the first time. + is_dynamic_size_ = true; + size_ += sizeof(Closure::captured_[0].dynamic_.size_); + } + + // A closure may be sized dynamically, so always query it for the true size. + size_ += closure->GetSize(); +} + +size_t ClosureBuilder::GetSize() const { + return size_; +} + +size_t ClosureBuilder::GetCaptureCount() const { + return values_.size(); +} + +Closure* ClosureBuilder::CreateInPlace(void* memory, ArtLambdaMethod* target_method) const { + DCHECK(memory != nullptr); + DCHECK(target_method != nullptr); + DCHECK_EQ(is_dynamic_size_, target_method->IsDynamicSize()); + + CHECK_EQ(target_method->GetNumberOfCapturedVariables(), values_.size()) + << "number of variables captured at runtime does not match " + << "number of variables captured at compile time"; + + Closure* closure = new (memory) Closure; + closure->lambda_info_ = target_method; + + static_assert(offsetof(Closure, captured_) == kInitialSize, "wrong initial size"); + + size_t written_size; + if (UNLIKELY(is_dynamic_size_)) { + // The closure size must be set dynamically (i.e. nested lambdas). + closure->captured_[0].dynamic_.size_ = GetSize(); + size_t header_size = offsetof(Closure, captured_[0].dynamic_.variables_); + DCHECK_LE(header_size, GetSize()); + size_t variables_size = GetSize() - header_size; + written_size = + WriteValues(target_method, + closure->captured_[0].dynamic_.variables_, + header_size, + variables_size); + } else { + // The closure size is known statically (i.e. no nested lambdas). + DCHECK(GetSize() == target_method->GetStaticClosureSize()); + size_t header_size = offsetof(Closure, captured_[0].static_variables_); + DCHECK_LE(header_size, GetSize()); + size_t variables_size = GetSize() - header_size; + written_size = + WriteValues(target_method, + closure->captured_[0].static_variables_, + header_size, + variables_size); + } + + DCHECK_EQ(written_size, closure->GetSize()); + + return closure; +} + +size_t ClosureBuilder::WriteValues(ArtLambdaMethod* target_method, + uint8_t variables[], + size_t header_size, + size_t variables_size) const { + size_t total_size = header_size; + const char* shorty_types = target_method->GetCapturedVariablesShortyTypeDescriptor(); + + size_t variables_offset = 0; + size_t remaining_size = variables_size; + + const size_t shorty_count = target_method->GetNumberOfCapturedVariables(); + for (size_t i = 0; i < shorty_count; ++i) { + ShortyFieldType shorty{shorty_types[i]}; // NOLINT [readability/braces] [4] + + size_t var_size; + if (LIKELY(shorty.IsStaticSize())) { + // TODO: needs more work to support concurrent GC, e.g. read barriers + if (kUseReadBarrier == false) { + if (UNLIKELY(shorty.IsObject())) { + UNIMPLEMENTED(FATAL) << "can't yet safely write objects with read barrier"; + } + } else { + if (UNLIKELY(shorty.IsObject())) { + UNIMPLEMENTED(FATAL) << "writing objects not yet supported, no GC support"; + } + } + + var_size = shorty.GetStaticSize(); + DCHECK_LE(var_size, sizeof(values_[i])); + + // Safe even for objects (non-read barrier case) if we never suspend + // while the ClosureBuilder is live. + // FIXME: Need to add GC support for references in a closure. + memcpy(&variables[variables_offset], &values_[i], var_size); + } else { + DCHECK(shorty.IsLambda()) + << " don't support writing dynamically sized types other than lambda"; + + ShortyFieldTypeTraits::MaxType closure_raw = values_[i]; + Closure* nested_closure = reinterpret_cast<Closure*>(closure_raw); + + DCHECK(nested_closure != nullptr); + nested_closure->CopyTo(&variables[variables_offset], remaining_size); + + var_size = nested_closure->GetSize(); + } + + total_size += var_size; + DCHECK_GE(remaining_size, var_size); + remaining_size -= var_size; + + variables_offset += var_size; + } + + DCHECK_EQ('\0', shorty_types[shorty_count]); + DCHECK_EQ(variables_offset, variables_size); + + return total_size; +} + + +} // namespace lambda +} // namespace art diff --git a/runtime/lambda/closure_builder.h b/runtime/lambda/closure_builder.h new file mode 100644 index 0000000000..542e12afaa --- /dev/null +++ b/runtime/lambda/closure_builder.h @@ -0,0 +1,101 @@ +/* + * 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. + */ +#ifndef ART_RUNTIME_LAMBDA_CLOSURE_BUILDER_H_ +#define ART_RUNTIME_LAMBDA_CLOSURE_BUILDER_H_ + +#include "base/macros.h" +#include "base/mutex.h" // For Locks::mutator_lock_. +#include "base/value_object.h" +#include "lambda/shorty_field_type.h" + +#include <stdint.h> +#include <vector> + +namespace art { +class ArtMethod; // forward declaration + +namespace mirror { +class Object; // forward declaration +} // namespace mirror + +namespace lambda { +class ArtLambdaMethod; // forward declaration + +// Build a closure by capturing variables one at a time. +// When all variables have been marked captured, the closure can be created in-place into +// a target memory address. +// +// The mutator lock must be held for the duration of the lifetime of this object, +// since it needs to temporarily store heap references into an internal list. +class ClosureBuilder : ValueObject { + public: + using ShortyTypeEnum = decltype(ShortyFieldType::kByte); + + + // Mark this primitive value to be captured as the specified type. + template <typename T, ShortyTypeEnum kShortyType> + void CaptureVariablePrimitive(T value); + + // Mark this object reference to be captured. + void CaptureVariableObject(mirror::Object* object) SHARED_REQUIRES(Locks::mutator_lock_); + + // Mark this lambda closure to be captured. + void CaptureVariableLambda(Closure* closure); + + // Get the size (in bytes) of the closure. + // This size is used to be able to allocate memory large enough to write the closure into. + // Call 'CreateInPlace' to actually write the closure out. + size_t GetSize() const; + + // Returns how many variables have been captured so far. + size_t GetCaptureCount() const; + + // Creates a closure in-place and writes out the data into 'memory'. + // Memory must be at least 'GetSize' bytes large. + // All previously marked data to be captured is now written out. + Closure* CreateInPlace(void* memory, ArtLambdaMethod* target_method) const + SHARED_REQUIRES(Locks::mutator_lock_); + + // Locks need to be held for entire lifetime of ClosureBuilder. + ClosureBuilder() SHARED_REQUIRES(Locks::mutator_lock_) + {} + + // Locks need to be held for entire lifetime of ClosureBuilder. + ~ClosureBuilder() SHARED_REQUIRES(Locks::mutator_lock_) + {} + + private: + // Initial size a closure starts out before any variables are written. + // Header size only. + static constexpr size_t kInitialSize = sizeof(ArtLambdaMethod*); + + // Write a Closure's variables field from the captured variables. + // variables_size specified in bytes, and only includes enough room to write variables into. + // Returns the calculated actual size of the closure. + size_t WriteValues(ArtLambdaMethod* target_method, + uint8_t variables[], + size_t header_size, + size_t variables_size) const SHARED_REQUIRES(Locks::mutator_lock_); + + size_t size_ = kInitialSize; + bool is_dynamic_size_ = false; + std::vector<ShortyFieldTypeTraits::MaxType> values_; +}; + +} // namespace lambda +} // namespace art + +#endif // ART_RUNTIME_LAMBDA_CLOSURE_BUILDER_H_ diff --git a/runtime/lambda/closure_test.cc b/runtime/lambda/closure_test.cc new file mode 100644 index 0000000000..7c1bd0d591 --- /dev/null +++ b/runtime/lambda/closure_test.cc @@ -0,0 +1,356 @@ +/* + * 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 "art_method.h" +#include "lambda/art_lambda_method.h" +#include "lambda/closure.h" +#include "lambda/closure_builder.h" +#include "lambda/closure_builder-inl.h" +#include "utils.h" + +#include <numeric> +#include <stdint.h> +#include <type_traits> +#include "gtest/gtest.h" + +// Turn this on for some extra printfs to help with debugging, since some code is optimized out. +static constexpr const bool kDebuggingClosureTest = true; + +namespace std { + using Closure = art::lambda::Closure; + + // Specialize std::default_delete so it knows how to properly delete closures + // through the way we allocate them in this test. + // + // This is test-only because we don't want the rest of Art to do this. + template <> + struct default_delete<Closure> { + void operator()(Closure* closure) const { + delete[] reinterpret_cast<char*>(closure); + } + }; +} // namespace std + +namespace art { + +// Fake lock acquisition to please clang lock checker. +// This doesn't actually acquire any locks because we don't need multiple threads in this gtest. +struct SCOPED_CAPABILITY ScopedFakeLock { + explicit ScopedFakeLock(MutatorMutex& mu) ACQUIRE(mu) + : mu_(mu) { + } + + ~ScopedFakeLock() RELEASE() + {} + + MutatorMutex& mu_; +}; + +namespace lambda { + +class ClosureTest : public ::testing::Test { + public: + ClosureTest() = default; + ~ClosureTest() = default; + + protected: + static void SetUpTestCase() { + } + + virtual void SetUp() { + // Create a completely dummy method here. + // It's "OK" because the Closure never needs to look inside of the ArtMethod + // (it just needs to be non-null). + uintptr_t ignore = 0xbadbad; + fake_method_ = reinterpret_cast<ArtMethod*>(ignore); + } + + static ::testing::AssertionResult IsResultSuccessful(bool result) { + if (result) { + return ::testing::AssertionSuccess(); + } else { + return ::testing::AssertionFailure(); + } + } + + // Create a closure that captures the static variables from 'args' by-value. + // The lambda method's captured variables types must match the ones in 'args'. + // -- This creates the closure directly in-memory by using memcpy. + template <typename ... Args> + static std::unique_ptr<Closure> CreateClosureStaticVariables(ArtLambdaMethod* lambda_method, + Args&& ... args) { + constexpr size_t header_size = sizeof(ArtLambdaMethod*); + const size_t static_size = GetArgsSize(args ...) + header_size; + EXPECT_GE(static_size, sizeof(Closure)); + + // Can't just 'new' the Closure since we don't know the size up front. + char* closure_as_char_array = new char[static_size]; + Closure* closure_ptr = new (closure_as_char_array) Closure; + + // Set up the data + closure_ptr->lambda_info_ = lambda_method; + CopyArgs(closure_ptr->captured_[0].static_variables_, args ...); + + // Make sure the entire thing is deleted once the unique_ptr goes out of scope. + return std::unique_ptr<Closure>(closure_ptr); // NOLINT [whitespace/braces] [5] + } + + // Copy variadic arguments into the destination array with memcpy. + template <typename T, typename ... Args> + static void CopyArgs(uint8_t destination[], T&& arg, Args&& ... args) { + memcpy(destination, &arg, sizeof(arg)); + CopyArgs(destination + sizeof(arg), args ...); + } + + // Base case: Done. + static void CopyArgs(uint8_t destination[]) { + UNUSED(destination); + } + + // Create a closure that captures the static variables from 'args' by-value. + // The lambda method's captured variables types must match the ones in 'args'. + // -- This uses ClosureBuilder interface to set up the closure indirectly. + template <typename ... Args> + static std::unique_ptr<Closure> CreateClosureStaticVariablesFromBuilder( + ArtLambdaMethod* lambda_method, + Args&& ... args) { + // Acquire a fake lock since closure_builder needs it. + ScopedFakeLock fake_lock(*Locks::mutator_lock_); + + ClosureBuilder closure_builder; + CaptureVariableFromArgsList(/*out*/closure_builder, args ...); + + EXPECT_EQ(sizeof...(args), closure_builder.GetCaptureCount()); + + constexpr size_t header_size = sizeof(ArtLambdaMethod*); + const size_t static_size = GetArgsSize(args ...) + header_size; + EXPECT_GE(static_size, sizeof(Closure)); + + // For static variables, no nested closure, so size must match exactly. + EXPECT_EQ(static_size, closure_builder.GetSize()); + + // Can't just 'new' the Closure since we don't know the size up front. + char* closure_as_char_array = new char[static_size]; + Closure* closure_ptr = new (closure_as_char_array) Closure; + + // The closure builder packs the captured variables into a Closure. + closure_builder.CreateInPlace(closure_ptr, lambda_method); + + // Make sure the entire thing is deleted once the unique_ptr goes out of scope. + return std::unique_ptr<Closure>(closure_ptr); // NOLINT [whitespace/braces] [5] + } + + // Call the correct ClosureBuilder::CaptureVariableXYZ function based on the type of args. + // Invokes for each arg in args. + template <typename ... Args> + static void CaptureVariableFromArgsList(/*out*/ClosureBuilder& closure_builder, Args ... args) { + int ignore[] = { + (CaptureVariableFromArgs(/*out*/closure_builder, args),0)... // NOLINT [whitespace/comma] [3] + }; + UNUSED(ignore); + } + + // ClosureBuilder::CaptureVariablePrimitive for types that are primitive only. + template <typename T> + typename std::enable_if<ShortyFieldTypeTraits::IsPrimitiveType<T>()>::type + static CaptureVariableFromArgs(/*out*/ClosureBuilder& closure_builder, T value) { + static_assert(ShortyFieldTypeTraits::IsPrimitiveType<T>(), "T must be a shorty primitive"); + closure_builder.CaptureVariablePrimitive<T, ShortyFieldTypeSelectEnum<T>::value>(value); + } + + // ClosureBuilder::CaptureVariableObject for types that are objects only. + template <typename T> + typename std::enable_if<ShortyFieldTypeTraits::IsObjectType<T>()>::type + static CaptureVariableFromArgs(/*out*/ClosureBuilder& closure_builder, const T* object) { + ScopedFakeLock fake_lock(*Locks::mutator_lock_); + closure_builder.CaptureVariableObject(object); + } + + // Sum of sizeof(Args...). + template <typename T, typename ... Args> + static constexpr size_t GetArgsSize(T&& arg, Args&& ... args) { + return sizeof(arg) + GetArgsSize(args ...); + } + + // Base case: Done. + static constexpr size_t GetArgsSize() { + return 0; + } + + // Take "U" and memcpy it into a "T". T starts out as (T)0. + template <typename T, typename U> + static T ExpandingBitCast(const U& val) { + static_assert(sizeof(T) >= sizeof(U), "U too large"); + T new_val = static_cast<T>(0); + memcpy(&new_val, &val, sizeof(U)); + return new_val; + } + + // Templatized extraction from closures by checking their type with enable_if. + template <typename T> + static typename std::enable_if<ShortyFieldTypeTraits::IsPrimitiveNarrowType<T>()>::type + ExpectCapturedVariable(const Closure* closure, size_t index, T value) { + EXPECT_EQ(ExpandingBitCast<uint32_t>(value), closure->GetCapturedPrimitiveNarrow(index)) + << " with index " << index; + } + + template <typename T> + static typename std::enable_if<ShortyFieldTypeTraits::IsPrimitiveWideType<T>()>::type + ExpectCapturedVariable(const Closure* closure, size_t index, T value) { + EXPECT_EQ(ExpandingBitCast<uint64_t>(value), closure->GetCapturedPrimitiveWide(index)) + << " with index " << index; + } + + // Templatized SFINAE for Objects so we can get better error messages. + template <typename T> + static typename std::enable_if<ShortyFieldTypeTraits::IsObjectType<T>()>::type + ExpectCapturedVariable(const Closure* closure, size_t index, const T* object) { + EXPECT_EQ(object, closure->GetCapturedObject(index)) + << " with index " << index; + } + + template <typename ... Args> + void TestPrimitive(const char *descriptor, Args ... args) { + const char* shorty = descriptor; + + SCOPED_TRACE(descriptor); + + ASSERT_EQ(strlen(shorty), sizeof...(args)) + << "test error: descriptor must have same # of types as the # of captured variables"; + + // Important: This fake lambda method needs to out-live any Closures we create with it. + ArtLambdaMethod lambda_method{fake_method_, // NOLINT [whitespace/braces] [5] + descriptor, // NOLINT [whitespace/blank_line] [2] + shorty, + }; + + std::unique_ptr<Closure> closure_a; + std::unique_ptr<Closure> closure_b; + + // Test the closure twice when it's constructed in different ways. + { + // Create the closure in a "raw" manner, that is directly with memcpy + // since we know the underlying data format. + // This simulates how the compiler would lay out the data directly. + SCOPED_TRACE("raw closure"); + std::unique_ptr<Closure> closure_raw = CreateClosureStaticVariables(&lambda_method, args ...); + + if (kDebuggingClosureTest) { + std::cerr << "closure raw address: " << closure_raw.get() << std::endl; + } + TestPrimitiveWithClosure(closure_raw.get(), descriptor, shorty, args ...); + closure_a = std::move(closure_raw); + } + + { + // Create the closure with the ClosureBuilder, which is done indirectly. + // This simulates how the interpreter would create the closure dynamically at runtime. + SCOPED_TRACE("closure from builder"); + std::unique_ptr<Closure> closure_built = + CreateClosureStaticVariablesFromBuilder(&lambda_method, args ...); + if (kDebuggingClosureTest) { + std::cerr << "closure built address: " << closure_built.get() << std::endl; + } + TestPrimitiveWithClosure(closure_built.get(), descriptor, shorty, args ...); + closure_b = std::move(closure_built); + } + + // The closures should be identical memory-wise as well. + EXPECT_EQ(closure_a->GetSize(), closure_b->GetSize()); + EXPECT_TRUE(memcmp(closure_a.get(), + closure_b.get(), + std::min(closure_a->GetSize(), closure_b->GetSize())) == 0); + } + + template <typename ... Args> + static void TestPrimitiveWithClosure(Closure* closure, + const char* descriptor, + const char* shorty, + Args ... args) { + EXPECT_EQ(sizeof(ArtLambdaMethod*) + GetArgsSize(args...), closure->GetSize()); + EXPECT_EQ(sizeof...(args), closure->GetNumberOfCapturedVariables()); + EXPECT_STREQ(descriptor, closure->GetCapturedVariablesTypeDescriptor()); + TestPrimitiveExpects(closure, shorty, /*index*/0, args ...); + } + + // Call EXPECT_EQ for each argument in the closure's #GetCapturedX. + template <typename T, typename ... Args> + static void TestPrimitiveExpects( + const Closure* closure, const char* shorty, size_t index, T arg, Args ... args) { + ASSERT_EQ(ShortyFieldType(shorty[index]).GetStaticSize(), sizeof(T)) + << "Test error: Type mismatch at index " << index; + ExpectCapturedVariable(closure, index, arg); + EXPECT_EQ(ShortyFieldType(shorty[index]), closure->GetCapturedShortyType(index)); + TestPrimitiveExpects(closure, shorty, index + 1, args ...); + } + + // Base case for EXPECT_EQ. + static void TestPrimitiveExpects(const Closure* closure, const char* shorty, size_t index) { + UNUSED(closure, shorty, index); + } + + ArtMethod* fake_method_; +}; + +TEST_F(ClosureTest, TestTrivial) { + ArtLambdaMethod lambda_method{fake_method_, // NOLINT [whitespace/braces] [5] + "", // No captured variables // NOLINT [whitespace/blank_line] [2] + "", // No captured variables + }; + + std::unique_ptr<Closure> closure = CreateClosureStaticVariables(&lambda_method); + + EXPECT_EQ(sizeof(ArtLambdaMethod*), closure->GetSize()); + EXPECT_EQ(0u, closure->GetNumberOfCapturedVariables()); +} // TEST_F + +TEST_F(ClosureTest, TestPrimitiveSingle) { + TestPrimitive("Z", true); + TestPrimitive("B", int8_t(0xde)); + TestPrimitive("C", uint16_t(0xbeef)); + TestPrimitive("S", int16_t(0xdead)); + TestPrimitive("I", int32_t(0xdeadbeef)); + TestPrimitive("F", 0.123f); + TestPrimitive("J", int64_t(0xdeadbeef00c0ffee)); + TestPrimitive("D", 123.456); +} // TEST_F + +TEST_F(ClosureTest, TestPrimitiveMany) { + TestPrimitive("ZZ", true, false); + TestPrimitive("ZZZ", true, false, true); + TestPrimitive("BBBB", int8_t(0xde), int8_t(0xa0), int8_t(0xff), int8_t(0xcc)); + TestPrimitive("CC", uint16_t(0xbeef), uint16_t(0xdead)); + TestPrimitive("SSSS", int16_t(0xdead), int16_t(0xc0ff), int16_t(0xf000), int16_t(0xbaba)); + TestPrimitive("III", int32_t(0xdeadbeef), int32_t(0xc0ffee), int32_t(0xbeefdead)); + TestPrimitive("FF", 0.123f, 555.666f); + TestPrimitive("JJJ", int64_t(0xdeadbeef00c0ffee), int64_t(0x123), int64_t(0xc0ffee)); + TestPrimitive("DD", 123.456, 777.888); +} // TEST_F + +TEST_F(ClosureTest, TestPrimitiveMixed) { + TestPrimitive("ZZBBCCSSIIFFJJDD", + true, false, + int8_t(0xde), int8_t(0xa0), + uint16_t(0xbeef), uint16_t(0xdead), + int16_t(0xdead), int16_t(0xc0ff), + int32_t(0xdeadbeef), int32_t(0xc0ffee), + 0.123f, 555.666f, + int64_t(0xdeadbeef00c0ffee), int64_t(0x123), + 123.456, 777.888); +} // TEST_F + +} // namespace lambda +} // namespace art diff --git a/runtime/lambda/shorty_field_type.h b/runtime/lambda/shorty_field_type.h new file mode 100644 index 0000000000..46ddaa9ab3 --- /dev/null +++ b/runtime/lambda/shorty_field_type.h @@ -0,0 +1,475 @@ +/* + * 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. + */ +#ifndef ART_RUNTIME_LAMBDA_SHORTY_FIELD_TYPE_H_ +#define ART_RUNTIME_LAMBDA_SHORTY_FIELD_TYPE_H_ + +#include "base/logging.h" +#include "base/macros.h" +#include "base/value_object.h" +#include "globals.h" +#include "runtime/primitive.h" + +#include <ostream> + +namespace art { + +namespace mirror { +class Object; // forward declaration +} // namespace mirror + +namespace lambda { + +struct Closure; // forward declaration + +// TODO: Refactor together with primitive.h + +// The short form of a field type descriptor. Corresponds to ShortyFieldType in dex specification. +// Only types usable by a field (and locals) are allowed (i.e. no void type). +// Note that arrays and objects are treated both as 'L'. +// +// This is effectively a 'char' enum-like zero-cost type-safe wrapper with extra helper functions. +struct ShortyFieldType : ValueObject { + // Use as if this was an enum class, e.g. 'ShortyFieldType::kBoolean'. + enum : char { + // Primitives (Narrow): + kBoolean = 'Z', + kByte = 'B', + kChar = 'C', + kShort = 'S', + kInt = 'I', + kFloat = 'F', + // Primitives (Wide): + kLong = 'J', + kDouble = 'D', + // Managed types: + kObject = 'L', // This can also be an array (which is otherwise '[' in a non-shorty). + kLambda = '\\', + }; // NOTE: This is an anonymous enum so we can get exhaustive switch checking from the compiler. + + // Implicitly construct from the enum above. Value must be one of the enum list members above. + // Always safe to use, does not do any DCHECKs. + inline constexpr ShortyFieldType(decltype(kByte) c) : value_(c) { + } + + // Default constructor. The initial value is undefined. Initialize before calling methods. + // This is very unsafe but exists as a convenience to having undefined values. + explicit ShortyFieldType() : value_(StaticCastValue(0)) { + } + + // Explicitly construct from a char. Value must be one of the enum list members above. + // Conversion is potentially unsafe, so DCHECKing is performed. + explicit inline ShortyFieldType(char c) : value_(StaticCastValue(c)) { + if (kIsDebugBuild) { + // Verify at debug-time that our conversion is safe. + ShortyFieldType ignored; + DCHECK(MaybeCreate(c, &ignored)) << "unknown shorty field type '" << c << "'"; + } + } + + // Attempts to parse the character in 'shorty_field_type' into its strongly typed version. + // Returns false if the character was out of range of the grammar. + static bool MaybeCreate(char shorty_field_type, ShortyFieldType* out) { + DCHECK(out != nullptr); + switch (shorty_field_type) { + case kBoolean: + case kByte: + case kChar: + case kShort: + case kInt: + case kFloat: + case kLong: + case kDouble: + case kObject: + case kLambda: + *out = ShortyFieldType(static_cast<decltype(kByte)>(shorty_field_type)); + return true; + default: + break; + } + + return false; + } + + // Convert the first type in a field type descriptor string into a shorty. + // Arrays are converted into objects. + // Does not work for 'void' types (as they are illegal in a field type descriptor). + static ShortyFieldType CreateFromFieldTypeDescriptor(const char* field_type_descriptor) { + DCHECK(field_type_descriptor != nullptr); + char c = *field_type_descriptor; + if (UNLIKELY(c == kArray)) { // Arrays are treated as object references. + c = kObject; + } + return ShortyFieldType{c}; // NOLINT [readability/braces] [4] + } + + // Parse the first type in the field type descriptor string into a shorty. + // See CreateFromFieldTypeDescriptor for more details. + // + // Returns the pointer offset into the middle of the field_type_descriptor + // that would either point to the next shorty type, or to null if there are + // no more types. + // + // DCHECKs that each of the nested types is a valid shorty field type. This + // means the type descriptor must be already valid. + static const char* ParseFromFieldTypeDescriptor(const char* field_type_descriptor, + ShortyFieldType* out_type) { + DCHECK(field_type_descriptor != nullptr); + + if (UNLIKELY(field_type_descriptor[0] == '\0')) { + // Handle empty strings by immediately returning null. + return nullptr; + } + + // All non-empty strings must be a valid list of field type descriptors, otherwise + // the DCHECKs will kick in and the program will crash. + const char shorter_type = *field_type_descriptor; + + ShortyFieldType safe_type; + bool type_set = MaybeCreate(shorter_type, &safe_type); + + // Lambda that keeps skipping characters until it sees ';'. + // Stops one character -after- the ';'. + auto skip_until_semicolon = [&field_type_descriptor]() { + while (*field_type_descriptor != ';' && *field_type_descriptor != '\0') { + ++field_type_descriptor; + } + DCHECK_NE(*field_type_descriptor, '\0') + << " type descriptor terminated too early: " << field_type_descriptor; + ++field_type_descriptor; // Skip the ';' + }; + + ++field_type_descriptor; + switch (shorter_type) { + case kObject: + skip_until_semicolon(); + + DCHECK(type_set); + DCHECK(safe_type == kObject); + break; + case kArray: + // Strip out all of the leading [[[[[s, we don't care if it's a multi-dimensional array. + while (*field_type_descriptor == '[' && *field_type_descriptor != '\0') { + ++field_type_descriptor; + } + DCHECK_NE(*field_type_descriptor, '\0') + << " type descriptor terminated too early: " << field_type_descriptor; + // Either a primitive, object, or closure left. No more arrays. + { + // Now skip all the characters that form the array's interior-most element type + // (which itself is guaranteed not to be an array). + ShortyFieldType array_interior_type; + type_set = MaybeCreate(*field_type_descriptor, &array_interior_type); + DCHECK(type_set) << " invalid remaining type descriptor " << field_type_descriptor; + + // Handle array-of-objects case like [[[[[LObject; and array-of-closures like [[[[[\Foo; + if (*field_type_descriptor == kObject || *field_type_descriptor == kLambda) { + skip_until_semicolon(); + } else { + // Handle primitives which are exactly one character we can skip. + DCHECK(array_interior_type.IsPrimitive()); + ++field_type_descriptor; + } + } + + safe_type = kObject; + type_set = true; + break; + case kLambda: + skip_until_semicolon(); + + DCHECK(safe_type == kLambda); + DCHECK(type_set); + break; + default: + DCHECK_NE(kVoid, shorter_type) << "cannot make a ShortyFieldType from a void type"; + break; + } + + DCHECK(type_set) << "invalid shorty type descriptor " << shorter_type; + + *out_type = safe_type; + return type_set ? field_type_descriptor : nullptr; + } + + // Explicitly convert to a char. + inline explicit operator char() const { + return value_; + } + + // Is this a primitive? + inline bool IsPrimitive() const { + return IsPrimitiveNarrow() || IsPrimitiveWide(); + } + + // Is this a narrow primitive (i.e. can fit into 1 virtual register)? + inline bool IsPrimitiveNarrow() const { + switch (value_) { + case kBoolean: + case kByte: + case kChar: + case kShort: + case kInt: + case kFloat: + return true; + default: + return false; + } + } + + // Is this a wide primitive (i.e. needs exactly 2 virtual registers)? + inline bool IsPrimitiveWide() const { + switch (value_) { + case kLong: + case kDouble: + return true; + default: + return false; + } + } + + // Is this an object reference (which can also be an array)? + inline bool IsObject() const { + return value_ == kObject; + } + + // Is this a lambda? + inline bool IsLambda() const { + return value_ == kLambda; + } + + // Is the size of this (to store inline as a field) always known at compile-time? + inline bool IsStaticSize() const { + return !IsLambda(); + } + + // Get the compile-time size (to be able to store it inline as a field or on stack). + // Dynamically-sized values such as lambdas return the guaranteed lower bound. + inline size_t GetStaticSize() const { + switch (value_) { + case kBoolean: + return sizeof(bool); + case kByte: + return sizeof(uint8_t); + case kChar: + return sizeof(int16_t); + case kShort: + return sizeof(uint16_t); + case kInt: + return sizeof(int32_t); + case kLong: + return sizeof(int64_t); + case kFloat: + return sizeof(float); + case kDouble: + return sizeof(double); + case kObject: + return kObjectReferenceSize; + case kLambda: + return sizeof(void*); // Large enough to store the ArtLambdaMethod + default: + DCHECK(false) << "unknown shorty field type '" << static_cast<char>(value_) << "'"; + UNREACHABLE(); + } + } + + // Implicitly convert to the anonymous nested inner type. Used for exhaustive switch detection. + inline operator decltype(kByte)() const { + return value_; + } + + // Returns a read-only static string representing the enum name, useful for printing/debug only. + inline const char* ToString() const { + switch (value_) { + case kBoolean: + return "kBoolean"; + case kByte: + return "kByte"; + case kChar: + return "kChar"; + case kShort: + return "kShort"; + case kInt: + return "kInt"; + case kLong: + return "kLong"; + case kFloat: + return "kFloat"; + case kDouble: + return "kDouble"; + case kObject: + return "kObject"; + case kLambda: + return "kLambda"; + default: + // Undefined behavior if we get this far. Pray the compiler gods are merciful. + return "<undefined>"; + } + } + + private: + static constexpr const char kArray = '['; + static constexpr const char kVoid = 'V'; + + // Helper to statically cast anything into our nested anonymous enum type. + template <typename T> + inline static decltype(kByte) StaticCastValue(const T& anything) { + return static_cast<decltype(value_)>(anything); + } + + // The only field in this struct. + decltype(kByte) value_; +}; + + + // Print to an output stream. +inline std::ostream& operator<<(std::ostream& ostream, ShortyFieldType shorty) { + return ostream << shorty.ToString(); +} + +static_assert(sizeof(ShortyFieldType) == sizeof(char), + "ShortyFieldType must be lightweight just like a char"); + +// Compile-time trait information regarding the ShortyFieldType. +// Used by static_asserts to verify that the templates are correctly used at compile-time. +// +// For example, +// ShortyFieldTypeTraits::IsPrimitiveNarrowType<int64_t>() == true +// ShortyFieldTypeTraits::IsObjectType<mirror::Object*>() == true +struct ShortyFieldTypeTraits { + // A type guaranteed to be large enough to holds any of the shorty field types. + using MaxType = uint64_t; + + // Type traits: Returns true if 'T' is a valid type that can be represented by a shorty field type. + template <typename T> + static inline constexpr bool IsType() { + return IsPrimitiveType<T>() || IsObjectType<T>() || IsLambdaType<T>(); + } + + // Returns true if 'T' is a primitive type (i.e. a built-in without nested references). + template <typename T> + static inline constexpr bool IsPrimitiveType() { + return IsPrimitiveNarrowType<T>() || IsPrimitiveWideType<T>(); + } + + // Returns true if 'T' is a primitive type that is narrow (i.e. can be stored into 1 vreg). + template <typename T> + static inline constexpr bool IsPrimitiveNarrowType() { + return IsPrimitiveNarrowTypeImpl(static_cast<T* const>(nullptr)); + } + + // Returns true if 'T' is a primitive type that is wide (i.e. needs 2 vregs for storage). + template <typename T> + static inline constexpr bool IsPrimitiveWideType() { + return IsPrimitiveWideTypeImpl(static_cast<T* const>(nullptr)); + } + + // Returns true if 'T' is an object (i.e. it is a managed GC reference). + // Note: This is equivalent to std::base_of<mirror::Object*, T>::value + template <typename T> + static inline constexpr bool IsObjectType() { + return IsObjectTypeImpl(static_cast<T* const>(nullptr)); + } + + // Returns true if 'T' is a lambda (i.e. it is a closure with unknown static data); + template <typename T> + static inline constexpr bool IsLambdaType() { + return IsLambdaTypeImpl(static_cast<T* const>(nullptr)); + } + + private: +#define IS_VALID_TYPE_SPECIALIZATION(type, name) \ + static inline constexpr bool Is ## name ## TypeImpl(type* const = 0) { \ + return true; \ + } \ + \ + static_assert(sizeof(MaxType) >= sizeof(type), "MaxType too small") + + IS_VALID_TYPE_SPECIALIZATION(bool, PrimitiveNarrow); + IS_VALID_TYPE_SPECIALIZATION(int8_t, PrimitiveNarrow); + IS_VALID_TYPE_SPECIALIZATION(uint8_t, PrimitiveNarrow); // Not strictly true, but close enough. + IS_VALID_TYPE_SPECIALIZATION(int16_t, PrimitiveNarrow); + IS_VALID_TYPE_SPECIALIZATION(uint16_t, PrimitiveNarrow); // Chars are unsigned. + IS_VALID_TYPE_SPECIALIZATION(int32_t, PrimitiveNarrow); + IS_VALID_TYPE_SPECIALIZATION(uint32_t, PrimitiveNarrow); // Not strictly true, but close enough. + IS_VALID_TYPE_SPECIALIZATION(float, PrimitiveNarrow); + IS_VALID_TYPE_SPECIALIZATION(int64_t, PrimitiveWide); + IS_VALID_TYPE_SPECIALIZATION(uint64_t, PrimitiveWide); // Not strictly true, but close enough. + IS_VALID_TYPE_SPECIALIZATION(double, PrimitiveWide); + IS_VALID_TYPE_SPECIALIZATION(mirror::Object*, Object); + IS_VALID_TYPE_SPECIALIZATION(Closure*, Lambda); +#undef IS_VALID_TYPE_SPECIALIZATION + +#define IS_VALID_TYPE_SPECIALIZATION_IMPL(name) \ + template <typename T> \ + static inline constexpr bool Is ## name ## TypeImpl(T* const = 0) { \ + return false; \ + } + + IS_VALID_TYPE_SPECIALIZATION_IMPL(PrimitiveNarrow); + IS_VALID_TYPE_SPECIALIZATION_IMPL(PrimitiveWide); + IS_VALID_TYPE_SPECIALIZATION_IMPL(Object); + IS_VALID_TYPE_SPECIALIZATION_IMPL(Lambda); + +#undef IS_VALID_TYPE_SPECIALIZATION_IMPL +}; + +// Maps the ShortyFieldType enum into it's C++ type equivalent, into the "type" typedef. +// For example: +// ShortyFieldTypeSelectType<ShortyFieldType::kBoolean>::type => bool +// ShortyFieldTypeSelectType<ShortyFieldType::kLong>::type => int64_t +// +// Invalid enums will not have the type defined. +template <decltype(ShortyFieldType::kByte) Shorty> +struct ShortyFieldTypeSelectType { +}; + +// Maps the C++ type into it's ShortyFieldType enum equivalent, into the "value" constexpr. +// For example: +// ShortyFieldTypeSelectEnum<bool>::value => ShortyFieldType::kBoolean +// ShortyFieldTypeSelectEnum<int64_t>::value => ShortyFieldType::kLong +// +// Signed-ness must match for a valid select, e.g. uint64_t will not map to kLong, but int64_t will. +// Invalid types will not have the value defined (see e.g. ShortyFieldTypeTraits::IsType<T>()) +template <typename T> +struct ShortyFieldTypeSelectEnum { +}; + +#define SHORTY_FIELD_TYPE_SELECT_IMPL(cpp_type, enum_element) \ +template <> \ +struct ShortyFieldTypeSelectType<ShortyFieldType::enum_element> { \ + using type = cpp_type; \ +}; \ +\ +template <> \ +struct ShortyFieldTypeSelectEnum<cpp_type> { \ + static constexpr const auto value = ShortyFieldType::enum_element; \ +}; \ + +SHORTY_FIELD_TYPE_SELECT_IMPL(bool, kBoolean); +SHORTY_FIELD_TYPE_SELECT_IMPL(int8_t, kByte); +SHORTY_FIELD_TYPE_SELECT_IMPL(int16_t, kShort); +SHORTY_FIELD_TYPE_SELECT_IMPL(uint16_t, kChar); +SHORTY_FIELD_TYPE_SELECT_IMPL(int32_t, kInt); +SHORTY_FIELD_TYPE_SELECT_IMPL(float, kFloat); +SHORTY_FIELD_TYPE_SELECT_IMPL(int64_t, kLong); +SHORTY_FIELD_TYPE_SELECT_IMPL(double, kDouble); +SHORTY_FIELD_TYPE_SELECT_IMPL(mirror::Object*, kObject); +SHORTY_FIELD_TYPE_SELECT_IMPL(Closure*, kLambda); + +} // namespace lambda +} // namespace art + +#endif // ART_RUNTIME_LAMBDA_SHORTY_FIELD_TYPE_H_ diff --git a/runtime/lambda/shorty_field_type_test.cc b/runtime/lambda/shorty_field_type_test.cc new file mode 100644 index 0000000000..32bade9b56 --- /dev/null +++ b/runtime/lambda/shorty_field_type_test.cc @@ -0,0 +1,354 @@ +/* + * 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/shorty_field_type.h" +#include "mirror/object_reference.h" + +#include "utils.h" +#include <numeric> +#include <stdint.h> +#include "gtest/gtest.h" + +#define EXPECT_NULL(expected) EXPECT_EQ(reinterpret_cast<const void*>(expected), \ + reinterpret_cast<void*>(nullptr)); + +namespace art { +namespace lambda { + +class ShortyFieldTypeTest : public ::testing::Test { + public: + ShortyFieldTypeTest() = default; + ~ShortyFieldTypeTest() = default; + + protected: + static void SetUpTestCase() { + } + + virtual void SetUp() { + } + + static ::testing::AssertionResult IsResultSuccessful(bool result) { + if (result) { + return ::testing::AssertionSuccess(); + } else { + return ::testing::AssertionFailure(); + } + } + + template <typename T> + static std::string ListToString(const T& list) { + std::stringstream stream; + + stream << "["; + for (auto&& val : list) { + stream << val << ", "; + } + stream << "]"; + + return stream.str(); + } + + // Compare two vector-like types for equality. + template <typename T> + static ::testing::AssertionResult AreListsEqual(const T& expected, const T& actual) { + bool success = true; + std::stringstream stream; + + if (expected.size() != actual.size()) { + success = false; + stream << "Expected list size: " << expected.size() + << ", but got list size: " << actual.size(); + stream << std::endl; + } + + for (size_t j = 0; j < std::min(expected.size(), actual.size()); ++j) { + if (expected[j] != actual[j]) { + success = false; + stream << "Expected element '" << j << "' to be '" << expected[j] << "', but got actual: '" + << actual[j] << "'."; + stream << std::endl; + } + } + + if (success) { + return ::testing::AssertionSuccess(); + } + + stream << "Expected list was: " << ListToString(expected) + << ", actual list was: " << ListToString(actual); + + return ::testing::AssertionFailure() << stream.str(); + } + + static std::vector<ShortyFieldType> ParseLongTypeDescriptorsToList(const char* type_descriptor) { + std::vector<ShortyFieldType> lst; + + ShortyFieldType shorty; + + const char* parsed = type_descriptor; + while ((parsed = ShortyFieldType::ParseFromFieldTypeDescriptor(parsed, &shorty)) != nullptr) { + lst.push_back(shorty); + } + + return lst; + } + + protected: + // Shorthands for the ShortyFieldType constants. + // The letters are the same as JNI letters, with kS_ being a lambda since \ is not available. + static constexpr ShortyFieldType kSZ = ShortyFieldType::kBoolean; + static constexpr ShortyFieldType kSB = ShortyFieldType::kByte; + static constexpr ShortyFieldType kSC = ShortyFieldType::kChar; + static constexpr ShortyFieldType kSS = ShortyFieldType::kShort; + static constexpr ShortyFieldType kSI = ShortyFieldType::kInt; + static constexpr ShortyFieldType kSF = ShortyFieldType::kFloat; + static constexpr ShortyFieldType kSJ = ShortyFieldType::kLong; + static constexpr ShortyFieldType kSD = ShortyFieldType::kDouble; + static constexpr ShortyFieldType kSL = ShortyFieldType::kObject; + static constexpr ShortyFieldType kS_ = ShortyFieldType::kLambda; +}; + +TEST_F(ShortyFieldTypeTest, TestMaybeCreate) { + ShortyFieldType shorty; + + std::vector<char> shorties = {'Z', 'B', 'C', 'S', 'I', 'F', 'J', 'D', 'L', '\\'}; + + // All valid 'shorty' characters are created successfully. + for (const char c : shorties) { + EXPECT_TRUE(ShortyFieldType::MaybeCreate(c, &shorty)) << c; + EXPECT_EQ(c, static_cast<char>(c)); + } + + // All other characters can never be created. + for (unsigned char c = 0; c < std::numeric_limits<unsigned char>::max(); ++c) { + // Skip the valid characters. + if (std::find(shorties.begin(), shorties.end(), c) != shorties.end()) { continue; } + // All invalid characters should fail. + EXPECT_FALSE(ShortyFieldType::MaybeCreate(static_cast<char>(c), &shorty)) << c; + } +} // TEST_F + +TEST_F(ShortyFieldTypeTest, TestCreateFromFieldTypeDescriptor) { + // Sample input. + std::vector<const char*> lengthies = { + "Z", "B", "C", "S", "I", "F", "J", "D", "LObject;", "\\Closure;", + "[Z", "[[B", "[[LObject;" + }; + + // Expected output. + std::vector<ShortyFieldType> expected = { + ShortyFieldType::kBoolean, + ShortyFieldType::kByte, + ShortyFieldType::kChar, + ShortyFieldType::kShort, + ShortyFieldType::kInt, + ShortyFieldType::kFloat, + ShortyFieldType::kLong, + ShortyFieldType::kDouble, + ShortyFieldType::kObject, + ShortyFieldType::kLambda, + // Arrays are always treated as objects. + ShortyFieldType::kObject, + ShortyFieldType::kObject, + ShortyFieldType::kObject, + }; + + // All valid lengthy types are correctly turned into the expected shorty type. + for (size_t i = 0; i < lengthies.size(); ++i) { + EXPECT_EQ(expected[i], ShortyFieldType::CreateFromFieldTypeDescriptor(lengthies[i])); + } +} // TEST_F + +TEST_F(ShortyFieldTypeTest, TestParseFromFieldTypeDescriptor) { + // Sample input. + std::vector<const char*> lengthies = { + // Empty list + "", + // Primitives + "Z", "B", "C", "S", "I", "F", "J", "D", + // Non-primitives + "LObject;", "\\Closure;", + // Arrays. The biggest PITA. + "[Z", "[[B", "[[LObject;", "[[[[\\Closure;", + // Multiple things at once: + "ZBCSIFJD", + "LObject;LObject;SSI", + "[[ZDDZ", + "[[LObject;[[Z[F\\Closure;LObject;", + }; + + // Expected output. + std::vector<std::vector<ShortyFieldType>> expected = { + // Empty list + {}, + // Primitives + {kSZ}, {kSB}, {kSC}, {kSS}, {kSI}, {kSF}, {kSJ}, {kSD}, + // Non-primitives. + { ShortyFieldType::kObject }, { ShortyFieldType::kLambda }, + // Arrays are always treated as objects. + { kSL }, { kSL }, { kSL }, { kSL }, + // Multiple things at once: + { kSZ, kSB, kSC, kSS, kSI, kSF, kSJ, kSD }, + { kSL, kSL, kSS, kSS, kSI }, + { kSL, kSD, kSD, kSZ }, + { kSL, kSL, kSL, kS_, kSL }, + }; + + // Sanity check that the expected/actual lists are the same size.. when adding new entries. + ASSERT_EQ(expected.size(), lengthies.size()); + + // All valid lengthy types are correctly turned into the expected shorty type. + for (size_t i = 0; i < expected.size(); ++i) { + const std::vector<ShortyFieldType>& expected_list = expected[i]; + std::vector<ShortyFieldType> actual_list = ParseLongTypeDescriptorsToList(lengthies[i]); + EXPECT_TRUE(AreListsEqual(expected_list, actual_list)); + } +} // TEST_F + +// Helper class to probe a shorty's characteristics by minimizing copy-and-paste tests. +template <typename T, decltype(ShortyFieldType::kByte) kShortyEnum> +struct ShortyTypeCharacteristics { + bool is_primitive_ = false; + bool is_primitive_narrow_ = false; + bool is_primitive_wide_ = false; + bool is_object_ = false; + bool is_lambda_ = false; + size_t size_ = sizeof(T); + bool is_dynamic_sized_ = false; + + void CheckExpects() { + ShortyFieldType shorty = kShortyEnum; + + // Test the main non-parsing-related ShortyFieldType characteristics. + EXPECT_EQ(is_primitive_, shorty.IsPrimitive()); + EXPECT_EQ(is_primitive_narrow_, shorty.IsPrimitiveNarrow()); + EXPECT_EQ(is_primitive_wide_, shorty.IsPrimitiveWide()); + EXPECT_EQ(is_object_, shorty.IsObject()); + EXPECT_EQ(is_lambda_, shorty.IsLambda()); + EXPECT_EQ(size_, shorty.GetStaticSize()); + EXPECT_EQ(is_dynamic_sized_, !shorty.IsStaticSize()); + + // Test compile-time ShortyFieldTypeTraits. + EXPECT_TRUE(ShortyFieldTypeTraits::IsType<T>()); + EXPECT_EQ(is_primitive_, ShortyFieldTypeTraits::IsPrimitiveType<T>()); + EXPECT_EQ(is_primitive_narrow_, ShortyFieldTypeTraits::IsPrimitiveNarrowType<T>()); + EXPECT_EQ(is_primitive_wide_, ShortyFieldTypeTraits::IsPrimitiveWideType<T>()); + EXPECT_EQ(is_object_, ShortyFieldTypeTraits::IsObjectType<T>()); + EXPECT_EQ(is_lambda_, ShortyFieldTypeTraits::IsLambdaType<T>()); + + // Test compile-time ShortyFieldType selectors + static_assert(std::is_same<T, typename ShortyFieldTypeSelectType<kShortyEnum>::type>::value, + "ShortyFieldType Enum->Type incorrect mapping"); + auto kActualEnum = ShortyFieldTypeSelectEnum<T>::value; // Do not ODR-use, avoid linker error. + EXPECT_EQ(kShortyEnum, kActualEnum); + } +}; + +TEST_F(ShortyFieldTypeTest, TestCharacteristicsAndTraits) { + // Boolean test + { + SCOPED_TRACE("boolean"); + ShortyTypeCharacteristics<bool, ShortyFieldType::kBoolean> chars; + chars.is_primitive_ = true; + chars.is_primitive_narrow_ = true; + chars.CheckExpects(); + } + + // Byte test + { + SCOPED_TRACE("byte"); + ShortyTypeCharacteristics<int8_t, ShortyFieldType::kByte> chars; + chars.is_primitive_ = true; + chars.is_primitive_narrow_ = true; + chars.CheckExpects(); + } + + // Char test + { + SCOPED_TRACE("char"); + ShortyTypeCharacteristics<uint16_t, ShortyFieldType::kChar> chars; // Char is unsigned. + chars.is_primitive_ = true; + chars.is_primitive_narrow_ = true; + chars.CheckExpects(); + } + + // Short test + { + SCOPED_TRACE("short"); + ShortyTypeCharacteristics<int16_t, ShortyFieldType::kShort> chars; + chars.is_primitive_ = true; + chars.is_primitive_narrow_ = true; + chars.CheckExpects(); + } + + // Int test + { + SCOPED_TRACE("int"); + ShortyTypeCharacteristics<int32_t, ShortyFieldType::kInt> chars; + chars.is_primitive_ = true; + chars.is_primitive_narrow_ = true; + chars.CheckExpects(); + } + + // Long test + { + SCOPED_TRACE("long"); + ShortyTypeCharacteristics<int64_t, ShortyFieldType::kLong> chars; + chars.is_primitive_ = true; + chars.is_primitive_wide_ = true; + chars.CheckExpects(); + } + + // Float test + { + SCOPED_TRACE("float"); + ShortyTypeCharacteristics<float, ShortyFieldType::kFloat> chars; + chars.is_primitive_ = true; + chars.is_primitive_narrow_ = true; + chars.CheckExpects(); + } + + // Double test + { + SCOPED_TRACE("double"); + ShortyTypeCharacteristics<double, ShortyFieldType::kDouble> chars; + chars.is_primitive_ = true; + chars.is_primitive_wide_ = true; + chars.CheckExpects(); + } + + // Object test + { + SCOPED_TRACE("object"); + ShortyTypeCharacteristics<mirror::Object*, ShortyFieldType::kObject> chars; + chars.is_object_ = true; + chars.size_ = kObjectReferenceSize; + chars.CheckExpects(); + EXPECT_EQ(kObjectReferenceSize, sizeof(mirror::CompressedReference<mirror::Object>)); + } + + // Lambda test + { + SCOPED_TRACE("lambda"); + ShortyTypeCharacteristics<Closure*, ShortyFieldType::kLambda> chars; + chars.is_lambda_ = true; + chars.is_dynamic_sized_ = true; + chars.CheckExpects(); + } +} + +} // namespace lambda +} // namespace art |