Skip to content

Commit a6e75d5

Browse files
committed
[1/N] Add BackendOptions class
Pull Request resolved: #11389 Introduce backend option as discussed in #10216 Step 1: Introducd Backend Option class In later stage, it will be plugged in with the rest of the stack. BackendOptions is pretty much a list of BackendOption, and backend option is a key value pair. The key is a string, and the value can be 3 different types, including bool, string and int. ghstack-source-id: 290059228 Differential Revision: [D75993712](https://our.internmc.facebook.com/intern/diff/D75993712/)
1 parent f7cc72f commit a6e75d5

File tree

4 files changed

+234
-1
lines changed

4 files changed

+234
-1
lines changed

runtime/backend/backend_options.h

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 <cstddef>
12+
#include <cstring>
13+
#include <variant>
14+
15+
namespace executorch {
16+
namespace runtime {
17+
18+
// Strongly-typed option key template
19+
template <typename T>
20+
struct OptionKey {
21+
const char* key;
22+
constexpr explicit OptionKey(const char* k) : key(k) {}
23+
};
24+
25+
// Union replaced with std::variant
26+
using OptionValue = std::variant<bool, int, const char*>;
27+
28+
struct BackendOption {
29+
const char* key; // key is the name of the backend option, like num_threads,
30+
// enable_profiling, etc
31+
OptionValue
32+
value; // value is the value of the backend option, like 4, true, etc
33+
};
34+
35+
template <size_t MaxCapacity>
36+
class BackendOptions {
37+
public:
38+
// Initialize with zero options
39+
BackendOptions() : size_(0) {}
40+
41+
// Type-safe setters
42+
template <typename T>
43+
void set_option(OptionKey<T> key, T value) {
44+
const char* k = key.key;
45+
// Update existing if found
46+
for (size_t i = 0; i < size_; ++i) {
47+
if (strcmp(options_[i].key, k) == 0) {
48+
options_[i].value = value;
49+
return;
50+
}
51+
}
52+
// Add new option if space available
53+
if (size_ < MaxCapacity) {
54+
options_[size_++] = BackendOption{k, value};
55+
}
56+
}
57+
58+
// Type-safe getters
59+
template <typename T>
60+
Error get_option(OptionKey<T> key, T& out) const {
61+
const char* k = key.key;
62+
for (size_t i = 0; i < size_; ++i) {
63+
if (strcmp(options_[i].key, k) == 0) {
64+
if (auto* val = std::get_if<T>(&options_[i].value)) {
65+
out = *val;
66+
return Error::Ok;
67+
}
68+
return Error::InvalidArgument;
69+
}
70+
}
71+
return Error::NotFound;
72+
}
73+
74+
private:
75+
BackendOption options_[MaxCapacity]{}; // Storage for backend options
76+
size_t size_; // Current number of options
77+
};
78+
79+
// Helper functions for creating typed option keys (unchanged)
80+
constexpr OptionKey<bool> BoolKey(const char* k) {
81+
return OptionKey<bool>(k);
82+
}
83+
84+
constexpr OptionKey<int> IntKey(const char* k) {
85+
return OptionKey<int>(k);
86+
}
87+
88+
constexpr OptionKey<const char*> StrKey(const char* k) {
89+
return OptionKey<const char*>(k);
90+
}
91+
92+
} // namespace runtime
93+
} // 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+
"backend_options.h",
2021
"interface.h",
2122
],
2223
preprocessor_flags = ["-DUSE_ATEN_LIB"] if aten_mode else [],
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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/backend_options.h>
10+
#include <executorch/runtime/platform/runtime.h>
11+
12+
#include <gtest/gtest.h>
13+
14+
using namespace ::testing;
15+
using executorch::runtime::BackendOptions;
16+
using executorch::runtime::BoolKey;
17+
using executorch::runtime::Error;
18+
using executorch::runtime::IntKey;
19+
using executorch::runtime::OptionKey;
20+
using executorch::runtime::StrKey;
21+
22+
class BackendOptionsTest : public ::testing::Test {
23+
protected:
24+
void SetUp() override {
25+
// Since these tests cause ET_LOG to be called, the PAL must be initialized
26+
// first.
27+
executorch::runtime::runtime_init();
28+
}
29+
BackendOptions<5> options; // Capacity of 5 for testing limits
30+
};
31+
32+
// Test basic string functionality
33+
TEST_F(BackendOptionsTest, HandlesStringOptions) {
34+
// Set and retrieve valid string
35+
options.set_option(StrKey("backend_type"), "GPU");
36+
const char* result = nullptr;
37+
EXPECT_EQ(options.get_option(StrKey("backend_type"), result), Error::Ok);
38+
EXPECT_STREQ(result, "GPU");
39+
40+
// Update existing key
41+
options.set_option(StrKey("backend_type"), "CPU");
42+
EXPECT_EQ(options.get_option(StrKey("backend_type"), result), Error::Ok);
43+
EXPECT_STREQ(result, "CPU");
44+
}
45+
46+
// Test boolean options
47+
TEST_F(BackendOptionsTest, HandlesBoolOptions) {
48+
options.set_option(BoolKey("debug"), true);
49+
bool debug = false;
50+
EXPECT_EQ(options.get_option(BoolKey("debug"), debug), Error::Ok);
51+
EXPECT_TRUE(debug);
52+
53+
// Test false value
54+
options.set_option(BoolKey("verbose"), false);
55+
EXPECT_EQ(options.get_option(BoolKey("verbose"), debug), Error::Ok);
56+
EXPECT_FALSE(debug);
57+
}
58+
59+
// Test integer options
60+
TEST_F(BackendOptionsTest, HandlesIntOptions) {
61+
options.set_option(IntKey("num_threads"), 256);
62+
int num_threads = 0;
63+
EXPECT_EQ(options.get_option(IntKey("num_threads"), num_threads), Error::Ok);
64+
EXPECT_EQ(num_threads, 256);
65+
}
66+
67+
// Test error conditions
68+
TEST_F(BackendOptionsTest, HandlesErrors) {
69+
// Non-existent key
70+
bool dummy_bool;
71+
EXPECT_EQ(
72+
options.get_option(BoolKey("missing"), dummy_bool), Error::NotFound);
73+
74+
// Type mismatch
75+
options.set_option(IntKey("threshold"), 100);
76+
const char* dummy_str = nullptr;
77+
EXPECT_EQ(
78+
options.get_option(StrKey("threshold"), dummy_str),
79+
Error::InvalidArgument);
80+
81+
// Null value handling
82+
options.set_option(StrKey("nullable"), static_cast<const char*>(nullptr));
83+
EXPECT_EQ(options.get_option(StrKey("nullable"), dummy_str), Error::Ok);
84+
EXPECT_EQ(dummy_str, nullptr);
85+
}
86+
87+
// Test capacity limits
88+
TEST_F(BackendOptionsTest, HandlesCapacity) {
89+
// Use persistent storage for keys
90+
std::vector<std::string> keys = {"key0", "key1", "key2", "key3", "key4"};
91+
92+
// Fill to capacity with persistent keys
93+
for (int i = 0; i < 5; i++) {
94+
options.set_option(IntKey(keys[i].c_str()), i);
95+
}
96+
97+
// Verify all exist
98+
int value;
99+
for (int i = 0; i < 5; i++) {
100+
EXPECT_EQ(options.get_option(IntKey(keys[i].c_str()), value), Error::Ok);
101+
EXPECT_EQ(value, i);
102+
}
103+
104+
// Add beyond capacity - should fail
105+
const char* overflow_key = "overflow";
106+
options.set_option(IntKey(overflow_key), 99);
107+
EXPECT_EQ(options.get_option(IntKey(overflow_key), value), Error::NotFound);
108+
109+
// Update existing within capacity
110+
options.set_option(IntKey(keys[2].c_str()), 222);
111+
EXPECT_EQ(options.get_option(IntKey(keys[2].c_str()), value), Error::Ok);
112+
EXPECT_EQ(value, 222);
113+
}
114+
115+
// Test type-specific keys
116+
TEST_F(BackendOptionsTest, EnforcesKeyTypes) {
117+
// Same key name - later set operations overwrite earlier ones
118+
options.set_option(BoolKey("flag"), true);
119+
options.set_option(IntKey("flag"), 123); // Overwrites the boolean entry
120+
121+
bool bval;
122+
int ival;
123+
124+
// Boolean get should fail - type was overwritten to INT
125+
EXPECT_EQ(options.get_option(BoolKey("flag"), bval), Error::InvalidArgument);
126+
127+
// Integer get should succeed with correct value
128+
EXPECT_EQ(options.get_option(IntKey("flag"), ival), Error::Ok);
129+
EXPECT_EQ(ival, 123);
130+
}

runtime/backend/test/targets.bzl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1+
load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime")
2+
13
def define_common_targets():
24
"""Defines targets that should be shared between fbcode and xplat.
35
46
The directory containing this targets.bzl file should also contain both
57
TARGETS and BUCK files that call this function.
68
"""
7-
pass
9+
runtime.cxx_test(
10+
name = "backend_options_test",
11+
srcs = ["backend_options_test.cpp"],
12+
deps = [
13+
"//executorch/runtime/core:core",
14+
"//executorch/runtime/backend:interface",
15+
],
16+
)

0 commit comments

Comments
 (0)