Skip to content

Commit 25e8f81

Browse files
author
Github Executorch
committed
Add safe_numel() and compute_numel_overflow()
Introduces two overflow-checked numel helpers as experimental API additions. - safe_numel(sizes, dim): returns Result; use this where possible - compute_numel_overflow(sizes, dim): aborts on overflow; use this in ctors and other places that cannot return result. Long-term change is to migrate fully to safe_numel. Currently, we have compute_numel, which silently overflows. Authored with Claude. Differential Revision: [D102070375](https://our.internmc.facebook.com/intern/diff/D102070375/) [ghstack-poisoned]
1 parent 069a793 commit 25e8f81

4 files changed

Lines changed: 129 additions & 1 deletion

File tree

runtime/core/exec_aten/exec_aten.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88

99
#pragma once
1010

11+
#include <executorch/runtime/core/error.h> // @manual
12+
#include <executorch/runtime/core/result.h> // @manual
1113
#include <executorch/runtime/core/tensor_shape_dynamism.h> // @manual
14+
#include <executorch/runtime/platform/assert.h> // @manual
1215
#include <executorch/runtime/platform/compiler.h>
1316
#ifdef USE_ATEN_LIB
1417
#include <ATen/Tensor.h> // @manual
@@ -28,6 +31,7 @@
2831
#include <c10/util/quint2x4.h> // @manual
2932
#include <c10/util/quint4x2.h> // @manual
3033
#include <c10/util/quint8.h> // @manual
34+
#include <c10/util/safe_numerics.h> // @manual
3135
#include <c10/util/string_view.h> // @manual
3236
#include <torch/torch.h>
3337
#else // use executor
@@ -110,6 +114,50 @@ inline ssize_t compute_numel(const SizesType* sizes, ssize_t dim) {
110114
c10::multiply_integers(c10::ArrayRef<SizesType>(sizes, dim)));
111115
}
112116

117+
ET_EXPERIMENTAL inline ::executorch::runtime::Result<ssize_t> safe_numel(
118+
const SizesType* sizes,
119+
ssize_t dim) {
120+
ssize_t numel = 1;
121+
for (ssize_t i = 0; i < dim; i++) {
122+
ET_CHECK_OR_RETURN_ERROR(
123+
sizes[i] >= 0,
124+
InvalidArgument,
125+
"Size must be non-negative, got %zd at dimension %zd",
126+
static_cast<ssize_t>(sizes[i]),
127+
i);
128+
ssize_t next_numel;
129+
ET_CHECK_OR_RETURN_ERROR(
130+
!c10::mul_overflows(
131+
numel, static_cast<ssize_t>(sizes[i]), &next_numel),
132+
InvalidArgument,
133+
"Overflow computing numel at dimension %zd",
134+
i);
135+
numel = next_numel;
136+
}
137+
return numel;
138+
}
139+
140+
ET_EXPERIMENTAL inline ssize_t compute_numel_overflow(
141+
const SizesType* sizes,
142+
ssize_t dim) {
143+
ssize_t numel = 1;
144+
for (ssize_t i = 0; i < dim; i++) {
145+
ET_CHECK_MSG(
146+
sizes[i] >= 0,
147+
"Size must be non-negative, got %zd at dimension %zd",
148+
static_cast<ssize_t>(sizes[i]),
149+
i);
150+
ssize_t next_numel;
151+
ET_CHECK_MSG(
152+
!c10::mul_overflows(
153+
numel, static_cast<ssize_t>(sizes[i]), &next_numel),
154+
"Overflow computing numel at dimension %zd",
155+
i);
156+
numel = next_numel;
157+
}
158+
return numel;
159+
}
160+
113161
#undef ET_PRI_TENSOR_SIZE
114162
#define ET_PRI_TENSOR_SIZE PRId64
115163

@@ -158,6 +206,8 @@ using OptionalArrayRef =
158206
using OptionalIntArrayRef = OptionalArrayRef<int64_t>;
159207

160208
using torch::executor::compute_numel;
209+
using torch::executor::compute_numel_overflow;
210+
using torch::executor::safe_numel;
161211

162212
#endif // Use ExecuTorch types
163213

runtime/core/exec_aten/targets.bzl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ def define_common_targets():
1616
exported_headers = ["exec_aten.h"],
1717
exported_preprocessor_flags = ["-DUSE_ATEN_LIB"] if aten_mode else [],
1818
visibility = ["PUBLIC"],
19-
exported_deps = ["//executorch/runtime/core:tensor_shape_dynamism"] + ([] if aten_mode else ["//executorch/runtime/core/portable_type:portable_type"]),
19+
exported_deps = [
20+
"//executorch/runtime/core:core",
21+
"//executorch/runtime/core:tensor_shape_dynamism",
22+
] + ([] if aten_mode else ["//executorch/runtime/core/portable_type:portable_type"]),
2023
exported_external_deps = ["libtorch"] if aten_mode else [],
2124
)

runtime/core/portable_type/tensor_impl.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <cstdint>
1313

1414
#include <c10/util/irange.h>
15+
#include <c10/util/safe_numerics.h>
1516

1617
#include <executorch/runtime/core/exec_aten/util/dim_order_util.h>
1718
#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
@@ -43,6 +44,57 @@ ssize_t compute_numel(const TensorImpl::SizesType* sizes, ssize_t dim) {
4344
return numel;
4445
}
4546

47+
ssize_t compute_numel_overflow(
48+
const TensorImpl::SizesType* sizes,
49+
ssize_t dim) {
50+
ET_CHECK_MSG(
51+
dim == 0 || sizes != nullptr,
52+
"Sizes must be provided for non-scalar tensors");
53+
ssize_t numel = 1;
54+
for (const auto i : c10::irange(dim)) {
55+
ET_CHECK_MSG(
56+
sizes[i] >= 0,
57+
"Size must be non-negative, got %zd at dimension %zd",
58+
static_cast<ssize_t>(sizes[i]),
59+
i);
60+
ssize_t next_numel;
61+
ET_CHECK_MSG(
62+
!c10::mul_overflows(
63+
numel, static_cast<ssize_t>(sizes[i]), &next_numel),
64+
"Overflow computing numel at dimension %zd",
65+
i);
66+
numel = next_numel;
67+
}
68+
return numel;
69+
}
70+
71+
::executorch::runtime::Result<ssize_t> safe_numel(
72+
const TensorImpl::SizesType* sizes,
73+
ssize_t dim) {
74+
ET_CHECK_OR_RETURN_ERROR(
75+
dim == 0 || sizes != nullptr,
76+
InvalidArgument,
77+
"Sizes must be provided for non-scalar tensors");
78+
ssize_t numel = 1;
79+
for (const auto i : c10::irange(dim)) {
80+
ET_CHECK_OR_RETURN_ERROR(
81+
sizes[i] >= 0,
82+
InvalidArgument,
83+
"Size must be non-negative, got %zd at dimension %zd",
84+
static_cast<ssize_t>(sizes[i]),
85+
i);
86+
ssize_t next_numel;
87+
ET_CHECK_OR_RETURN_ERROR(
88+
!c10::mul_overflows(
89+
numel, static_cast<ssize_t>(sizes[i]), &next_numel),
90+
InvalidArgument,
91+
"Overflow computing numel at dimension %zd",
92+
i);
93+
numel = next_numel;
94+
}
95+
return numel;
96+
}
97+
4698
TensorImpl::TensorImpl(
4799
ScalarType type,
48100
ssize_t dim,

runtime/core/portable_type/tensor_impl.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
#include <executorch/runtime/core/error.h>
1313
#include <executorch/runtime/core/portable_type/device.h>
1414
#include <executorch/runtime/core/portable_type/scalar_type.h>
15+
#include <executorch/runtime/core/result.h>
1516
#include <executorch/runtime/core/tensor_shape_dynamism.h>
17+
#include <executorch/runtime/platform/compiler.h>
1618

1719
// Forward declaration of a helper that provides access to internal resizing
1820
// methods of TensorImpl. Real definition is in
@@ -293,6 +295,25 @@ ssize_t compute_numel(
293295
const ::executorch::runtime::etensor::TensorImpl::SizesType* sizes,
294296
ssize_t dim);
295297

298+
/**
299+
* EXPERIMENTAL. Compute the number of elements based on the sizes of a tensor.
300+
* Returns Error::InvalidArgument if any intermediate multiplication would
301+
* overflow ssize_t, or if a size is negative. Prefer this over compute_numel()
302+
* for paths that can propagate an Error upward.
303+
*/
304+
ET_EXPERIMENTAL ::executorch::runtime::Result<ssize_t> safe_numel(
305+
const ::executorch::runtime::etensor::TensorImpl::SizesType* sizes,
306+
ssize_t dim);
307+
308+
/**
309+
* EXPERIMENTAL. Like safe_numel() but aborts on overflow or invalid sizes.
310+
* Prefer safe_numel() where possible; use this only in paths that cannot
311+
* propagate an Error (e.g., constructors returning a value directly).
312+
*/
313+
ET_EXPERIMENTAL ssize_t compute_numel_overflow(
314+
const ::executorch::runtime::etensor::TensorImpl::SizesType* sizes,
315+
ssize_t dim);
316+
296317
/// Appropriate format specifier for the result of calling
297318
/// size(). Must be used instead of using zd directly to support ATen
298319
/// mode.
@@ -322,6 +343,8 @@ namespace executor {
322343
// TODO(T197294990): Remove these deprecated aliases once all users have moved
323344
// to the new `::executorch` namespaces.
324345
using ::executorch::runtime::etensor::compute_numel;
346+
using ::executorch::runtime::etensor::compute_numel_overflow;
347+
using ::executorch::runtime::etensor::safe_numel;
325348
using ::executorch::runtime::etensor::TensorImpl;
326349
} // namespace executor
327350
} // namespace torch

0 commit comments

Comments
 (0)