Skip to content

Commit c0b745f

Browse files
committed
Add function to stringify tensor shapes
First step toward fixing #7902 ghstack-source-id: 2d87ce2 ghstack-comment-id: 2613189307 Pull Request resolved: #7943
1 parent c0676fe commit c0b745f

File tree

4 files changed

+163
-1
lines changed

4 files changed

+163
-1
lines changed

runtime/core/exec_aten/util/targets.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def define_common_targets():
5050

5151
runtime.cxx_library(
5252
name = "tensor_util" + aten_suffix,
53-
srcs = ["tensor_util_aten.cpp"] if aten_mode else ["tensor_util_portable.cpp"],
53+
srcs = ["tensor_util.cpp"] + (["tensor_util_aten.cpp"] if aten_mode else ["tensor_util_portable.cpp"]),
5454
exported_headers = [
5555
"tensor_util.h",
5656
],
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include "tensor_util.h"
2+
3+
/*
4+
* Copyright (c) Meta Platforms, Inc. and affiliates.
5+
* All rights reserved.
6+
*
7+
* This source code is licensed under the BSD-style license found in the
8+
* LICENSE file in the root directory of this source tree.
9+
*/
10+
11+
#include <executorch/runtime/core/exec_aten/util/tensor_util.h>
12+
13+
#include <executorch/runtime/platform/assert.h>
14+
15+
namespace executorch::runtime {
16+
/**
17+
* Shared implementation for tensor_util.h, may only contain code that
18+
* works whether or not ATen mode is active.
19+
*/
20+
std::array<char, kTensorShapeStringSizeLimit> tensor_shape_to_c_string(
21+
executorch::runtime::Span<const executorch::aten::SizesType> shape) {
22+
std::array<char, kTensorShapeStringSizeLimit> out;
23+
char* p = out.data();
24+
if ET_UNLIKELY (shape.size() > kTensorDimensionLimit) {
25+
static constexpr char kLimitExceededError[] =
26+
"(ERR: tensor ndim exceeds limit, can't happen)";
27+
static_assert(sizeof(kLimitExceededError) <= kTensorShapeStringSizeLimit);
28+
std::memcpy(p, kLimitExceededError, sizeof(kLimitExceededError));
29+
return out;
30+
}
31+
*p++ = '(';
32+
for (const auto elem : shape) {
33+
if (elem < 0 || elem > internal::kMaximumPrintableTensorShapeElement) {
34+
static_assert(
35+
internal::kMaximumPrintableTensorShapeElement > 99999,
36+
"must have room for error string!");
37+
strcpy(p, "ERR, ");
38+
p += strlen("ERR, ");
39+
} else {
40+
// snprintf returns characters *except* the NUL terminator, which is what
41+
// we want.
42+
p += snprintf(
43+
p,
44+
kTensorShapeStringSizeLimit - (p - out.data()),
45+
"%" PRIu32 ", ",
46+
static_cast<uint32_t>(elem));
47+
}
48+
}
49+
*(p - 2) = ')';
50+
*(p - 1) = '\0';
51+
return out;
52+
}
53+
54+
} // namespace executorch::runtime

runtime/core/exec_aten/util/tensor_util.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <executorch/runtime/core/exec_aten/exec_aten.h>
2121
#include <executorch/runtime/core/exec_aten/util/dim_order_util.h>
2222
#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
23+
#include <executorch/runtime/core/span.h>
2324
#include <executorch/runtime/platform/assert.h>
2425
#include <executorch/runtime/platform/compiler.h>
2526

@@ -1120,6 +1121,36 @@ bool extract_scalar_tensor(executorch::aten::Tensor tensor, BOOL_T* out_val) {
11201121
return true;
11211122
}
11221123

1124+
/**
1125+
* Maximum size of a string returned by tensor_shape_to_c_string, for
1126+
* stack allocation.
1127+
*/
1128+
constexpr size_t kTensorShapeStringSizeLimit = 1 + /* opening parenthesis */
1129+
10 * kTensorDimensionLimit + /* maximum digits we will print; update
1130+
* kMaximumPrintableTensorShapeElement
1131+
* if changing */
1132+
2 * kTensorDimensionLimit + /* comma and space after each item,
1133+
* overwritten with closing paren and
1134+
* NUL terminator for last element */
1135+
1; /* padding for temporary NUL terminator for simplicity of implementation
1136+
*/
1137+
1138+
namespace internal {
1139+
constexpr size_t kMaximumPrintableTensorShapeElement =
1140+
std::is_same_v<executorch::aten::SizesType, int32_t>
1141+
? std::numeric_limits<int32_t>::max()
1142+
: std::numeric_limits<uint32_t>::max();
1143+
} // namespace internal
1144+
1145+
/**
1146+
* Convert a shape to a NUL-terminated C string with limited size. If
1147+
* elements of the shape are larger than
1148+
* kMaximumPrintableTensorShapeElement, those elements will be
1149+
* rendered as ERR instead.
1150+
*/
1151+
std::array<char, kTensorShapeStringSizeLimit> tensor_shape_to_c_string(
1152+
executorch::runtime::Span<const executorch::aten::SizesType> shape);
1153+
11231154
/// These APIs should not be used outside of Executor.cpp.
11241155
namespace internal {
11251156
/**

runtime/core/exec_aten/util/test/tensor_util_test.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@
1212
#include <executorch/runtime/platform/runtime.h>
1313
#include <executorch/test/utils/DeathTest.h>
1414
#include <cmath>
15+
#include <cstring>
1516
#include <limits>
1617

1718
using namespace ::testing;
1819
using executorch::aten::ScalarType;
1920
using executorch::aten::Tensor;
2021
using executorch::runtime::extract_scalar_tensor;
22+
using executorch::runtime::kTensorDimensionLimit;
23+
using executorch::runtime::kTensorShapeStringSizeLimit;
24+
using executorch::runtime::Span;
25+
using executorch::runtime::tensor_shape_to_c_string;
26+
using executorch::runtime::internal::kMaximumPrintableTensorShapeElement;
2127
using executorch::runtime::testing::TensorFactory;
2228

2329
class TensorUtilTest : public ::testing::Test {
@@ -605,3 +611,74 @@ TEST_F(TensorUtilTest, SameShapesDifferentDimOrder) {
605611
EXPECT_FALSE(tensors_have_same_dim_order(a, c, b));
606612
EXPECT_FALSE(tensors_have_same_dim_order(c, b, a));
607613
}
614+
615+
TEST_F(TensorUtilTest, TensorShapeToCStringBasic) {
616+
std::array<executorch::aten::SizesType, 3> sizes = {123, 456, 789};
617+
auto str = tensor_shape_to_c_string(
618+
Span<const executorch::aten::SizesType>(sizes.data(), sizes.size()));
619+
EXPECT_STREQ(str.data(), "(123, 456, 789)");
620+
621+
std::array<executorch::aten::SizesType, 1> one_size = {1234567890};
622+
str = tensor_shape_to_c_string(Span<const executorch::aten::SizesType>(
623+
one_size.data(), one_size.size()));
624+
EXPECT_STREQ(str.data(), "(1234567890)");
625+
}
626+
627+
TEST_F(TensorUtilTest, TensorShapeToCStringNegativeItems) {
628+
std::array<executorch::aten::SizesType, 4> sizes = {-1, -3, -2, 4};
629+
auto str = tensor_shape_to_c_string(
630+
Span<const executorch::aten::SizesType>(sizes.data(), sizes.size()));
631+
EXPECT_STREQ(str.data(), "(ERR, ERR, ERR, 4)");
632+
633+
std::array<executorch::aten::SizesType, 1> one_size = {-1234567890};
634+
str = tensor_shape_to_c_string(Span<const executorch::aten::SizesType>(
635+
one_size.data(), one_size.size()));
636+
if constexpr (std::numeric_limits<executorch::aten::SizesType>::is_signed) {
637+
EXPECT_STREQ(str.data(), "(ERR)");
638+
} else {
639+
EXPECT_EQ(str.data(), "(" + std::to_string(one_size[0]) + ")");
640+
}
641+
}
642+
TEST_F(TensorUtilTest, TensorShapeToCStringMaximumElement) {
643+
std::array<executorch::aten::SizesType, 3> sizes = {
644+
123, std::numeric_limits<executorch::aten::SizesType>::max(), 789};
645+
auto str = tensor_shape_to_c_string(
646+
Span<const executorch::aten::SizesType>(sizes.data(), sizes.size()));
647+
std::ostringstream expected;
648+
expected << '(';
649+
for (const auto elem : sizes) {
650+
expected << elem << ", ";
651+
}
652+
auto expected_str = expected.str();
653+
expected_str.pop_back();
654+
expected_str.back() = ')';
655+
EXPECT_EQ(str.data(), expected_str);
656+
}
657+
658+
TEST_F(TensorUtilTest, TensorShapeToCStringMaximumLength) {
659+
std::array<executorch::aten::SizesType, kTensorDimensionLimit> sizes;
660+
std::fill(sizes.begin(), sizes.end(), kMaximumPrintableTensorShapeElement);
661+
662+
auto str = tensor_shape_to_c_string(
663+
Span<const executorch::aten::SizesType>(sizes.data(), sizes.size()));
664+
665+
std::ostringstream expected;
666+
expected << '(' << kMaximumPrintableTensorShapeElement;
667+
for (int ii = 0; ii < kTensorDimensionLimit - 1; ++ii) {
668+
expected << ", " << kMaximumPrintableTensorShapeElement;
669+
}
670+
expected << ')';
671+
auto expected_str = expected.str();
672+
673+
EXPECT_EQ(expected_str, str.data());
674+
}
675+
676+
TEST_F(TensorUtilTest, TensorShapeToCStringExceedsDimensionLimit) {
677+
std::array<executorch::aten::SizesType, kTensorDimensionLimit + 1> sizes;
678+
std::fill(sizes.begin(), sizes.end(), kMaximumPrintableTensorShapeElement);
679+
680+
auto str = tensor_shape_to_c_string(
681+
Span<const executorch::aten::SizesType>(sizes.data(), sizes.size()));
682+
683+
EXPECT_STREQ(str.data(), "(ERR: tensor ndim exceeds limit, can't happen)");
684+
}

0 commit comments

Comments
 (0)