Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions runtime/backend/backend_init_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
#include <executorch/runtime/core/result.h>
#include <executorch/runtime/core/span.h>

#include <cstdint>
#include <cstring>
#include <type_traits>

#ifdef __GNUC__
// Disable -Wdeprecated-declarations, as some builds use 'Werror'.
Expand Down Expand Up @@ -97,7 +99,7 @@ class BackendInitContext final {
/**
* Get a runtime spec value by key and type.
*
* @tparam T The expected type (bool, int, or const char*)
* @tparam T The expected type (bool, int, int64_t, or const char*)
* @param key The option key to look up.
* @return Result containing the value if found and type matches,
* Error::NotFound if key doesn't exist,
Expand All @@ -107,8 +109,8 @@ class BackendInitContext final {
Result<T> get_runtime_spec(const char* key) const {
static_assert(
std::is_same_v<T, bool> || std::is_same_v<T, int> ||
std::is_same_v<T, const char*>,
"get_runtime_spec<T> only supports bool, int, and const char*");
std::is_same_v<T, int64_t> || std::is_same_v<T, const char*>,
"get_runtime_spec<T> only supports bool, int, int64_t, and const char*");
Comment thread
shoumikhin marked this conversation as resolved.

for (size_t i = 0; i < runtime_specs_.size(); ++i) {
const auto& opt = runtime_specs_[i];
Expand Down
79 changes: 68 additions & 11 deletions runtime/backend/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
#pragma once
#include <executorch/runtime/core/error.h>
#include <executorch/runtime/core/span.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
Comment thread
shoumikhin marked this conversation as resolved.
#include <type_traits>
#include <variant>

namespace executorch {
Expand All @@ -20,11 +23,25 @@ namespace runtime {
static constexpr size_t kMaxOptionKeyLength = 64;
static constexpr size_t kMaxOptionValueLength = 256;

// All string keys and values must have static storage duration (string
// literals, static const char arrays, or global constants). The BackendOptions
// class does NOT take ownership of strings.
// String keys: must fit in kMaxOptionKeyLength characters (including null
// terminator). The public set_option() templates enforce this at compile time
// via static_assert on the key array's length, so overlong keys fail to
// compile rather than being silently truncated.
// String values: COPIED into the internal `std::array<char, ...>` variant arm
// and truncated at kMaxOptionValueLength - 1 characters (null-terminated).
// Callers do NOT need to keep the source strings alive after the set_option()
// call returns.
// The int64_t arm lets callers pass pointer-sized opaque handles (e.g.,
// driver handles like CUgreenCtx, cudaStream_t). Round-trip the pointer
// through uintptr_t so the cast is well-defined on all platforms:
// opts.set_option("cuda_stream",
// static_cast<int64_t>(reinterpret_cast<uintptr_t>(stream)));
// auto* stream = reinterpret_cast<cudaStream_t>(
// static_cast<uintptr_t>(value));
// On 32-bit platforms the int64_t arm is wider than necessary for pointers
// but remains correct.
using OptionValue =
std::variant<bool, int, std::array<char, kMaxOptionValueLength>>;
std::variant<bool, int, int64_t, std::array<char, kMaxOptionValueLength>>;

struct BackendOption {
// key is the name of the backend option, like num_threads, enable_profiling,
Expand All @@ -40,7 +57,9 @@ struct BackendOption {
*
* This class provides a type-safe way to store key-value pairs for backend
* configuration, with compile-time capacity limits and runtime type checking.
* It supports bool, int, and const char* value types.
* It supports bool, int, int64_t, and const char* value types. The int64_t
* arm allows callers to pass pointer-sized opaque handles (e.g., driver
* handles like CUgreenCtx) by reinterpret_cast to/from int64_t.
Comment on lines +61 to +62
*
* @tparam MaxCapacity The maximum number of options that can be stored
*/
Expand Down Expand Up @@ -113,16 +132,44 @@ class BackendOptions {
return set_option_impl(key, value);
}

/**
* Sets an int64_t option value for the given key.
*
* Useful for pointer-sized opaque handles. Round-trip the pointer through
* uintptr_t so the cast is well-defined on all platforms:
* opts.set_option("cuda_stream",
* static_cast<int64_t>(reinterpret_cast<uintptr_t>(stream)));
*
* Note: bare integer literals like `42` resolve to `int` (NOT `int64_t`),
* and `42L` resolves to `int64_t` only on platforms where `long` is 64-bit
* (Linux) but `int` on Windows. To target the int64_t arm unambiguously,
* use `static_cast<int64_t>(value)` at the call site.
*
* If the key already exists, updates its value. Otherwise, adds a new option.
*
* @tparam N The length of the key string (automatically deduced)
* @param key The option key (must be a string literal or array)
* @param value The int64_t value to set
* @return Error::Ok on success, Error::InvalidArgument if storage is full
*/
template <size_t N>
Error set_option(const char (&key)[N], int64_t value) noexcept {
static_assert(N <= kMaxOptionKeyLength, "Option key is too long");
return set_option_impl(key, value);
}

/**
* Sets a string option value for the given key.
* If the key already exists, updates its value. Otherwise, adds a new option.
*
* Note: The string value must have static storage duration. This class does
* NOT take ownership of the string - it only stores the pointer.
* The string value is copied into an internal fixed-size buffer (truncated
* at kMaxOptionValueLength - 1 characters and null-terminated). The caller
* does NOT need to keep the source string alive after this call returns.
*
* @tparam N The length of the key string (automatically deduced)
* @param key The option key (must be a string literal or array)
* @param value The string value to set (must have static storage duration)
* @param value The string value to set (copied; truncated at
* kMaxOptionValueLength - 1 characters)
* @return Error::Ok on success, Error::InvalidArgument if storage is full
*/
template <size_t N>
Expand All @@ -137,7 +184,8 @@ class BackendOptions {
/**
* Retrieves an option value by key and type.
*
* @tparam T The expected type of the option value (bool, int, or const char*)
* @tparam T The expected type of the option value (bool, int, int64_t, or
* const char*)
* @tparam KeyLen The length of the key string (automatically deduced)
* @param key The option key to look up
* @param out Reference to store the retrieved value
Expand All @@ -157,7 +205,7 @@ class BackendOptions {
return Error::Ok;
}
}
// Default handling for bool/int
// Default handling for bool/int/int64_t
else if (auto* val = std::get_if<T>(&options_[i].value)) {
out = *val;
return Error::Ok;
Expand All @@ -176,13 +224,22 @@ class BackendOptions {
* Internal implementation for setting option values.
* Handles both updating existing options and adding new ones.
*
* @tparam T The type of the value (bool, int, or const char*)
* @tparam T The type of the value (bool, int, int64_t, or const char*)
* @param key The option key
* @param value The value to set
* @return Error::Ok on success, Error::InvalidArgument if storage is full
*/
template <typename T>
Error set_option_impl(const char* key, T value) {
static_assert(
std::variant_size_v<OptionValue> == 4,
"OptionValue arm count changed; audit set_option_impl + get_option");
static_assert(
std::is_same_v<T, bool> || std::is_same_v<T, int> ||
std::is_same_v<T, int64_t> ||
std::is_same_v<T, std::array<char, kMaxOptionValueLength>>,
"set_option_impl<T> only supports the variant arms: bool, int, "
"int64_t, and the fixed-size string array");
// Update existing if found
for (size_t i = 0; i < size_; ++i) {
if (strcmp(options_[i].key, key) == 0) {
Expand Down
39 changes: 38 additions & 1 deletion runtime/backend/test/backend_init_context_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,31 @@ TEST_F(BackendInitContextTest, GetRuntimeSpecIntValid) {
EXPECT_EQ(result2.get(), 32);
}

// Test get_runtime_spec<int64_t> with valid key (covers pointer-sized handle
// use case, e.g., CUDA driver handles like CUgreenCtx).
TEST_F(BackendInitContextTest, GetRuntimeSpecInt64Valid) {
BackendOptions<2> opts;
// Use a value with the top bit clear so the literal is well-defined as a
// signed int64_t across toolchains.
constexpr int64_t kHandle = static_cast<int64_t>(0x123456789ABCDEF0LL);
constexpr int64_t kLargeOffset = static_cast<int64_t>(0x7FFFFFFFFFFFFFFFLL);
opts.set_option("opaque_handle", kHandle);
opts.set_option("large_offset", kLargeOffset);

auto view = opts.view();
Span<const BackendOption> const_span(view.data(), view.size());

BackendInitContext context(nullptr, nullptr, nullptr, nullptr, const_span);

auto result1 = context.get_runtime_spec<int64_t>("opaque_handle");
EXPECT_TRUE(result1.ok());
EXPECT_EQ(result1.get(), kHandle);

auto result2 = context.get_runtime_spec<int64_t>("large_offset");
EXPECT_TRUE(result2.ok());
EXPECT_EQ(result2.get(), kLargeOffset);
}

// Test get_runtime_spec<const char*> with valid key
TEST_F(BackendInitContextTest, GetRuntimeSpecStringValid) {
BackendOptions<2> opts;
Expand Down Expand Up @@ -142,9 +167,10 @@ TEST_F(BackendInitContextTest, GetRuntimeSpecNotFound) {

// Test get_runtime_spec<T> with wrong type returns InvalidArgument
TEST_F(BackendInitContextTest, GetRuntimeSpecTypeMismatch) {
BackendOptions<3> opts;
BackendOptions<4> opts;
opts.set_option("bool_opt", true);
opts.set_option("int_opt", 42);
opts.set_option("int64_opt", static_cast<int64_t>(0x123456789ABCDEF0LL));
opts.set_option("string_opt", "hello");

auto view = opts.view();
Expand All @@ -166,6 +192,17 @@ TEST_F(BackendInitContextTest, GetRuntimeSpecTypeMismatch) {
auto result3 = context.get_runtime_spec<bool>("string_opt");
EXPECT_FALSE(result3.ok());
EXPECT_EQ(result3.error(), Error::InvalidArgument);

// int and int64_t are distinct variant arms; reading an int as int64_t
// (and vice versa) must fail with InvalidArgument rather than implicit
// narrowing/widening.
auto result4 = context.get_runtime_spec<int64_t>("int_opt");
EXPECT_FALSE(result4.ok());
EXPECT_EQ(result4.error(), Error::InvalidArgument);

auto result5 = context.get_runtime_spec<int>("int64_opt");
EXPECT_FALSE(result5.ok());
EXPECT_EQ(result5.error(), Error::InvalidArgument);
}

// Test empty runtime specs
Expand Down
25 changes: 25 additions & 0 deletions runtime/backend/test/backend_options_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,31 @@ TEST_F(BackendOptionsTest, HandlesIntOptions) {
EXPECT_EQ(num_threads, 256);
}

// Test int64_t options - exercises the variant arm that holds pointer-sized
// opaque handles. Uses a value with the high 32 bits set to ensure the value
// is not silently routed through (or truncated by) the int arm.
TEST_F(BackendOptionsTest, HandlesInt64Options) {
constexpr int64_t kHandle = static_cast<int64_t>(0x123456789ABCDEF0LL);
options.set_option("handle", kHandle);

int64_t handle = 0;
EXPECT_EQ(options.get_option("handle", handle), Error::Ok);
EXPECT_EQ(handle, kHandle);

// Reading the same key as int should fail because the variant arm differs.
int as_int = 0;
EXPECT_EQ(options.get_option("handle", as_int), Error::InvalidArgument);

// Update existing key with a new int64_t value. Use a value with the top
// bit clear so the literal is well-defined as a signed int64_t across all
// toolchains (0xFEDCBA98... would be treated as an unsigned literal on many
// compilers and the cast back to int64_t would be implementation-defined).
constexpr int64_t kHandle2 = static_cast<int64_t>(0x7EDCBA9876543210LL);
options.set_option("handle", kHandle2);
EXPECT_EQ(options.get_option("handle", handle), Error::Ok);
EXPECT_EQ(handle, kHandle2);
}

// Test error conditions
TEST_F(BackendOptionsTest, HandlesErrors) {
// Non-existent key
Expand Down
Loading