blob: 94e99bcc098f6c87e6e467193323846b9634ff20 [file] [log] [blame]
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <executorch/extension/runner_util/inputs.h>
#include <executorch/extension/data_loader/file_data_loader.h>
#include <executorch/runtime/core/exec_aten/exec_aten.h>
#include <executorch/runtime/core/span.h>
#include <executorch/runtime/executor/method.h>
#include <executorch/runtime/executor/program.h>
#include <executorch/runtime/executor/test/managed_memory_manager.h>
#include <executorch/runtime/platform/runtime.h>
#include <gtest/gtest.h>
using namespace ::testing;
using exec_aten::ScalarType;
using exec_aten::Tensor;
using torch::executor::Error;
using torch::executor::EValue;
using torch::executor::MemoryAllocator;
using torch::executor::MemoryManager;
using torch::executor::Method;
using torch::executor::Program;
using torch::executor::Result;
using torch::executor::Span;
using torch::executor::Tag;
using torch::executor::Tensor;
using torch::executor::testing::ManagedMemoryManager;
using torch::executor::util::BufferCleanup;
using torch::executor::util::FileDataLoader;
using torch::executor::util::prepare_input_tensors;
class InputsTest : public ::testing::Test {
protected:
void SetUp() override {
torch::executor::runtime_init();
// Create a loader for the serialized ModuleAdd program.
const char* path = std::getenv("ET_MODULE_ADD_PATH");
Result<FileDataLoader> loader = FileDataLoader::from(path);
ASSERT_EQ(loader.error(), Error::Ok);
loader_ = std::make_unique<FileDataLoader>(std::move(loader.get()));
// Use it to load the program.
Result<Program> program = Program::load(
loader_.get(), Program::Verification::InternalConsistency);
ASSERT_EQ(program.error(), Error::Ok);
program_ = std::make_unique<Program>(std::move(program.get()));
mmm_ = std::make_unique<ManagedMemoryManager>(
/*planned_memory_bytes=*/32 * 1024U,
/*method_allocator_bytes=*/32 * 1024U);
// Load the forward method.
Result<Method> method = program_->load_method("forward", &mmm_->get());
ASSERT_EQ(method.error(), Error::Ok);
method_ = std::make_unique<Method>(std::move(method.get()));
}
private:
// Must outlive method_, but tests shouldn't need to touch them.
std::unique_ptr<FileDataLoader> loader_;
std::unique_ptr<ManagedMemoryManager> mmm_;
std::unique_ptr<Program> program_;
protected:
std::unique_ptr<Method> method_;
};
TEST_F(InputsTest, Smoke) {
Result<BufferCleanup> input_buffers = prepare_input_tensors(*method_);
ASSERT_EQ(input_buffers.error(), Error::Ok);
// We can't look at the input tensors, but we can check that the outputs make
// sense after executing the method.
Error status = method_->execute();
ASSERT_EQ(status, Error::Ok);
// Get the single output, which should be a floating-point Tensor.
ASSERT_EQ(method_->outputs_size(), 1);
const EValue& output_value = method_->get_output(0);
ASSERT_EQ(output_value.tag, Tag::Tensor);
Tensor output = output_value.toTensor();
ASSERT_EQ(output.scalar_type(), ScalarType::Float);
// ModuleAdd adds its two inputs together, so if the input elements were set
// to 1, the output elemements should all be 2.
Span<float> elements(output.mutable_data_ptr<float>(), output.numel());
EXPECT_GT(elements.size(), 0); // Make sure we're actually testing something.
for (float e : elements) {
EXPECT_EQ(e, 2.0);
}
// Although it's tough to test directly, ASAN should let us know if
// BufferCleanup doesn't behave properly: either freeing too soon or leaking
// the pointers.
}
TEST(BufferCleanupTest, Smoke) {
// Returns the size of the buffer at index `i`.
auto test_buffer_size = [](size_t i) {
// Use multiples of OS page sizes. As this gets bigger, we're more
// likely to allocate outside the main heap in a separate page, making
// it easier to catch uses-after-free.
return 4096 << i;
};
// Create some buffers.
constexpr size_t kNumBuffers = 8;
void** buffers = (void**)malloc(kNumBuffers * sizeof(void*));
for (int i = 0; i < kNumBuffers; i++) {
size_t nbytes = test_buffer_size(i);
buffers[i] = malloc(nbytes);
memset(reinterpret_cast<char*>(buffers[i]), 0x00, nbytes);
}
std::unique_ptr<BufferCleanup> bc2;
{
// bc1 should own `buffers` and the buffers that its entries point to.
BufferCleanup bc1({buffers, kNumBuffers});
// They're still alive; no segfaults or ASAN complaints if we write to them.
for (int i = 0; i < kNumBuffers; i++) {
size_t nbytes = test_buffer_size(i);
memset(reinterpret_cast<char*>(buffers[i]), 0xff, nbytes);
}
// Move ownership to a new object.
bc2 = std::make_unique<BufferCleanup>(std::move(bc1));
// Still alive.
for (int i = 0; i < kNumBuffers; i++) {
size_t nbytes = test_buffer_size(i);
memset(reinterpret_cast<char*>(buffers[i]), 0x00, nbytes);
}
// bc1 goes out of scope here. If it thinks it owns the buffers, it will
// try to free them.
}
// bc2 should own the buffers now, and they should still be alive.
for (int i = 0; i < kNumBuffers; i++) {
size_t nbytes = test_buffer_size(i);
memset(reinterpret_cast<char*>(buffers[i]), 0xff, nbytes);
}
// Destroy bc2, which should destroy the buffers. There's no way for us to
// check that it happened, but the sanitizer should complain if there's a
// memory leak. And if bc1 freed them before, we should get a double-free
// complaint.
bc2.reset();
}