Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fca896f
feat(incr-core): seed shared core with Cells strategy and primitive l…
Anyesh May 20, 2026
1558d81
feat(incr-core): port segmented node store behind the strategy
Anyesh May 20, 2026
dc8af09
feat(incr-core): add Value trait and GenericArena<T, C>
Anyesh May 20, 2026
3f01dff
feat(incr-core): add Lock + DepStack strategy abstractions
Anyesh May 20, 2026
11c8c5c
feat(incr-core): end-to-end runtime with create/get/set/early-cutoff
Anyesh May 20, 2026
a019118
perf(incr-core): static-dep fast path in publish_deps; bench parity hit
Anyesh May 20, 2026
3e917d7
feat(incr-core): collection layer with filter/map/count/reduce
Anyesh May 20, 2026
64c03c1
feat(incr-core): sort_by_key, pairwise, window operators
Anyesh May 20, 2026
bd7d3b3
fix(incr-core): panic on set() against query nodes
Anyesh May 20, 2026
6b685e9
feat(incr-core): group_by + join operators; full operator suite complete
Anyesh May 20, 2026
4a6ee22
feat: convert incr-compute and incr-concurrent to thin incr-core wrap…
Anyesh May 20, 2026
89a3949
bench(incr-compute): chain + diamond benches through the wrapper
Anyesh May 20, 2026
8fa2a70
test(incr-core): unified proptest suite over both strategies
Anyesh May 20, 2026
c0ac985
test(incr-core): operator proptest suite for filter/map/reduce/sort
Anyesh May 20, 2026
2434616
feat(incr-core): overflow-dep storage; queries with >7 deps now work
Anyesh May 20, 2026
cb6381c
feat(incr-core): real per-node tracing through get_traced
Anyesh May 20, 2026
bb1fb12
perf(incr-core): incremental count operator; operator bench parity hit
Anyesh May 20, 2026
40293c9
docs: rewrite READMEs for v0.2 architecture
Anyesh May 20, 2026
7e83df4
feat(incr-core): graveyard reclamation for displaced overflow-dep lists
Anyesh May 20, 2026
3f089f3
chore: bump 0.2.0-alpha.1 -> 0.2.0-beta.1 after end-to-end demo verif…
Anyesh May 20, 2026
7da158a
test: miri-validated unsafe code paths, 79 unit tests UB-clean
Anyesh May 20, 2026
0d43e61
ci: update workflows for v0.2 architecture; clippy-clean codebase
Anyesh May 20, 2026
4131508
feat(python): re-implement v0.2 bindings; both wheels build and run
Anyesh May 20, 2026
926e89e
feat(incr-core): hazard-pointer reclamation for overflow-dep lists
Anyesh May 20, 2026
2944f5d
docs: CHANGELOG, README updates for haphazard reclamation, rustdoc fix
Anyesh May 20, 2026
f8a2cfb
ci: restore PyPI publish + add Python wheel build to PR checks
Anyesh May 20, 2026
6d6c7a3
fix(incr-core): remove needless return in for_each_dep hot path
Anyesh May 20, 2026
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
70 changes: 48 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,55 +12,81 @@ env:

jobs:
test:
name: Test (${{ matrix.crate }})
name: Test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
crate: [incr-compute, incr-concurrent]
crate: [incr-core, incr-compute, incr-concurrent]
env:
CRATE: ${{ matrix.crate }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test -p ${{ matrix.crate }}
- run: cargo test -p "$CRATE" --release

build-python:
name: Build Python (${{ matrix.crate }})
miri:
name: Miri
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
crate: [incr-python, incr-concurrent-python]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@nightly
with:
components: miri
- uses: Swatinem/rust-cache@v2
- run: cargo build -p ${{ matrix.crate }}
- run: cargo +nightly miri test -p incr-core --lib -- --test-threads=1

bench:
name: Benchmark (${{ matrix.crate }})
name: Benchmark
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- crate: incr-compute
bench: regression
- crate: incr-concurrent
bench: regression
- crate: incr-concurrent
bench: concurrent_throughput
bench: [chain, operators]
env:
BENCH: ${{ matrix.bench }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo bench -p ${{ matrix.crate }} --bench ${{ matrix.bench }} -- --output-format bencher | tee bench-output.txt
- run: cargo bench -p incr-core --bench "$BENCH" -- --output-format bencher | tee bench-output.txt
- name: Upload benchmark results
uses: actions/upload-artifact@v4
with:
name: bench-${{ matrix.crate }}-${{ matrix.bench }}
name: bench-${{ matrix.bench }}
path: bench-output.txt

examples:
name: Example apps
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo build --release -p incr-concurrent-server -p incr-spreadsheet

python:
name: Python wheels
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
manifest:
- crates/incr-python/Cargo.toml
- crates/incr-concurrent-python/Cargo.toml
env:
MANIFEST: ${{ matrix.manifest }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: Swatinem/rust-cache@v2
- run: pip install maturin
- run: maturin build --release --manifest-path "$MANIFEST"

clippy:
name: Clippy
runs-on: ubuntu-latest
Expand All @@ -70,7 +96,7 @@ jobs:
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy -p incr-compute -p incr-concurrent -- -D warnings
- run: cargo clippy --workspace -- -D warnings

fmt:
name: Format
Expand Down
26 changes: 16 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test -p incr-compute -p incr-concurrent
- run: cargo test --release -p incr-core -p incr-compute -p incr-concurrent

publish-crates:
name: Publish to crates.io
Expand All @@ -25,6 +25,12 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Publish incr-core
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish -p incr-core --no-verify
- name: Wait for crates.io index
run: sleep 30
- name: Publish incr-compute
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
Expand All @@ -37,25 +43,25 @@ jobs:
run: cargo publish -p incr-concurrent --no-verify

publish-pypi:
name: Publish to PyPI (${{ matrix.package }})
name: Publish to PyPI
needs: test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- package: incr-python
manifest: crates/incr-python/Cargo.toml
- package: incr-concurrent-python
manifest: crates/incr-concurrent-python/Cargo.toml
manifest:
- crates/incr-python/Cargo.toml
- crates/incr-concurrent-python/Cargo.toml
env:
MANIFEST: ${{ matrix.manifest }}
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install maturin
run: pip install maturin
- run: pip install maturin
- name: Build and publish
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: maturin publish --manifest-path ${{ matrix.manifest }} --no-sdist
run: maturin publish --manifest-path "$MANIFEST" --no-sdist
73 changes: 73 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Changelog

All notable changes to this project are documented here. Format roughly follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0-beta.1] — 2026-05-20

### Architecture

The big break: `incr-compute` and `incr-concurrent` are now thin re-export wrappers over a shared engine crate, `incr-core`. The engine is parameterized over a `Cells` strategy trait (`Local` for single-threaded, `Shared` for `Send + Sync`); the compiler monomorphizes each surface crate into the appropriate variant. The full algorithm — dependency tracking, ensure_clean's iterative post-order walker, red-green early cutoff, the segmented node store, the typed value arenas, all nine operators — lives in one place. v0.1's parallel implementations are deleted.

### Breaking changes

- **`Value` bound** is now `Clone + PartialEq + Send + Sync + 'static` in **both** crates (was `Any + Clone + PartialEq + 'static` in `incr-compute` v0.1). Most user types already meet the bound; types that don't will need wrapping (e.g., `Arc<Mutex<T>>` instead of bare `Rc<...>`).
- **Single `Runtime` per crate** rather than the v0.1 split. `incr_compute::Runtime` is `Runtime<Local>`; `incr_concurrent::Runtime` is `Runtime<Shared>`. The public method names match v0.1.
- **`NodeId::raw()` → `NodeId.0`**. The struct is `pub struct NodeId(pub u32)`; the field is accessed directly.
- **`Incr<T>::node_id()` → `Incr<T>::slot()`**. The handle returns its u32 slot index.
- **`IncrCollection::version_node_id()` removed**. Use `version_node()` which returns `Incr<u64>`.
- **`count()` returns `Incr<u64>`** (was `Incr<usize>` in `incr-concurrent` v0.1). Sized to the network-portable type.
- **`Runtime::set_label`** takes a `u32` slot directly (was `NodeId` in v0.1).
- **`Runtime::set_tracing` removed**. `get_traced` now arms tracing internally for the duration of the call.
- **`SortedCollection::entries()` → `snapshot()`**.
- **`IncrCollection::delete` returns `bool`** indicating whether a delete was actually recorded (was: silently dropped the inner result in production v0.1).
- **`Runtime::set` on a query node panics** with a clear message. This was undefined behavior in v0.1 (would overwrite the arena slot and corrupt the state machine).
- **`Runtime` `!Send + !Sync` under Local**; `Send + Sync` under Shared (was: mixed in v0.1).

### Added

- **`incr-core` published crate** as the shared engine. Re-exported types include `Cells`, `Local`, `Shared`, `PtrCell`, `Lock`, `DepStack`, `LocalDepStack`, `SharedDepStack`, `LocalLock`. Users who want to build a custom concurrency strategy on top of the engine can do so.
- **Overflow-dep storage**: queries with more than 7 dependencies are now supported (was: hard limit of 7 in v0.1's inline-only path). Overflow lists live in a heap-allocated `DepList`, reclaimed via the [`haphazard`](https://crates.io/crates/haphazard) global hazard-pointer domain. Concurrent readers hold a hazard pointer during traversal; writers retire displaced lists for deferred free.
- **Real per-node tracing** in `get_traced`: every node visit during a get records a `NodeTrace` (`VerifiedClean` or `Recomputed { value_changed }`). Aggregates (`nodes_recomputed`, `nodes_cutoff`) populated from the trace. Hot-path cost: one Relaxed u8 load per compute when disarmed (~1 ns).
- **Property tests under both strategies**: the same generator + verifier (`verify_incremental_matches_batch<C: Cells>`) runs against `Local` and `Shared`. 1000 random function graphs × 2 strategies + 500 random collection op sequences × 6 tests = ~5000 random scenarios per `cargo test` run.
- **Concurrent stress test** for `incr-concurrent`: 4 reader threads + 1 writer thread × 1000 iterations with torn-read detection.
- **Miri validation**: `cargo +nightly miri test -p incr-core --lib` covers all unsafe paths (segmented store, hazard-pointer reclamation, state machine CAS races). Zero undefined behavior reported across 79 unit tests.
- **`Runtime::graph_snapshot`** returns real per-node `NodeInfo` with dependencies (read from inline-7 + overflow storage) and dependents (from inner state).

### Performance

Per-node propagation cost on this machine (criterion --quick):

| Workload | `incr-compute` | `incr-concurrent` | Salsa |
|---|---|---|---|
| Diamond (4 nodes, propagate input through) | 647 ns | 764 ns | 1,066 ns |
| Early cutoff (input changes, clamped output doesn't) | 314 ns | 404 ns | 469 ns |
| Per-node propagation (chain) | ~135 ns | ~169 ns | ~387 ns |

Collection insert through `filter → map → count`:

| Size | `incr-compute` insert | From-scratch batch | Speedup |
|---|---|---|---|
| 1K | 673 ns | 102 µs | **152x** |
| 10K | 657 ns | 67 µs | **102x** |
| 100K | 661 ns | 156 µs | **236x** |

The "incremental cost is constant in collection size" property holds. Production v0.1 README claimed 186x at 100K; v0.2 beats that by 27%. Lab notes in the wiki devlog.

### Fixed

- `count()` operator is now O(new deltas) per get rather than O(N) (was: summed over the entire multiset on every get in v0.1).
- `publish_deps` static-dep fast path (was: O(N) churn on `dependents` lists in v0.1 due to a bug that grew the lists unbounded across iterations).

### Removed

- `incr-python` and `incr-concurrent-python` crates have been **re-implemented** against the v0.2 engine; their public Python API matches v0.1 but they internally use the new types. PyPI publish is gated on the next 0.2.x patch alongside the wheel-build job in CI.

### Architecture decisions

- See [`wiki/projects/incr/decisions/unification-into-incr-core.md`](https://github.com/Anyesh/incr/) for the architectural reset that motivated v0.2.
- See [`wiki/projects/incr/plans/incr-core-consolidation.md`](https://github.com/Anyesh/incr/) for the migration plan.
- 21 commits on the `v0.2-rewrite` branch (cut from main 2026-05-20).

## [0.1.x]

The v0.1 line shipped two independent crates (`incr-compute` and `incr-concurrent`) with shared API names but separate implementations. See git history for the per-release notes.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[workspace]
resolver = "2"
members = ["crates/incr-compute", "crates/incr-concurrent", "crates/incr-python", "crates/incr-concurrent-python", "examples/concurrent-server", "examples/spreadsheet"]
members = ["crates/incr-compute", "crates/incr-concurrent", "crates/incr-concurrent-python", "crates/incr-core", "crates/incr-python", "examples/concurrent-server", "examples/spreadsheet"]
Loading
Loading