Skip to content

Commit fb2d829

Browse files
committed
fix: Do not mutate external TensorInfo in import_memory() when using soft_init
When a TensorInfo is shared with a Tensor via allocator()->soft_init(), the allocator stores a reference to the caller-provided TensorInfo. If import_memory() is later called, the allocator currently sets TensorInfo::is_resizable(false). Because the TensorInfo is shared, this mutates the caller-owned metadata. Subsequent operations that expect the TensorInfo to remain resizable (e.g. extend_padding()) can fail. This is observable in multi-threaded or reused inference paths (for example multi-threaded CpuGemmConv2d), where the same TensorInfo is reused across runs. In such cases import_memory() unexpectedly changes the caller-visible TensorInfo state. Fix this by distinguishing between allocator-owned and external TensorInfo instances: - Track ownership with `_owns_info`. - Track imported buffers with `_is_imported`. - `soft_init()` marks the TensorInfo as externally owned. - `import_memory()` only mutates `TensorInfo::is_resizable()` when the allocator owns the TensorInfo. - When referencing external TensorInfo, allocator logic relies on the internal `_is_imported` state instead of mutating the caller metadata. This preserves existing behaviour for allocator-owned TensorInfo while avoiding unexpected mutations of caller-provided metadata. The change is minimal and does not modify public APIs. Test: - Add/enable UNIT test `ImportMemoryDoesNotMutateExternalInfo`. - Verify that `shared_info.is_resizable()` remains true after import_memory() and that `extend_padding()` succeeds. Change-Id: Iac9d5807789a8ed74205cdee098b36d0f5269d59 Signed-off-by: Pablo Marquez Tello <pablo.tello@arm.com>
1 parent d52e297 commit fb2d829

File tree

5 files changed

+148
-11
lines changed

5 files changed

+148
-11
lines changed

arm_compute/runtime/ITensorAllocator.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016-2021, 2024-2025 Arm Limited.
2+
* Copyright (c) 2016-2021, 2024-2026 Arm Limited.
33
*
44
* SPDX-License-Identifier: MIT
55
*
@@ -110,10 +110,23 @@ class ITensorAllocator
110110
/** Interface to be implemented by the child class to unlock the memory allocation after the CPU is done accessing it. */
111111
virtual void unlock() = 0;
112112

113+
/** Returns whether allocator currently owns the TensorInfo metadata object. */
114+
bool owns_info() const;
115+
/** Returns whether the allocator has imported external backing memory. */
116+
bool is_imported() const;
117+
/** Track import state for derived allocators when ownership of backing memory changes. */
118+
void set_imported(bool imported);
119+
/** For external TensorInfo metadata, import state determines resizable behavior from allocator perspective. */
120+
bool allocator_considers_resizable() const;
121+
/** Update TensorInfo::is_resizable only if allocator owns the metadata. */
122+
void set_resizable_if_info_owned(bool is_resizable);
123+
113124
private:
114125
TensorInfo _info_owned{}; /**< Tensor's metadata. */
115126
TensorInfo *_info_external{nullptr}; /**< External Tensor's metadata */
116127
size_t _alignment{}; /**< Tensor's alignment in bytes */
128+
bool _owns_info{true}; /**< True when allocator owns metadata; false for soft_init(). */
129+
bool _is_imported{false}; /**< True when memory was imported instead of allocated by allocator. */
117130
};
118131
} // namespace arm_compute
119132
#endif // ACL_ARM_COMPUTE_RUNTIME_ITENSORALLOCATOR_H

src/runtime/CL/CLTensorAllocator.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016-2021, 2024 Arm Limited.
2+
* Copyright (c) 2016-2021, 2024, 2026 Arm Limited.
33
*
44
* SPDX-License-Identifier: MIT
55
*
@@ -157,15 +157,17 @@ void CLTensorAllocator::allocate()
157157
}
158158

159159
// Lock allocator
160-
info().set_is_resizable(false);
160+
set_imported(false);
161+
set_resizable_if_info_owned(false);
161162
}
162163

163164
void CLTensorAllocator::free()
164165
{
165166
_mapping = nullptr;
166167
_memory.set_region(nullptr);
167168
clear_quantization_arrays(_scale, _offset);
168-
info().set_is_resizable(true);
169+
set_imported(false);
170+
set_resizable_if_info_owned(true);
169171
}
170172

171173
bool CLTensorAllocator::is_allocated() const
@@ -182,7 +184,8 @@ Status CLTensorAllocator::import_memory(cl::Buffer buffer)
182184

183185
_memory.set_owned_region(std::make_unique<CLBufferMemoryRegion>(buffer));
184186

185-
info().set_is_resizable(false);
187+
set_imported(true);
188+
set_resizable_if_info_owned(false);
186189
return Status{};
187190
}
188191

src/runtime/ITensorAllocator.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016-2021 Arm Limited.
2+
* Copyright (c) 2016-2021, 2026 Arm Limited.
33
*
44
* SPDX-License-Identifier: MIT
55
*
@@ -35,12 +35,16 @@ void ITensorAllocator::init(const TensorInfo &input, size_t alignment)
3535
_info_owned = input;
3636
_info_external = nullptr;
3737
_alignment = alignment;
38+
_owns_info = true;
39+
_is_imported = false;
3840
}
3941

4042
void ITensorAllocator::soft_init(TensorInfo &input, size_t alignment)
4143
{
4244
_info_external = &input;
4345
_alignment = alignment;
46+
_owns_info = false;
47+
_is_imported = false;
4448
}
4549

4650
TensorInfo &ITensorAllocator::info()
@@ -57,3 +61,36 @@ size_t ITensorAllocator::alignment() const
5761
{
5862
return _alignment;
5963
}
64+
65+
bool ITensorAllocator::owns_info() const
66+
{
67+
return _owns_info;
68+
}
69+
70+
bool ITensorAllocator::is_imported() const
71+
{
72+
return _is_imported;
73+
}
74+
75+
void ITensorAllocator::set_imported(bool imported)
76+
{
77+
_is_imported = imported;
78+
}
79+
80+
bool ITensorAllocator::allocator_considers_resizable() const
81+
{
82+
if (_owns_info)
83+
{
84+
const TensorInfo *info_ptr = (_info_external != nullptr) ? _info_external : &_info_owned;
85+
return (info_ptr != nullptr) ? info_ptr->is_resizable() : true;
86+
}
87+
return !_is_imported;
88+
}
89+
90+
void ITensorAllocator::set_resizable_if_info_owned(bool is_resizable)
91+
{
92+
if (_owns_info)
93+
{
94+
info().set_is_resizable(is_resizable);
95+
}
96+
}

src/runtime/TensorAllocator.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016-2020, 2024 Arm Limited.
2+
* Copyright (c) 2016-2020, 2024, 2026 Arm Limited.
33
*
44
* SPDX-License-Identifier: MIT
55
*
@@ -71,7 +71,7 @@ TensorAllocator::TensorAllocator(IMemoryManageable *owner) : _owner(owner), _ass
7171

7272
TensorAllocator::~TensorAllocator()
7373
{
74-
info().set_is_resizable(true);
74+
set_resizable_if_info_owned(true);
7575
}
7676

7777
TensorAllocator::TensorAllocator(TensorAllocator &&o) noexcept
@@ -142,13 +142,15 @@ void TensorAllocator::allocate()
142142
{
143143
_associated_memory_group->finalize_memory(_owner, _memory, info().total_size(), alignment_to_use);
144144
}
145-
info().set_is_resizable(false);
145+
set_imported(false);
146+
set_resizable_if_info_owned(false);
146147
}
147148

148149
void TensorAllocator::free()
149150
{
150151
_memory.set_region(nullptr);
151-
info().set_is_resizable(true);
152+
set_imported(false);
153+
set_resizable_if_info_owned(true);
152154
}
153155

154156
bool TensorAllocator::is_allocated() const
@@ -163,7 +165,8 @@ Status TensorAllocator::import_memory(void *memory)
163165
ARM_COMPUTE_RETURN_ERROR_ON(alignment() != 0 && !arm_compute::utility::check_aligned(memory, alignment()));
164166

165167
_memory.set_owned_region(std::make_unique<MemoryRegion>(memory, info().total_size()));
166-
info().set_is_resizable(false);
168+
set_imported(true);
169+
set_resizable_if_info_owned(false);
167170

168171
return Status{};
169172
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) 2026 Arm Limited.
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to
8+
* deal in the Software without restriction, including without limitation the
9+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10+
* sell copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
#include "arm_compute/core/TensorInfo.h"
25+
#include "arm_compute/core/TensorShape.h"
26+
#include "arm_compute/core/Types.h"
27+
#include "arm_compute/runtime/Tensor.h"
28+
29+
#include "tests/framework/Asserts.h"
30+
#include "tests/framework/Macros.h"
31+
#include "tests/validation/Validation.h"
32+
33+
namespace arm_compute
34+
{
35+
namespace test
36+
{
37+
namespace validation
38+
{
39+
TEST_SUITE(UNIT)
40+
TEST_SUITE(TensorInfo)
41+
42+
TEST_CASE(ImportMemoryDoesNotMutateExternalInfo, framework::DatasetMode::ALL)
43+
{
44+
// Use F16 if available; otherwise fall back to F32.
45+
DataType test_dt = DataType::F16;
46+
#if !defined(ARM_COMPUTE_ENABLE_FP16)
47+
test_dt = DataType::F32;
48+
#endif
49+
50+
TensorInfo out_info(TensorShape(16U, 4U, 4U), 1, test_dt, DataLayout::NHWC);
51+
out_info.set_is_resizable(true);
52+
53+
Tensor out_tensor;
54+
out_tensor.allocator()->init(out_info);
55+
out_tensor.allocator()->allocate();
56+
57+
// Simulate a shared TensorInfo used as a view wrapper.
58+
TensorInfo shared_info(out_info);
59+
shared_info.set_is_resizable(true);
60+
61+
Tensor view_tensor;
62+
view_tensor.allocator()->soft_init(shared_info);
63+
64+
// Ensure it's still resizable before import.
65+
ARM_COMPUTE_EXPECT(shared_info.is_resizable(), framework::LogLevel::ERRORS);
66+
67+
// Import memory into the view tensor.
68+
ARM_COMPUTE_ASSERT(bool(view_tensor.allocator()->import_memory(out_tensor.buffer())));
69+
70+
// Regression assert: import_memory must NOT mutate the caller-owned shared_info.
71+
ARM_COMPUTE_EXPECT(shared_info.is_resizable(), framework::LogLevel::ERRORS);
72+
73+
// extend_padding should succeed (not throw).
74+
ARM_COMPUTE_EXPECT_NO_THROW(shared_info.extend_padding(PaddingSize(1, 1, 1, 1)), framework::LogLevel::ERRORS);
75+
}
76+
77+
TEST_SUITE_END() // TensorInfo
78+
TEST_SUITE_END() // UNIT
79+
} // namespace validation
80+
} // namespace test
81+
} // namespace arm_compute

0 commit comments

Comments
 (0)