Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a76d7e9
cpp/core: extract conversions and tensor_helpers modules
barhanc Jul 1, 2026
b70d91a
cpp: migrate callers to conversions and tensor_helpers
barhanc Jul 1, 2026
84c703c
cpp/core: fix size_t/uint64_t duplicate specialization on Linux
barhanc Jul 1, 2026
cdfbbf0
style: add explicit `tensor::fromJs`
barhanc Jul 2, 2026
e60c5df
feat: Add RangeDim support to C++ SymbolicShape and refactor model ru…
barhanc Jul 2, 2026
139eb27
fix: fix error message
barhanc Jul 2, 2026
7305cad
refactor(cpp/core): clean up model execution and error context handling
barhanc Jul 2, 2026
f5511f1
refactor(cpp/core): clean up shape and tensor comparisons in tensor h…
barhanc Jul 2, 2026
28adfc5
refactor(cpp): restore parameter validation and clean up redundant ty…
barhanc Jul 2, 2026
086fc5c
refactor(cpp): implement strict range-checked template conversions fo…
barhanc Jul 2, 2026
051c6ac
refactor(cpp): restore execution error messages and use getRequiredPr…
barhanc Jul 2, 2026
0762abe
refactor(cpp): simplify shapeToString using range-based loop and pop_…
barhanc Jul 2, 2026
a01f7ec
refactor(cpp): preserve output tensor JS object identity and clean up…
barhanc Jul 2, 2026
fb4a7ed
refactor(cpp): restore execution profiling and original short execute…
barhanc Jul 2, 2026
db99e73
style: fix using declaration in model.cpp
barhanc Jul 2, 2026
91c2864
refactor(cpp): replace manual JSI type checking with asType specializ…
barhanc Jul 2, 2026
13e4bcd
refactor(cpp): use macro for template specializations and delete prim…
barhanc Jul 3, 2026
1b0caa3
refactor(cpp): safe copy/move return for jsi::Value and const correct…
barhanc Jul 3, 2026
05f298b
refactor(cpp): implement declarative shape validation in applyColorma…
barhanc Jul 3, 2026
e3edcc0
refactor(cpp): model and tokenizer path const correctness, conversion…
barhanc Jul 3, 2026
463c4e8
refactor(cpp): add initializer_list overload to fromJs and simplify s…
barhanc Jul 3, 2026
45f1097
refactor(cpp): remove const from modelPath_ and tokenizerPath_ privat…
barhanc Jul 3, 2026
563365e
refactor(cpp): use auto for static_cast double conversions
barhanc Jul 3, 2026
1ba3355
refactor(cpp): use std::format for string construction in conversions…
barhanc Jul 3, 2026
d6345d6
fix(core): add bounds checks for offset and length in tensor methods
barhanc Jul 3, 2026
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
119 changes: 119 additions & 0 deletions packages/react-native-executorch/cpp/core/conversions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#include "conversions.h"
#include <cmath>
#include <limits>

namespace rnexecutorch::core::conversions {

constexpr auto kMaxInt64Double = static_cast<double>(std::numeric_limits<int64_t>::max());
constexpr auto kMinInt64Double = static_cast<double>(std::numeric_limits<int64_t>::min());
constexpr auto kMaxUint64Double = static_cast<double>(std::numeric_limits<uint64_t>::max());

constexpr auto kMinInt32Double = static_cast<double>(std::numeric_limits<int32_t>::min());
constexpr auto kMaxInt32Double = static_cast<double>(std::numeric_limits<int32_t>::max());

constexpr auto kMinUint8Double = static_cast<double>(std::numeric_limits<uint8_t>::min());
constexpr auto kMaxUint8Double = static_cast<double>(std::numeric_limits<uint8_t>::max());

template <>
double asType<double>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
if (!val.isNumber()) {
throw jsi::JSError(rt, ctx + " must be a number");
}
return val.asNumber();
}

template <>
float asType<float>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
return static_cast<float>(asType<double>(rt, ctx, val));
}

template <>
int32_t asType<int32_t>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
double v = asType<double>(rt, ctx, val);
if (std::isnan(v) || std::isinf(v) || v != std::trunc(v) || v < kMinInt32Double || v > kMaxInt32Double) {
throw jsi::JSError(rt, ctx + " must be a 32-bit integer");
}
return static_cast<int32_t>(v);
}

template <>
int64_t asType<int64_t>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
double v = asType<double>(rt, ctx, val);
if (std::isnan(v) || std::isinf(v) || v != std::trunc(v) || v < kMinInt64Double || v >= kMaxInt64Double) {
throw jsi::JSError(rt, ctx + " must be a 64-bit integer");
}
return static_cast<int64_t>(v);
}

template <>
uint64_t asType<uint64_t>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
double v = asType<double>(rt, ctx, val);
if (std::isnan(v) || std::isinf(v) || v != std::trunc(v) || v < 0.0 || v >= kMaxUint64Double) {
throw jsi::JSError(rt, ctx + " must be a non-negative integer");
}
return static_cast<uint64_t>(v);
}
Comment thread
barhanc marked this conversation as resolved.

template <>
uint8_t asType<uint8_t>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
double v = asType<double>(rt, ctx, val);
if (std::isnan(v) || std::isinf(v) || v != std::trunc(v) || v < kMinUint8Double || v > kMaxUint8Double) {
throw jsi::JSError(rt, ctx + " must be an integer between 0 and 255");
}
return static_cast<uint8_t>(v);
}

template <>
bool asType<bool>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
if (!val.isBool()) {
throw jsi::JSError(rt, ctx + " must be a boolean");
}
return val.asBool();
}

template <>
std::string asType<std::string>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
if (!val.isString()) {
throw jsi::JSError(rt, ctx + " must be a string");
}
return val.asString(rt).utf8(rt);
}

template <>
jsi::Value asType<jsi::Value>(jsi::Runtime &rt, const std::string & /*ctx*/, const jsi::Value &val) {
return jsi::Value(rt, val);
}

template <>
jsi::ArrayBuffer asType<jsi::ArrayBuffer>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
if (!val.isObject() || !val.asObject(rt).isArrayBuffer(rt)) {
throw jsi::JSError(rt, ctx + " must be an ArrayBuffer");
}
return val.asObject(rt).getArrayBuffer(rt);
}

template <>
jsi::Object asType<jsi::Object>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
if (!val.isObject()) {
throw jsi::JSError(rt, ctx + " must be an object");
}
return val.asObject(rt);
}

template <>
jsi::Array asType<jsi::Array>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
if (!val.isObject() || !val.asObject(rt).isArray(rt)) {
throw jsi::JSError(rt, ctx + " must be an Array");
}
return val.asObject(rt).asArray(rt);
}

template <>
jsi::Function asType<jsi::Function>(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
if (!val.isObject() || !val.asObject(rt).isFunction(rt)) {
throw jsi::JSError(rt, ctx + " must be a function");
}
return val.asObject(rt).asFunction(rt);
}

} // namespace rnexecutorch::core::conversions
139 changes: 139 additions & 0 deletions packages/react-native-executorch/cpp/core/conversions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#pragma once

#include <cstddef>
#include <cstdint>
#include <format>
#include <optional>
#include <string>
#include <type_traits>
#include <vector>

#include <jsi/jsi.h>

namespace rnexecutorch::core::conversions {
namespace jsi = facebook::jsi;

/**
* Converts a facebook::jsi::Value to a specified target C++ or JSI type. Throws
* a facebook::jsi::JSError with contextual error info if the conversion fails.
*
* @tparam T The target type to convert to.
* @param rt The JSI runtime instance.
* @param ctx Context description used to generate helpful error messages.
* @param val The JSI value to convert.
* @return The converted value of type T.
*/
template <typename T>
T asType(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) = delete;

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage): macro is used for succinct template specialization declarations
#define DECLARE_ASTYPE_SPECIALIZATION(Type) \
template <> \
Type asType<Type>(jsi::Runtime & rt, const std::string &ctx, const jsi::Value &val)
DECLARE_ASTYPE_SPECIALIZATION(double);
DECLARE_ASTYPE_SPECIALIZATION(float);
DECLARE_ASTYPE_SPECIALIZATION(int32_t);
DECLARE_ASTYPE_SPECIALIZATION(int64_t);
DECLARE_ASTYPE_SPECIALIZATION(uint64_t);
DECLARE_ASTYPE_SPECIALIZATION(uint8_t);
DECLARE_ASTYPE_SPECIALIZATION(bool);
DECLARE_ASTYPE_SPECIALIZATION(std::string);
DECLARE_ASTYPE_SPECIALIZATION(jsi::Value);
DECLARE_ASTYPE_SPECIALIZATION(jsi::Object);
DECLARE_ASTYPE_SPECIALIZATION(jsi::Array);
DECLARE_ASTYPE_SPECIALIZATION(jsi::Function);
DECLARE_ASTYPE_SPECIALIZATION(jsi::ArrayBuffer);
#undef DECLARE_ASTYPE_SPECIALIZATION

/**
* Retrieves a required property from a JSI object, converting it to the
* specified type. Throws a facebook::jsi::JSError if the property is missing or
* of an incorrect type.
*
* @tparam T The target type to convert the property to.
* @param rt The JSI runtime instance.
* @param ctx Context description used for error messages.
* @param obj The JSI object containing the property.
* @param propName The name of the property to retrieve.
* @return The converted property value.
*/
template <typename T>
T getRequiredProperty(jsi::Runtime &rt, const std::string &ctx, const jsi::Object &obj, const std::string &propName) {
if (!obj.hasProperty(rt, propName.c_str())) {
throw jsi::JSError(rt, std::format("{}: option '{}' is required", ctx, propName));
}
return asType<T>(rt, std::format("{}: option '{}'", ctx, propName), obj.getProperty(rt, propName.c_str()));
}

/**
* Retrieves an optional property from a JSI object, returning std::nullopt if
* the property is missing, null, or undefined. Throws a facebook::jsi::JSError
* if the property exists but cannot be converted to the target type.
*
* @tparam T The target type to convert the property to if present.
* @param rt The JSI runtime instance.
* @param ctx Context description used for error messages.
* @param obj The JSI object containing the property.
* @param propName The name of the property to retrieve.
* @return An optional containing the converted property value, or std::nullopt.
*/
template <typename T>
std::optional<T> getOptionalProperty(jsi::Runtime &rt, const std::string &ctx, const jsi::Object &obj, const std::string &propName) {
if (!obj.hasProperty(rt, propName.c_str())) {
return std::nullopt;
}
auto val = obj.getProperty(rt, propName.c_str());
if (val.isUndefined() || val.isNull()) {
return std::nullopt;
}
return asType<T>(rt, std::format("{}: option '{}'", ctx, propName), val);
}

/**
* Converts a JSI Array to a std::vector of the specified type. Converts each
* element of the array individually and propagates index details in the error
* context.
*
* @tparam T The target element type.
* @param rt The JSI runtime instance.
* @param ctx Context description used for error messages.
* @param val The JSI value (must be an Array).
* @return A std::vector containing the converted elements.
*/
template <typename T>
std::vector<T> asVector(jsi::Runtime &rt, const std::string &ctx, const jsi::Value &val) {
auto arr = asType<jsi::Array>(rt, ctx, val);
std::vector<T> vec;
const size_t len = arr.size(rt);
vec.reserve(len);
for (size_t i = 0; i < len; ++i) {
vec.push_back(asType<T>(rt, std::format("{}[{}]", ctx, i), arr.getValueAtIndex(rt, i)));
}
return vec;
}

/**
* Converts a std::vector of values to a new facebook::jsi::Array.
* Handles strings, booleans, and numeric types appropriately.
*
* @tparam T The element type in the vector.
* @param rt The JSI runtime instance.
* @param vec The source vector to convert.
* @return A new facebook::jsi::Array containing the elements from the vector.
*/
template <typename T>
jsi::Array toJsiArray(jsi::Runtime &rt, const std::vector<T> &vec) {
jsi::Array arr(rt, vec.size());
for (size_t i = 0; i < vec.size(); ++i) {
if constexpr (std::is_same_v<T, std::string>) {
arr.setValueAtIndex(rt, i, jsi::String::createFromUtf8(rt, vec[i]));
} else if constexpr (std::is_same_v<T, bool>) {
arr.setValueAtIndex(rt, i, jsi::Value(vec[i]));
} else {
arr.setValueAtIndex(rt, i, jsi::Value(static_cast<double>(vec[i])));
}
}
return arr;
}

} // namespace rnexecutorch::core::conversions
Loading