Skip to content

Commit 8f1ded2

Browse files
pytorchbotcccclai
andauthored
[1/N] Add BackendOptions class (#11874)
This PR was created by the merge bot to help merge the original PR into the main branch. ghstack PR number: #11389 by @cccclai ^ Please use this as the source of truth for the PR details, comments, and reviews ghstack PR base: https://github.com/pytorch/executorch/tree/gh/cccclai/21/base ghstack PR head: https://github.com/pytorch/executorch/tree/gh/cccclai/21/head Merge bot PR base: https://github.com/pytorch/executorch/tree/main Merge bot PR head: https://github.com/pytorch/executorch/tree/gh/cccclai/21/orig @diff-train-skip-merge Co-authored-by: Chen Lai <[email protected]>
1 parent 6f44a79 commit 8f1ded2

File tree

4 files changed

+384
-1
lines changed

4 files changed

+384
-1
lines changed

runtime/backend/options.h

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#pragma once
10+
#include <executorch/runtime/core/error.h>
11+
#include <executorch/runtime/core/span.h>
12+
#include <array>
13+
#include <cstddef>
14+
#include <cstring>
15+
#include <variant>
16+
17+
namespace executorch {
18+
namespace runtime {
19+
20+
static constexpr size_t kMaxOptionKeyLength = 64;
21+
static constexpr size_t kMaxOptionValueLength = 256;
22+
23+
// All string keys and values must have static storage duration (string
24+
// literals, static const char arrays, or global constants). The BackendOptions
25+
// class does NOT take ownership of strings.
26+
using OptionValue =
27+
std::variant<bool, int, std::array<char, kMaxOptionValueLength>>;
28+
29+
struct BackendOption {
30+
// key is the name of the backend option, like num_threads, enable_profiling,
31+
// etc
32+
char key[kMaxOptionKeyLength]{};
33+
// value is the value of the backend option, like 4, true, etc
34+
OptionValue value;
35+
};
36+
37+
/**
38+
* A template class for storing and managing backend-specific configuration
39+
* options.
40+
*
41+
* This class provides a type-safe way to store key-value pairs for backend
42+
* configuration, with compile-time capacity limits and runtime type checking.
43+
* It supports bool, int, and const char* value types.
44+
*
45+
* @tparam MaxCapacity The maximum number of options that can be stored
46+
*/
47+
template <size_t MaxCapacity>
48+
class BackendOptions {
49+
public:
50+
/**
51+
* Copy constructor
52+
*/
53+
BackendOptions(const BackendOptions& other) : size_(other.size_) {
54+
for (size_t i = 0; i < size_; ++i) {
55+
options_[i] = other.options_[i];
56+
}
57+
}
58+
59+
/**
60+
* Copy assignment operator
61+
*/
62+
BackendOptions& operator=(const BackendOptions& other) {
63+
if (this != &other) {
64+
size_ = other.size_;
65+
for (size_t i = 0; i < size_; ++i) {
66+
options_[i] = other.options_[i];
67+
}
68+
}
69+
return *this;
70+
}
71+
72+
/**
73+
* Default constructor - initializes with zero options.
74+
*/
75+
BackendOptions() : size_(0) {}
76+
77+
/**
78+
* Returns a mutable view of all stored options as a Span.
79+
*
80+
* @return A mutable Span containing all BackendOption entries
81+
*/
82+
executorch::runtime::Span<BackendOption> view() {
83+
return executorch::runtime::Span<BackendOption>(options_, size_);
84+
}
85+
86+
/**
87+
* Sets a boolean option value for the given key.
88+
* If the key already exists, updates its value. Otherwise, adds a new option.
89+
*
90+
* @tparam N The length of the key string (automatically deduced)
91+
* @param key The option key (must be a string literal or array)
92+
* @param value The boolean value to set
93+
* @return Error::Ok on success, Error::InvalidArgument if storage is full
94+
*/
95+
template <size_t N>
96+
Error set_option(const char (&key)[N], bool value) noexcept {
97+
static_assert(N <= kMaxOptionKeyLength, "Option key is too long");
98+
return set_option_impl(key, value);
99+
}
100+
101+
/**
102+
* Sets an integer option value for the given key.
103+
* If the key already exists, updates its value. Otherwise, adds a new option.
104+
*
105+
* @tparam N The length of the key string (automatically deduced)
106+
* @param key The option key (must be a string literal or array)
107+
* @param value The integer value to set
108+
* @return Error::Ok on success, Error::InvalidArgument if storage is full
109+
*/
110+
template <size_t N>
111+
Error set_option(const char (&key)[N], int value) noexcept {
112+
static_assert(N <= kMaxOptionKeyLength, "Option key is too long");
113+
return set_option_impl(key, value);
114+
}
115+
116+
/**
117+
* Sets a string option value for the given key.
118+
* If the key already exists, updates its value. Otherwise, adds a new option.
119+
*
120+
* Note: The string value must have static storage duration. This class does
121+
* NOT take ownership of the string - it only stores the pointer.
122+
*
123+
* @tparam N The length of the key string (automatically deduced)
124+
* @param key The option key (must be a string literal or array)
125+
* @param value The string value to set (must have static storage duration)
126+
* @return Error::Ok on success, Error::InvalidArgument if storage is full
127+
*/
128+
template <size_t N>
129+
Error set_option(const char (&key)[N], const char* value) noexcept {
130+
static_assert(N <= kMaxOptionKeyLength, "Option key is too long");
131+
// Create a fixed-size array and copy the string
132+
std::array<char, kMaxOptionValueLength> arr{};
133+
strncpy(arr.data(), value, kMaxOptionValueLength - 1);
134+
arr[kMaxOptionValueLength - 1] = '\0'; // Ensure null termination
135+
return set_option_impl(key, arr);
136+
}
137+
/**
138+
* Retrieves an option value by key and type.
139+
*
140+
* @tparam T The expected type of the option value (bool, int, or const char*)
141+
* @tparam KeyLen The length of the key string (automatically deduced)
142+
* @param key The option key to look up
143+
* @param out Reference to store the retrieved value
144+
* @return Error::Ok if found and type matches, Error::NotFound if key doesn't
145+
* exist, Error::InvalidArgument if type doesn't match
146+
*/
147+
template <typename T, size_t KeyLen>
148+
Error get_option(const char (&key)[KeyLen], T& out) const {
149+
static_assert(KeyLen <= kMaxOptionKeyLength, "Option key is too long");
150+
for (size_t i = 0; i < size_; ++i) {
151+
if (std::strcmp(options_[i].key, key) == 0) {
152+
// Special handling for string (convert array to const char*)
153+
if constexpr (std::is_same_v<T, const char*>) {
154+
if (auto* arr = std::get_if<std::array<char, kMaxOptionValueLength>>(
155+
&options_[i].value)) {
156+
out = arr->data(); // Return pointer to stored array
157+
return Error::Ok;
158+
}
159+
}
160+
// Default handling for bool/int
161+
else if (auto* val = std::get_if<T>(&options_[i].value)) {
162+
out = *val;
163+
return Error::Ok;
164+
}
165+
return Error::InvalidArgument;
166+
}
167+
}
168+
return Error::NotFound;
169+
}
170+
171+
private:
172+
BackendOption options_[MaxCapacity]{}; // Storage for backend options
173+
size_t size_; // Current number of options
174+
175+
/**
176+
* Internal implementation for setting option values.
177+
* Handles both updating existing options and adding new ones.
178+
*
179+
* @tparam T The type of the value (bool, int, or const char*)
180+
* @param key The option key
181+
* @param value The value to set
182+
* @return Error::Ok on success, Error::InvalidArgument if storage is full
183+
*/
184+
template <typename T>
185+
Error set_option_impl(const char* key, T value) {
186+
// Update existing if found
187+
for (size_t i = 0; i < size_; ++i) {
188+
if (strcmp(options_[i].key, key) == 0) {
189+
options_[i].value = value;
190+
return Error::Ok;
191+
}
192+
}
193+
if (size_ < MaxCapacity) {
194+
BackendOption new_option;
195+
const size_t key_len = std::strlen(key);
196+
const size_t copy_len = std::min(key_len, kMaxOptionKeyLength - 1);
197+
std::memcpy(new_option.key, key, copy_len);
198+
new_option.key[copy_len] = '\0';
199+
new_option.value = value; // Restored value assignment
200+
options_[size_++] = new_option; // Store option and increment size
201+
return Error::Ok;
202+
}
203+
return Error::InvalidArgument;
204+
}
205+
};
206+
207+
} // namespace runtime
208+
} // namespace executorch

runtime/backend/targets.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def define_common_targets():
1717
exported_headers = [
1818
"backend_execution_context.h",
1919
"backend_init_context.h",
20+
"options.h",
2021
"interface.h",
2122
],
2223
preprocessor_flags = ["-DUSE_ATEN_LIB"] if aten_mode else [],
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include <executorch/runtime/backend/options.h>
10+
#include <executorch/runtime/platform/runtime.h>
11+
#include <executorch/test/utils/DeathTest.h>
12+
13+
#include <gtest/gtest.h>
14+
15+
using namespace ::testing;
16+
using executorch::runtime::BackendOptions;
17+
using executorch::runtime::Error;
18+
19+
class BackendOptionsTest : public ::testing::Test {
20+
protected:
21+
void SetUp() override {
22+
// Since these tests cause ET_LOG to be called, the PAL must be initialized
23+
// first.
24+
executorch::runtime::runtime_init();
25+
}
26+
BackendOptions<5> options; // Capacity of 5 for testing limits
27+
};
28+
29+
// Test basic string functionality
30+
TEST_F(BackendOptionsTest, HandlesStringOptions) {
31+
// Set and retrieve valid string
32+
options.set_option("backend_type", "GPU");
33+
const char* result = nullptr;
34+
EXPECT_EQ(options.get_option("backend_type", result), Error::Ok);
35+
EXPECT_STREQ(result, "GPU");
36+
37+
// Update existing key
38+
options.set_option("backend_type", "CPU");
39+
EXPECT_EQ(options.get_option("backend_type", result), Error::Ok);
40+
EXPECT_STREQ(result, "CPU");
41+
}
42+
43+
// Test boolean options
44+
TEST_F(BackendOptionsTest, HandlesBoolOptions) {
45+
options.set_option("debug", true);
46+
bool debug = false;
47+
EXPECT_EQ(options.get_option("debug", debug), Error::Ok);
48+
EXPECT_TRUE(debug);
49+
50+
// Test false value
51+
options.set_option("verbose", false);
52+
EXPECT_EQ(options.get_option("verbose", debug), Error::Ok);
53+
EXPECT_FALSE(debug);
54+
}
55+
56+
// Test integer options
57+
TEST_F(BackendOptionsTest, HandlesIntOptions) {
58+
options.set_option("num_threads", 256);
59+
int num_threads = 0;
60+
EXPECT_EQ(options.get_option("num_threads", num_threads), Error::Ok);
61+
EXPECT_EQ(num_threads, 256);
62+
}
63+
64+
// Test error conditions
65+
TEST_F(BackendOptionsTest, HandlesErrors) {
66+
// Non-existent key
67+
bool dummy_bool;
68+
EXPECT_EQ(options.get_option("missing", dummy_bool), Error::NotFound);
69+
70+
// Type mismatch
71+
options.set_option("threshold", 100);
72+
const char* dummy_str = nullptr;
73+
EXPECT_EQ(options.get_option("threshold", dummy_str), Error::InvalidArgument);
74+
75+
// Null value handling, should expect failure
76+
ET_EXPECT_DEATH(
77+
options.set_option("nullable", static_cast<const char*>(nullptr)), "");
78+
}
79+
80+
// Test type-specific keys
81+
TEST_F(BackendOptionsTest, EnforcesKeyTypes) {
82+
// Same key name - later set operations overwrite earlier ones
83+
options.set_option("flag", true);
84+
options.set_option("flag", 123); // Overwrites the boolean entry
85+
86+
bool bval;
87+
int ival;
88+
89+
// Boolean get should fail - type was overwritten to INT
90+
EXPECT_EQ(options.get_option("flag", bval), Error::InvalidArgument);
91+
92+
// Integer get should succeed with correct value
93+
EXPECT_EQ(options.get_option("flag", ival), Error::Ok);
94+
EXPECT_EQ(ival, 123);
95+
}
96+
97+
TEST_F(BackendOptionsTest, MutableOption) {
98+
int ival;
99+
options.set_option("flag", 0);
100+
// Integer get should succeed with correct value
101+
EXPECT_EQ(options.get_option("flag", ival), Error::Ok);
102+
EXPECT_EQ(ival, 0);
103+
104+
options.view()[0].value = 123; // Overwrites the entry
105+
106+
// Integer get should succeed with the updated value
107+
EXPECT_EQ(options.get_option("flag", ival), Error::Ok);
108+
EXPECT_EQ(ival, 123);
109+
}
110+
111+
// Test copy constructor
112+
TEST_F(BackendOptionsTest, CopyConstructor) {
113+
// Set up original option
114+
options.set_option("debug", true);
115+
116+
// Create copy using copy constructor
117+
BackendOptions<5> copied_options(options);
118+
119+
// Verify option was copied correctly
120+
bool debug_val;
121+
EXPECT_EQ(copied_options.get_option("debug", debug_val), Error::Ok);
122+
EXPECT_TRUE(debug_val);
123+
124+
// Verify independence - modifying original doesn't affect copy
125+
options.set_option("debug", false);
126+
EXPECT_EQ(copied_options.get_option("debug", debug_val), Error::Ok);
127+
EXPECT_TRUE(debug_val); // Should still be true in copy
128+
129+
// Verify independence - modifying copy doesn't affect original
130+
copied_options.set_option("debug", false);
131+
EXPECT_EQ(options.get_option("debug", debug_val), Error::Ok);
132+
EXPECT_FALSE(debug_val); // Should be false in original
133+
}
134+
135+
// Test copy assignment operator
136+
TEST_F(BackendOptionsTest, CopyAssignmentOperator) {
137+
// Set up original option
138+
options.set_option("enable_profiling", true);
139+
140+
// Create another options object and assign to it
141+
BackendOptions<5> assigned_options;
142+
assigned_options.set_option("temp_option", false); // Add something first
143+
144+
assigned_options = options;
145+
146+
// Verify option was copied correctly
147+
bool profiling_val;
148+
EXPECT_EQ(
149+
assigned_options.get_option("enable_profiling", profiling_val),
150+
Error::Ok);
151+
EXPECT_TRUE(profiling_val);
152+
153+
// Verify the temp_option was overwritten (not present in assigned object)
154+
bool temp_val;
155+
EXPECT_EQ(
156+
assigned_options.get_option("temp_option", temp_val), Error::NotFound);
157+
158+
// Verify independence - modifying original doesn't affect assigned copy
159+
options.set_option("enable_profiling", false);
160+
EXPECT_EQ(
161+
assigned_options.get_option("enable_profiling", profiling_val),
162+
Error::Ok);
163+
EXPECT_TRUE(profiling_val); // Should still be true in assigned copy
164+
}

0 commit comments

Comments
 (0)