Skip to content

Commit 2d0c08c

Browse files
cccclaifacebook-github-bot
authored andcommitted
Add backend option (#11288)
Summary: 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. Differential Revision: D75770142
1 parent 1bc36c7 commit 2d0c08c

File tree

4 files changed

+310
-1
lines changed

4 files changed

+310
-1
lines changed

runtime/backend/backend_option.h

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#include <executorch/runtime/core/error.h>
2+
#include <cstddef>
3+
#include <cstring>
4+
5+
namespace executorch {
6+
namespace ET_RUNTIME_NAMESPACE {
7+
8+
// Strongly-typed option key template
9+
// Wraps a string key with type information for type-safe option access
10+
template <typename T>
11+
struct OptionKey {
12+
const char* key; // String identifier for the option
13+
constexpr explicit OptionKey(const char* k) : key(k) {}
14+
};
15+
16+
// Supported option data types
17+
enum class OptionType { BOOL, INT, STRING };
18+
19+
// Union-like container for option values (only one member is valid per option)
20+
struct OptionValue {
21+
bool bool_value; // Storage for boolean values
22+
int int_value; // Storage for integer values
23+
const char* string_value; // Storage for string values
24+
};
25+
26+
// Represents a single backend configuration option
27+
struct BackendOption {
28+
const char* key; // Name of the option
29+
OptionType type; // Data type of the option
30+
OptionValue value; // Current value of the option
31+
};
32+
33+
// Fixed-capacity container for backend options with type-safe access
34+
// MaxCapacity: Maximum number of options this container can hold
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+
43+
/// Sets or updates a boolean option
44+
/// @param key: Typed option key
45+
/// @param value: Boolean value to set
46+
void set_option(OptionKey<bool> key, bool value) {
47+
set_option_internal(key.key, OptionType::BOOL, {.bool_value = value});
48+
}
49+
50+
/// Sets or updates an integer option
51+
/// @param key: Typed option key
52+
/// @param value: Integer value to set
53+
void set_option(OptionKey<int> key, int value) {
54+
set_option_internal(key.key, OptionType::INT, {.int_value = value});
55+
}
56+
57+
/// Sets or updates a string option
58+
/// @param key: Typed option key
59+
/// @param value: Null-terminated string value to set
60+
void set_option(OptionKey<const char*> key, const char* value) {
61+
set_option_internal(key.key, OptionType::STRING, {.string_value = value});
62+
}
63+
64+
// Type-safe getters ---------------------------------------------------
65+
66+
/// Retrieves a boolean option value
67+
/// @param key: Typed option key
68+
/// @param out_value: Reference to store retrieved value
69+
/// @return: Error code (Ok on success)
70+
executorch::runtime::Error get_option(OptionKey<bool> key, bool& out_value)
71+
const {
72+
OptionValue val{};
73+
executorch::runtime::Error err =
74+
get_option_internal(key.key, OptionType::BOOL, val);
75+
if (err == executorch::runtime::Error::Ok){
76+
out_value = val.bool_value;
77+
}
78+
return err;
79+
}
80+
81+
/// Retrieves an integer option value
82+
/// @param key: Typed option key
83+
/// @param out_value: Reference to store retrieved value
84+
/// @return: Error code (Ok on success)
85+
executorch::runtime::Error get_option(OptionKey<int> key, int& out_value)
86+
const {
87+
OptionValue val{};
88+
executorch::runtime::Error err =
89+
get_option_internal(key.key, OptionType::INT, val);
90+
if (err == executorch::runtime::Error::Ok){
91+
out_value = val.int_value;
92+
}
93+
return err;
94+
}
95+
96+
/// Retrieves a string option value
97+
/// @param key: Typed option key
98+
/// @param out_value: Reference to store retrieved pointer
99+
/// @return: Error code (Ok on success)
100+
executorch::runtime::Error get_option(
101+
OptionKey<const char*> key,
102+
const char*& out_value) const {
103+
OptionValue val{};
104+
executorch::runtime::Error err =
105+
get_option_internal(key.key, OptionType::STRING, val);
106+
if (err == executorch::runtime::Error::Ok){
107+
out_value = val.string_value;
108+
}
109+
return err;
110+
}
111+
112+
private:
113+
BackendOption options[MaxCapacity]{}; // Storage for options
114+
size_t size; // Current number of options
115+
116+
// Internal helper to set/update an option
117+
void
118+
set_option_internal(const char* key, OptionType type, OptionValue value) {
119+
// Update existing key if found
120+
for (size_t i = 0; i < size; ++i) {
121+
if (strcmp(options[i].key, key) == 0) {
122+
options[i].type = type;
123+
options[i].value = value;
124+
return;
125+
}
126+
}
127+
// Add new option if capacity allows
128+
if (size < MaxCapacity) {
129+
options[size++] = {key, type, value};
130+
}
131+
}
132+
133+
// Internal helper to get an option value with type checking
134+
executorch::runtime::Error get_option_internal(
135+
const char* key,
136+
OptionType expected_type,
137+
OptionValue& out) const {
138+
for (size_t i = 0; i < size; ++i) {
139+
if (strcmp(options[i].key, key) == 0) {
140+
// Verify type matches expectation
141+
if (options[i].type != expected_type) {
142+
return executorch::runtime::Error::InvalidArgument;
143+
}
144+
out = options[i].value;
145+
return executorch::runtime::Error::Ok;
146+
}
147+
}
148+
return executorch::runtime::Error::NotFound; // Key not found
149+
}
150+
};
151+
152+
// Helper functions for creating typed option keys --------------------------
153+
154+
/// Creates a boolean option key
155+
constexpr OptionKey<bool> BoolKey(const char* k) {
156+
return OptionKey<bool>(k);
157+
}
158+
159+
/// Creates an integer option key
160+
constexpr OptionKey<int> IntKey(const char* k) {
161+
return OptionKey<int>(k);
162+
}
163+
164+
/// Creates a string option key
165+
constexpr OptionKey<const char*> StrKey(const char* k) {
166+
return OptionKey<const char*>(k);
167+
}
168+
} // namespace ET_RUNTIME_NAMESPACE
169+
} // 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_option.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_option.h>
10+
#include <executorch/runtime/platform/runtime.h>
11+
12+
#include <gtest/gtest.h>
13+
14+
using namespace ::testing;
15+
using executorch::ET_RUNTIME_NAMESPACE::BackendOptions;
16+
using executorch::ET_RUNTIME_NAMESPACE::BoolKey;
17+
using executorch::ET_RUNTIME_NAMESPACE::IntKey;
18+
using executorch::ET_RUNTIME_NAMESPACE::OptionKey;
19+
using executorch::ET_RUNTIME_NAMESPACE::StrKey;
20+
using executorch::runtime::Error;
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 size = 0;
63+
EXPECT_EQ(options.get_option(IntKey("num_threads"), size), Error::Ok);
64+
EXPECT_EQ(size, 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"), 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_option_test",
11+
srcs = ["backend_option_test.cpp"],
12+
deps = [
13+
"//executorch/runtime/core:core",
14+
"//executorch/runtime/backend:interface",
15+
],
16+
)

0 commit comments

Comments
 (0)