-
Notifications
You must be signed in to change notification settings - Fork 584
[1/N] Add backend option #11288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[1/N] Add backend option #11288
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
#include <executorch/runtime/core/error.h> | ||
#include <cstddef> | ||
#include <cstring> | ||
|
||
namespace executorch { | ||
namespace ET_RUNTIME_NAMESPACE { | ||
|
||
// Strongly-typed option key template | ||
// Wraps a string key with type information for type-safe option access | ||
template <typename T> | ||
struct OptionKey { | ||
const char* key; // String identifier for the option | ||
constexpr explicit OptionKey(const char* k) : key(k) {} | ||
}; | ||
|
||
// Supported option data types | ||
enum class OptionType { BOOL, INT, STRING }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just use std::varaint |
||
|
||
// Union-like container for option values (only one member is valid per option) | ||
struct OptionValue { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use a union? Or structure with a template? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or std::variant that can hold only bool, int and char *? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. union can be a good option, let me try |
||
bool bool_value; // Storage for boolean values | ||
int int_value; // Storage for integer values | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. int64_t |
||
const char* string_value; // Storage for string values | ||
}; | ||
|
||
// Represents a single backend configuration option | ||
struct BackendOption { | ||
const char* key; // Name of the option | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can also do
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will introduce std library I assume? |
||
OptionType type; // Data type of the option | ||
OptionValue value; // Current value of the option | ||
}; | ||
|
||
// Fixed-capacity container for backend options with type-safe access | ||
// MaxCapacity: Maximum number of options this container can hold | ||
template <size_t MaxCapacity> | ||
class BackendOptions { | ||
public: | ||
// Initialize with zero options | ||
BackendOptions() : size(0) {} | ||
|
||
// Type-safe setters --------------------------------------------------- | ||
|
||
/// Sets or updates a boolean option | ||
/// @param key: Typed option key | ||
/// @param value: Boolean value to set | ||
void set_option(OptionKey<bool> key, bool value) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just make this If tomorrow you expand BackendOption to have other types like float or int8 you just have to update allowed types in BackendOpton. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and assert to on allowable types There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
set_option_internal(key.key, OptionType::BOOL, {.bool_value = value}); | ||
} | ||
|
||
/// Sets or updates an integer option | ||
/// @param key: Typed option key | ||
/// @param value: Integer value to set | ||
void set_option(OptionKey<int> key, int value) { | ||
set_option_internal(key.key, OptionType::INT, {.int_value = value}); | ||
} | ||
|
||
/// Sets or updates a string option | ||
/// @param key: Typed option key | ||
/// @param value: Null-terminated string value to set | ||
void set_option(OptionKey<const char*> key, const char* value) { | ||
set_option_internal(key.key, OptionType::STRING, {.string_value = value}); | ||
} | ||
|
||
// Type-safe getters --------------------------------------------------- | ||
|
||
/// Retrieves a boolean option value | ||
/// @param key: Typed option key | ||
/// @param out_value: Reference to store retrieved value | ||
/// @return: Error code (Ok on success) | ||
executorch::runtime::Error get_option(OptionKey<bool> key, bool& out_value) | ||
const { | ||
OptionValue val{}; | ||
executorch::runtime::Error err = | ||
get_option_internal(key.key, OptionType::BOOL, val); | ||
if (err == executorch::runtime::Error::Ok) { | ||
out_value = val.bool_value; | ||
} | ||
return err; | ||
} | ||
|
||
/// Retrieves an integer option value | ||
/// @param key: Typed option key | ||
/// @param out_value: Reference to store retrieved value | ||
/// @return: Error code (Ok on success) | ||
executorch::runtime::Error get_option(OptionKey<int> key, int& out_value) | ||
const { | ||
OptionValue val{}; | ||
executorch::runtime::Error err = | ||
get_option_internal(key.key, OptionType::INT, val); | ||
if (err == executorch::runtime::Error::Ok) { | ||
out_value = val.int_value; | ||
} | ||
return err; | ||
} | ||
|
||
/// Retrieves a string option value | ||
/// @param key: Typed option key | ||
/// @param out_value: Reference to store retrieved pointer | ||
/// @return: Error code (Ok on success) | ||
executorch::runtime::Error get_option( | ||
OptionKey<const char*> key, | ||
const char*& out_value) const { | ||
OptionValue val{}; | ||
executorch::runtime::Error err = | ||
get_option_internal(key.key, OptionType::STRING, val); | ||
if (err == executorch::runtime::Error::Ok) { | ||
out_value = val.string_value; | ||
} | ||
return err; | ||
} | ||
|
||
private: | ||
BackendOption options[MaxCapacity]{}; // Storage for options | ||
size_t size; // Current number of options | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. size_ for private variables. |
||
|
||
// Internal helper to set/update an option | ||
void | ||
set_option_internal(const char* key, OptionType type, OptionValue value) { | ||
// Update existing key if found | ||
for (size_t i = 0; i < size; ++i) { | ||
if (strcmp(options[i].key, key) == 0) { | ||
options[i].type = type; | ||
options[i].value = value; | ||
return; | ||
} | ||
} | ||
// Add new option if capacity allows | ||
if (size < MaxCapacity) { | ||
options[size++] = {key, type, value}; | ||
} | ||
} | ||
|
||
// Internal helper to get an option value with type checking | ||
executorch::runtime::Error get_option_internal( | ||
const char* key, | ||
OptionType expected_type, | ||
OptionValue& out) const { | ||
Comment on lines
+134
to
+137
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it necessary that type is specified? If we use std::variant we can just make this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. std::variant is part of the std library, and this code needs to stay in core |
||
for (size_t i = 0; i < size; ++i) { | ||
if (strcmp(options[i].key, key) == 0) { | ||
// Verify type matches expectation | ||
if (options[i].type != expected_type) { | ||
return executorch::runtime::Error::InvalidArgument; | ||
} | ||
out = options[i].value; | ||
return executorch::runtime::Error::Ok; | ||
} | ||
} | ||
return executorch::runtime::Error::NotFound; // Key not found | ||
} | ||
}; | ||
|
||
// Helper functions for creating typed option keys -------------------------- | ||
|
||
/// Creates a boolean option key | ||
constexpr OptionKey<bool> BoolKey(const char* k) { | ||
return OptionKey<bool>(k); | ||
} | ||
|
||
/// Creates an integer option key | ||
constexpr OptionKey<int> IntKey(const char* k) { | ||
return OptionKey<int>(k); | ||
} | ||
|
||
/// Creates a string option key | ||
constexpr OptionKey<const char*> StrKey(const char* k) { | ||
return OptionKey<const char*>(k); | ||
} | ||
} // namespace ET_RUNTIME_NAMESPACE | ||
} // namespace executorch |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
#include <executorch/runtime/backend/backend_option.h> | ||
#include <executorch/runtime/platform/runtime.h> | ||
|
||
#include <gtest/gtest.h> | ||
|
||
using namespace ::testing; | ||
using executorch::ET_RUNTIME_NAMESPACE::BackendOptions; | ||
using executorch::ET_RUNTIME_NAMESPACE::BoolKey; | ||
using executorch::ET_RUNTIME_NAMESPACE::IntKey; | ||
using executorch::ET_RUNTIME_NAMESPACE::OptionKey; | ||
using executorch::ET_RUNTIME_NAMESPACE::StrKey; | ||
using executorch::runtime::Error; | ||
|
||
class BackendOptionsTest : public ::testing::Test { | ||
protected: | ||
void SetUp() override { | ||
// Since these tests cause ET_LOG to be called, the PAL must be initialized | ||
// first. | ||
executorch::runtime::runtime_init(); | ||
} | ||
BackendOptions<5> options; // Capacity of 5 for testing limits | ||
}; | ||
|
||
// Test basic string functionality | ||
TEST_F(BackendOptionsTest, HandlesStringOptions) { | ||
// Set and retrieve valid string | ||
options.set_option(StrKey("backend_type"), "GPU"); | ||
const char* result = nullptr; | ||
EXPECT_EQ(options.get_option(StrKey("backend_type"), result), Error::Ok); | ||
EXPECT_STREQ(result, "GPU"); | ||
|
||
// Update existing key | ||
options.set_option(StrKey("backend_type"), "CPU"); | ||
EXPECT_EQ(options.get_option(StrKey("backend_type"), result), Error::Ok); | ||
EXPECT_STREQ(result, "CPU"); | ||
} | ||
|
||
// Test boolean options | ||
TEST_F(BackendOptionsTest, HandlesBoolOptions) { | ||
options.set_option(BoolKey("debug"), true); | ||
bool debug = false; | ||
EXPECT_EQ(options.get_option(BoolKey("debug"), debug), Error::Ok); | ||
EXPECT_TRUE(debug); | ||
|
||
// Test false value | ||
options.set_option(BoolKey("verbose"), false); | ||
EXPECT_EQ(options.get_option(BoolKey("verbose"), debug), Error::Ok); | ||
EXPECT_FALSE(debug); | ||
} | ||
|
||
// Test integer options | ||
TEST_F(BackendOptionsTest, HandlesIntOptions) { | ||
options.set_option(IntKey("num_threads"), 256); | ||
int size = 0; | ||
EXPECT_EQ(options.get_option(IntKey("num_threads"), size), Error::Ok); | ||
EXPECT_EQ(size, 256); | ||
} | ||
|
||
// Test error conditions | ||
TEST_F(BackendOptionsTest, HandlesErrors) { | ||
// Non-existent key | ||
bool dummy_bool; | ||
EXPECT_EQ( | ||
options.get_option(BoolKey("missing"), dummy_bool), Error::NotFound); | ||
|
||
// Type mismatch | ||
options.set_option(IntKey("threshold"), 100); | ||
const char* dummy_str = nullptr; | ||
EXPECT_EQ( | ||
options.get_option(StrKey("threshold"), dummy_str), | ||
Error::InvalidArgument); | ||
|
||
// Null value handling | ||
options.set_option(StrKey("nullable"), nullptr); | ||
EXPECT_EQ(options.get_option(StrKey("nullable"), dummy_str), Error::Ok); | ||
EXPECT_EQ(dummy_str, nullptr); | ||
} | ||
|
||
// Test capacity limits | ||
TEST_F(BackendOptionsTest, HandlesCapacity) { | ||
// Use persistent storage for keys | ||
std::vector<std::string> keys = {"key0", "key1", "key2", "key3", "key4"}; | ||
|
||
// Fill to capacity with persistent keys | ||
for (int i = 0; i < 5; i++) { | ||
options.set_option(IntKey(keys[i].c_str()), i); | ||
} | ||
|
||
// Verify all exist | ||
int value; | ||
for (int i = 0; i < 5; i++) { | ||
EXPECT_EQ(options.get_option(IntKey(keys[i].c_str()), value), Error::Ok); | ||
EXPECT_EQ(value, i); | ||
} | ||
|
||
// Add beyond capacity - should fail | ||
const char* overflow_key = "overflow"; | ||
options.set_option(IntKey(overflow_key), 99); | ||
EXPECT_EQ(options.get_option(IntKey(overflow_key), value), Error::NotFound); | ||
|
||
// Update existing within capacity | ||
options.set_option(IntKey(keys[2].c_str()), 222); | ||
EXPECT_EQ(options.get_option(IntKey(keys[2].c_str()), value), Error::Ok); | ||
EXPECT_EQ(value, 222); | ||
} | ||
|
||
// Test type-specific keys | ||
TEST_F(BackendOptionsTest, EnforcesKeyTypes) { | ||
// Same key name - later set operations overwrite earlier ones | ||
options.set_option(BoolKey("flag"), true); | ||
options.set_option(IntKey("flag"), 123); // Overwrites the boolean entry | ||
|
||
bool bval; | ||
int ival; | ||
|
||
// Boolean get should fail - type was overwritten to INT | ||
EXPECT_EQ(options.get_option(BoolKey("flag"), bval), Error::InvalidArgument); | ||
|
||
// Integer get should succeed with correct value | ||
EXPECT_EQ(options.get_option(IntKey("flag"), ival), Error::Ok); | ||
EXPECT_EQ(ival, 123); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,16 @@ | ||
load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime") | ||
|
||
def define_common_targets(): | ||
"""Defines targets that should be shared between fbcode and xplat. | ||
|
||
The directory containing this targets.bzl file should also contain both | ||
TARGETS and BUCK files that call this function. | ||
""" | ||
pass | ||
runtime.cxx_test( | ||
name = "backend_option_test", | ||
srcs = ["backend_option_test.cpp"], | ||
deps = [ | ||
"//executorch/runtime/core:core", | ||
"//executorch/runtime/backend:interface", | ||
], | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this capitalized?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's used across the whole ExecuTorch stack, but I find now it's not needed so switch to
executorch::runtime
namespace