| /* |
| * 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 |