Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .agents/skills/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Agent Skills Directory
# Development Skills Directory

This directory contains specialized agent skills (recipes) to guide AI coding agents (like Antigravity or Claude Code) in extending the codebase cleanly and consistently.
This directory contains specialized skills (recipes) to guide contributors and AI coding assistants in extending the codebase cleanly and consistently.

## Available Skills

Expand Down
44 changes: 26 additions & 18 deletions .agents/skills/add-native-extension/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ Use this guide to add custom, performance-critical native operations in C++ and
Before writing any C++ code, ensure you adhere to the following principles:

1. **Amdahl's Law & Premature Optimization**:
* 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.
- 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.

2. **Destination Tensors & Local Memory**:
* **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.
* **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)`).
* **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).
* *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.
- **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.
- **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)`).
- **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).
- _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.

## 🚫 Avoid / Anti-Patterns

* **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.
* **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.
* **Do NOT perform in-place mutation without safety checks:** Never allow inputs and outputs to share the same underlying instance.
- **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.
- **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.
- **Do NOT perform in-place mutation without safety checks:** Never allow inputs and outputs to share the same underlying instance.

---

Expand All @@ -38,10 +38,13 @@ Before writing any C++ code, ensure you adhere to the following principles:
## 🛠️ Step-by-Step Implementation

### Step 1: Create the Native Operation Files

Under `cpp/extensions/<domain>/`, create or modify the header and implementation files for your operations:

#### 1. Header (`cpp/extensions/<domain>/operations.h`)

Keep the header clean and specify exact JSI install functions:

```cpp
#pragma once
#include <jsi/jsi.h>
Expand All @@ -53,9 +56,10 @@ namespace rnexecutorch::extensions::<domain>
```

#### 2. Source (`cpp/extensions/<domain>/operations.cpp`)
* Extract input and output tensors as `TensorHostObject` pointers.
* Check bounds, shapes, types, and verify that the output tensor is **not the same instance** as the input (no unsafely managed in-place mutation).
* Lock tensors using `std::shared_lock` (for inputs) and `std::unique_lock` (for outputs).

- Extract input and output tensors as `TensorHostObject` pointers.
- Check bounds, shapes, types, and verify that the output tensor is **not the same instance** as the input (no unsafely managed in-place mutation).
- Lock tensors using `std::shared_lock` (for inputs) and `std::unique_lock` (for outputs).

```cpp
#include "operations.h"
Expand Down Expand Up @@ -139,6 +143,7 @@ namespace rnexecutorch::extensions::<domain>
### Step 2: Register in Extension and Core JSI Installs

1. **Extension Register** (`cpp/extensions/<domain>/install.cpp`):

```cpp
#include "install.h"
#include "operations.h"
Expand All @@ -154,21 +159,23 @@ namespace rnexecutorch::extensions::<domain>
}
```

2. **Core Register** ([cpp/RnExecutorch.cpp](../cpp/RnExecutorch.cpp)):
2. **Core Register** ([cpp/RnExecutorch.cpp](../../../packages/react-native-executorch/cpp/RnExecutorch.cpp)):
```cpp
#include "extensions/<domain>/install.h"
// ... inside rnexecutorch::install ...
rnexecutorch::extensions::<domain>::install(jsiRuntime, myModule);
rnexecutorch::extensions::<domain>::install(jsiRuntime, module);
```

---

### Step 3: TypeScript Bridge & Wrappers

Under `src/extensions/<domain>.ts` or `src/extensions/<domain>/index.ts`:
* **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.
* Expose the TypeScript wrapper.
* Handle default values here instead of the C++ layer.
* Mark wrapper functions with the `"worklet";` directive.

- **Use the `rnexecutorchJsi` Symbol**: You must import and interact with native bindings using the `rnexecutorchJsi` symbol exported from [src/native/bridge.ts](../../../packages/react-native-executorch/src/native/bridge.ts). **Do not** reference the global `__rnexecutorch_jsi__` directly throughout your wrapper files.
- Expose the TypeScript wrapper.
- Handle default values here instead of the C++ layer.
- Mark wrapper functions with the `"worklet";` directive.

```typescript
import { rnexecutorchJsi } from '../native/bridge';
Expand All @@ -191,11 +198,12 @@ export function customOp(src: Tensor, dst: Tensor, factor: number = 1.0): Tensor
## 📋 Verification Checklist

When adding a native extension, verify that:

- [ ] You only implemented in C++ if the operation takes `> 5%` of the total inference budget.
- [ ] No JSI Tensors are implicitly allocated and returned in the C++ code.
- [ ] Input and output tensors are locked using `std::shared_lock` and `std::unique_lock` respectively.
- [ ] In-place mutation is explicitly prevented by checking that `src != dst`.
- [ ] No default parameter values are defined in the C++ header/source files.
- [ ] The custom operation install function is registered in both the domain `install` function and core [cpp/RnExecutorch.cpp](../cpp/RnExecutorch.cpp).
- [ ] The custom operation install function is registered in both the domain `install` function and core [cpp/RnExecutorch.cpp](../../../packages/react-native-executorch/cpp/RnExecutorch.cpp).
- [ ] The TypeScript wrapper imports and uses `rnexecutorchJsi` instead of the global `__rnexecutorch_jsi__`.
- [ ] The TypeScript wrapper is marked with the `"worklet";` directive and defines all default parameter values.
Loading