356 lines
14 KiB
C++
356 lines
14 KiB
C++
/*
|
|
* 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
|