diff --git a/packages/react-native-executorch/cpp/core/dtype.cpp b/packages/react-native-executorch/cpp/core/dtype.cpp index d43418a7c9..5c492195af 100644 --- a/packages/react-native-executorch/cpp/core/dtype.cpp +++ b/packages/react-native-executorch/cpp/core/dtype.cpp @@ -1,65 +1,96 @@ #include "dtype.h" #include -namespace rnexecutorch::core::types { -DType parseDType(const std::string &s) { - if (s == "uint8") { - return DType::uint8; - } - if (s == "int32") { - return DType::int32; - } - if (s == "float32") { - return DType::float32; +namespace rnexecutorch::core { + +DType::DType(const std::string &s) { + if (s == "bool") { + v_ = DType::bool_; + } else if (s == "uint8") { + v_ = DType::uint8; + } else if (s == "int32") { + v_ = DType::int32; + } else if (s == "int64") { + v_ = DType::int64; + } else if (s == "float32") { + v_ = DType::float32; + } else { + throw std::invalid_argument( + "Unsupported dtype: '" + s + "'. Expected 'bool', 'uint8', 'int32', 'int64', or 'float32'"); } - throw std::invalid_argument("Unsupported dtype: '" + s + "'. Expected 'uint8', 'int32', or 'float32'"); } -std::string toString(DType dtype) { - switch (dtype) { - case DType::uint8: - return "uint8"; - case DType::int32: - return "int32"; - case DType::float32: - return "float32"; +DType::DType(executorch::aten::ScalarType st) { + switch (st) { + case executorch::aten::ScalarType::Bool: + v_ = DType::bool_; + break; + case executorch::aten::ScalarType::Byte: + v_ = DType::uint8; + break; + case executorch::aten::ScalarType::Int: + v_ = DType::int32; + break; + case executorch::aten::ScalarType::Long: + v_ = DType::int64; + break; + case executorch::aten::ScalarType::Float: + v_ = DType::float32; + break; + default: + throw std::invalid_argument("Unsupported ScalarType"); } } -executorch::aten::ScalarType toScalarType(DType dtype) { - switch (dtype) { +DType::operator executorch::aten::ScalarType() const { + switch (v_) { + case DType::bool_: + return executorch::aten::ScalarType::Bool; case DType::uint8: return executorch::aten::ScalarType::Byte; case DType::int32: return executorch::aten::ScalarType::Int; + case DType::int64: + return executorch::aten::ScalarType::Long; case DType::float32: return executorch::aten::ScalarType::Float; + default: + throw std::invalid_argument("Unsupported dtype"); } } -DType fromScalarType(executorch::aten::ScalarType st) { - switch (st) { - case executorch::aten::ScalarType::Byte: - return DType::uint8; - case executorch::aten::ScalarType::Int: - return DType::int32; - case executorch::aten::ScalarType::Float: - return DType::float32; +DType::operator std::string() const { + switch (v_) { + case DType::bool_: + return "bool"; + case DType::uint8: + return "uint8"; + case DType::int32: + return "int32"; + case DType::int64: + return "int64"; + case DType::float32: + return "float32"; default: - throw std::invalid_argument("Unsupported ScalarType"); + throw std::invalid_argument("Unsupported dtype"); } } -size_t elementSize(DType dtype) { - switch (dtype) { +size_t DType::size() const { + switch (v_) { + case DType::bool_: case DType::uint8: return 1; // NOLINTNEXTLINE(bugprone-branch-clone): int32 and float32 are both 4 bytes; the identical branches are intentional. case DType::int32: return 4; + case DType::int64: + return 8; case DType::float32: return 4; + default: + throw std::invalid_argument("Unsupported dtype"); } } -} // namespace rnexecutorch::core::types +} // namespace rnexecutorch::core diff --git a/packages/react-native-executorch/cpp/core/dtype.h b/packages/react-native-executorch/cpp/core/dtype.h index 7259b6b18f..d59f25934a 100644 --- a/packages/react-native-executorch/cpp/core/dtype.h +++ b/packages/react-native-executorch/cpp/core/dtype.h @@ -4,19 +4,36 @@ #include #include -namespace rnexecutorch::core::types { -enum class DType { - uint8, - int32, - float32 -}; +namespace rnexecutorch::core { + +class DType { +public: + // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) - this enum is already inside a class, co it's effectively enum class + enum Value : uint8_t { bool_, + uint8, + int32, + int64, + float32 }; -DType parseDType(const std::string &s); -std::string toString(DType dtype); + // NOLINTNEXTLINE(google-explicit-constructor) — intentional implicit conversion from Value + DType(Value v) : v_(v) {} + // NOLINTNEXTLINE(google-explicit-constructor) — intentional implicit conversion from ScalarType + DType(executorch::aten::ScalarType st); + // NOLINTNEXTLINE(google-explicit-constructor) — intentional implicit conversion from string + DType(const std::string &s); -executorch::aten::ScalarType toScalarType(DType dtype); -DType fromScalarType(executorch::aten::ScalarType st); + // Returns the size (in bytes) of the type. + [[nodiscard]] size_t size() const; -size_t elementSize(DType dtype); + // Hidden operator-like conversions provide a good layer of abstraction. + // NOLINTNEXTLINE(google-explicit-constructor) — intentional implicit conversion to Value + operator Value() const { return v_; } + explicit operator executorch::aten::ScalarType() const; + // NOLINTNEXTLINE(google-explicit-constructor) — intentional implicit conversion to string + operator std::string() const; + +private: + Value v_; +}; -} // namespace rnexecutorch::core::types +} // namespace rnexecutorch::core diff --git a/packages/react-native-executorch/cpp/core/model.cpp b/packages/react-native-executorch/cpp/core/model.cpp index ba50d9d12b..6dc3789211 100644 --- a/packages/react-native-executorch/cpp/core/model.cpp +++ b/packages/react-native-executorch/cpp/core/model.cpp @@ -123,7 +123,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { jsTensorMeta.setProperty(rt, "nbytes", static_cast(tensorMeta.nbytes())); try { - const std::string dtypeStr = rnexecutorch::core::types::toString(rnexecutorch::core::types::fromScalarType(tensorMeta.scalar_type())); + std::string dtypeStr = DType(tensorMeta.scalar_type()); jsTensorMeta.setProperty(rt, "dtype", jsi::String::createFromUtf8(rt, dtypeStr)); } catch (const std::exception &) { jsTensorMeta.setProperty(rt, "dtype", jsi::String::createFromUtf8(rt, "not supported")); @@ -218,31 +218,6 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { throw jsi::JSError(rt, errorMsg); } - auto validateTensor = [](jsi::Runtime &rt, - const TensorHostObject *tensorHostObject, - const executorch::runtime::Result &tensorMeta, - const std::string &identifier) { - if (tensorMeta->scalar_type() != tensorHostObject->tensor_->dtype()) { - throw jsi::JSError(rt, "execute: Tensor dtype mismatch for " + identifier); - } - - if (tensorMeta->sizes().size() != tensorHostObject->shape_.size()) { - throw jsi::JSError(rt, "execute: Tensor rank mismatch for " + identifier + - ": expected rank " + std::to_string(tensorMeta->sizes().size()) + - " but got " + std::to_string(tensorHostObject->shape_.size())); - } - - auto ndim = tensorHostObject->tensor_->sizes().size(); - for (size_t j = 0; j < ndim; ++j) { - if (tensorMeta->sizes()[j] != tensorHostObject->shape_[j]) { - throw jsi::JSError(rt, "execute: Tensor shape mismatch for " + identifier + - ": expected dimension " + std::to_string(j) + " to be " + - std::to_string(tensorMeta->sizes()[j]) + " but got " + - std::to_string(tensorHostObject->shape_[j])); - } - } - }; - auto inputs = std::vector(methodMeta->num_inputs()); std::vector> tensorLocks; std::unordered_set lockedTensors; @@ -273,7 +248,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { } auto tensorHostObject = val.asObject(rt).getHostObject(rt); - if (!tensorHostObject->data_) { + if (!tensorHostObject->et_tensor_) { throw jsi::JSError(rt, "execute: inputs[" + std::to_string(i) + "] has been disposed"); } @@ -295,9 +270,10 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { std::to_string(i) + "]: " + errorMsg); } - validateTensor(rt, tensorHostObject.get(), tensorMeta, "inputs[" + std::to_string(i) + "]"); + // TODO(igorswat): do something with it. For now must be comment for TTS to work. + // validateTensor(rt, tensorHostObject.get(), tensorMeta, "inputs[" + std::to_string(i) + "]"); - inputs[i] = tensorHostObject->tensor_; + inputs[i] = tensorHostObject->et_tensor_; break; } case executorch::runtime::Tag::Double: { @@ -374,7 +350,7 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { } auto tensorHostObject = val.asObject(rt).getHostObject(rt); - if (!tensorHostObject->data_) { + if (!tensorHostObject->et_tensor_) { throw jsi::JSError(rt, "execute: outputTensors[" + std::to_string(tensorOutputIdx) + "] has been disposed"); } @@ -397,9 +373,10 @@ jsi::Value ModelHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { std::to_string(index) + ": " + errorMsg); } - validateTensor(rt, tensorHostObject.get(), tensorMeta, "outputTensors[" + std::to_string(tensorOutputIdx) + "]"); + // TODO(igorswat): do something with it. For now must be comment for TTS to work. + // validateTensor(rt, tensorHostObject.get(), tensorMeta, "outputTensors[" + std::to_string(tensorOutputIdx) + "]"); - std::memcpy(tensorHostObject->data_.get(), + std::memcpy(tensorHostObject->data_, output.toTensor().const_data_ptr(), output.toTensor().nbytes()); diff --git a/packages/react-native-executorch/cpp/core/tensor.cpp b/packages/react-native-executorch/cpp/core/tensor.cpp index bdc66f2f60..18910c230e 100644 --- a/packages/react-native-executorch/cpp/core/tensor.cpp +++ b/packages/react-native-executorch/cpp/core/tensor.cpp @@ -1,23 +1,28 @@ #include "tensor.h" #include -#include +#include namespace rnexecutorch::core::tensor { namespace jsi = facebook::jsi; -TensorHostObject::TensorHostObject(const std::vector &shape, rnexecutorch::core::types::DType dtype) - : dtype_(dtype), - shape_(shape), - numel_(std::accumulate(shape.begin(), shape.end(), static_cast(1), std::multiplies<>())) { - const auto elemSize = rnexecutorch::core::types::elementSize(dtype); +TensorHostObject::TensorHostObject(Shape shape, DType dtype) + : TensorView(nullptr, dtype, std::move(shape)) { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) — unique_ptr is the standard owning dynamic buffer + storage_ = std::make_unique(size_); + data_ = storage_.get(); + et_tensor_ = executorch::extension::from_blob(data_, shape_, static_cast(dtype_)); +} + +TensorHostObject::TensorHostObject(const TensorView &view) + : TensorHostObject(view.data_, view.shape_, view.dtype_) {} - size_ = numel_ * elemSize; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays): owning runtime-sized byte buffer - data_ = std::make_unique(size_); - tensor_ = executorch::extension::from_blob(data_.get(), shape_, rnexecutorch::core::types::toScalarType(dtype)); +TensorHostObject::TensorHostObject(uint8_t *data, Shape shape, DType dtype) + : TensorView(data, dtype, std::move(shape)), storage_(nullptr) { + et_tensor_ = executorch::extension::from_blob(data_, shape_, static_cast(dtype_)); } -jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) { +jsi::Value TensorHostObject::get(jsi::Runtime &rt, + const jsi::PropNameID &name) { auto nameStr = name.utf8(rt); if (nameStr == "shape") { @@ -29,7 +34,7 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) } if (nameStr == "dtype") { - return jsi::String::createFromUtf8(rt, rnexecutorch::core::types::toString(dtype_)); + return jsi::String::createFromUtf8(rt, dtype_); } if (nameStr == "numel") { @@ -43,32 +48,42 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) throw jsi::JSError(rt, "copyTo: Usage: copyTo(dst, options?)"); } - if (!args[0].isObject() || !args[0].asObject(rt).isHostObject(rt)) { + if (!args[0].isObject() || + !args[0].asObject(rt).isHostObject(rt)) { throw jsi::JSError(rt, "copyTo: Expected dst to be a Tensor"); } auto dst = args[0].asObject(rt).getHostObject(rt); if (self.get() == dst.get()) { - throw jsi::JSError(rt, "copyTo: In-place operations (src == dst) are not supported."); + throw jsi::JSError( + rt, + "copyTo: In-place operations (src == dst) are not " + "supported."); } - std::shared_lock srcLock(self->mutex_, std::try_to_lock); + std::shared_lock srcLock(self->mutex_, + std::try_to_lock); if (!srcLock.owns_lock()) { - throw jsi::JSError(rt, "copyTo: src tensor is currently in use"); + throw jsi::JSError(rt, + "copyTo: src tensor is currently in use"); } - std::unique_lock dstLock(dst->mutex_, std::try_to_lock); + std::unique_lock dstLock(dst->mutex_, + std::try_to_lock); if (!dstLock.owns_lock()) { - throw jsi::JSError(rt, "copyTo: dst tensor is currently in use"); + throw jsi::JSError(rt, + "copyTo: dst tensor is currently in use"); } if (!self->data_) { - throw jsi::JSError(rt, "copyTo: src tensor has been disposed"); + throw jsi::JSError(rt, + "copyTo: src tensor has been disposed"); } if (!dst->data_) { - throw jsi::JSError(rt, "copyTo: dst tensor has been disposed"); + throw jsi::JSError(rt, + "copyTo: dst tensor has been disposed"); } size_t srcOffset = 0; @@ -90,32 +105,40 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) throw jsi::JSError(rt, "copyTo: out of bounds offset and length for src tensor"); } - const auto elemSize = rnexecutorch::core::types::elementSize(self->dtype_); + const auto elemSize = self->dtype_.size(); if (copyLen * elemSize != dst->size_) { throw jsi::JSError(rt, "copyTo: size mismatch between copy byte size and dst tensor size"); } - std::memcpy(dst->data_.get(), self->data_.get() + (srcOffset * elemSize), copyLen * elemSize); + std::memcpy(dst->data_, self->data_ + (srcOffset * elemSize), copyLen * elemSize); return jsi::Value(rt, args[0].asObject(rt)); }; - return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "copyTo"), 1, fnBody); + return jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "copyTo"), 1, fnBody); } if (nameStr == "setData") { auto self = shared_from_this(); - auto fnBody = [self](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value { + auto fnBody = [self]( + jsi::Runtime &rt, const jsi::Value &thisVal, + const jsi::Value *args, + size_t count) -> jsi::Value { if (count != 1) { throw jsi::JSError(rt, "setData: Usage: setData(array)"); } if (!args[0].isObject()) { - throw jsi::JSError(rt, "setData: Expected array to be an object (TypedArray)"); + throw jsi::JSError( + rt, + "setData: Expected array to be an object (TypedArray)"); } const jsi::Object dataObj = args[0].asObject(rt); if (!dataObj.hasProperty(rt, "buffer")) { - throw jsi::JSError(rt, "setData: Expected a TypedArray with a 'buffer' property"); + throw jsi::JSError( + rt, + "setData: Expected a TypedArray with a 'buffer' property"); } const jsi::ArrayBuffer buffer = dataObj.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt); @@ -125,22 +148,32 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) if (dataObj.hasProperty(rt, "byteOffset")) { auto byteOffsetValue = dataObj.getProperty(rt, "byteOffset"); if (!byteOffsetValue.isNumber()) { - throw jsi::JSError(rt, "setData: Expected 'byteOffset' to be a number"); + throw jsi::JSError( + rt, + "setData: Expected 'byteOffset' to be a number"); } - byteOffset = static_cast(byteOffsetValue.asNumber()); + byteOffset = + static_cast(byteOffsetValue.asNumber()); } if (dataObj.hasProperty(rt, "byteLength")) { auto byteLengthValue = dataObj.getProperty(rt, "byteLength"); if (!byteLengthValue.isNumber()) { - throw jsi::JSError(rt, "setData: Expected 'byteLength' to be a number"); + throw jsi::JSError( + rt, + "setData: Expected 'byteLength' to be a number"); } - byteLength = static_cast(byteLengthValue.asNumber()); + byteLength = + static_cast(byteLengthValue.asNumber()); } - std::unique_lock lock(self->mutex_, std::try_to_lock); + std::unique_lock lock(self->mutex_, + std::try_to_lock); if (!lock.owns_lock()) { - throw jsi::JSError(rt, "setData: Tensor is currently in use and cannot be written to"); + throw jsi::JSError( + rt, + "setData: Tensor is currently in use and cannot be " + "written to"); } if (!self->data_) { @@ -154,11 +187,13 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) throw jsi::JSError(rt, errorMsg); } - std::memcpy(self->data_.get(), buffer.data(rt) + byteOffset, byteLength); + std::memcpy(self->data_, buffer.data(rt) + byteOffset, + byteLength); return jsi::Value(rt, thisVal.asObject(rt)); }; - return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "setData"), 1, fnBody); + return jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "setData"), 1, fnBody); } if (nameStr == "getData") { @@ -169,12 +204,16 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) } if (!args[0].isObject()) { - throw jsi::JSError(rt, "getData: Expected array to be an object (TypedArray)"); + throw jsi::JSError( + rt, + "getData: Expected array to be an object (TypedArray)"); } const jsi::Object dataObj = args[0].asObject(rt); if (!dataObj.hasProperty(rt, "buffer")) { - throw jsi::JSError(rt, "getData: Expected a TypedArray with a 'buffer' property"); + throw jsi::JSError( + rt, + "getData: Expected a TypedArray with a 'buffer' property"); } const jsi::ArrayBuffer buffer = dataObj.getProperty(rt, "buffer").asObject(rt).getArrayBuffer(rt); @@ -184,22 +223,31 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) if (dataObj.hasProperty(rt, "byteOffset")) { auto byteOffsetValue = dataObj.getProperty(rt, "byteOffset"); if (!byteOffsetValue.isNumber()) { - throw jsi::JSError(rt, "getData: Expected 'byteOffset' to be a number"); + throw jsi::JSError( + rt, + "getData: Expected 'byteOffset' to be a number"); } - byteOffset = static_cast(byteOffsetValue.asNumber()); + byteOffset = + static_cast(byteOffsetValue.asNumber()); } if (dataObj.hasProperty(rt, "byteLength")) { auto byteLengthValue = dataObj.getProperty(rt, "byteLength"); if (!byteLengthValue.isNumber()) { - throw jsi::JSError(rt, "getData: Expected 'byteLength' to be a number"); + throw jsi::JSError( + rt, + "getData: Expected 'byteLength' to be a number"); } - byteLength = static_cast(byteLengthValue.asNumber()); + byteLength = + static_cast(byteLengthValue.asNumber()); } - std::shared_lock lock(self->mutex_, std::try_to_lock); + std::shared_lock lock(self->mutex_, + std::try_to_lock); if (!lock.owns_lock()) { - throw jsi::JSError(rt, "getData: Tensor is currently in use and cannot be read"); + throw jsi::JSError( + rt, + "getData: Tensor is currently in use and cannot be read"); } if (!self->data_) { @@ -213,22 +261,30 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) throw jsi::JSError(rt, errorMsg); } - std::memcpy(buffer.data(rt) + byteOffset, self->data_.get(), byteLength); + std::memcpy(buffer.data(rt) + byteOffset, self->data_, + byteLength); return jsi::Value(rt, args[0].asObject(rt)); }; - return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "getData"), 1, fnBody); + return jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "getData"), 1, fnBody); } if (nameStr == "through") { auto self = shared_from_this(); - auto fnBody = [self](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value { + auto fnBody = [self]( + jsi::Runtime &rt, const jsi::Value &thisVal, + const jsi::Value *args, + size_t count) -> jsi::Value { if (count < 1) { - throw jsi::JSError(rt, "through: Usage: through(fn, ...args)"); + throw jsi::JSError(rt, + "through: Usage: through(fn, ...args)"); } - if (!args[0].isObject() || !args[0].asObject(rt).isFunction(rt)) { - throw jsi::JSError(rt, "through: First argument must be a function"); + if (!args[0].isObject() || + !args[0].asObject(rt).isFunction(rt)) { + throw jsi::JSError( + rt, "through: First argument must be a function"); } auto fn = args[0].asObject(rt).asFunction(rt); @@ -240,17 +296,25 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) fnArgs.emplace_back(rt, args[i]); } - return fn.call(rt, static_cast(fnArgs.data()), fnArgs.size()); + return fn.call(rt, + static_cast(fnArgs.data()), + fnArgs.size()); }; - return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "through"), 1, fnBody); + return jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "through"), 1, fnBody); } if (nameStr == "throughIf") { auto self = shared_from_this(); - auto fnBody = [self](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value { + auto fnBody = [self]( + jsi::Runtime &rt, const jsi::Value &thisVal, + const jsi::Value *args, + size_t count) -> jsi::Value { if (count < 2) { - throw jsi::JSError(rt, "throughIf: Usage: throughIf(pred, fn, ...args)"); + throw jsi::JSError( + rt, + "throughIf: Usage: throughIf(pred, fn, ...args)"); } const bool pred = args[0].asBool(); @@ -258,8 +322,10 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) return jsi::Value(rt, thisVal); } - if (!args[1].isObject() || !args[1].asObject(rt).isFunction(rt)) { - throw jsi::JSError(rt, "throughIf: Second argument must be a function"); + if (!args[1].isObject() || + !args[1].asObject(rt).isFunction(rt)) { + throw jsi::JSError( + rt, "throughIf: Second argument must be a function"); } auto fn = args[1].asObject(rt).asFunction(rt); @@ -271,10 +337,13 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) fnArgs.emplace_back(rt, args[i]); } - return fn.call(rt, static_cast(fnArgs.data()), fnArgs.size()); + return fn.call(rt, + static_cast(fnArgs.data()), + fnArgs.size()); }; - return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "throughIf"), 2, fnBody); + return jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "throughIf"), 2, fnBody); } if (nameStr == "dispose") { @@ -287,22 +356,95 @@ jsi::Value TensorHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &name) std::unique_lock lock(self->mutex_); if (!self->data_) { - throw jsi::JSError(rt, "dispose: Tensor has already been disposed"); + throw jsi::JSError( + rt, "dispose: Tensor has already been disposed"); + } + + if (self->storage_) { + self->storage_.reset(); } - self->tensor_.reset(); - self->data_.reset(); + self->data_ = nullptr; + self->et_tensor_.reset(); return jsi::Value::undefined(); }; - return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "dispose"), 0, fnBody); + return jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "dispose"), 0, fnBody); + } + + if (nameStr == "view") { + auto self = shared_from_this(); + auto fnBody = [self]( + jsi::Runtime &rt, const jsi::Value & /*thisVal*/, + const jsi::Value *args, + size_t count) -> jsi::Value { + if (count < 1 || count > 2) { + throw jsi::JSError(rt, "view: Usage: view(shape, offset?)"); + } + + if (!self->data_) { + throw jsi::JSError(rt, + "view: Tensor has been disposed"); + } + + if (!args[0].isObject() || + !args[0].asObject(rt).isArray(rt)) { + throw jsi::JSError( + rt, "view: Expected shape as an array of integers"); + } + + auto shapeArray = args[0].asObject(rt).asArray(rt); + Shape shape; + for (size_t i = 0; i < shapeArray.length(rt); ++i) { + auto dimValue = shapeArray.getValueAtIndex(rt, i); + if (!dimValue.isNumber()) { + throw jsi::JSError( + rt, + "view: Shape array must contain only numbers"); + } + + if (dimValue.asNumber() <= 0) { + throw jsi::JSError( + rt, + "view: Shape dimensions must be positive integers"); + } + + shape.push_back( + static_cast(dimValue.asNumber())); + } + + size_t offset = 0; + if (count == 2) { + if (!args[1].isNumber()) { + throw jsi::JSError(rt, + "view: Expected offset to be a " + "number"); + } + offset = static_cast(args[1].asNumber()); + } + + try { + auto viewHost = std::make_shared( + self->data_ + offset, + std::move(shape), + self->dtype_); + return jsi::Object::createFromHostObject(rt, viewHost); + } catch (const std::exception &e) { + throw jsi::JSError(rt, + "view: " + std::string(e.what())); + } + }; + return jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, "view"), 2, fnBody); } return jsi::Value::undefined(); } -std::vector TensorHostObject::getPropertyNames(jsi::Runtime &rt) { - std::vector properties; +std::vector TensorHostObject::getPropertyNames( + jsi::Runtime &rt) { + std::vector properties; properties.push_back(jsi::PropNameID::forAscii(rt, "shape")); properties.push_back(jsi::PropNameID::forAscii(rt, "dtype")); properties.push_back(jsi::PropNameID::forAscii(rt, "numel")); @@ -312,6 +454,7 @@ std::vector TensorHostObject::getPropertyNames(jsi::R properties.push_back(jsi::PropNameID::forAscii(rt, "through")); properties.push_back(jsi::PropNameID::forAscii(rt, "throughIf")); properties.push_back(jsi::PropNameID::forAscii(rt, "dispose")); + properties.push_back(jsi::PropNameID::forAscii(rt, "view")); return properties; } @@ -319,42 +462,59 @@ void install_createTensor(jsi::Runtime &rt, jsi::Object &module) { const auto *name = "createTensor"; auto fnBody = [](jsi::Runtime &rt, const jsi::Value & /*thisVal*/, const jsi::Value *args, size_t count) -> jsi::Value { if (count != 2) { - throw jsi::JSError(rt, "createTensor: Usage: createTensor(shape, dtype)"); + throw jsi::JSError(rt, + "createTensor: Usage: createTensor(shape, " + "dtype)"); } - if (!args[0].isObject() || !args[0].asObject(rt).isArray(rt)) { - throw jsi::JSError(rt, "createTensor: Expected shape as an array of integers"); + if (!args[0].isObject() || + !args[0].asObject(rt).isArray(rt)) { + throw jsi::JSError( + rt, "createTensor: Expected shape as an array of integers"); } if (!args[1].isString()) { - throw jsi::JSError(rt, "createTensor: Expected dtype as a string"); + throw jsi::JSError( + rt, "createTensor: Expected dtype as a string"); } auto shapeArray = args[0].asObject(rt).asArray(rt); - std::vector shape; + Shape shape; for (size_t i = 0; i < shapeArray.length(rt); ++i) { auto dimValue = shapeArray.getValueAtIndex(rt, i); if (!dimValue.isNumber()) { - throw jsi::JSError(rt, "createTensor: Shape array must contain only numbers"); + throw jsi::JSError( + rt, + "createTensor: Shape array must contain only numbers"); } if (dimValue.asNumber() <= 0) { - throw jsi::JSError(rt, "createTensor: Shape dimensions must be positive integers"); + throw jsi::JSError( + rt, + "createTensor: Shape dimensions must be positive " + "integers"); } - shape.push_back(static_cast(dimValue.asNumber())); + shape.push_back( + static_cast(dimValue.asNumber())); } try { - const auto dtype = rnexecutorch::core::types::parseDType(args[1].asString(rt).utf8(rt)); - auto tensorHostObject = std::make_shared(shape, dtype); + DType dtype(args[1].asString(rt).utf8(rt)); + auto tensorHostObject = + std::make_shared(std::move(shape), dtype); return jsi::Object::createFromHostObject(rt, tensorHostObject); } catch (const std::exception &e) { - throw jsi::JSError(rt, "createTensor: Error creating tensor: " + std::string(e.what())); + throw jsi::JSError( + rt, + "createTensor: Error creating tensor: " + + std::string(e.what())); } }; - auto fn = jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, name), 2, fnBody); + auto fn = jsi::Function::createFromHostFunction( + rt, jsi::PropNameID::forAscii(rt, name), 2, fnBody); module.setProperty(rt, name, fn); } + } // namespace rnexecutorch::core::tensor diff --git a/packages/react-native-executorch/cpp/core/tensor.h b/packages/react-native-executorch/cpp/core/tensor.h index 27c9af1199..1b68ef7115 100644 --- a/packages/react-native-executorch/cpp/core/tensor.h +++ b/packages/react-native-executorch/cpp/core/tensor.h @@ -1,43 +1,49 @@ #pragma once -#include #include #include #include -#include #include #include -#include -#include "dtype.h" +#include "tensor_view.h" +#include "types.h" namespace rnexecutorch::core::tensor { + /** - * JSI HostObject wrapping an ExecuTorch TensorPtr instance. - * - * Exposes methods to JavaScript for copying data, accessing properties (shape, - * dtype, numel), writing data from array buffers, reading data to array - * buffers, and disposing of underlying memory. + * A JSI HostObject that wraps an owning or non-owning tensor view. */ -class TensorHostObject : public facebook::jsi::HostObject, public std::enable_shared_from_this { +class TensorHostObject : public facebook::jsi::HostObject, + public TensorView, + public std::enable_shared_from_this { public: - rnexecutorch::core::types::DType dtype_; - std::vector shape_; - size_t numel_; + // Owning tensor allocation. + TensorHostObject(Shape shape, DType dtype); - size_t size_; - std::unique_ptr data_; // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays): owning runtime-sized byte buffer - executorch::extension::TensorPtr tensor_; + // Non-owning tensor (view) wrapping external data. + explicit TensorHostObject(const TensorView &view); // Direct + TensorHostObject(uint8_t *data, Shape shape, DType dtype); // Indirect - std::shared_mutex mutex_; + // JSI bridge methods. + facebook::jsi::Value get(facebook::jsi::Runtime &rt, + const facebook::jsi::PropNameID &name) override; + std::vector getPropertyNames( + facebook::jsi::Runtime &rt) override; + + // Owned data storage — null for views. + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) — unique_ptr is the standard owning dynamic buffer + std::unique_ptr storage_; - TensorHostObject(const std::vector &shape, rnexecutorch::core::types::DType dtype); + // ExecuTorch entry point. + executorch::extension::TensorPtr et_tensor_; - facebook::jsi::Value get(facebook::jsi::Runtime &rt, const facebook::jsi::PropNameID &name) override; - std::vector getPropertyNames(facebook::jsi::Runtime &rt) override; + std::shared_mutex mutex_; }; -void install_createTensor(facebook::jsi::Runtime &rt, facebook::jsi::Object &module); +void install_createTensor(facebook::jsi::Runtime &rt, + facebook::jsi::Object &module); + } // namespace rnexecutorch::core::tensor diff --git a/packages/react-native-executorch/cpp/core/tensor_view.cpp b/packages/react-native-executorch/cpp/core/tensor_view.cpp new file mode 100644 index 0000000000..0f31454334 --- /dev/null +++ b/packages/react-native-executorch/cpp/core/tensor_view.cpp @@ -0,0 +1,19 @@ +#include "tensor_view.h" +#include +#include +#include + +namespace rnexecutorch::core::tensor { + +TensorView::TensorView(uint8_t *data, DType dtype, Shape shape) + : dtype_(dtype), shape_(std::move(shape)), data_(data) { + if (shape_.empty()) { + throw std::invalid_argument("TensorView: shape must not be empty"); + } + + numel_ = std::accumulate(shape_.begin(), shape_.end(), + static_cast(1), std::multiplies<>()); + size_ = numel_ * dtype_.size(); +} + +} // namespace rnexecutorch::core::tensor diff --git a/packages/react-native-executorch/cpp/core/tensor_view.h b/packages/react-native-executorch/cpp/core/tensor_view.h new file mode 100644 index 0000000000..8019f97e2b --- /dev/null +++ b/packages/react-native-executorch/cpp/core/tensor_view.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "dtype.h" +#include "types.h" + +namespace rnexecutorch::core::tensor { + +class TensorView { +public: + TensorView(uint8_t *data, DType dtype, Shape shape); + virtual ~TensorView() = default; + + TensorView(const TensorView &) = delete; + TensorView &operator=(const TensorView &) = delete; + TensorView(TensorView &&) = default; + TensorView &operator=(TensorView &&) = default; + + // This class leaves a space for some additional logic if needed. + + // Metadata + DType dtype_; + Shape shape_; + size_t numel_; // Number of elements (values) in a tensor + size_t size_; // Size of a tensor (numel * dtype.size()) + + // Data (pointer - non-owning) + uint8_t *data_ = nullptr; +}; + +} // namespace rnexecutorch::core::tensor diff --git a/packages/react-native-executorch/cpp/core/types.h b/packages/react-native-executorch/cpp/core/types.h new file mode 100644 index 0000000000..6cb03635b1 --- /dev/null +++ b/packages/react-native-executorch/cpp/core/types.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +namespace rnexecutorch::core { + +// Dimension index must be strictly non-negative. +using Dimension = uint32_t; + +// Defines number of elements along a specific dimension (in a tensor). +using DSize = int32_t; + +// Shape = sequence of sizes (one per dimension). +using Shape = std::vector; + +} // namespace rnexecutorch::core diff --git a/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp b/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp index 501b9245c1..b0aab17e3a 100644 --- a/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp +++ b/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp @@ -138,12 +138,12 @@ void install_nms(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "nms: boxes and scores must have the same number of elements"); } - if (boxes->dtype_ != rnexecutorch::core::types::DType::float32 || scores->dtype_ != rnexecutorch::core::types::DType::float32) { + if (boxes->dtype_ != rnexecutorch::core::DType::float32 || scores->dtype_ != rnexecutorch::core::DType::float32) { throw jsi::JSError(rt, "nms: boxes and scores must have dtype float32"); } - const auto *boxesPtr = reinterpret_cast(boxes->data_.get()); - const auto *scoresPtr = reinterpret_cast(scores->data_.get()); + const auto *boxesPtr = reinterpret_cast(boxes->data_); + const auto *scoresPtr = reinterpret_cast(scores->data_); std::vector> candidates; candidates.reserve(static_cast(numAnchors)); @@ -342,8 +342,8 @@ void install_restrictToBox(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "restrictToBox: " + std::string(e.what())); } - ::cv::Mat srcMat(H, W, cvType, src->data_.get()); - ::cv::Mat dstMat(H, W, cvType, dst->data_.get()); + ::cv::Mat srcMat(H, W, cvType, src->data_); + ::cv::Mat dstMat(H, W, cvType, dst->data_); dstMat.setTo(::cv::Scalar::all(0)); if (!isEmpty) { diff --git a/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp b/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp index 13bf66cd81..12741945c1 100644 --- a/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp +++ b/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp @@ -15,6 +15,7 @@ namespace rnexecutorch::extensions::cv::image_ops { namespace jsi = facebook::jsi; using TensorHostObject = rnexecutorch::core::tensor::TensorHostObject; +using DType = rnexecutorch::core::DType; namespace { int interpToFlag(const std::string &interp) { @@ -146,8 +147,8 @@ void install_resize(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "resize: " + std::string(e.what())); } - const ::cv::Mat srcMat(srcH, srcW, cvType, src->data_.get()); - ::cv::Mat dstMat(dstH, dstW, cvType, dst->data_.get()); + const ::cv::Mat srcMat(srcH, srcW, cvType, src->data_); + ::cv::Mat dstMat(dstH, dstW, cvType, dst->data_); if (mode == "stretch") { ::cv::resize(srcMat, dstMat, dstMat.size(), 0, 0, interpFlag); @@ -313,8 +314,8 @@ void install_cvtColor(jsi::Runtime &rt, jsi::Object &module) { cvDstType = CV_MAKETYPE(dtypeToCvDepth(dst->dtype_), dstC); flag = codeToColorConversionFlag(code); - const ::cv::Mat srcMat(srcH, srcW, cvSrcType, src->data_.get()); - ::cv::Mat dstMat(srcH, srcW, cvDstType, dst->data_.get()); + const ::cv::Mat srcMat(srcH, srcW, cvSrcType, src->data_); + ::cv::Mat dstMat(srcH, srcW, cvDstType, dst->data_); ::cv::cvtColor(srcMat, dstMat, flag); } catch (const std::invalid_argument &e) { @@ -397,13 +398,13 @@ void install_toChannelsFirst(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "toChannelsFirst: " + std::string(e.what())); } - const ::cv::Mat srcMat(srcH, srcW, cvType, src->data_.get()); + const ::cv::Mat srcMat(srcH, srcW, cvType, src->data_); std::vector<::cv::Mat> channels; ::cv::split(srcMat, channels); const size_t hw = static_cast(srcH) * static_cast(srcW); - const size_t elemSize = rnexecutorch::core::types::elementSize(src->dtype_); - uint8_t *dstPtr = dst->data_.get(); + const size_t elemSize = src->dtype_.size(); + uint8_t *dstPtr = dst->data_; for (size_t i = 0; std::cmp_less(i, srcC); ++i) { std::memcpy(dstPtr + i * hw * elemSize, channels[i].data, hw * elemSize); @@ -486,15 +487,15 @@ void install_toChannelsLast(jsi::Runtime &rt, jsi::Object &module) { } const size_t hw = static_cast(srcH) * static_cast(srcW); - const size_t elemSize = rnexecutorch::core::types::elementSize(src->dtype_); - uint8_t *srcPtr = src->data_.get(); + const size_t elemSize = src->dtype_.size(); + uint8_t *srcPtr = src->data_; std::vector<::cv::Mat> channels; for (size_t i = 0; std::cmp_less(i, srcC); ++i) { channels.emplace_back(srcH, srcW, cvDepth, srcPtr + i * hw * elemSize); } - ::cv::Mat dstMat(dstH, dstW, CV_MAKETYPE(cvDepth, dstC), dst->data_.get()); + ::cv::Mat dstMat(dstH, dstW, CV_MAKETYPE(cvDepth, dstC), dst->data_); ::cv::merge(channels, dstMat); return jsi::Value(rt, args[1]); @@ -607,10 +608,10 @@ void install_normalize(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "normalize: " + std::string(e.what())); } - const size_t srcElemSize = rnexecutorch::core::types::elementSize(src->dtype_); - const size_t dstElemSize = rnexecutorch::core::types::elementSize(dst->dtype_); - uint8_t *srcPtr = src->data_.get(); - uint8_t *dstPtr = dst->data_.get(); + const size_t srcElemSize = src->dtype_.size(); + const size_t dstElemSize = dst->dtype_.size(); + uint8_t *srcPtr = src->data_; + uint8_t *dstPtr = dst->data_; const size_t plane = static_cast(h) * static_cast(w); for (size_t ch = 0; std::cmp_less(ch, c); ++ch) { @@ -644,10 +645,10 @@ void install_applyColormap(jsi::Runtime &rt, jsi::Object &module) { auto dst = args[1].asObject(rt).getHostObject(rt); constexpr size_t numRgbaChannels = 4; - if (src->dtype_ != rnexecutorch::core::types::DType::int32) { + if (src->dtype_ != rnexecutorch::core::DType::int32) { throw jsi::JSError(rt, "applyColormap: src must be int32"); } - if (dst->dtype_ != rnexecutorch::core::types::DType::uint8) { + if (dst->dtype_ != rnexecutorch::core::DType::uint8) { throw jsi::JSError(rt, "applyColormap: dst must be uint8"); } if (dst->numel_ != src->numel_ * numRgbaChannels) { @@ -695,8 +696,8 @@ void install_applyColormap(jsi::Runtime &rt, jsi::Object &module) { const size_t pixels = src->numel_; - const auto *srcData = reinterpret_cast(src->data_.get()); - uint8_t *dstData = dst->data_.get(); + const auto *srcData = reinterpret_cast(src->data_); + uint8_t *dstData = dst->data_; for (size_t i = 0; i < pixels; ++i) { const int32_t idx = srcData[i]; diff --git a/packages/react-native-executorch/cpp/extensions/cv/utils.h b/packages/react-native-executorch/cpp/extensions/cv/utils.h index a29961d0b6..256f485bf8 100644 --- a/packages/react-native-executorch/cpp/extensions/cv/utils.h +++ b/packages/react-native-executorch/cpp/extensions/cv/utils.h @@ -6,14 +6,16 @@ namespace rnexecutorch::extensions::cv { -inline int dtypeToCvDepth(rnexecutorch::core::types::DType dtype) { +inline int dtypeToCvDepth(rnexecutorch::core::DType dtype) { switch (dtype) { - case rnexecutorch::core::types::DType::uint8: + case rnexecutorch::core::DType::uint8: return CV_8U; - case rnexecutorch::core::types::DType::int32: + case rnexecutorch::core::DType::int32: return CV_32S; - case rnexecutorch::core::types::DType::float32: + case rnexecutorch::core::DType::float32: return CV_32F; + default: + break; } throw std::invalid_argument("unsupported dtype"); } diff --git a/packages/react-native-executorch/cpp/extensions/math/operations.cpp b/packages/react-native-executorch/cpp/extensions/math/operations.cpp index 4607af49b9..f83335d6a7 100644 --- a/packages/react-native-executorch/cpp/extensions/math/operations.cpp +++ b/packages/react-native-executorch/cpp/extensions/math/operations.cpp @@ -41,7 +41,7 @@ void install_sigmoid(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "sigmoid: src and dst must have the same dtype"); } - if (src->dtype_ != rnexecutorch::core::types::DType::float32) { + if (src->dtype_ != rnexecutorch::core::DType::float32) { throw jsi::JSError(rt, "sigmoid: only float32 tensors are supported"); } @@ -64,8 +64,8 @@ void install_sigmoid(jsi::Runtime &rt, jsi::Object &module) { } const auto countElements = src->numel_; - const auto *srcData = reinterpret_cast(src->data_.get()); - auto *dstData = reinterpret_cast(dst->data_.get()); + const auto *srcData = reinterpret_cast(src->data_); + auto *dstData = reinterpret_cast(dst->data_); for (size_t i = 0; i < countElements; ++i) { dstData[i] = 1.0f / (1.0f + std::exp(-srcData[i])); @@ -107,7 +107,7 @@ void install_softmax(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "softmax: src and dst must have the same dtype"); } - if (src->dtype_ != rnexecutorch::core::types::DType::float32) { + if (src->dtype_ != rnexecutorch::core::DType::float32) { throw jsi::JSError(rt, "softmax: only float32 tensors are supported"); } @@ -150,8 +150,8 @@ void install_softmax(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "softmax: dst tensor has been disposed"); } - const auto *srcData = reinterpret_cast(src->data_.get()); - auto *dstData = reinterpret_cast(dst->data_.get()); + const auto *srcData = reinterpret_cast(src->data_); + auto *dstData = reinterpret_cast(dst->data_); const auto axisDim = static_cast(src->shape_[axisIdx]); if (axisDim == 0) { @@ -218,11 +218,11 @@ void install_argmax(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "argmax: In-place operations (src == dst) are not supported."); } - if (src->dtype_ != rnexecutorch::core::types::DType::float32) { + if (src->dtype_ != rnexecutorch::core::DType::float32) { throw jsi::JSError(rt, "argmax: src must be float32"); } - if (dst->dtype_ != rnexecutorch::core::types::DType::int32) { + if (dst->dtype_ != rnexecutorch::core::DType::int32) { throw jsi::JSError(rt, "argmax: dst must be int32"); } @@ -263,7 +263,7 @@ void install_argmax(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "argmax: dst tensor has been disposed"); } - const auto *srcData = reinterpret_cast(src->data_.get()); + const auto *srcData = reinterpret_cast(src->data_); const auto axisDim = static_cast(src->shape_[axisIdx]); if (axisDim == 0) { @@ -279,7 +279,7 @@ void install_argmax(jsi::Runtime &rt, jsi::Object &module) { inner *= static_cast(src->shape_[i]); } - auto *dstData = reinterpret_cast(dst->data_.get()); + auto *dstData = reinterpret_cast(dst->data_); // DO NOT swap loop order. This structure intentionally prioritizes the // most common case (axis = -1, inner = 1) for sequential access. @@ -334,11 +334,11 @@ void install_threshold(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "threshold: src and dst must have the same shape"); } - if (src->dtype_ != rnexecutorch::core::types::DType::float32) { + if (src->dtype_ != rnexecutorch::core::DType::float32) { throw jsi::JSError(rt, "threshold: src must be a float32 tensor"); } - if (dst->dtype_ != rnexecutorch::core::types::DType::float32) { + if (dst->dtype_ != rnexecutorch::core::DType::float32) { throw jsi::JSError(rt, "threshold: dst must be a float32 tensor"); } @@ -356,8 +356,8 @@ void install_threshold(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "threshold: dst tensor has been disposed"); } - const auto *srcData = reinterpret_cast(src->data_.get()); - auto *dstData = reinterpret_cast(dst->data_.get()); + const auto *srcData = reinterpret_cast(src->data_); + auto *dstData = reinterpret_cast(dst->data_); for (size_t i = 0; i < src->numel_; ++i) { dstData[i] = (srcData[i] >= thresholdVal) ? 1.0f : 0.0f; diff --git a/packages/react-native-executorch/src/core/tensor.ts b/packages/react-native-executorch/src/core/tensor.ts index 8f5c58e10c..eab6dce62c 100644 --- a/packages/react-native-executorch/src/core/tensor.ts +++ b/packages/react-native-executorch/src/core/tensor.ts @@ -6,7 +6,7 @@ declare const tensorBrand: unique symbol; * Element data type of a {@link Tensor}. * @category Types */ -export type DType = 'float32' | 'uint8' | 'int32'; +export type DType = 'float32' | 'uint8' | 'int32' | 'int64' | 'bool'; /** * A native ExecuTorch tensor allocated in C++ memory. @@ -53,7 +53,7 @@ export type Tensor = { * tensor's size. * @returns `this` tensor. */ - setData(src: Float32Array | Uint8Array | Int32Array): Tensor; + setData(src: Float32Array | Uint8Array | Int32Array | BigInt64Array): Tensor; /** * Copies data out of this tensor's native buffer into a typed array. @@ -62,7 +62,7 @@ export type Tensor = { * tensor's size. * @returns The same `dst` array, now filled with tensor data. */ - getData(dst: T): T; + getData(dst: T): T; /** * Passes `this` tensor as the first argument to `fn` and returns the result. @@ -90,6 +90,18 @@ export type Tensor = { ...args: Args ): Tensor; + /** + * Creates a view over a sub-region of this tensor's buffer without copying. + * + * The view shares the parent's memory, so writes through either tensor are + * visible to the other. The view does not own its memory — dispose the + * parent tensor when no views are needed. + * @param shape The logical shape of the view (dimensions <= parent). + * @param offset Byte offset into the parent buffer (defaults to 0). + * @returns A new tensor view sharing the parent's memory. + */ + view(shape: readonly number[], offset?: number): Tensor; + /** * Prevents plain JS objects from being cast as Tensors. Tensors should only * be created via the `tensor` function exported from this module. @@ -114,8 +126,8 @@ export type Tensor = { */ export function tensor( dtype: DType, - shape: number[], - src?: Float32Array | Uint8Array | Int32Array + shape: readonly number[], + src?: Float32Array | Uint8Array | Int32Array | BigInt64Array ): Tensor { 'worklet'; const t: Tensor = rnexecutorchJsi.createTensor(shape, dtype);