Memory Domain Runtime: Explicit memory copying.

- Implement ANNMemory_copy.
- Add validation tests.

Bug: 141353602
Bug: 141363565
Test: NNT_static
Change-Id: Id0ec2431cf9cfff650cd8885de4d15abee306a9c
Merged-In: Id0ec2431cf9cfff650cd8885de4d15abee306a9c
(cherry picked from commit 9af11e783aadacf78330a5e6e4e8255f41789c13)
diff --git a/runtime/ExecutionBuilder.cpp b/runtime/ExecutionBuilder.cpp
index 6c18e1d..174faa0 100644
--- a/runtime/ExecutionBuilder.cpp
+++ b/runtime/ExecutionBuilder.cpp
@@ -603,7 +603,7 @@
     for (const auto& output : mOutputs) {
         if (output.state != ModelArgumentInfo::MEMORY) continue;
         const Memory* memory = mMemories[output.locationAndLength.poolIndex];
-        NN_RET_CHECK(memory->getValidator().updateDimensions(output.dimensions));
+        NN_RET_CHECK(memory->getValidator().updateMetadata({.dimensions = output.dimensions}));
     }
     return true;
 }
diff --git a/runtime/Memory.cpp b/runtime/Memory.cpp
index 561df07..f8f90f7 100644
--- a/runtime/Memory.cpp
+++ b/runtime/Memory.cpp
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include "CompilationBuilder.h"
+#include "CpuExecutor.h"
 #include "ExecutionBurstController.h"
 #include "Manager.h"
 #include "MemoryUtils.h"
@@ -52,6 +53,11 @@
         return true;
     }
 
+    Metadata getMetadata() const override { return {.logicalSize = kSize}; }
+    bool updateMetadata(const Metadata& metadata) override {
+        return metadata.logicalSize == 0 || metadata.logicalSize == kSize;
+    }
+
    private:
     const uint32_t kSize;
 };
@@ -73,6 +79,9 @@
                 << ") for Non-BLOB format AHardwareBuffer.";
         return true;
     }
+
+    Metadata getMetadata() const override { return {}; }
+    bool updateMetadata(const Metadata&) override { return true; }
 };
 
 // The validator for a memory created from ANNMemory_createFromDesc.
@@ -80,10 +89,10 @@
 // with both offset and length set to zero.
 class DeviceMemoryValidator : public MemoryValidatorBase {
    public:
-    DeviceMemoryValidator(std::set<CompilationRole> roles, hal::OperandType type,
+    DeviceMemoryValidator(std::set<CompilationRole> roles, Operand operand,
                           std::vector<uint32_t> dimensions)
         : kCompilationRoles(std::move(roles)),
-          mDataType(type),
+          kOperand(std::move(operand)),
           kInitialDimensions(std::move(dimensions)),
           mUpdatedDimensions(kInitialDimensions) {}
 
@@ -95,7 +104,7 @@
         NN_RET_CHECK(offset == 0 && length == 0)
                 << "non-zero offset and/or length for driver-allocated memory.";
         if (type) {
-            const bool isTensor = TypeManager::get()->isTensorType(mDataType);
+            const bool isTensor = TypeManager::get()->isTensorType(kOperand.type);
             NN_RET_CHECK(isTensor || type->dimensionCount == 0)
                     << "invalid dimensions for scalar memory.";
             std::vector<uint32_t> dimensions(type->dimensions,
@@ -119,19 +128,40 @@
         return true;
     }
 
-    bool updateDimensions(const std::vector<uint32_t>& dimensions) override {
-        NN_RET_CHECK(TypeManager::get()->isTensorType(mDataType) || dimensions.empty());
-        auto combined = combineDimensions(dimensions, kInitialDimensions);
+    Metadata getMetadata() const override {
+        CHECK(mInitialized);
+        return {.logicalSize = TypeManager::get()->getSizeOfData(kOperand.type, mUpdatedDimensions),
+                .dimensions = mUpdatedDimensions,
+                .operand = kOperand};
+    }
+
+    bool updateMetadata(const Metadata& metadata) override {
+        NN_RET_CHECK(!metadata.operand.has_value() ||
+                     (metadata.operand->type == kOperand.type &&
+                      metadata.operand->scale == kOperand.scale &&
+                      metadata.operand->zeroPoint == kOperand.zeroPoint &&
+                      metadata.operand->extraParams == kOperand.extraParams));
+
+        NN_RET_CHECK(metadata.dimensions.empty() ||
+                     TypeManager::get()->isTensorType(kOperand.type));
+        auto combined = combineDimensions(metadata.dimensions, kInitialDimensions);
         NN_RET_CHECK(combined.has_value());
+        NN_RET_CHECK(metadata.logicalSize == 0 ||
+                     metadata.logicalSize ==
+                             TypeManager::get()->getSizeOfData(kOperand.type, combined.value()));
         mUpdatedDimensions = std::move(combined.value());
         return true;
     }
 
     void setInitialized(bool initialized) override { mInitialized = initialized; }
+    bool isInitialized() const override { return mInitialized; }
 
    private:
     const std::set<CompilationRole> kCompilationRoles;
-    OperandType mDataType;
+
+    // Keep track of the data type, scale, zero point, and extra parameters of the target operand.
+    // Other fields will be ignored, including dimensions, lifetime, location, etc.
+    const Operand kOperand;
 
     // The dimensions of the memory when the memory object is created.
     // May have unknown dimensions or rank.
@@ -182,6 +212,90 @@
     mUsedBy.emplace(burst.get(), burst);
 }
 
+static int copyHidlMemories(const hidl_memory& src, const hidl_memory& dst) {
+    if (src.size() != dst.size()) {
+        LOG(ERROR) << "ANeuralNetworksMemory_copy -- incompatible memory size";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+    auto srcPool = RunTimePoolInfo::createFromHidlMemory(src);
+    auto dstPool = RunTimePoolInfo::createFromHidlMemory(dst);
+    if (!srcPool.has_value() || !dstPool.has_value()) {
+        LOG(ERROR) << "ANeuralNetworksMemory_copy -- unable to map memory";
+        return ANEURALNETWORKS_UNMAPPABLE;
+    }
+    CHECK(srcPool->getBuffer() != nullptr);
+    CHECK(dstPool->getBuffer() != nullptr);
+    std::copy(srcPool->getBuffer(), srcPool->getBuffer() + src.size(), dstPool->getBuffer());
+    dstPool->flush();
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+static int copyIBufferToHidlMemory(const sp<IBuffer>& src, const hidl_memory& dst) {
+    const auto ret = src->copyTo(dst);
+    if (!ret.isOk()) {
+        LOG(ERROR) << "ANeuralNetworksMemory_copy failure: " << ret.description();
+        return ANEURALNETWORKS_OP_FAILED;
+    }
+    return convertErrorStatusToResultCode(static_cast<ErrorStatus>(ret));
+}
+
+static int copyHidlMemoryToIBuffer(const hidl_memory& src, const sp<IBuffer>& dst,
+                                   const std::vector<uint32_t>& dimensions) {
+    const auto ret = dst->copyFrom(src, dimensions);
+    if (!ret.isOk()) {
+        LOG(ERROR) << "ANeuralNetworksMemory_copy failure: " << ret.description();
+        return ANEURALNETWORKS_OP_FAILED;
+    }
+    return convertErrorStatusToResultCode(static_cast<ErrorStatus>(ret));
+}
+
+static int copyIBuffers(const sp<IBuffer>& src, const sp<IBuffer>& dst,
+                        const MemoryValidatorBase::Metadata& srcMetadata) {
+    // TODO(xusongw): Use BLOB mode AHardwareBuffer.
+    hidl_memory hidlMemory = allocateSharedMemory(srcMetadata.logicalSize);
+    if (!hidlMemory.valid()) return ANEURALNETWORKS_OUT_OF_MEMORY;
+    NN_RETURN_IF_ERROR(copyIBufferToHidlMemory(src, hidlMemory));
+    NN_RETURN_IF_ERROR(copyHidlMemoryToIBuffer(hidlMemory, dst, srcMetadata.dimensions));
+    return ANEURALNETWORKS_NO_ERROR;
+}
+
+static int copyInternal(const Memory& src, const Memory& dst) {
+    if (&src == &dst) return ANEURALNETWORKS_NO_ERROR;
+
+    if (!src.getValidator().isInitialized()) {
+        LOG(ERROR) << "ANeuralNetworksMemory_copy -- uninitialized source memory";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+
+    const auto srcMetadata = src.getValidator().getMetadata();
+    if (!dst.getValidator().updateMetadata(srcMetadata)) {
+        LOG(ERROR) << "ANeuralNetworksMemory_copy -- incompatible memories";
+        return ANEURALNETWORKS_BAD_DATA;
+    }
+
+    bool srcHasHidlMemory = src.getHidlMemory().valid();
+    bool dstHasHidlMemory = dst.getHidlMemory().valid();
+    bool srcHasIBuffer = src.getIBuffer() != nullptr;
+    bool dstHasIBuffer = dst.getIBuffer() != nullptr;
+    if (srcHasIBuffer && dstHasIBuffer) {
+        return copyIBuffers(src.getIBuffer(), dst.getIBuffer(), srcMetadata);
+    } else if (srcHasHidlMemory && dstHasHidlMemory) {
+        return copyHidlMemories(src.getHidlMemory(), dst.getHidlMemory());
+    } else if (srcHasHidlMemory && dstHasIBuffer) {
+        return copyHidlMemoryToIBuffer(src.getHidlMemory(), dst.getIBuffer(),
+                                       srcMetadata.dimensions);
+    } else if (srcHasIBuffer && dstHasHidlMemory) {
+        return copyIBufferToHidlMemory(src.getIBuffer(), dst.getHidlMemory());
+    }
+    return ANEURALNETWORKS_OP_FAILED;
+}
+
+int Memory::copy(const Memory& src, const Memory& dst) {
+    int n = copyInternal(src, dst);
+    dst.getValidator().setInitialized(n == ANEURALNETWORKS_NO_ERROR);
+    return n;
+}
+
 bool MemoryBuilder::badState(const char* name) const {
     if (mFinished) {
         LOG(ERROR) << "ANeuralNetworksMemoryDesc_" << name << " can't modify after finished";
@@ -375,7 +489,7 @@
     if (n == ANEURALNETWORKS_NO_ERROR) {
         CHECK(memory != nullptr);
         auto validator =
-                std::make_unique<DeviceMemoryValidator>(mRoles, mOperand->type, mDesc.dimensions);
+                std::make_unique<DeviceMemoryValidator>(mRoles, mOperand.value(), mDesc.dimensions);
         memory->setValidator(std::move(validator));
     }
     return {n, std::move(memory)};
diff --git a/runtime/Memory.h b/runtime/Memory.h
index 5394338..5044b87 100644
--- a/runtime/Memory.h
+++ b/runtime/Memory.h
@@ -133,9 +133,27 @@
     // Validate the memory dimensional information at the beginning of a computation.
     virtual bool validateInputDimensions(const std::vector<uint32_t>&) const { return true; }
 
-    virtual bool updateDimensions(const std::vector<uint32_t>&) { return true; }
+    // The validation metadata for this memory.
+    struct Metadata {
+        // The byte size of the memory when it is transformed to a closely packed layout.
+        // Set to 0 if unknown (e.g. non-BLOB mode AHWB or device memory with dynamic shape).
+        uint32_t logicalSize;
+
+        // The dimensions of the memory. Set to empty if undefined.
+        std::vector<uint32_t> dimensions;
+
+        // The data type, scale, zero point, and extra parameters of the target operand.
+        // Other fields will be ignored, including dimensions, lifetime, location, etc.
+        // Set to std::nullopt if undefined.
+        std::optional<hal::Operand> operand;
+    };
+    virtual Metadata getMetadata() const;
+
+    // Try update the memory metadata with the provided metadata. Return false if incompatible.
+    virtual bool updateMetadata(const Metadata& metadata);
 
     virtual void setInitialized(bool) {}
+    virtual bool isInitialized() const { return true; }
 };
 
 // Represents a memory region.
@@ -151,6 +169,7 @@
     hal::Request::MemoryPool getMemoryPool() const;
     const hal::hidl_memory& getHidlMemory() const { return kHidlMemory; }
     const sp<hal::IBuffer>& getIBuffer() const { return kBuffer; }
+    virtual uint32_t getSize() const { return getHidlMemory().size(); }
 
     MemoryValidatorBase& getValidator() const {
         CHECK(mValidator != nullptr);
@@ -169,6 +188,8 @@
     // the bursts' memory cache.
     void usedBy(const std::shared_ptr<ExecutionBurstController>& burst) const;
 
+    static int copy(const Memory& src, const Memory& dst);
+
    protected:
     Memory(hal::hidl_memory memory);
     Memory(hal::hidl_memory memory, std::unique_ptr<MemoryValidatorBase> validator);
diff --git a/runtime/NeuralNetworks.cpp b/runtime/NeuralNetworks.cpp
index f9d60d0..6b4114e 100644
--- a/runtime/NeuralNetworks.cpp
+++ b/runtime/NeuralNetworks.cpp
@@ -909,10 +909,15 @@
     return ANEURALNETWORKS_NO_ERROR;
 }
 
-int ANeuralNetworksMemory_copy(const ANeuralNetworksMemory* /*src*/,
-                               const ANeuralNetworksMemory* /*dst*/) {
-    // TODO(xusongw): Implement.
-    return ANEURALNETWORKS_OP_FAILED;
+int ANeuralNetworksMemory_copy(const ANeuralNetworksMemory* src, const ANeuralNetworksMemory* dst) {
+    NNTRACE_RT(NNTRACE_PHASE_EXECUTION, "ANeuralNetworksMemory_copy");
+    if (!src || !dst) {
+        LOG(ERROR) << "ANeuralNetworksMemory_copy passed a nullptr";
+        return ANEURALNETWORKS_UNEXPECTED_NULL;
+    }
+    const Memory* s = reinterpret_cast<const Memory*>(src);
+    const Memory* d = reinterpret_cast<const Memory*>(dst);
+    return Memory::copy(*s, *d);
 }
 
 int ANeuralNetworksMemory_createFromFd(size_t size, int prot, int fd, size_t offset,
diff --git a/runtime/test/TestValidation.cpp b/runtime/test/TestValidation.cpp
index 45c80b5..1401441 100644
--- a/runtime/test/TestValidation.cpp
+++ b/runtime/test/TestValidation.cpp
@@ -225,10 +225,25 @@
     }
     virtual void TearDown() {
         ANeuralNetworksMemoryDesc_free(mDesc);
+        for (auto* memory : mMemories) ANeuralNetworksMemory_free(memory);
+        for (int fd : mFds) close(fd);
         ValidationTestCompilation::TearDown();
     }
 
+    ANeuralNetworksMemory* createAshmem(uint32_t size) {
+        int fd = ASharedMemory_create("nnMemory", size);
+        EXPECT_GT(fd, 0);
+        mFds.push_back(fd);
+        ANeuralNetworksMemory* ashmem = nullptr;
+        EXPECT_EQ(ANeuralNetworksMemory_createFromFd(size, PROT_READ | PROT_WRITE, fd, 0, &ashmem),
+                  ANEURALNETWORKS_NO_ERROR);
+        mMemories.push_back(ashmem);
+        return ashmem;
+    }
+
     ANeuralNetworksMemoryDesc* mDesc = nullptr;
+    std::vector<ANeuralNetworksMemory*> mMemories;
+    std::vector<int> mFds;
 };
 
 class ValidationTestExecutionDeviceMemory : public ValidationTest {
@@ -2418,6 +2433,64 @@
     ANeuralNetworksMemory_free(memory);
 }
 
+TEST_F(ValidationTestMemoryDesc, MemoryCopying) {
+    ASSERT_EQ(ANeuralNetworksCompilation_finish(mCompilation), ANEURALNETWORKS_NO_ERROR);
+
+    uint32_t goodSize = sizeof(float) * 2, badSize1 = sizeof(float), badSize2 = sizeof(float) * 4;
+    ANeuralNetworksMemory* goodAshmem = createAshmem(goodSize);
+    ANeuralNetworksMemory* badAshmem1 = createAshmem(badSize1);
+    ANeuralNetworksMemory* badAshmem2 = createAshmem(badSize2);
+
+    ANeuralNetworksMemory *deviceMemory1 = nullptr, *deviceMemory2 = nullptr;
+    EXPECT_EQ(ANeuralNetworksMemoryDesc_create(&mDesc), ANEURALNETWORKS_NO_ERROR);
+    EXPECT_EQ(ANeuralNetworksMemoryDesc_addInputRole(mDesc, mCompilation, 0, 1.0f),
+              ANEURALNETWORKS_NO_ERROR);
+    EXPECT_EQ(ANeuralNetworksMemoryDesc_finish(mDesc), ANEURALNETWORKS_NO_ERROR);
+    EXPECT_EQ(ANeuralNetworksMemory_createFromDesc(mDesc, &deviceMemory1),
+              ANEURALNETWORKS_NO_ERROR);
+    EXPECT_EQ(ANeuralNetworksMemory_createFromDesc(mDesc, &deviceMemory2),
+              ANEURALNETWORKS_NO_ERROR);
+
+    EXPECT_EQ(ANeuralNetworksMemory_copy(nullptr, deviceMemory1), ANEURALNETWORKS_UNEXPECTED_NULL);
+    EXPECT_EQ(ANeuralNetworksMemory_copy(deviceMemory1, nullptr), ANEURALNETWORKS_UNEXPECTED_NULL);
+
+    // Ashmem -> Ashmem
+    // Bad memory size.
+    EXPECT_EQ(ANeuralNetworksMemory_copy(goodAshmem, badAshmem1), ANEURALNETWORKS_BAD_DATA);
+    EXPECT_EQ(ANeuralNetworksMemory_copy(goodAshmem, badAshmem2), ANEURALNETWORKS_BAD_DATA);
+    EXPECT_EQ(ANeuralNetworksMemory_copy(badAshmem1, goodAshmem), ANEURALNETWORKS_BAD_DATA);
+    EXPECT_EQ(ANeuralNetworksMemory_copy(badAshmem2, goodAshmem), ANEURALNETWORKS_BAD_DATA);
+
+    // Ashmem -> Device Memory
+    // Bad memory size.
+    EXPECT_EQ(ANeuralNetworksMemory_copy(badAshmem1, deviceMemory1), ANEURALNETWORKS_BAD_DATA);
+    EXPECT_EQ(ANeuralNetworksMemory_copy(badAshmem2, deviceMemory1), ANEURALNETWORKS_BAD_DATA);
+
+    // Device Memory -> Ashmem
+    // Uninitialized source device memory.
+    EXPECT_EQ(ANeuralNetworksMemory_copy(deviceMemory1, goodAshmem), ANEURALNETWORKS_BAD_DATA);
+    // Bad memory size.
+    EXPECT_EQ(ANeuralNetworksMemory_copy(goodAshmem, deviceMemory1), ANEURALNETWORKS_NO_ERROR);
+    EXPECT_EQ(ANeuralNetworksMemory_copy(deviceMemory1, badAshmem1), ANEURALNETWORKS_BAD_DATA);
+    // Uninitialized source device memory (after a failed copy).
+    EXPECT_EQ(ANeuralNetworksMemory_copy(badAshmem1, deviceMemory1), ANEURALNETWORKS_BAD_DATA);
+    EXPECT_EQ(ANeuralNetworksMemory_copy(deviceMemory1, goodAshmem), ANEURALNETWORKS_BAD_DATA);
+    // Bad memory size.
+    EXPECT_EQ(ANeuralNetworksMemory_copy(goodAshmem, deviceMemory1), ANEURALNETWORKS_NO_ERROR);
+    EXPECT_EQ(ANeuralNetworksMemory_copy(deviceMemory1, badAshmem2), ANEURALNETWORKS_BAD_DATA);
+
+    // Device Memory -> Device Memory
+    // Uninitialized source device memory.
+    EXPECT_EQ(ANeuralNetworksMemory_copy(deviceMemory2, deviceMemory1), ANEURALNETWORKS_BAD_DATA);
+
+    // TODO: Additionally validate the following:
+    //       - Device memories with incompatible dimensions
+    //       - Deinitialized device memory
+
+    ANeuralNetworksMemory_free(deviceMemory1);
+    ANeuralNetworksMemory_free(deviceMemory2);
+}
+
 #ifndef NNTEST_ONLY_PUBLIC_API
 TEST(ValidationTestDevice, GetExtensionSupport) {
     bool result;