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 593c86bbd2..1c6a624db8 100644 --- a/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp +++ b/packages/react-native-executorch/cpp/extensions/cv/box_ops.cpp @@ -3,10 +3,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -60,9 +62,13 @@ NmsType parseNmsType(const std::string &s) { throw std::invalid_argument("unsupported nmsType '" + s + "'"); } -std::array decodeToXyxy( - float a, float b, float c, float d, - BoxFormat format) { +constexpr size_t kBoxCoords = 4; + +std::array decodeToXyxy(std::span box, BoxFormat format) { + const float a = box[0]; + const float b = box[1]; + const float c = box[2]; + const float d = box[3]; switch (format) { case BoxFormat::XYXY: return {a, b, c, d}; @@ -104,14 +110,20 @@ void install_nms(jsi::Runtime &rt, jsi::Object &module) { } std::int32_t numAnchors = scores->shape_[0]; - const auto *boxesPtr = reinterpret_cast(boxes->data_.get()); - const auto *scoresPtr = reinterpret_cast(scores->data_.get()); + const std::span boxesData(reinterpret_cast(boxes->data_.get()), boxes->numel_); + const std::span scoresData(reinterpret_cast(scores->data_.get()), scores->numel_); + + // Coordinates of anchor `i` occupy the i-th 4-element row of `boxesData`. + auto boxAt = [boxesData](std::int32_t index) -> std::span { + return std::span( + boxesData.subspan(static_cast(index) * kBoxCoords, kBoxCoords)); + }; std::vector> candidates; candidates.reserve(static_cast(numAnchors)); - for (size_t idx = 0; std::cmp_less(idx, numAnchors); ++idx) { - float score = scoresPtr[idx]; + for (std::int32_t idx = 0; idx < numAnchors; ++idx) { + const float score = scoresData[static_cast(idx)]; if (score >= confidenceThreshold) { candidates.emplace_back(idx, score); @@ -134,12 +146,7 @@ void install_nms(jsi::Runtime &rt, jsi::Object &module) { std::int32_t idxI = candidates[i].first; - auto [xminA, yminA, xmaxA, ymaxA] = decodeToXyxy( - boxesPtr[idxI * 4 + 0], - boxesPtr[idxI * 4 + 1], - boxesPtr[idxI * 4 + 2], - boxesPtr[idxI * 4 + 3], - boxFormat); + auto [xminA, yminA, xmaxA, ymaxA] = decodeToXyxy(boxAt(idxI), boxFormat); const float areaA = (xmaxA - xminA) * (ymaxA - yminA); @@ -152,12 +159,7 @@ void install_nms(jsi::Runtime &rt, jsi::Object &module) { std::int32_t idxJ = candidates[j].first; - auto [xminB, yminB, xmaxB, ymaxB] = decodeToXyxy( - boxesPtr[idxJ * 4 + 0], - boxesPtr[idxJ * 4 + 1], - boxesPtr[idxJ * 4 + 2], - boxesPtr[idxJ * 4 + 3], - boxFormat); + auto [xminB, yminB, xmaxB, ymaxB] = decodeToXyxy(boxAt(idxJ), boxFormat); const float areaB = (xmaxB - xminB) * (ymaxB - yminB); @@ -220,7 +222,7 @@ void install_restrictToBox(jsi::Runtime &rt, jsi::Object &module) { auto dstLock = tensor::tryLockUnique(rt, "restrictToBox: dst", dst); auto boxVec = conversions::asVector(rt, "restrictToBox: boxTuple", args[2]); - if (boxVec.size() != 4) { + if (boxVec.size() != kBoxCoords) { throw jsi::JSError(rt, "restrictToBox: boxTuple must contain exactly 4 coordinates"); } @@ -232,12 +234,7 @@ void install_restrictToBox(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, std::format("restrictToBox: {}", e.what())); } - float a = boxVec[0]; - float b = boxVec[1]; - float c = boxVec[2]; - float d = boxVec[3]; - - auto [xmin, ymin, xmax, ymax] = decodeToXyxy(a, b, c, d, boxFormat); + auto [xmin, ymin, xmax, ymax] = decodeToXyxy(std::span(boxVec), boxFormat); int32_t H = src->shape_[0]; int32_t W = src->shape_[1]; 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 25716c576c..151050475a 100644 --- a/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp +++ b/packages/react-native-executorch/cpp/extensions/cv/image_ops.cpp @@ -2,9 +2,11 @@ #include #include -#include +#include +#include #include #include +#include #include #include @@ -261,10 +263,12 @@ void install_toChannelsFirst(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 *dstPtr = dst->data_.get(); + const size_t planeBytes = hw * elemSize; + const std::span dstBytes(dst->data_.get(), dst->size_); for (size_t i = 0; std::cmp_less(i, srcC); ++i) { - std::memcpy(dstPtr + i * hw * elemSize, channels[i].data, hw * elemSize); + const std::span plane(channels[i].data, planeBytes); + std::ranges::copy(plane, dstBytes.subspan(i * planeBytes, planeBytes).begin()); } } catch (const std::exception &e) { throw jsi::JSError(rt, "toChannelsFirst: " + std::string(e.what())); @@ -299,11 +303,12 @@ 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 planeBytes = hw * elemSize; + const std::span srcBytes(src->data_.get(), src->size_); 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); + channels.emplace_back(srcH, srcW, cvDepth, srcBytes.subspan(i * planeBytes, planeBytes).data()); } ::cv::Mat dstMat(srcH, srcW, CV_MAKETYPE(cvDepth, srcC), dst->data_.get()); @@ -362,13 +367,18 @@ void install_normalize(jsi::Runtime &rt, jsi::Object &module) { 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 std::span srcBytes(src->data_.get(), src->size_); + const std::span dstBytes(dst->data_.get(), dst->size_); const size_t plane = static_cast(h) * static_cast(w); + const size_t srcPlaneBytes = plane * srcElemSize; + const size_t dstPlaneBytes = plane * dstElemSize; + for (size_t ch = 0; std::cmp_less(ch, c); ++ch) { - const ::cv::Mat srcChannel(h, w, srcDepthType, srcPtr + ch * plane * srcElemSize); - ::cv::Mat dstChannel(h, w, dstDepthType, dstPtr + ch * plane * dstElemSize); + const ::cv::Mat srcChannel(h, w, srcDepthType, + srcBytes.subspan(ch * srcPlaneBytes, srcPlaneBytes).data()); + ::cv::Mat dstChannel(h, w, dstDepthType, + dstBytes.subspan(ch * dstPlaneBytes, dstPlaneBytes).data()); srcChannel.convertTo(dstChannel, dstDepthType, alpha[ch], beta[ch]); } @@ -411,21 +421,18 @@ 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 std::span srcData(reinterpret_cast(src->data_.get()), src->numel_); + const std::span dstData(dst->data_.get(), dst->numel_); - for (size_t i = 0; i < pixels; ++i) { + for (size_t i = 0; i < srcData.size(); ++i) { const int32_t idx = srcData[i]; if (idx < 0 || std::cmp_greater_equal(idx, numColors)) { throw jsi::JSError(rt, "applyColormap: tensor contains class index (" + std::to_string(idx) + ") that exceeds provided colormap size (" + std::to_string(numColors) + ")"); } - for (size_t c = 0; c < numRgbaChannels; ++c) { - dstData[i * numRgbaChannels + c] = lut[static_cast(idx)][c]; - } + std::ranges::copy(lut[static_cast(idx)], + dstData.subspan(i * numRgbaChannels, numRgbaChannels).begin()); } return jsi::Value(rt, args[1]); diff --git a/packages/react-native-executorch/cpp/extensions/math/operations.cpp b/packages/react-native-executorch/cpp/extensions/math/operations.cpp index 739f2ac626..76fb2f8e40 100644 --- a/packages/react-native-executorch/cpp/extensions/math/operations.cpp +++ b/packages/react-native-executorch/cpp/extensions/math/operations.cpp @@ -2,8 +2,11 @@ #include #include +#include +#include #include #include +#include #include #include "core/tensor.h" @@ -30,13 +33,12 @@ void install_sigmoid(jsi::Runtime &rt, jsi::Object &module) { auto srcLock = tensor::tryLockShared(rt, "sigmoid: src", src); auto dstLock = tensor::tryLockUnique(rt, "sigmoid: dst", dst); - const auto countElements = src->numel_; - const auto *srcData = reinterpret_cast(src->data_.get()); - auto *dstData = reinterpret_cast(dst->data_.get()); + const std::span srcData(reinterpret_cast(src->data_.get()), src->numel_); + const std::span dstData(reinterpret_cast(dst->data_.get()), dst->numel_); - for (size_t i = 0; i < countElements; ++i) { - dstData[i] = 1.0f / (1.0f + std::exp(-srcData[i])); - } + std::ranges::transform(srcData, dstData.begin(), [](const float value) { + return 1.0f / (1.0f + std::exp(-value)); + }); return jsi::Value(rt, args[1]); }; @@ -75,8 +77,8 @@ void install_softmax(jsi::Runtime &rt, jsi::Object &module) { } const auto axisIdx = static_cast(axis); - const auto *srcData = reinterpret_cast(src->data_.get()); - auto *dstData = reinterpret_cast(dst->data_.get()); + const std::span srcData(reinterpret_cast(src->data_.get()), src->numel_); + const std::span dstData(reinterpret_cast(dst->data_.get()), dst->numel_); const auto axisDim = static_cast(src->shape_[axisIdx]); if (axisDim == 0) { @@ -93,24 +95,30 @@ void install_softmax(jsi::Runtime &rt, jsi::Object &module) { inner *= static_cast(src->shape_[i]); } + // Elements along `axis` are strided by `inner`, so a lane spans from its + // first element to its last: (axisDim - 1) * inner + 1 elements. + const size_t laneSize = (axisDim - 1) * inner + 1; + for (size_t outerIndex = 0; outerIndex < outer; ++outerIndex) { for (size_t innerIndex = 0; innerIndex < inner; ++innerIndex) { const size_t base = outerIndex * axisDim * inner + innerIndex; + const auto srcLane = srcData.subspan(base, laneSize); + const auto dstLane = dstData.subspan(base, laneSize); float maxValue = -std::numeric_limits::infinity(); for (size_t axisIndex = 0; axisIndex < axisDim; ++axisIndex) { - maxValue = std::max(maxValue, srcData[base + axisIndex * inner]); + maxValue = std::max(maxValue, srcLane[axisIndex * inner]); } float sum = 0.0f; for (size_t axisIndex = 0; axisIndex < axisDim; ++axisIndex) { - const float value = std::exp(srcData[base + axisIndex * inner] - maxValue); - dstData[base + axisIndex * inner] = value; + const float value = std::exp(srcLane[axisIndex * inner] - maxValue); + dstLane[axisIndex * inner] = value; sum += value; } for (size_t axisIndex = 0; axisIndex < axisDim; ++axisIndex) { - dstData[base + axisIndex * inner] /= sum; + dstLane[axisIndex * inner] /= sum; } } } @@ -154,7 +162,7 @@ void install_argmax(jsi::Runtime &rt, jsi::Object &module) { throw jsi::JSError(rt, "argmax: dst shape must match src shape but with axis dimension 1"); } - const auto *srcData = reinterpret_cast(src->data_.get()); + const std::span srcData(reinterpret_cast(src->data_.get()), src->numel_); const auto axisDim = static_cast(src->shape_[axisIdx]); if (axisDim == 0) { @@ -170,16 +178,22 @@ void install_argmax(jsi::Runtime &rt, jsi::Object &module) { inner *= static_cast(src->shape_[i]); } - auto *dstData = reinterpret_cast(dst->data_.get()); + const std::span dstData(reinterpret_cast(dst->data_.get()), dst->numel_); + + // Elements along `axis` are strided by `inner`, so a lane spans from its + // first element to its last: (axisDim - 1) * inner + 1 elements. + const size_t laneSize = (axisDim - 1) * inner + 1; // DO NOT swap loop order. This structure intentionally prioritizes the // most common case (axis = -1, inner = 1) for sequential access. for (size_t o = 0; o < outer; ++o) { for (size_t i = 0; i < inner; ++i) { + const auto srcLane = srcData.subspan(o * axisDim * inner + i, laneSize); + float maxVal = -std::numeric_limits::infinity(); int32_t maxIdx = 0; for (size_t d = 0; d < axisDim; ++d) { - const float val = srcData[o * axisDim * inner + d * inner + i]; + const float val = srcLane[d * inner]; if (val > maxVal) { maxVal = val; maxIdx = static_cast(d); @@ -210,12 +224,12 @@ void install_threshold(jsi::Runtime &rt, jsi::Object &module) { auto thresholdVal = conversions::asType(rt, "threshold: threshold", args[2]); - const auto *srcData = reinterpret_cast(src->data_.get()); - auto *dstData = reinterpret_cast(dst->data_.get()); + const std::span srcData(reinterpret_cast(src->data_.get()), src->numel_); + const std::span dstData(reinterpret_cast(dst->data_.get()), dst->numel_); - for (size_t i = 0; i < src->numel_; ++i) { - dstData[i] = (srcData[i] >= thresholdVal) ? 1.0f : 0.0f; - } + std::ranges::transform(srcData, dstData.begin(), [thresholdVal](const float value) { + return (value >= thresholdVal) ? 1.0f : 0.0f; + }); return jsi::Value(rt, args[1]); }; diff --git a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp index a1ceab4518..e17109e86f 100644 --- a/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp +++ b/packages/react-native-executorch/cpp/extensions/nlp/tokenizer.cpp @@ -47,6 +47,14 @@ std::string toString(tokenizers::Error error) { } return "Unknown(" + std::to_string(static_cast(error)) + ")"; } + +template +T unwrap(jsi::Runtime &rt, const std::string &ctx, tokenizers::Result result) { + if (!result.ok()) { + throw jsi::JSError(rt, std::format("{}: {}", ctx, toString(result.error()))); + } + return std::move(result.get()); +} } // namespace TokenizerHostObject::TokenizerHostObject(std::string tokenizerPath) @@ -83,12 +91,10 @@ jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &nam } auto text = conversions::asType(rt, "encode: text", args[0]); - auto result = self->tokenizer_->encode(text, kNumAddedBosTokens, kNumAddedEosTokens); - if (!result.ok()) { - throw jsi::JSError(rt, std::format("encode: Failed to encode input: {}", toString(result.error()))); - } + auto tokens = unwrap(rt, "encode: Failed to encode input", + self->tokenizer_->encode(text, kNumAddedBosTokens, kNumAddedEosTokens)); - return conversions::toJsiArray(rt, result.get()); + return conversions::toJsiArray(rt, tokens); }; return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "encode"), 1, fnBody); } @@ -121,12 +127,10 @@ jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &nam return jsi::String::createFromUtf8(rt, ""); } - auto result = self->tokenizer_->decode(tokens, skipSpecialTokens); - if (!result.ok()) { - throw jsi::JSError(rt, std::format("decode: Failed to decode tokens: {}", toString(result.error()))); - } + auto text = unwrap(rt, "decode: Failed to decode tokens", + self->tokenizer_->decode(tokens, skipSpecialTokens)); - return jsi::String::createFromUtf8(rt, result.get()); + return jsi::String::createFromUtf8(rt, text); }; return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "decode"), 1, fnBody); } @@ -169,12 +173,10 @@ jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &nam } auto tokenId = conversions::asType(rt, "idToToken: id", args[0]); - auto result = self->tokenizer_->id_to_piece(tokenId); - if (!result.ok()) { - throw jsi::JSError(rt, std::format("idToToken: Failed to convert id to token: {}", toString(result.error()))); - } + auto token = unwrap(rt, "idToToken: Failed to convert id to token", + self->tokenizer_->id_to_piece(tokenId)); - return jsi::String::createFromUtf8(rt, result.get()); + return jsi::String::createFromUtf8(rt, token); }; return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "idToToken"), 1, fnBody); } @@ -196,12 +198,10 @@ jsi::Value TokenizerHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &nam } auto token = conversions::asType(rt, "tokenToId: token", args[0]); - auto result = self->tokenizer_->piece_to_id(token); - if (!result.ok()) { - throw jsi::JSError(rt, std::format("tokenToId: Failed to convert token to id: {}", toString(result.error()))); - } + auto tokenId = unwrap(rt, "tokenToId: Failed to convert token to id", + self->tokenizer_->piece_to_id(token)); - return static_cast(result.get()); + return static_cast(tokenId); }; return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "tokenToId"), 1, fnBody); }