diff options
Diffstat (limited to 'runtime/lambda/closure_test.cc')
-rw-r--r-- | runtime/lambda/closure_test.cc | 356 |
1 files changed, 356 insertions, 0 deletions
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 |