Skip to content

Commit fc41ba4

Browse files
committed
chore: scaffold rne-rewrite target branch (#1255)
Set up the base for all RNE rewrite PRs (see #1208). - Remove apps/ and the legacy packages/react-native-executorch. - Stand 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 deferred to the per-task block PRs. - Native ExecuTorch third-party binaries are NOT committed; CI is TypeScript-only and does not compile native code. They are provisioned separately before an on-device build (see third-party/README.md); wiring the on-demand mechanism is a follow-up. The common/phonemis submodule is retained for later TTS use. - Adopt the clang-format config from #1230 (.clang-format) and reformat the ported C++ sources to the LLVM-based style. - Replace the usage-oriented skills/ with the PoC's build/extend skills under .agents/skills (discoverable by AI coding agents), aligned to the rnexecutorch naming. - Remove docs/docs content (the documentation will be rewritten). - CI: drop the apps/llm native build workflows and the stale error-code verification step; run CI on the rne-rewrite branch. - GitHub meta: scope lint/typecheck/build to the core package and keep the resource-fetcher and webrtc packages on disk but excluded from CI until their surface is reworked.
1 parent bce2a5e commit fc41ba4

1,127 files changed

Lines changed: 3629 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)