Skip to content

Commit ae9e805

Browse files
authored
chore: scaffold rne-rewrite target branch (#1255) (#1256)
## Description Sets up the long-lived `rne-rewrite` branch that all RNE rewrite PRs will target (see #1208). Keeps the repo's yarn monorepo layout, establishes the **core first**, and leaves per-task functionality to follow as separate block PRs. - Removes `apps/` and the legacy `packages/react-native-executorch`. - Stands up the new core package by porting the `rnet-poc` lower-level API: `cpp/core` (tensor/model/dtype/install/utils), `src/core`, and the native bridge. `MyLib` → `rnexecutorch` rename throughout. OpenCV and task extensions are intentionally deferred to the per-task PRs. - CI: deletes the `apps/llm` native build workflows and the stale error-code verification step; runs CI on `rne-rewrite`. - Meta: scopes lint/typecheck/build to the core package; the `*-resource-fetcher` and `-webrtc` packages stay on disk but are excluded from CI until their surface is reworked. ### Introduces a breaking change? - [ ] Yes - [x] No (Targets the `rne-rewrite` dev branch; the published package on `main` is untouched.) ### Type of change - [ ] Bug fix (change which fixes an issue) - [ ] New feature (change which adds functionality) - [ ] Documentation update (improves or adds clarity to existing documentation) - [x] Other (chores, tests, code style improvements etc.) ### Tested on - [ ] iOS - [ ] Android ### Testing instructions CI is TypeScript-only here (no native compile). From the repo root: ``` yarn install yarn workspaces foreach --all --exclude react-native-executorch-bare-resource-fetcher --exclude react-native-executorch-expo-resource-fetcher --exclude react-native-executorch-webrtc --topological-dev run prepare yarn typecheck yarn lint ``` All four pass green. On-device native build is a follow-up. ### Related issues Closes #1255. Part of #1208. ### Checklist - [x] I have performed a self-review of my code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have updated the documentation accordingly - [ ] My changes generate no new warnings ### Additional notes - Native ExecuTorch `third-party` binaries are **not** committed (see `packages/react-native-executorch/third-party/README.md`). CI doesn't need them; provisioning them (ideally via the repo's on-demand artifact mechanism) is a follow-up. OpenCV is dropped since CV is a later block. - The `common/phonemis` submodule is retained (unchanged from `main`) for later use by the TTS block. - The error-code codegen system is parked (the POC has none); `scripts/generate-errors.ts` stays dormant for later reintroduction. - Root submodules (`tokenizers-cpp`, `googletest`) are left in place for likely reuse by the LLM block; they aren't checked out in CI. - `react-native-worklets` emits peer-dep warnings (`@babel/core`, `@react-native/metro-config`) — harmless app-integration peers, not needed to build the lib.
1 parent bce2a5e commit ae9e805

1,128 files changed

Lines changed: 3592 additions & 303044 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.agents/skills/README.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Agent Skills Directory
2+
3+
This directory contains specialized agent skills (recipes) to guide AI coding agents (like Antigravity or Claude Code) in extending the codebase cleanly and consistently.
4+
5+
## Available Skills
6+
7+
- [Core Guidelines](./core-guidelines/SKILL.md) — Symmetrical architecture, conventions, and model rules.
8+
- [Add Native Extension](./add-native-extension/SKILL.md) — C++ operations and JSI bindings.
9+
- [Add Task Pipeline](./add-task-pipeline/SKILL.md) — TypeScript task pipelines and React hooks.
10+
- [Model Schema Validation](./model-schema-validation/SKILL.md) — SymbolicTensor schemas and validation.
11+
- [Verify and Build](./verify-and-build/SKILL.md) — TypeScript typechecking, native rebuilding, and troubleshooting.
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
---
2+
name: add-native-extension
3+
description: Use when adding a C++ extension, writing native JSI host functions, registering functions in C++ install maps, or compiling native code.
4+
metadata:
5+
id: add_native_extension
6+
scope: cpp/extensions/*, src/extensions/*
7+
---
8+
9+
# Skill: Add a Native C++ Extension & JSI Bindings
10+
11+
Use this guide to add custom, performance-critical native operations in C++ and expose them to TypeScript via React Native JSI.
12+
13+
---
14+
15+
## 🚦 Architectural Guidelines
16+
17+
Before writing any C++ code, ensure you adhere to the following principles:
18+
19+
1. **Amdahl's Law & Premature Optimization**:
20+
* Evaluate what percentage of total inference/pipeline time the processing step occupies. If the preprocessing/postprocessing step takes `< 5%` of the total inference budget, write it in **pure TypeScript** to reduce codebase complexity and maintenance overhead.
21+
22+
2. **Destination Tensors & Local Memory**:
23+
* **Local Memory is Allowed**: You can allocate temporary native C++ memory (such as stack variables, `std::vector`s, or dynamic memory cleaned up before the function exits) for intermediate calculations.
24+
* **Destination Tensors**: If the operation writes dense output, the destination tensor must be pre-allocated by the caller (in TypeScript) and passed as an argument (e.g., `sigmoid(src, dst)`).
25+
* **Primitive Array Returns**: If the operation produces variable-sized non-dense outputs (like bounding box indices in Non-Maximum Suppression (NMS)), return a plain `jsi::Array` of primitives (like indices or coordinates).
26+
* *Example*: `nms(boxes, scores, options)` returns a `jsi::Array` of indices (e.g., `[0, 4, 12]`) rather than a new tensor. This avoids all native memory management overhead for variable-sized outputs.
27+
28+
## 🚫 Avoid / Anti-Patterns
29+
30+
* **Do NOT return implicitly allocated JSI Tensors:** Never return newly created `TensorHostObject` instances from C++. This forces the JavaScript layer to reason about their garbage collection and manual lifetimes, leading to native memory leaks.
31+
* **Do NOT define default parameters in C++:** Native C++ functions must never define default argument values (e.g. `axis = -1`). Define all default values explicitly in the TypeScript wrapper layer instead.
32+
* **Do NOT perform in-place mutation without safety checks:** Never allow inputs and outputs to share the same underlying instance.
33+
34+
---
35+
36+
---
37+
38+
## 🛠️ Step-by-Step Implementation
39+
40+
### Step 1: Create the Native Operation Files
41+
Under `cpp/extensions/<domain>/`, create or modify the header and implementation files for your operations:
42+
43+
#### 1. Header (`cpp/extensions/<domain>/operations.h`)
44+
Keep the header clean and specify exact JSI install functions:
45+
```cpp
46+
#pragma once
47+
#include <jsi/jsi.h>
48+
49+
namespace rnexecutorch::extensions::<domain>
50+
{
51+
void install_customOp(facebook::jsi::Runtime &rt, facebook::jsi::Object &module);
52+
}
53+
```
54+
55+
#### 2. Source (`cpp/extensions/<domain>/operations.cpp`)
56+
* Extract input and output tensors as `TensorHostObject` pointers.
57+
* Check bounds, shapes, types, and verify that the output tensor is **not the same instance** as the input (no unsafely managed in-place mutation).
58+
* Lock tensors using `std::shared_lock` (for inputs) and `std::unique_lock` (for outputs).
59+
60+
```cpp
61+
#include "operations.h"
62+
#include "core/tensor.h"
63+
#include <algorithm>
64+
65+
namespace rnexecutorch::extensions::<domain>
66+
{
67+
namespace jsi = facebook::jsi;
68+
using TensorHostObject = rnexecutorch::core::tensor::TensorHostObject;
69+
70+
void install_customOp(jsi::Runtime &rt, jsi::Object &module)
71+
{
72+
auto name = "customOp";
73+
auto fnBody = [](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value
74+
{
75+
// 1. Strict argument count validation (No default values here!)
76+
if (count != 3)
77+
{
78+
throw jsi::JSError(rt, "Usage: customOp(src, dst, factor)");
79+
}
80+
81+
// 2. Validate input and output types
82+
auto srcObj = args[0].asObject(rt);
83+
auto dstObj = args[1].asObject(rt);
84+
if (!srcObj.isHostObject<TensorHostObject>(rt) || !dstObj.isHostObject<TensorHostObject>(rt))
85+
{
86+
throw jsi::JSError(rt, "customOp: Arguments src and dst must be Tensors");
87+
}
88+
89+
auto src = srcObj.getHostObject<TensorHostObject>(rt);
90+
auto dst = dstObj.getHostObject<TensorHostObject>(rt);
91+
double factor = args[2].asNumber();
92+
93+
// 3. Prevent in-place mutations
94+
if (src.get() == dst.get())
95+
{
96+
throw jsi::JSError(rt, "customOp: In-place operations (src == dst) are not supported.");
97+
}
98+
99+
// 4. Validate metadata compatibility
100+
if (src->shape_ != dst->shape_ || src->dtype_ != dst->dtype_)
101+
{
102+
throw jsi::JSError(rt, "customOp: src and dst shape and dtype must match");
103+
}
104+
105+
// 5. Lock underlying buffers
106+
std::shared_lock<std::shared_mutex> src_lock(src->mutex_, std::try_to_lock);
107+
std::unique_lock<std::shared_mutex> dst_lock(dst->mutex_, std::try_to_lock);
108+
if (!src_lock.owns_lock() || !dst_lock.owns_lock())
109+
{
110+
throw jsi::JSError(rt, "customOp: Tensors are currently in use");
111+
}
112+
113+
if (!src->data_ || !dst->data_)
114+
{
115+
throw jsi::JSError(rt, "customOp: Tensor has been disposed");
116+
}
117+
118+
// 6. Perform the computation
119+
const float *srcData = reinterpret_cast<const float *>(src->data_.get());
120+
float *dstData = reinterpret_cast<float *>(dst->data_.get());
121+
size_t size = src->size();
122+
123+
for (size_t i = 0; i < size; ++i)
124+
{
125+
dstData[i] = srcData[i] * static_cast<float>(factor);
126+
}
127+
128+
// Always return the destination tensor (args[1]) as the JSI result
129+
return jsi::Value(rt, args[1]);
130+
};
131+
132+
module.setProperty(rt, name, jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, name), 3, fnBody));
133+
}
134+
}
135+
```
136+
137+
---
138+
139+
### Step 2: Register in Extension and Core JSI Installs
140+
141+
1. **Extension Register** (`cpp/extensions/<domain>/install.cpp`):
142+
```cpp
143+
#include "install.h"
144+
#include "operations.h"
145+
146+
namespace rnexecutorch::extensions::<domain>
147+
{
148+
void install(facebook::jsi::Runtime &rt, facebook::jsi::Object &module)
149+
{
150+
facebook::jsi::Object subModule(rt);
151+
install_customOp(rt, subModule);
152+
module.setProperty(rt, "<domain>", subModule);
153+
}
154+
}
155+
```
156+
157+
2. **Core Register** ([cpp/RnExecutorch.cpp](../cpp/RnExecutorch.cpp)):
158+
```cpp
159+
#include "extensions/<domain>/install.h"
160+
// ... inside rnexecutorch::install ...
161+
rnexecutorch::extensions::<domain>::install(jsiRuntime, myModule);
162+
```
163+
164+
---
165+
166+
### Step 3: TypeScript Bridge & Wrappers
167+
Under `src/extensions/<domain>.ts` or `src/extensions/<domain>/index.ts`:
168+
* **Use the `rnexecutorchJsi` Symbol**: You must import and interact with native bindings using the `rnexecutorchJsi` symbol exported from [src/native/bridge.ts](../src/native/bridge.ts). **Do not** reference the global `__rnexecutorch_jsi__` directly throughout your wrapper files.
169+
* Expose the TypeScript wrapper.
170+
* Handle default values here instead of the C++ layer.
171+
* Mark wrapper functions with the `"worklet";` directive.
172+
173+
```typescript
174+
import { rnexecutorchJsi } from '../native/bridge';
175+
import { type Tensor } from '../core/tensor';
176+
177+
/**
178+
* Applies a custom operation scaling the src tensor by factor.
179+
* @param src Input Tensor.
180+
* @param dst Pre-allocated Destination Tensor.
181+
* @param factor Scale factor. Defaults to 1.0.
182+
*/
183+
export function customOp(src: Tensor, dst: Tensor, factor: number = 1.0): Tensor {
184+
'worklet';
185+
return rnexecutorchJsi.<domain>.customOp(src, dst, factor);
186+
}
187+
```
188+
189+
---
190+
191+
## 📋 Verification Checklist
192+
193+
When adding a native extension, verify that:
194+
- [ ] You only implemented in C++ if the operation takes `> 5%` of the total inference budget.
195+
- [ ] No JSI Tensors are implicitly allocated and returned in the C++ code.
196+
- [ ] Input and output tensors are locked using `std::shared_lock` and `std::unique_lock` respectively.
197+
- [ ] In-place mutation is explicitly prevented by checking that `src != dst`.
198+
- [ ] No default parameter values are defined in the C++ header/source files.
199+
- [ ] The custom operation install function is registered in both the domain `install` function and core [cpp/RnExecutorch.cpp](../cpp/RnExecutorch.cpp).
200+
- [ ] The TypeScript wrapper imports and uses `rnexecutorchJsi` instead of the global `__rnexecutorch_jsi__`.
201+
- [ ] The TypeScript wrapper is marked with the `"worklet";` directive and defines all default parameter values.

0 commit comments

Comments
 (0)