Skip to content

Commit b478c32

Browse files
shoumikhinfacebook-github-bot
authored andcommitted
Add int64_t support to BackendOptions for pointer-sized handles
Summary: Add `int64_t` as a new variant arm to `OptionValue` in `BackendOptions` and extend `BackendInitContext::get_runtime_spec<T>` to support it. Lets backends receive pointer-sized opaque handles (e.g., `cudaStream_t`, `CUgreenCtx`) as runtime specs at delegate `init()` time. Motivation: GPU delegates today have no public surface to receive caller-owned device resources, so they default to allocating their own streams from the device's primary-context stream pool. This silently bypasses caller-set CUDA Green Contexts, which is exactly the gap MRS's MLRT runtime hits when partitioning a Jetson Thor across concurrent models. With this change, callers that own a GPU context can pass a green-ctx-bound stream into a Module's load path via `BackendOptions`, and GPU delegates that opt in (TensorRT, CUDA, future Vulkan / Metal) honor it instead of allocating their own. Fully additive: - New `int64_t` variant arm + matching `set_option(int64_t)` overload. - `get_option<int64_t>` falls through the existing `bool/int` path. - `BackendInitContext::get_runtime_spec<int64_t>` extends the existing `static_assert`. - No behavior change for existing `bool` / `int` / `const char*` callers. - No public API removal or signature change. Documentation: - The int64_t arm docstring spells out the canonical `uintptr_t` round-trip pattern so callers don't bikeshed the cast — raw `reinterpret_cast<int64_t>(ptr)` is implementation-defined per the standard; via `uintptr_t` it is well-defined on every platform. - Calls out the literal-overload-resolution gotcha (`42` → `int`, `42L` → platform-dependent) and recommends `static_cast<int64_t>` at the call site. Defensive scaffolding: - `static_assert(std::variant_size_v<OptionValue> == 4, ...)` in `set_option_impl` guards against future variant arm additions silently breaking call sites. - Companion `static_assert` constrains the impl template `T` to the four valid variant arms so accidental misuse fails at compile time. Differential Revision: D103241865
1 parent e84a418 commit b478c32

4 files changed

Lines changed: 136 additions & 15 deletions

File tree

runtime/backend/backend_init_context.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
#include <executorch/runtime/core/result.h>
1616
#include <executorch/runtime/core/span.h>
1717

18+
#include <cstdint>
1819
#include <cstring>
20+
#include <type_traits>
1921

2022
#ifdef __GNUC__
2123
// Disable -Wdeprecated-declarations, as some builds use 'Werror'.
@@ -97,7 +99,7 @@ class BackendInitContext final {
9799
/**
98100
* Get a runtime spec value by key and type.
99101
*
100-
* @tparam T The expected type (bool, int, or const char*)
102+
* @tparam T The expected type (bool, int, int64_t, or const char*)
101103
* @param key The option key to look up.
102104
* @return Result containing the value if found and type matches,
103105
* Error::NotFound if key doesn't exist,
@@ -107,8 +109,8 @@ class BackendInitContext final {
107109
Result<T> get_runtime_spec(const char* key) const {
108110
static_assert(
109111
std::is_same_v<T, bool> || std::is_same_v<T, int> ||
110-
std::is_same_v<T, const char*>,
111-
"get_runtime_spec<T> only supports bool, int, and const char*");
112+
std::is_same_v<T, int64_t> || std::is_same_v<T, const char*>,
113+
"get_runtime_spec<T> only supports bool, int, int64_t, and const char*");
112114

113115
for (size_t i = 0; i < runtime_specs_.size(); ++i) {
114116
const auto& opt = runtime_specs_[i];

runtime/backend/options.h

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
#pragma once
1010
#include <executorch/runtime/core/error.h>
1111
#include <executorch/runtime/core/span.h>
12+
#include <algorithm>
1213
#include <array>
1314
#include <cstddef>
15+
#include <cstdint>
1416
#include <cstring>
17+
#include <type_traits>
1518
#include <variant>
1619

1720
namespace executorch {
@@ -20,11 +23,25 @@ namespace runtime {
2023
static constexpr size_t kMaxOptionKeyLength = 64;
2124
static constexpr size_t kMaxOptionValueLength = 256;
2225

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+
// String keys: must fit in kMaxOptionKeyLength characters (including null
27+
// terminator). The public set_option() templates enforce this at compile time
28+
// via static_assert on the key array's length, so overlong keys fail to
29+
// compile rather than being silently truncated.
30+
// String values: COPIED into the internal `std::array<char, ...>` variant arm
31+
// and truncated at kMaxOptionValueLength - 1 characters (null-terminated).
32+
// Callers do NOT need to keep the source strings alive after the set_option()
33+
// call returns.
34+
// The int64_t arm lets callers pass pointer-sized opaque handles (e.g.,
35+
// driver handles like CUgreenCtx, cudaStream_t). Round-trip the pointer
36+
// through uintptr_t so the cast is well-defined on all platforms:
37+
// opts.set_option("cuda_stream",
38+
// static_cast<int64_t>(reinterpret_cast<uintptr_t>(stream)));
39+
// auto* stream = reinterpret_cast<cudaStream_t>(
40+
// static_cast<uintptr_t>(value));
41+
// On 32-bit platforms the int64_t arm is wider than necessary for pointers
42+
// but remains correct.
2643
using OptionValue =
27-
std::variant<bool, int, std::array<char, kMaxOptionValueLength>>;
44+
std::variant<bool, int, int64_t, std::array<char, kMaxOptionValueLength>>;
2845

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

135+
/**
136+
* Sets an int64_t option value for the given key.
137+
*
138+
* Useful for pointer-sized opaque handles. Round-trip the pointer through
139+
* uintptr_t so the cast is well-defined on all platforms:
140+
* opts.set_option("cuda_stream",
141+
* static_cast<int64_t>(reinterpret_cast<uintptr_t>(stream)));
142+
*
143+
* Note: bare integer literals like `42` resolve to `int` (NOT `int64_t`),
144+
* and `42L` resolves to `int64_t` only on platforms where `long` is 64-bit
145+
* (Linux) but `int` on Windows. To target the int64_t arm unambiguously,
146+
* use `static_cast<int64_t>(value)` at the call site.
147+
*
148+
* If the key already exists, updates its value. Otherwise, adds a new option.
149+
*
150+
* @tparam N The length of the key string (automatically deduced)
151+
* @param key The option key (must be a string literal or array)
152+
* @param value The int64_t value to set
153+
* @return Error::Ok on success, Error::InvalidArgument if storage is full
154+
*/
155+
template <size_t N>
156+
Error set_option(const char (&key)[N], int64_t value) noexcept {
157+
static_assert(N <= kMaxOptionKeyLength, "Option key is too long");
158+
return set_option_impl(key, value);
159+
}
160+
116161
/**
117162
* Sets a string option value for the given key.
118163
* If the key already exists, updates its value. Otherwise, adds a new option.
119164
*
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.
165+
* The string value is copied into an internal fixed-size buffer (truncated
166+
* at kMaxOptionValueLength - 1 characters and null-terminated). The caller
167+
* does NOT need to keep the source string alive after this call returns.
122168
*
123169
* @tparam N The length of the key string (automatically deduced)
124170
* @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)
171+
* @param value The string value to set (copied; truncated at
172+
* kMaxOptionValueLength - 1 characters)
126173
* @return Error::Ok on success, Error::InvalidArgument if storage is full
127174
*/
128175
template <size_t N>
@@ -137,7 +184,8 @@ class BackendOptions {
137184
/**
138185
* Retrieves an option value by key and type.
139186
*
140-
* @tparam T The expected type of the option value (bool, int, or const char*)
187+
* @tparam T The expected type of the option value (bool, int, int64_t, or
188+
* const char*)
141189
* @tparam KeyLen The length of the key string (automatically deduced)
142190
* @param key The option key to look up
143191
* @param out Reference to store the retrieved value
@@ -157,7 +205,7 @@ class BackendOptions {
157205
return Error::Ok;
158206
}
159207
}
160-
// Default handling for bool/int
208+
// Default handling for bool/int/int64_t
161209
else if (auto* val = std::get_if<T>(&options_[i].value)) {
162210
out = *val;
163211
return Error::Ok;
@@ -176,13 +224,22 @@ class BackendOptions {
176224
* Internal implementation for setting option values.
177225
* Handles both updating existing options and adding new ones.
178226
*
179-
* @tparam T The type of the value (bool, int, or const char*)
227+
* @tparam T The type of the value (bool, int, int64_t, or const char*)
180228
* @param key The option key
181229
* @param value The value to set
182230
* @return Error::Ok on success, Error::InvalidArgument if storage is full
183231
*/
184232
template <typename T>
185233
Error set_option_impl(const char* key, T value) {
234+
static_assert(
235+
std::variant_size_v<OptionValue> == 4,
236+
"OptionValue arm count changed; audit set_option_impl + get_option");
237+
static_assert(
238+
std::is_same_v<T, bool> || std::is_same_v<T, int> ||
239+
std::is_same_v<T, int64_t> ||
240+
std::is_same_v<T, std::array<char, kMaxOptionValueLength>>,
241+
"set_option_impl<T> only supports the variant arms: bool, int, "
242+
"int64_t, and the fixed-size string array");
186243
// Update existing if found
187244
for (size_t i = 0; i < size_; ++i) {
188245
if (strcmp(options_[i].key, key) == 0) {

runtime/backend/test/backend_init_context_test.cpp

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,31 @@ TEST_F(BackendInitContextTest, GetRuntimeSpecIntValid) {
9797
EXPECT_EQ(result2.get(), 32);
9898
}
9999

100+
// Test get_runtime_spec<int64_t> with valid key (covers pointer-sized handle
101+
// use case, e.g., CUDA driver handles like CUgreenCtx).
102+
TEST_F(BackendInitContextTest, GetRuntimeSpecInt64Valid) {
103+
BackendOptions<2> opts;
104+
// Use a value with the top bit clear so the literal is well-defined as a
105+
// signed int64_t across toolchains.
106+
constexpr int64_t kHandle = static_cast<int64_t>(0x123456789ABCDEF0LL);
107+
constexpr int64_t kLargeOffset = static_cast<int64_t>(0x7FFFFFFFFFFFFFFFLL);
108+
opts.set_option("opaque_handle", kHandle);
109+
opts.set_option("large_offset", kLargeOffset);
110+
111+
auto view = opts.view();
112+
Span<const BackendOption> const_span(view.data(), view.size());
113+
114+
BackendInitContext context(nullptr, nullptr, nullptr, nullptr, const_span);
115+
116+
auto result1 = context.get_runtime_spec<int64_t>("opaque_handle");
117+
EXPECT_TRUE(result1.ok());
118+
EXPECT_EQ(result1.get(), kHandle);
119+
120+
auto result2 = context.get_runtime_spec<int64_t>("large_offset");
121+
EXPECT_TRUE(result2.ok());
122+
EXPECT_EQ(result2.get(), kLargeOffset);
123+
}
124+
100125
// Test get_runtime_spec<const char*> with valid key
101126
TEST_F(BackendInitContextTest, GetRuntimeSpecStringValid) {
102127
BackendOptions<2> opts;
@@ -142,9 +167,10 @@ TEST_F(BackendInitContextTest, GetRuntimeSpecNotFound) {
142167

143168
// Test get_runtime_spec<T> with wrong type returns InvalidArgument
144169
TEST_F(BackendInitContextTest, GetRuntimeSpecTypeMismatch) {
145-
BackendOptions<3> opts;
170+
BackendOptions<4> opts;
146171
opts.set_option("bool_opt", true);
147172
opts.set_option("int_opt", 42);
173+
opts.set_option("int64_opt", static_cast<int64_t>(0x123456789ABCDEF0LL));
148174
opts.set_option("string_opt", "hello");
149175

150176
auto view = opts.view();
@@ -166,6 +192,17 @@ TEST_F(BackendInitContextTest, GetRuntimeSpecTypeMismatch) {
166192
auto result3 = context.get_runtime_spec<bool>("string_opt");
167193
EXPECT_FALSE(result3.ok());
168194
EXPECT_EQ(result3.error(), Error::InvalidArgument);
195+
196+
// int and int64_t are distinct variant arms; reading an int as int64_t
197+
// (and vice versa) must fail with InvalidArgument rather than implicit
198+
// narrowing/widening.
199+
auto result4 = context.get_runtime_spec<int64_t>("int_opt");
200+
EXPECT_FALSE(result4.ok());
201+
EXPECT_EQ(result4.error(), Error::InvalidArgument);
202+
203+
auto result5 = context.get_runtime_spec<int>("int64_opt");
204+
EXPECT_FALSE(result5.ok());
205+
EXPECT_EQ(result5.error(), Error::InvalidArgument);
169206
}
170207

171208
// Test empty runtime specs

runtime/backend/test/backend_options_test.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,31 @@ TEST_F(BackendOptionsTest, HandlesIntOptions) {
6161
EXPECT_EQ(num_threads, 256);
6262
}
6363

64+
// Test int64_t options - exercises the variant arm that holds pointer-sized
65+
// opaque handles. Uses a value with the high 32 bits set to ensure the value
66+
// is not silently routed through (or truncated by) the int arm.
67+
TEST_F(BackendOptionsTest, HandlesInt64Options) {
68+
constexpr int64_t kHandle = static_cast<int64_t>(0x123456789ABCDEF0LL);
69+
options.set_option("handle", kHandle);
70+
71+
int64_t handle = 0;
72+
EXPECT_EQ(options.get_option("handle", handle), Error::Ok);
73+
EXPECT_EQ(handle, kHandle);
74+
75+
// Reading the same key as int should fail because the variant arm differs.
76+
int as_int = 0;
77+
EXPECT_EQ(options.get_option("handle", as_int), Error::InvalidArgument);
78+
79+
// Update existing key with a new int64_t value. Use a value with the top
80+
// bit clear so the literal is well-defined as a signed int64_t across all
81+
// toolchains (0xFEDCBA98... would be treated as an unsigned literal on many
82+
// compilers and the cast back to int64_t would be implementation-defined).
83+
constexpr int64_t kHandle2 = static_cast<int64_t>(0x7EDCBA9876543210LL);
84+
options.set_option("handle", kHandle2);
85+
EXPECT_EQ(options.get_option("handle", handle), Error::Ok);
86+
EXPECT_EQ(handle, kHandle2);
87+
}
88+
6489
// Test error conditions
6590
TEST_F(BackendOptionsTest, HandlesErrors) {
6691
// Non-existent key

0 commit comments

Comments
 (0)