├── .github
├── DOCS.md
├── codecov.yml
├── dependabot.yml
└── workflows
│ ├── check.yml
│ ├── safety.yml
│ ├── scheduled.yml
│ └── test.yml
├── .gitignore
├── .neoconf.json
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── benches
├── calc.rs
└── fibbo.rs
├── examples
├── async.rs
├── calculator.rs
├── heavy_fibbo.rs
└── heavy_fibbo_base.rs
└── src
├── allocator.rs
├── defer.rs
├── lib.rs
├── ptr.rs
├── stack
├── future.rs
├── mod.rs
├── runner.rs
├── stk.rs
└── test.rs
├── stub_waker.rs
├── test.rs
├── tree
├── future.rs
├── mod.rs
├── runner.rs
├── schedular
│ ├── atomic_waker.rs
│ ├── mod.rs
│ ├── queue.rs
│ └── waker.rs
├── stk.rs
└── test.rs
└── vtable.rs
/.github/DOCS.md:
--------------------------------------------------------------------------------
1 | # Github config and workflows
2 |
3 | In this folder there is configuration for codecoverage, dependabot, and ci
4 | workflows that check the library more deeply than the default configurations.
5 |
6 | This folder can be or was merged using a --allow-unrelated-histories merge
7 | strategy from which provides a
8 | reasonably sensible base for writing your own ci on. By using this strategy
9 | the history of the CI repo is included in your repo, and future updates to
10 | the CI can be merged later.
11 |
12 | To perform this merge run:
13 |
14 | ```shell
15 | git remote add ci https://github.com/jonhoo/rust-ci-conf.git
16 | git fetch ci
17 | git merge --allow-unrelated-histories ci/main
18 | ```
19 |
20 | An overview of the files in this project is available at:
21 | , which contains some
22 | rationale for decisions and runs through an example of solving minimal version
23 | and OpenSSL issues.
24 |
--------------------------------------------------------------------------------
/.github/codecov.yml:
--------------------------------------------------------------------------------
1 | # ref: https://docs.codecov.com/docs/codecovyml-reference
2 | coverage:
3 | # Hold ourselves to a high bar
4 | range: 85..100
5 | round: down
6 | precision: 1
7 | status:
8 | # ref: https://docs.codecov.com/docs/commit-status
9 | project:
10 | default:
11 | # Avoid false negatives
12 | threshold: 1%
13 |
14 | # Test files aren't important for coverage
15 | ignore:
16 | - "tests"
17 |
18 | # Make comments less noisy
19 | comment:
20 | layout: "files"
21 | require_changes: true
22 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: /
5 | schedule:
6 | interval: daily
7 | - package-ecosystem: cargo
8 | directory: /
9 | schedule:
10 | interval: daily
11 | ignore:
12 | - dependency-name: "*"
13 | # patch and minor updates don't matter for libraries as consumers of this library build
14 | # with their own lockfile, rather than the version specified in this library's lockfile
15 | # remove this ignore rule if your package has binaries to ensure that the binaries are
16 | # built with the exact set of dependencies and those are up to date.
17 | update-types:
18 | - "version-update:semver-patch"
19 | - "version-update:semver-minor"
20 |
--------------------------------------------------------------------------------
/.github/workflows/check.yml:
--------------------------------------------------------------------------------
1 | # This workflow runs whenever a PR is opened or updated, or a commit is pushed to main. It runs
2 | # several checks:
3 | # - fmt: checks that the code is formatted according to rustfmt
4 | # - clippy: checks that the code does not contain any clippy warnings
5 | # - doc: checks that the code can be documented without errors
6 | # - hack: check combinations of feature flags
7 | # - msrv: check that the msrv specified in the crate is correct
8 | permissions:
9 | contents: read
10 | # This configuration allows maintainers of this repo to create a branch and pull request based on
11 | # the new branch. Restricting the push trigger to the main branch ensures that the PR only gets
12 | # built once.
13 | on:
14 | push:
15 | branches: [main]
16 | pull_request:
17 | # If new code is pushed to a PR branch, then cancel in progress workflows for that PR. Ensures that
18 | # we don't waste CI time, and returns results quicker https://github.com/jonhoo/rust-ci-conf/pull/5
19 | concurrency:
20 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
21 | cancel-in-progress: true
22 | name: check
23 | jobs:
24 | fmt:
25 | runs-on: ubuntu-latest
26 | name: stable / fmt
27 | steps:
28 | - uses: actions/checkout@v4
29 | with:
30 | submodules: true
31 | - name: Install stable
32 | uses: dtolnay/rust-toolchain@stable
33 | with:
34 | components: rustfmt
35 | - name: cargo fmt --check
36 | run: cargo fmt --check
37 | clippy:
38 | runs-on: ubuntu-latest
39 | name: ${{ matrix.toolchain }} / clippy
40 | permissions:
41 | contents: read
42 | checks: write
43 | strategy:
44 | fail-fast: false
45 | matrix:
46 | # Get early warning of new lints which are regularly introduced in beta channels.
47 | toolchain: [stable, beta]
48 | steps:
49 | - uses: actions/checkout@v4
50 | with:
51 | submodules: true
52 | - name: Install ${{ matrix.toolchain }}
53 | uses: dtolnay/rust-toolchain@master
54 | with:
55 | toolchain: ${{ matrix.toolchain }}
56 | components: clippy
57 | - name: cargo clippy
58 | uses: giraffate/clippy-action@v1
59 | with:
60 | reporter: 'github-pr-check'
61 | github_token: ${{ secrets.GITHUB_TOKEN }}
62 | doc:
63 | # run docs generation on nightly rather than stable. This enables features like
64 | # https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an
65 | # API be documented as only available in some specific platforms.
66 | runs-on: ubuntu-latest
67 | name: nightly / doc
68 | steps:
69 | - uses: actions/checkout@v4
70 | with:
71 | submodules: true
72 | - name: Install nightly
73 | uses: dtolnay/rust-toolchain@nightly
74 | - name: cargo doc
75 | run: cargo doc --no-deps --all-features
76 | env:
77 | RUSTDOCFLAGS: --cfg docsrs
78 | # hack:
79 | # # cargo-hack checks combinations of feature flags to ensure that features are all additive
80 | # # which is required for feature unification
81 | # runs-on: ubuntu-latest
82 | # name: ubuntu / stable / features
83 | # steps:
84 | # - uses: actions/checkout@v4
85 | # with:
86 | # submodules: true
87 | # - name: Install stable
88 | # uses: dtolnay/rust-toolchain@stable
89 | # - name: cargo install cargo-hack
90 | # uses: taiki-e/install-action@cargo-hack
91 | # # intentionally no target specifier; see https://github.com/jonhoo/rust-ci-conf/pull/4
92 | # # --feature-powerset runs for every combination of features
93 | # - name: cargo hack
94 | # run: cargo hack --feature-powerset check
95 | msrv:
96 | # check that we can build using the minimal rust version that is specified by this crate
97 | runs-on: ubuntu-latest
98 | # we use a matrix here just because env can't be used in job names
99 | # https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
100 | strategy:
101 | matrix:
102 | msrv: ["1.84"] # strict/exposed_provinece were stablized in 1.84
103 | name: ubuntu / ${{ matrix.msrv }}
104 | steps:
105 | - uses: actions/checkout@v4
106 | with:
107 | submodules: true
108 | - name: Install ${{ matrix.msrv }}
109 | uses: dtolnay/rust-toolchain@master
110 | with:
111 | toolchain: ${{ matrix.msrv }}
112 | - name: cargo +${{ matrix.msrv }} check
113 | run: cargo check
114 |
--------------------------------------------------------------------------------
/.github/workflows/safety.yml:
--------------------------------------------------------------------------------
1 | # This workflow runs checks for unsafe code. In crates that don't have any unsafe code, this can be
2 | # removed. Runs:
3 | # - miri - detects undefined behavior and memory leaks
4 | # - address sanitizer - detects memory errors
5 | # - leak sanitizer - detects memory leaks
6 | # - loom - Permutation testing for concurrent code https://crates.io/crates/loom
7 | # See check.yml for information about how the concurrency cancellation and workflow triggering works
8 | permissions:
9 | contents: read
10 | on:
11 | push:
12 | branches: [main]
13 | pull_request:
14 | concurrency:
15 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
16 | cancel-in-progress: true
17 | name: safety
18 | jobs:
19 | sanitizers:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v4
23 | with:
24 | submodules: true
25 | - name: Install nightly
26 | uses: dtolnay/rust-toolchain@nightly
27 | - run: |
28 | # to get the symbolizer for debug symbol resolution
29 | sudo apt install llvm
30 | # to fix buggy leak analyzer:
31 | # https://github.com/japaric/rust-san#unrealiable-leaksanitizer
32 | # ensure there's a profile.dev section
33 | if ! grep -qE '^[ \t]*[profile.dev]' Cargo.toml; then
34 | echo >> Cargo.toml
35 | echo '[profile.dev]' >> Cargo.toml
36 | fi
37 | # remove pre-existing opt-levels in profile.dev
38 | sed -i '/^\s*\[profile.dev\]/,/^\s*\[/ {/^\s*opt-level/d}' Cargo.toml
39 | # now set opt-level to 1
40 | sed -i '/^\s*\[profile.dev\]/a opt-level = 1' Cargo.toml
41 | cat Cargo.toml
42 | name: Enable debug symbols
43 | - name: Install rust-src
44 | run: |
45 | rustup component add rust-src
46 | - name: cargo test -Zsanitizer=address
47 | # only --lib --tests b/c of https://github.com/rust-lang/rust/issues/53945
48 | run: cargo test -Zbuild-std --all-features --target x86_64-unknown-linux-gnu -- --skip forget
49 | env:
50 | RUSTFLAGS: "-Z sanitizer=address"
51 | # - name: cargo test -Zsanitizer=leak
52 | # if: always()
53 | # run: cargo test --all-features --target x86_64-unknown-linux-gnu
54 | # env:
55 | # LSAN_OPTIONS: "suppressions=lsan-suppressions.txt"
56 | # RUSTFLAGS: "-Z sanitizer=leak"
57 | miri:
58 | runs-on: ubuntu-latest
59 | steps:
60 | - uses: actions/checkout@v4
61 | with:
62 | submodules: true
63 | - run: |
64 | echo "NIGHTLY=nightly-$(curl -s https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/miri)" >> $GITHUB_ENV
65 | - name: Install ${{ env.NIGHTLY }}
66 | uses: dtolnay/rust-toolchain@master
67 | with:
68 | toolchain: ${{ env.NIGHTLY }}
69 | components: miri
70 | - name: cargo miri test
71 | run: cargo miri test --all-features
72 | env:
73 | MIRIFLAGS: ""
74 |
75 |
76 | # loom:
77 | # runs-on: ubuntu-latest
78 | # steps:
79 | # - uses: actions/checkout@v4
80 | # with:
81 | # submodules: true
82 | # - name: Install stable
83 | # uses: dtolnay/rust-toolchain@stable
84 | # - name: cargo test --test loom
85 | # run: cargo test --release --test loom
86 | # env:
87 | # LOOM_MAX_PREEMPTIONS: 2
88 | # RUSTFLAGS: "--cfg loom"
89 |
--------------------------------------------------------------------------------
/.github/workflows/scheduled.yml:
--------------------------------------------------------------------------------
1 | # Run scheduled (rolling) jobs on a nightly basis, as your crate may break independently of any
2 | # given PR. E.g., updates to rust nightly and updates to this crates dependencies. See check.yml for
3 | # information about how the concurrency cancellation and workflow triggering works
4 | permissions:
5 | contents: read
6 | on:
7 | push:
8 | branches: [main]
9 | pull_request:
10 | schedule:
11 | - cron: '7 7 * * *'
12 | concurrency:
13 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
14 | cancel-in-progress: true
15 | name: rolling
16 | jobs:
17 | # https://twitter.com/mycoliza/status/1571295690063753218
18 | nightly:
19 | runs-on: ubuntu-latest
20 | name: ubuntu / nightly
21 | steps:
22 | - uses: actions/checkout@v4
23 | with:
24 | submodules: true
25 | - name: Install nightly
26 | uses: dtolnay/rust-toolchain@nightly
27 | - name: cargo generate-lockfile
28 | if: hashFiles('Cargo.lock') == ''
29 | run: cargo generate-lockfile
30 | - name: cargo test --locked
31 | run: cargo test --locked --all-features --all-targets
32 | # https://twitter.com/alcuadrado/status/1571291687837732873
33 | update:
34 | # This action checks that updating the dependencies of this crate to the latest available that
35 | # satisfy the versions in Cargo.toml does not break this crate. This is important as consumers
36 | # of this crate will generally use the latest available crates. This is subject to the standard
37 | # Cargo semver rules (i.e cargo does not update to a new major version unless explicitly told
38 | # to).
39 | runs-on: ubuntu-latest
40 | name: ubuntu / beta / updated
41 | # There's no point running this if no Cargo.lock was checked in in the first place, since we'd
42 | # just redo what happened in the regular test job. Unfortunately, hashFiles only works in if on
43 | # steps, so we repeat it.
44 | steps:
45 | - uses: actions/checkout@v4
46 | with:
47 | submodules: true
48 | - name: Install beta
49 | if: hashFiles('Cargo.lock') != ''
50 | uses: dtolnay/rust-toolchain@beta
51 | - name: cargo update
52 | if: hashFiles('Cargo.lock') != ''
53 | run: cargo update
54 | - name: cargo test
55 | if: hashFiles('Cargo.lock') != ''
56 | run: cargo test --locked --features tree --all-targets
57 | env:
58 | RUSTFLAGS: -D deprecated
59 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | # This is the main CI workflow that runs the test suite on all pushes to main and all pull requests.
2 | # It runs the following jobs:
3 | # - required: runs the test suite on ubuntu with stable and beta rust toolchains
4 | # - minimal: runs the test suite with the minimal versions of the dependencies that satisfy the
5 | # requirements of this crate, and its dependencies
6 | # - os-check: runs the test suite on mac and windows
7 | # - coverage: runs the test suite and collects coverage information
8 | # See check.yml for information about how the concurrency cancellation and workflow triggering works
9 | permissions:
10 | contents: read
11 | on:
12 | push:
13 | branches: [main]
14 | pull_request:
15 | concurrency:
16 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
17 | cancel-in-progress: true
18 | name: test
19 | jobs:
20 | required:
21 | runs-on: ubuntu-latest
22 | name: ubuntu / ${{ matrix.toolchain }}
23 | strategy:
24 | matrix:
25 | # run on stable and beta to ensure that tests won't break on the next version of the rust
26 | # toolchain
27 | # toolchain: [stable, beta]
28 | toolchain: [beta]
29 | steps:
30 | - uses: actions/checkout@v4
31 | with:
32 | submodules: true
33 | - name: Install ${{ matrix.toolchain }}
34 | uses: dtolnay/rust-toolchain@master
35 | with:
36 | toolchain: ${{ matrix.toolchain }}
37 | - name: cargo generate-lockfile
38 | # enable this ci template to run regardless of whether the lockfile is checked in or not
39 | if: hashFiles('Cargo.lock') == ''
40 | run: cargo generate-lockfile
41 | # https://twitter.com/jonhoo/status/1571290371124260865
42 | - name: cargo test --locked
43 | run: cargo test --locked --features tree --all-targets
44 | # https://github.com/rust-lang/cargo/issues/6669
45 | - name: cargo test --doc
46 | run: cargo test --locked --features tree --doc
47 | minimal:
48 | # This action chooses the oldest version of the dependencies permitted by Cargo.toml to ensure
49 | # that this crate is compatible with the minimal version that this crate and its dependencies
50 | # require. This will pickup issues where this create relies on functionality that was introduced
51 | # later than the actual version specified (e.g., when we choose just a major version, but a
52 | # method was added after this version).
53 | #
54 | # This particular check can be difficult to get to succeed as often transitive dependencies may
55 | # be incorrectly specified (e.g., a dependency specifies 1.0 but really requires 1.1.5). There
56 | # is an alternative flag available -Zdirect-minimal-versions that uses the minimal versions for
57 | # direct dependencies of this crate, while selecting the maximal versions for the transitive
58 | # dependencies. Alternatively, you can add a line in your Cargo.toml to artificially increase
59 | # the minimal dependency, which you do with e.g.:
60 | # ```toml
61 | # # for minimal-versions
62 | # [target.'cfg(any())'.dependencies]
63 | # openssl = { version = "0.10.55", optional = true } # needed to allow foo to build with -Zminimal-versions
64 | # ```
65 | # The optional = true is necessary in case that dependency isn't otherwise transitively required
66 | # by your library, and the target bit is so that this dependency edge never actually affects
67 | # Cargo build order. See also
68 | # https://github.com/jonhoo/fantoccini/blob/fde336472b712bc7ebf5b4e772023a7ba71b2262/Cargo.toml#L47-L49.
69 | # This action is run on ubuntu with the stable toolchain, as it is not expected to fail
70 | runs-on: ubuntu-latest
71 | name: ubuntu / stable / minimal-versions
72 | steps:
73 | - uses: actions/checkout@v4
74 | with:
75 | submodules: true
76 | - name: Install stable
77 | uses: dtolnay/rust-toolchain@stable
78 | - name: Install nightly for -Zminimal-versions
79 | uses: dtolnay/rust-toolchain@nightly
80 | - name: rustup default stable
81 | run: rustup default stable
82 | - name: cargo update -Zminimal-versions
83 | run: cargo +nightly update -Zminimal-versions
84 | - name: cargo test
85 | run: cargo test --locked --features tree --all-targets
86 | os-check:
87 | # run cargo test on mac and windows
88 | runs-on: ${{ matrix.os }}
89 | name: ${{ matrix.os }} / stable
90 | strategy:
91 | fail-fast: false
92 | matrix:
93 | os: [macos-latest, windows-latest]
94 | steps:
95 | # if your project needs OpenSSL, uncomment this to fix Windows builds.
96 | # it's commented out by default as the install command takes 5-10m.
97 | # - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append
98 | # if: runner.os == 'Windows'
99 | # - run: vcpkg install openssl:x64-windows-static-md
100 | # if: runner.os == 'Windows'
101 | - uses: actions/checkout@v4
102 | with:
103 | submodules: true
104 | - name: Install stable
105 | uses: dtolnay/rust-toolchain@stable
106 | - name: cargo generate-lockfile
107 | if: hashFiles('Cargo.lock') == ''
108 | run: cargo generate-lockfile
109 | - name: cargo test
110 | run: cargo test --locked --features tree --all-targets
111 | coverage:
112 | # use llvm-cov to build and collect coverage and outputs in a format that
113 | # is compatible with codecov.io
114 | #
115 | # note that codecov as of v4 requires that CODECOV_TOKEN from
116 | #
117 | # https://app.codecov.io/gh///settings
118 | #
119 | # is set in two places on your repo:
120 | #
121 | # - https://github.com/jonhoo/guardian/settings/secrets/actions
122 | # - https://github.com/jonhoo/guardian/settings/secrets/dependabot
123 | #
124 | # (the former is needed for codecov uploads to work with Dependabot PRs)
125 | #
126 | # PRs coming from forks of your repo will not have access to the token, but
127 | # for those, codecov allows uploading coverage reports without a token.
128 | # it's all a little weird and inconvenient. see
129 | #
130 | # https://github.com/codecov/feedback/issues/112
131 | #
132 | # for lots of more discussion
133 | runs-on: ubuntu-latest
134 | name: ubuntu / stable / coverage
135 | steps:
136 | - uses: actions/checkout@v4
137 | with:
138 | submodules: true
139 | - name: Install stable
140 | uses: dtolnay/rust-toolchain@stable
141 | with:
142 | components: llvm-tools-preview
143 | - name: cargo install cargo-llvm-cov
144 | uses: taiki-e/install-action@cargo-llvm-cov
145 | - name: cargo generate-lockfile
146 | if: hashFiles('Cargo.lock') == ''
147 | run: cargo generate-lockfile
148 | - name: cargo llvm-cov
149 | run: cargo llvm-cov --locked --features tree --lcov --output-path lcov.info
150 | - name: Record Rust version
151 | run: echo "RUST=$(rustc --version)" >> "$GITHUB_ENV"
152 | - name: Upload to codecov.io
153 | uses: codecov/codecov-action@v5
154 | with:
155 | fail_ci_if_error: true
156 | token: ${{ secrets.CODECOV_TOKEN }}
157 | env_vars: OS,RUST
158 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 |
--------------------------------------------------------------------------------
/.neoconf.json:
--------------------------------------------------------------------------------
1 | {
2 | "lspconfig": {
3 | "rust_analyzer": {
4 | "rust-analyzer.cargo.features": [
5 | "tree"
6 | ],
7 | "rust-analyzer.check.command": "clippy"
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "addr2line"
7 | version = "0.24.2"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10 | dependencies = [
11 | "gimli",
12 | ]
13 |
14 | [[package]]
15 | name = "adler2"
16 | version = "2.0.0"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19 |
20 | [[package]]
21 | name = "aho-corasick"
22 | version = "1.1.3"
23 | source = "registry+https://github.com/rust-lang/crates.io-index"
24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
25 | dependencies = [
26 | "memchr",
27 | ]
28 |
29 | [[package]]
30 | name = "anes"
31 | version = "0.1.6"
32 | source = "registry+https://github.com/rust-lang/crates.io-index"
33 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
34 |
35 | [[package]]
36 | name = "anstyle"
37 | version = "1.0.10"
38 | source = "registry+https://github.com/rust-lang/crates.io-index"
39 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
40 |
41 | [[package]]
42 | name = "autocfg"
43 | version = "1.4.0"
44 | source = "registry+https://github.com/rust-lang/crates.io-index"
45 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
46 |
47 | [[package]]
48 | name = "backtrace"
49 | version = "0.3.74"
50 | source = "registry+https://github.com/rust-lang/crates.io-index"
51 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
52 | dependencies = [
53 | "addr2line",
54 | "cfg-if",
55 | "libc",
56 | "miniz_oxide",
57 | "object",
58 | "rustc-demangle",
59 | "windows-targets",
60 | ]
61 |
62 | [[package]]
63 | name = "bitflags"
64 | version = "2.9.0"
65 | source = "registry+https://github.com/rust-lang/crates.io-index"
66 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
67 |
68 | [[package]]
69 | name = "bumpalo"
70 | version = "3.17.0"
71 | source = "registry+https://github.com/rust-lang/crates.io-index"
72 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
73 |
74 | [[package]]
75 | name = "bytes"
76 | version = "1.10.1"
77 | source = "registry+https://github.com/rust-lang/crates.io-index"
78 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
79 |
80 | [[package]]
81 | name = "cast"
82 | version = "0.3.0"
83 | source = "registry+https://github.com/rust-lang/crates.io-index"
84 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
85 |
86 | [[package]]
87 | name = "cfg-if"
88 | version = "1.0.0"
89 | source = "registry+https://github.com/rust-lang/crates.io-index"
90 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
91 |
92 | [[package]]
93 | name = "ciborium"
94 | version = "0.2.2"
95 | source = "registry+https://github.com/rust-lang/crates.io-index"
96 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
97 | dependencies = [
98 | "ciborium-io",
99 | "ciborium-ll",
100 | "serde",
101 | ]
102 |
103 | [[package]]
104 | name = "ciborium-io"
105 | version = "0.2.2"
106 | source = "registry+https://github.com/rust-lang/crates.io-index"
107 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
108 |
109 | [[package]]
110 | name = "ciborium-ll"
111 | version = "0.2.2"
112 | source = "registry+https://github.com/rust-lang/crates.io-index"
113 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
114 | dependencies = [
115 | "ciborium-io",
116 | "half",
117 | ]
118 |
119 | [[package]]
120 | name = "clap"
121 | version = "4.5.35"
122 | source = "registry+https://github.com/rust-lang/crates.io-index"
123 | checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
124 | dependencies = [
125 | "clap_builder",
126 | ]
127 |
128 | [[package]]
129 | name = "clap_builder"
130 | version = "4.5.35"
131 | source = "registry+https://github.com/rust-lang/crates.io-index"
132 | checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
133 | dependencies = [
134 | "anstyle",
135 | "clap_lex",
136 | ]
137 |
138 | [[package]]
139 | name = "clap_lex"
140 | version = "0.7.4"
141 | source = "registry+https://github.com/rust-lang/crates.io-index"
142 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
143 |
144 | [[package]]
145 | name = "criterion"
146 | version = "0.5.1"
147 | source = "registry+https://github.com/rust-lang/crates.io-index"
148 | checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
149 | dependencies = [
150 | "anes",
151 | "cast",
152 | "ciborium",
153 | "clap",
154 | "criterion-plot",
155 | "is-terminal",
156 | "itertools",
157 | "num-traits",
158 | "once_cell",
159 | "oorandom",
160 | "plotters",
161 | "rayon",
162 | "regex",
163 | "serde",
164 | "serde_derive",
165 | "serde_json",
166 | "tinytemplate",
167 | "walkdir",
168 | ]
169 |
170 | [[package]]
171 | name = "criterion-plot"
172 | version = "0.5.0"
173 | source = "registry+https://github.com/rust-lang/crates.io-index"
174 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
175 | dependencies = [
176 | "cast",
177 | "itertools",
178 | ]
179 |
180 | [[package]]
181 | name = "crossbeam-deque"
182 | version = "0.8.6"
183 | source = "registry+https://github.com/rust-lang/crates.io-index"
184 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
185 | dependencies = [
186 | "crossbeam-epoch",
187 | "crossbeam-utils",
188 | ]
189 |
190 | [[package]]
191 | name = "crossbeam-epoch"
192 | version = "0.9.18"
193 | source = "registry+https://github.com/rust-lang/crates.io-index"
194 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
195 | dependencies = [
196 | "crossbeam-utils",
197 | ]
198 |
199 | [[package]]
200 | name = "crossbeam-utils"
201 | version = "0.8.21"
202 | source = "registry+https://github.com/rust-lang/crates.io-index"
203 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
204 |
205 | [[package]]
206 | name = "crunchy"
207 | version = "0.2.3"
208 | source = "registry+https://github.com/rust-lang/crates.io-index"
209 | checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
210 |
211 | [[package]]
212 | name = "either"
213 | version = "1.15.0"
214 | source = "registry+https://github.com/rust-lang/crates.io-index"
215 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
216 |
217 | [[package]]
218 | name = "futures-core"
219 | version = "0.3.31"
220 | source = "registry+https://github.com/rust-lang/crates.io-index"
221 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
222 |
223 | [[package]]
224 | name = "futures-macro"
225 | version = "0.3.31"
226 | source = "registry+https://github.com/rust-lang/crates.io-index"
227 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
228 | dependencies = [
229 | "proc-macro2",
230 | "quote",
231 | "syn",
232 | ]
233 |
234 | [[package]]
235 | name = "futures-task"
236 | version = "0.3.31"
237 | source = "registry+https://github.com/rust-lang/crates.io-index"
238 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
239 |
240 | [[package]]
241 | name = "futures-util"
242 | version = "0.3.31"
243 | source = "registry+https://github.com/rust-lang/crates.io-index"
244 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
245 | dependencies = [
246 | "futures-core",
247 | "futures-macro",
248 | "futures-task",
249 | "pin-project-lite",
250 | "pin-utils",
251 | "slab",
252 | ]
253 |
254 | [[package]]
255 | name = "gimli"
256 | version = "0.31.1"
257 | source = "registry+https://github.com/rust-lang/crates.io-index"
258 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
259 |
260 | [[package]]
261 | name = "half"
262 | version = "2.5.0"
263 | source = "registry+https://github.com/rust-lang/crates.io-index"
264 | checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1"
265 | dependencies = [
266 | "cfg-if",
267 | "crunchy",
268 | ]
269 |
270 | [[package]]
271 | name = "hermit-abi"
272 | version = "0.5.0"
273 | source = "registry+https://github.com/rust-lang/crates.io-index"
274 | checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e"
275 |
276 | [[package]]
277 | name = "is-terminal"
278 | version = "0.4.16"
279 | source = "registry+https://github.com/rust-lang/crates.io-index"
280 | checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
281 | dependencies = [
282 | "hermit-abi",
283 | "libc",
284 | "windows-sys 0.59.0",
285 | ]
286 |
287 | [[package]]
288 | name = "itertools"
289 | version = "0.10.5"
290 | source = "registry+https://github.com/rust-lang/crates.io-index"
291 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
292 | dependencies = [
293 | "either",
294 | ]
295 |
296 | [[package]]
297 | name = "itoa"
298 | version = "1.0.15"
299 | source = "registry+https://github.com/rust-lang/crates.io-index"
300 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
301 |
302 | [[package]]
303 | name = "js-sys"
304 | version = "0.3.77"
305 | source = "registry+https://github.com/rust-lang/crates.io-index"
306 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
307 | dependencies = [
308 | "once_cell",
309 | "wasm-bindgen",
310 | ]
311 |
312 | [[package]]
313 | name = "libc"
314 | version = "0.2.171"
315 | source = "registry+https://github.com/rust-lang/crates.io-index"
316 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
317 |
318 | [[package]]
319 | name = "lock_api"
320 | version = "0.4.12"
321 | source = "registry+https://github.com/rust-lang/crates.io-index"
322 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
323 | dependencies = [
324 | "autocfg",
325 | "scopeguard",
326 | ]
327 |
328 | [[package]]
329 | name = "log"
330 | version = "0.4.27"
331 | source = "registry+https://github.com/rust-lang/crates.io-index"
332 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
333 |
334 | [[package]]
335 | name = "memchr"
336 | version = "2.7.4"
337 | source = "registry+https://github.com/rust-lang/crates.io-index"
338 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
339 |
340 | [[package]]
341 | name = "miniz_oxide"
342 | version = "0.8.7"
343 | source = "registry+https://github.com/rust-lang/crates.io-index"
344 | checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
345 | dependencies = [
346 | "adler2",
347 | ]
348 |
349 | [[package]]
350 | name = "mio"
351 | version = "1.0.3"
352 | source = "registry+https://github.com/rust-lang/crates.io-index"
353 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
354 | dependencies = [
355 | "libc",
356 | "wasi",
357 | "windows-sys 0.52.0",
358 | ]
359 |
360 | [[package]]
361 | name = "num-traits"
362 | version = "0.2.19"
363 | source = "registry+https://github.com/rust-lang/crates.io-index"
364 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
365 | dependencies = [
366 | "autocfg",
367 | ]
368 |
369 | [[package]]
370 | name = "object"
371 | version = "0.36.7"
372 | source = "registry+https://github.com/rust-lang/crates.io-index"
373 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
374 | dependencies = [
375 | "memchr",
376 | ]
377 |
378 | [[package]]
379 | name = "once_cell"
380 | version = "1.21.3"
381 | source = "registry+https://github.com/rust-lang/crates.io-index"
382 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
383 |
384 | [[package]]
385 | name = "oorandom"
386 | version = "11.1.5"
387 | source = "registry+https://github.com/rust-lang/crates.io-index"
388 | checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
389 |
390 | [[package]]
391 | name = "parking_lot"
392 | version = "0.12.3"
393 | source = "registry+https://github.com/rust-lang/crates.io-index"
394 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
395 | dependencies = [
396 | "lock_api",
397 | "parking_lot_core",
398 | ]
399 |
400 | [[package]]
401 | name = "parking_lot_core"
402 | version = "0.9.10"
403 | source = "registry+https://github.com/rust-lang/crates.io-index"
404 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
405 | dependencies = [
406 | "cfg-if",
407 | "libc",
408 | "redox_syscall",
409 | "smallvec",
410 | "windows-targets",
411 | ]
412 |
413 | [[package]]
414 | name = "pin-project-lite"
415 | version = "0.2.16"
416 | source = "registry+https://github.com/rust-lang/crates.io-index"
417 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
418 |
419 | [[package]]
420 | name = "pin-utils"
421 | version = "0.1.0"
422 | source = "registry+https://github.com/rust-lang/crates.io-index"
423 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
424 |
425 | [[package]]
426 | name = "plotters"
427 | version = "0.3.7"
428 | source = "registry+https://github.com/rust-lang/crates.io-index"
429 | checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
430 | dependencies = [
431 | "num-traits",
432 | "plotters-backend",
433 | "plotters-svg",
434 | "wasm-bindgen",
435 | "web-sys",
436 | ]
437 |
438 | [[package]]
439 | name = "plotters-backend"
440 | version = "0.3.7"
441 | source = "registry+https://github.com/rust-lang/crates.io-index"
442 | checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
443 |
444 | [[package]]
445 | name = "plotters-svg"
446 | version = "0.3.7"
447 | source = "registry+https://github.com/rust-lang/crates.io-index"
448 | checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
449 | dependencies = [
450 | "plotters-backend",
451 | ]
452 |
453 | [[package]]
454 | name = "pollster"
455 | version = "0.3.0"
456 | source = "registry+https://github.com/rust-lang/crates.io-index"
457 | checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2"
458 |
459 | [[package]]
460 | name = "proc-macro2"
461 | version = "1.0.94"
462 | source = "registry+https://github.com/rust-lang/crates.io-index"
463 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
464 | dependencies = [
465 | "unicode-ident",
466 | ]
467 |
468 | [[package]]
469 | name = "quote"
470 | version = "1.0.40"
471 | source = "registry+https://github.com/rust-lang/crates.io-index"
472 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
473 | dependencies = [
474 | "proc-macro2",
475 | ]
476 |
477 | [[package]]
478 | name = "rayon"
479 | version = "1.10.0"
480 | source = "registry+https://github.com/rust-lang/crates.io-index"
481 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
482 | dependencies = [
483 | "either",
484 | "rayon-core",
485 | ]
486 |
487 | [[package]]
488 | name = "rayon-core"
489 | version = "1.12.1"
490 | source = "registry+https://github.com/rust-lang/crates.io-index"
491 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
492 | dependencies = [
493 | "crossbeam-deque",
494 | "crossbeam-utils",
495 | ]
496 |
497 | [[package]]
498 | name = "reblessive"
499 | version = "0.4.3"
500 | dependencies = [
501 | "ciborium",
502 | "criterion",
503 | "futures-util",
504 | "pollster",
505 | "regex",
506 | "tokio",
507 | ]
508 |
509 | [[package]]
510 | name = "redox_syscall"
511 | version = "0.5.11"
512 | source = "registry+https://github.com/rust-lang/crates.io-index"
513 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
514 | dependencies = [
515 | "bitflags",
516 | ]
517 |
518 | [[package]]
519 | name = "regex"
520 | version = "1.11.1"
521 | source = "registry+https://github.com/rust-lang/crates.io-index"
522 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
523 | dependencies = [
524 | "aho-corasick",
525 | "memchr",
526 | "regex-automata",
527 | "regex-syntax",
528 | ]
529 |
530 | [[package]]
531 | name = "regex-automata"
532 | version = "0.4.9"
533 | source = "registry+https://github.com/rust-lang/crates.io-index"
534 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
535 | dependencies = [
536 | "aho-corasick",
537 | "memchr",
538 | "regex-syntax",
539 | ]
540 |
541 | [[package]]
542 | name = "regex-syntax"
543 | version = "0.8.5"
544 | source = "registry+https://github.com/rust-lang/crates.io-index"
545 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
546 |
547 | [[package]]
548 | name = "rustc-demangle"
549 | version = "0.1.24"
550 | source = "registry+https://github.com/rust-lang/crates.io-index"
551 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
552 |
553 | [[package]]
554 | name = "rustversion"
555 | version = "1.0.20"
556 | source = "registry+https://github.com/rust-lang/crates.io-index"
557 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
558 |
559 | [[package]]
560 | name = "ryu"
561 | version = "1.0.20"
562 | source = "registry+https://github.com/rust-lang/crates.io-index"
563 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
564 |
565 | [[package]]
566 | name = "same-file"
567 | version = "1.0.6"
568 | source = "registry+https://github.com/rust-lang/crates.io-index"
569 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
570 | dependencies = [
571 | "winapi-util",
572 | ]
573 |
574 | [[package]]
575 | name = "scopeguard"
576 | version = "1.2.0"
577 | source = "registry+https://github.com/rust-lang/crates.io-index"
578 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
579 |
580 | [[package]]
581 | name = "serde"
582 | version = "1.0.219"
583 | source = "registry+https://github.com/rust-lang/crates.io-index"
584 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
585 | dependencies = [
586 | "serde_derive",
587 | ]
588 |
589 | [[package]]
590 | name = "serde_derive"
591 | version = "1.0.219"
592 | source = "registry+https://github.com/rust-lang/crates.io-index"
593 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
594 | dependencies = [
595 | "proc-macro2",
596 | "quote",
597 | "syn",
598 | ]
599 |
600 | [[package]]
601 | name = "serde_json"
602 | version = "1.0.140"
603 | source = "registry+https://github.com/rust-lang/crates.io-index"
604 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
605 | dependencies = [
606 | "itoa",
607 | "memchr",
608 | "ryu",
609 | "serde",
610 | ]
611 |
612 | [[package]]
613 | name = "signal-hook-registry"
614 | version = "1.4.2"
615 | source = "registry+https://github.com/rust-lang/crates.io-index"
616 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
617 | dependencies = [
618 | "libc",
619 | ]
620 |
621 | [[package]]
622 | name = "slab"
623 | version = "0.4.9"
624 | source = "registry+https://github.com/rust-lang/crates.io-index"
625 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
626 | dependencies = [
627 | "autocfg",
628 | ]
629 |
630 | [[package]]
631 | name = "smallvec"
632 | version = "1.15.0"
633 | source = "registry+https://github.com/rust-lang/crates.io-index"
634 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
635 |
636 | [[package]]
637 | name = "socket2"
638 | version = "0.5.9"
639 | source = "registry+https://github.com/rust-lang/crates.io-index"
640 | checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
641 | dependencies = [
642 | "libc",
643 | "windows-sys 0.52.0",
644 | ]
645 |
646 | [[package]]
647 | name = "syn"
648 | version = "2.0.100"
649 | source = "registry+https://github.com/rust-lang/crates.io-index"
650 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
651 | dependencies = [
652 | "proc-macro2",
653 | "quote",
654 | "unicode-ident",
655 | ]
656 |
657 | [[package]]
658 | name = "tinytemplate"
659 | version = "1.2.1"
660 | source = "registry+https://github.com/rust-lang/crates.io-index"
661 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
662 | dependencies = [
663 | "serde",
664 | "serde_json",
665 | ]
666 |
667 | [[package]]
668 | name = "tokio"
669 | version = "1.44.2"
670 | source = "registry+https://github.com/rust-lang/crates.io-index"
671 | checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
672 | dependencies = [
673 | "backtrace",
674 | "bytes",
675 | "libc",
676 | "mio",
677 | "parking_lot",
678 | "pin-project-lite",
679 | "signal-hook-registry",
680 | "socket2",
681 | "tokio-macros",
682 | "windows-sys 0.52.0",
683 | ]
684 |
685 | [[package]]
686 | name = "tokio-macros"
687 | version = "2.5.0"
688 | source = "registry+https://github.com/rust-lang/crates.io-index"
689 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
690 | dependencies = [
691 | "proc-macro2",
692 | "quote",
693 | "syn",
694 | ]
695 |
696 | [[package]]
697 | name = "unicode-ident"
698 | version = "1.0.18"
699 | source = "registry+https://github.com/rust-lang/crates.io-index"
700 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
701 |
702 | [[package]]
703 | name = "walkdir"
704 | version = "2.5.0"
705 | source = "registry+https://github.com/rust-lang/crates.io-index"
706 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
707 | dependencies = [
708 | "same-file",
709 | "winapi-util",
710 | ]
711 |
712 | [[package]]
713 | name = "wasi"
714 | version = "0.11.0+wasi-snapshot-preview1"
715 | source = "registry+https://github.com/rust-lang/crates.io-index"
716 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
717 |
718 | [[package]]
719 | name = "wasm-bindgen"
720 | version = "0.2.100"
721 | source = "registry+https://github.com/rust-lang/crates.io-index"
722 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
723 | dependencies = [
724 | "cfg-if",
725 | "once_cell",
726 | "rustversion",
727 | "wasm-bindgen-macro",
728 | ]
729 |
730 | [[package]]
731 | name = "wasm-bindgen-backend"
732 | version = "0.2.100"
733 | source = "registry+https://github.com/rust-lang/crates.io-index"
734 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
735 | dependencies = [
736 | "bumpalo",
737 | "log",
738 | "proc-macro2",
739 | "quote",
740 | "syn",
741 | "wasm-bindgen-shared",
742 | ]
743 |
744 | [[package]]
745 | name = "wasm-bindgen-macro"
746 | version = "0.2.100"
747 | source = "registry+https://github.com/rust-lang/crates.io-index"
748 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
749 | dependencies = [
750 | "quote",
751 | "wasm-bindgen-macro-support",
752 | ]
753 |
754 | [[package]]
755 | name = "wasm-bindgen-macro-support"
756 | version = "0.2.100"
757 | source = "registry+https://github.com/rust-lang/crates.io-index"
758 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
759 | dependencies = [
760 | "proc-macro2",
761 | "quote",
762 | "syn",
763 | "wasm-bindgen-backend",
764 | "wasm-bindgen-shared",
765 | ]
766 |
767 | [[package]]
768 | name = "wasm-bindgen-shared"
769 | version = "0.2.100"
770 | source = "registry+https://github.com/rust-lang/crates.io-index"
771 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
772 | dependencies = [
773 | "unicode-ident",
774 | ]
775 |
776 | [[package]]
777 | name = "web-sys"
778 | version = "0.3.77"
779 | source = "registry+https://github.com/rust-lang/crates.io-index"
780 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
781 | dependencies = [
782 | "js-sys",
783 | "wasm-bindgen",
784 | ]
785 |
786 | [[package]]
787 | name = "winapi-util"
788 | version = "0.1.9"
789 | source = "registry+https://github.com/rust-lang/crates.io-index"
790 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
791 | dependencies = [
792 | "windows-sys 0.59.0",
793 | ]
794 |
795 | [[package]]
796 | name = "windows-sys"
797 | version = "0.52.0"
798 | source = "registry+https://github.com/rust-lang/crates.io-index"
799 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
800 | dependencies = [
801 | "windows-targets",
802 | ]
803 |
804 | [[package]]
805 | name = "windows-sys"
806 | version = "0.59.0"
807 | source = "registry+https://github.com/rust-lang/crates.io-index"
808 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
809 | dependencies = [
810 | "windows-targets",
811 | ]
812 |
813 | [[package]]
814 | name = "windows-targets"
815 | version = "0.52.6"
816 | source = "registry+https://github.com/rust-lang/crates.io-index"
817 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
818 | dependencies = [
819 | "windows_aarch64_gnullvm",
820 | "windows_aarch64_msvc",
821 | "windows_i686_gnu",
822 | "windows_i686_gnullvm",
823 | "windows_i686_msvc",
824 | "windows_x86_64_gnu",
825 | "windows_x86_64_gnullvm",
826 | "windows_x86_64_msvc",
827 | ]
828 |
829 | [[package]]
830 | name = "windows_aarch64_gnullvm"
831 | version = "0.52.6"
832 | source = "registry+https://github.com/rust-lang/crates.io-index"
833 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
834 |
835 | [[package]]
836 | name = "windows_aarch64_msvc"
837 | version = "0.52.6"
838 | source = "registry+https://github.com/rust-lang/crates.io-index"
839 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
840 |
841 | [[package]]
842 | name = "windows_i686_gnu"
843 | version = "0.52.6"
844 | source = "registry+https://github.com/rust-lang/crates.io-index"
845 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
846 |
847 | [[package]]
848 | name = "windows_i686_gnullvm"
849 | version = "0.52.6"
850 | source = "registry+https://github.com/rust-lang/crates.io-index"
851 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
852 |
853 | [[package]]
854 | name = "windows_i686_msvc"
855 | version = "0.52.6"
856 | source = "registry+https://github.com/rust-lang/crates.io-index"
857 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
858 |
859 | [[package]]
860 | name = "windows_x86_64_gnu"
861 | version = "0.52.6"
862 | source = "registry+https://github.com/rust-lang/crates.io-index"
863 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
864 |
865 | [[package]]
866 | name = "windows_x86_64_gnullvm"
867 | version = "0.52.6"
868 | source = "registry+https://github.com/rust-lang/crates.io-index"
869 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
870 |
871 | [[package]]
872 | name = "windows_x86_64_msvc"
873 | version = "0.52.6"
874 | source = "registry+https://github.com/rust-lang/crates.io-index"
875 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
876 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "reblessive"
3 | version = "0.4.3"
4 | edition = "2021"
5 | rust-version = "1.84"
6 | license = "MIT"
7 | readme = "README.md"
8 | description = "A small runtime for running deeply nested recursive functions"
9 | keywords = ["stack","call","async","memory","runtime"]
10 | repository = "https://github.com/DelSkayn/reblessive.git"
11 |
12 | [dev-dependencies]
13 | criterion = "0.5.1"
14 | futures-util = "0.3.30"
15 | pollster = "0.3.0"
16 | tokio = { version = "1.36.0", features = ["full"] }
17 | # Pinned so that criterion compiles when minimal version is used.
18 | ciborium = "0.2.2"
19 | regex = "1.5.3"
20 |
21 | [features]
22 | tree = []
23 | nightly = []
24 |
25 | [package.metadata.docs.rs]
26 | # document all features
27 | all-features = true
28 | # defines the configuration attribute `docsrs`
29 | rustdoc-args = ["--cfg", "docsrs"]
30 |
31 | [[bench]]
32 | name = "calc"
33 | harness = false
34 | [[bench]]
35 | name = "fibbo"
36 | harness = false
37 |
38 | [lints.rust]
39 | unexpected_cfgs = { level = "allow", check-cfg = ['cfg(docrs)'] }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Mees Delzenne
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://codecov.io/gh/DelSkayn/reblessive)
2 | [](https://crates.io/crates/reblessive/)
3 |
4 |
5 | # Reblessive
6 |
7 | A heap allocated runtime for deeply recursive algorithms.
8 |
9 | Turn your cursed recursive algorithm into a blessed heap allocated structure which won't
10 | overflow the stack, regardless of depth.
11 |
12 | ## What is this crate for?
13 |
14 | There are some types of algorithms which are easiest to write as a recursive algorithm.
15 | Examples include a recursive descent parsers and tree-walking interpreters.
16 | These algorithms often need to keep track of complex stack of state and are therefore easiest to write as a set of recursive function calling each other.
17 | This does however have a major downside: The stack can be rather limited.
18 | Especially when the input of a algorithm is externally controlled, implementing it as a recursive algorithm is asking for stack overflows.
19 |
20 | This library is an attempt to solve that issue.
21 | It provides a small executor which is able to efficiently allocate new futures in a stack like order and then drive them in a single loop.
22 | With these executors you can write your recursive algorithm as a set of futures.
23 | The executor will then, whenever a function needs to call another, pause the current future to execute the newly scheduled future.
24 | This allows one to implement your algorithm in a way that still looks recursive but won't run out of stack if recursing very deep.
25 |
26 |
27 | ## Example
28 |
29 | ```rust
30 | use std::{
31 | mem::MaybeUninit,
32 | time::{Duration, Instant},
33 | };
34 |
35 | use reblessive::{Stack, Stk};
36 |
37 | async fn heavy_fibbo(ctx: &mut Stk, n: usize) -> usize {
38 | // An extra stack allocation to simulate a more complex function.
39 | let mut ballast: MaybeUninit<[u8; 1024 * 1024]> = std::mem::MaybeUninit::uninit();
40 | // Make sure the ballast isn't compiled out.
41 | std::hint::black_box(&mut ballast);
42 |
43 | match n {
44 | 0 => 1,
45 | 1 => 1,
46 | x => {
47 | ctx.run(move |ctx| heavy_fibbo(ctx, x - 1)).await
48 | + ctx.run(move |ctx| heavy_fibbo(ctx, x - 2)).await
49 | }
50 | }
51 | }
52 |
53 | fn main() {
54 | // Create a stack to run the function in.
55 | let mut stack = Stack::new();
56 |
57 | // run the function to completion on the stack.
58 | let res = stack.enter(|ctx| heavy_fibbo(ctx, 20)).finish();
59 | println!("result: {res}");
60 |
61 | assert_eq!(res, 10946);
62 |
63 | // Reblessive can also make any recursive function interuptable.
64 | let mut runner = stack.enter(|ctx| heavy_fibbo(ctx, 60));
65 |
66 | let start = Instant::now();
67 | loop {
68 | // run the function forward by a step.
69 | // If this returned Some than the function completed.
70 | if let Some(x) = runner.step() {
71 | println!("finished: {x}")
72 | }
73 | // We didn't complete the computation in time so we can just drop the runner and stop the
74 | // function.
75 | if start.elapsed() > Duration::from_secs(3) {
76 | println!("Timed out!");
77 | break;
78 | }
79 | }
80 | }
81 | ```
82 |
--------------------------------------------------------------------------------
/benches/calc.rs:
--------------------------------------------------------------------------------
1 | use std::fmt::{self, Write};
2 |
3 | use criterion::{black_box, criterion_group, criterion_main, Criterion};
4 | use reblessive::Stk;
5 |
6 | #[derive(Debug)]
7 | enum UnaryOperator {
8 | Neg,
9 | Pos,
10 | }
11 |
12 | #[derive(Eq, PartialEq, Debug)]
13 | enum BinaryOperator {
14 | Pow,
15 | Mul,
16 | Div,
17 | Add,
18 | Sub,
19 | }
20 |
21 | #[derive(Debug)]
22 | enum Expression {
23 | Number(f64),
24 | Covered(Box),
25 | Binary {
26 | left: Box,
27 | op: BinaryOperator,
28 | right: Box,
29 | },
30 | Unary {
31 | op: UnaryOperator,
32 | expr: Box,
33 | },
34 | }
35 |
36 | #[derive(Debug)]
37 | pub enum Error {
38 | Parse,
39 | }
40 |
41 | impl std::error::Error for Error {}
42 |
43 | impl fmt::Display for Error {
44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 | match self {
46 | Self::Parse => write!(f, "Failed to parse expression"),
47 | }
48 | }
49 | }
50 |
51 | fn is_number_char(v: u8) -> bool {
52 | v.is_ascii_digit() || matches!(v, b'.' | b'e' | b'E')
53 | }
54 |
55 | struct Buffer<'a>(&'a [u8]);
56 |
57 | impl Iterator for Buffer<'_> {
58 | type Item = u8;
59 |
60 | fn next(&mut self) -> Option {
61 | let (head, tail) = self.0.split_first()?;
62 | self.0 = tail;
63 | Some(*head)
64 | }
65 | }
66 |
67 | impl Buffer<'_> {
68 | pub fn get(&self, index: I) -> Option<&I::Output>
69 | where
70 | I: std::slice::SliceIndex<[u8]>,
71 | {
72 | self.0.get(index)
73 | }
74 | }
75 |
76 | async fn parse(
77 | ctx: &mut Stk,
78 | bytes: &mut Buffer<'_>,
79 | binding_power: u8,
80 | ) -> Result {
81 | let mut peek = bytes.get(0).copied();
82 | let mut lhs = loop {
83 | match peek {
84 | Some(b'+') => {
85 | bytes.next();
86 | let expr = ctx.run(|ctx| parse(ctx, bytes, 7)).await?;
87 | break Expression::Unary {
88 | op: UnaryOperator::Pos,
89 | expr: Box::new(expr),
90 | };
91 | }
92 | Some(b'-') => {
93 | bytes.next();
94 | let expr = ctx.run(|ctx| parse(ctx, bytes, 7)).await?;
95 | break Expression::Unary {
96 | op: UnaryOperator::Neg,
97 | expr: Box::new(expr),
98 | };
99 | }
100 | Some(b'(') => {
101 | bytes.next();
102 | let expr = ctx.run(|ctx| parse(ctx, bytes, 0)).await?;
103 | let Some(b')') = bytes.next() else {
104 | return Err(Error::Parse);
105 | };
106 | break Expression::Covered(Box::new(expr));
107 | }
108 | Some(x) if x.is_ascii_whitespace() => {
109 | bytes.next();
110 | peek = bytes.get(0).copied();
111 | continue;
112 | }
113 | Some(x) if is_number_char(x) => {
114 | let mut number = String::new();
115 | number.push(x as char);
116 | bytes.next();
117 | while bytes.get(0).copied().map(is_number_char).unwrap_or(false) {
118 | let c = bytes.next().unwrap();
119 | number.push(c as char);
120 | let n = bytes.get(0).copied();
121 | if n.map(|c| c.to_ascii_lowercase()) == Some(b'e') {
122 | number.push(n.unwrap() as char);
123 | let n = bytes.get(0).copied();
124 | if matches!(n, Some(b'-' | b'+')) {
125 | bytes.next();
126 | number.push(n.unwrap() as char);
127 | }
128 | }
129 | }
130 | let num = number.parse::().map_err(|_| Error::Parse)?;
131 | break Expression::Number(num);
132 | }
133 | _ => {
134 | return Err(Error::Parse);
135 | }
136 | };
137 | };
138 |
139 | loop {
140 | let (op, bp) = match bytes.get(0).copied() {
141 | Some(b'*') => {
142 | if let Some(b'*') = bytes.get(1) {
143 | (BinaryOperator::Pow, (5, 6))
144 | } else {
145 | (BinaryOperator::Mul, (3, 4))
146 | }
147 | }
148 | Some(b'/') => (BinaryOperator::Div, (3, 4)),
149 | Some(b'+') => (BinaryOperator::Add, (1, 2)),
150 | Some(b'-') => (BinaryOperator::Sub, (1, 2)),
151 | Some(x) if x.is_ascii_whitespace() => {
152 | bytes.next();
153 | continue;
154 | }
155 | _ => {
156 | break;
157 | }
158 | };
159 |
160 | if bp.0 < binding_power {
161 | break;
162 | }
163 |
164 | bytes.next();
165 | if op == BinaryOperator::Pow {
166 | bytes.next();
167 | }
168 |
169 | let rhs = ctx.run(|ctx| parse(ctx, bytes, bp.1)).await?;
170 |
171 | lhs = Expression::Binary {
172 | left: Box::new(lhs),
173 | op,
174 | right: Box::new(rhs),
175 | }
176 | }
177 |
178 | Ok(lhs)
179 | }
180 |
181 | async fn eval(ctx: &mut Stk, expr: &Expression) -> f64 {
182 | match expr {
183 | Expression::Number(x) => *x,
184 | Expression::Covered(ref x) => ctx.run(|ctx| eval(ctx, x)).await,
185 | Expression::Binary { left, op, right } => {
186 | let left = ctx.run(|ctx| eval(ctx, left)).await;
187 | let right = ctx.run(|ctx| eval(ctx, right)).await;
188 | match op {
189 | BinaryOperator::Pow => left.powf(right),
190 | BinaryOperator::Mul => left * right,
191 | BinaryOperator::Div => left / right,
192 | BinaryOperator::Add => left + right,
193 | BinaryOperator::Sub => left - right,
194 | }
195 | }
196 | Expression::Unary { op, expr } => {
197 | let expr = ctx.run(|ctx| eval(ctx, expr)).await;
198 | match op {
199 | UnaryOperator::Neg => -expr,
200 | UnaryOperator::Pos => expr,
201 | }
202 | }
203 | }
204 | }
205 |
206 | fn generate_expression(len: usize) -> String {
207 | struct Rand(u32);
208 |
209 | impl Rand {
210 | fn new() -> Self {
211 | Rand(0x194b93c)
212 | }
213 |
214 | fn next(&mut self) -> u32 {
215 | let mut x = self.0;
216 | x ^= x << 13;
217 | x ^= x >> 17;
218 | x ^= x << 5;
219 | self.0 = x;
220 | x
221 | }
222 | }
223 |
224 | let mut res = String::new();
225 | let mut rand = Rand::new();
226 | for _ in 0..len {
227 | let num = (rand.next() % 1000) as f32 * 0.01;
228 | write!(res, "{} ", num).unwrap();
229 | match rand.next() % 4 {
230 | 0 => write!(res, "+ ").unwrap(),
231 | 1 => write!(res, "* ").unwrap(),
232 | 2 => write!(res, "- ").unwrap(),
233 | 3 => write!(res, "/ ").unwrap(),
234 | _ => unreachable!(),
235 | }
236 | }
237 |
238 | let num = (rand.next() % 1000) as f32 * 0.01;
239 | write!(res, "{} ", num).unwrap();
240 |
241 | res
242 | }
243 |
244 | fn bench_expr(c: &mut Criterion) {
245 | c.bench_function("expr 100", |b| {
246 | let expr = generate_expression(100);
247 | b.iter(|| {
248 | let mut stack = reblessive::Stack::new();
249 | let mut tokens = Buffer(expr.as_bytes());
250 | let expr = stack
251 | .enter(|ctx| parse(ctx, &mut tokens, 0))
252 | .finish()
253 | .unwrap();
254 | black_box(stack.enter(|ctx| eval(ctx, &expr)).finish());
255 | })
256 | });
257 |
258 | c.bench_function("expr 100 no startup", |b| {
259 | let expr = generate_expression(100);
260 | let mut stack = reblessive::Stack::new();
261 | b.iter(|| {
262 | let mut tokens = Buffer(expr.as_bytes());
263 | let expr = stack
264 | .enter(|ctx| parse(ctx, &mut tokens, 0))
265 | .finish()
266 | .unwrap();
267 | black_box(stack.enter(|ctx| eval(ctx, &expr)).finish())
268 | })
269 | });
270 | }
271 | criterion_group!(benches, bench_expr);
272 | criterion_main!(benches);
273 |
--------------------------------------------------------------------------------
/benches/fibbo.rs:
--------------------------------------------------------------------------------
1 | use criterion::{criterion_group, criterion_main, Criterion};
2 | use reblessive::{Stack, Stk};
3 |
4 | async fn heavy_fibbo(ctx: &mut Stk, n: usize) -> usize {
5 | match n {
6 | 0 => 1,
7 | 1 => 1,
8 | x => {
9 | ctx.run(move |ctx| heavy_fibbo(ctx, x - 1)).await
10 | + ctx.run(move |ctx| heavy_fibbo(ctx, x - 2)).await
11 | }
12 | }
13 | }
14 |
15 | fn bench_fibbo(c: &mut Criterion) {
16 | c.bench_function("fib 15", |b| {
17 | b.iter(|| {
18 | // Create a stack to run the function in.
19 | let mut stack = Stack::new();
20 |
21 | // run the function to completion on the stack.
22 | let res = stack.enter(|ctx| heavy_fibbo(ctx, 15)).finish();
23 | assert_eq!(res, 987);
24 | })
25 | });
26 |
27 | c.bench_function("fib 20", |b| {
28 | b.iter(|| {
29 | // Create a stack to run the function in.
30 | let mut stack = Stack::new();
31 |
32 | // run the function to completion on the stack.
33 | let res = stack.enter(|ctx| heavy_fibbo(ctx, 20)).finish();
34 | assert_eq!(res, 10946);
35 | })
36 | });
37 |
38 | c.bench_function("fib 25", |b| {
39 | b.iter(|| {
40 | // Create a stack to run the function in.
41 | let mut stack = Stack::new();
42 |
43 | // run the function to completion on the stack.
44 | let res = stack.enter(|ctx| heavy_fibbo(ctx, 30)).finish();
45 | assert_eq!(res, 1346269);
46 | })
47 | });
48 | }
49 |
50 | fn bench_fibbo_async(c: &mut Criterion) {
51 | c.bench_function("fib async 15", |b| {
52 | let rt = tokio::runtime::Builder::new_current_thread()
53 | .build()
54 | .unwrap();
55 | b.iter(|| {
56 | rt.block_on(async {
57 | // Create a stack to run the function in.
58 | let mut stack = Stack::new();
59 |
60 | // run the function to completion on the stack.
61 | let res = stack.enter(|ctx| heavy_fibbo(ctx, 15)).finish_async().await;
62 | assert_eq!(res, 987);
63 | })
64 | })
65 | });
66 |
67 | c.bench_function("fib async 20", |b| {
68 | let rt = tokio::runtime::Builder::new_current_thread()
69 | .build()
70 | .unwrap();
71 | b.iter(|| {
72 | rt.block_on(async {
73 | // Create a stack to run the function in.
74 | let mut stack = Stack::new();
75 |
76 | // run the function to completion on the stack.
77 | let res = stack.enter(|ctx| heavy_fibbo(ctx, 20)).finish_async().await;
78 | assert_eq!(res, 10946);
79 | })
80 | })
81 | });
82 |
83 | c.bench_function("fib async 25", |b| {
84 | let rt = tokio::runtime::Builder::new_current_thread()
85 | .build()
86 | .unwrap();
87 | b.iter(|| {
88 | rt.block_on(async {
89 | // Create a stack to run the function in.
90 | let mut stack = Stack::new();
91 |
92 | // run the function to completion on the stack.
93 | let res = stack.enter(|ctx| heavy_fibbo(ctx, 30)).finish_async().await;
94 | assert_eq!(res, 1346269);
95 | })
96 | })
97 | });
98 | }
99 |
100 | criterion_group!(benches, bench_fibbo, bench_fibbo_async);
101 | criterion_main!(benches);
102 |
--------------------------------------------------------------------------------
/examples/async.rs:
--------------------------------------------------------------------------------
1 | use std::mem::MaybeUninit;
2 |
3 | use reblessive::{Stack, Stk};
4 |
5 | async fn deep_read(ctx: &mut Stk, n: usize) -> String {
6 | let mut ballast: MaybeUninit<[u8; 1024 * 1024]> = std::mem::MaybeUninit::uninit();
7 | std::hint::black_box(&mut ballast);
8 |
9 | if n == 0 {
10 | tokio::fs::read_to_string("./Cargo.toml").await.unwrap()
11 | } else {
12 | ctx.run(|ctx| deep_read(ctx, n - 1)).await
13 | }
14 | }
15 |
16 | #[tokio::main]
17 | async fn main() {
18 | let mut stack = Stack::new();
19 |
20 | let str = stack.enter(|ctx| deep_read(ctx, 20)).finish_async().await;
21 |
22 | println!("{}", str);
23 | }
24 |
--------------------------------------------------------------------------------
/examples/calculator.rs:
--------------------------------------------------------------------------------
1 | use std::fmt;
2 |
3 | use reblessive::Stk;
4 |
5 | #[derive(Debug)]
6 | enum UnaryOperator {
7 | Neg,
8 | Pos,
9 | }
10 |
11 | #[derive(Eq, PartialEq, Debug)]
12 | enum BinaryOperator {
13 | Pow,
14 | Mul,
15 | Div,
16 | Add,
17 | Sub,
18 | }
19 |
20 | #[derive(Debug)]
21 | enum Expression {
22 | Number(f64),
23 | Covered(Box),
24 | Binary {
25 | left: Box,
26 | op: BinaryOperator,
27 | right: Box,
28 | },
29 | Unary {
30 | op: UnaryOperator,
31 | expr: Box,
32 | },
33 | }
34 |
35 | #[derive(Debug)]
36 | pub enum Error {
37 | Parse,
38 | }
39 |
40 | impl std::error::Error for Error {}
41 |
42 | impl fmt::Display for Error {
43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 | match self {
45 | Self::Parse => write!(f, "Failed to parse expression"),
46 | }
47 | }
48 | }
49 |
50 | fn is_number_char(v: u8) -> bool {
51 | v.is_ascii_digit() || matches!(v, b'.' | b'e' | b'E')
52 | }
53 |
54 | struct Buffer<'a>(&'a [u8]);
55 |
56 | impl Iterator for Buffer<'_> {
57 | type Item = u8;
58 |
59 | fn next(&mut self) -> Option {
60 | let (head, tail) = self.0.split_first()?;
61 | self.0 = tail;
62 | Some(*head)
63 | }
64 | }
65 |
66 | impl Buffer<'_> {
67 | pub fn get(&self, index: I) -> Option<&I::Output>
68 | where
69 | I: std::slice::SliceIndex<[u8]>,
70 | {
71 | self.0.get(index)
72 | }
73 | }
74 |
75 | async fn parse(
76 | ctx: &mut Stk,
77 | bytes: &mut Buffer<'_>,
78 | binding_power: u8,
79 | ) -> Result {
80 | let peek = bytes.get(0).copied();
81 | let mut lhs = loop {
82 | match peek {
83 | Some(b'+') => {
84 | bytes.next();
85 | let expr = ctx.run(|ctx| parse(ctx, bytes, 7)).await?;
86 | break Expression::Unary {
87 | op: UnaryOperator::Pos,
88 | expr: Box::new(expr),
89 | };
90 | }
91 | Some(b'-') => {
92 | bytes.next();
93 | let expr = ctx.run(|ctx| parse(ctx, bytes, 7)).await?;
94 | break Expression::Unary {
95 | op: UnaryOperator::Neg,
96 | expr: Box::new(expr),
97 | };
98 | }
99 | Some(b'(') => {
100 | bytes.next();
101 | let expr = ctx.run(|ctx| parse(ctx, bytes, 0)).await?;
102 | let Some(b')') = bytes.next() else {
103 | return Err(Error::Parse);
104 | };
105 | break Expression::Covered(Box::new(expr));
106 | }
107 | Some(x) if x.is_ascii_whitespace() => continue,
108 | Some(x) if is_number_char(x) => {
109 | let mut number = String::new();
110 | number.push(x as char);
111 | bytes.next();
112 | while bytes.get(0).copied().map(is_number_char).unwrap_or(false) {
113 | let c = bytes.next().unwrap();
114 | number.push(c as char);
115 | if c.eq_ignore_ascii_case(&b'e') {
116 | let n = bytes.get(0).copied();
117 | if matches!(n, Some(b'-' | b'+')) {
118 | bytes.next();
119 | number.push(n.unwrap() as char);
120 | }
121 | }
122 | }
123 | let num = number.parse::().map_err(|_| Error::Parse)?;
124 | break Expression::Number(num);
125 | }
126 | _ => {
127 | return Err(Error::Parse);
128 | }
129 | };
130 | };
131 |
132 | loop {
133 | let (op, bp) = match bytes.get(0).copied() {
134 | Some(b'*') => {
135 | if let Some(b'*') = bytes.get(1) {
136 | (BinaryOperator::Pow, (5, 6))
137 | } else {
138 | (BinaryOperator::Mul, (3, 4))
139 | }
140 | }
141 | Some(b'/') => (BinaryOperator::Div, (3, 4)),
142 | Some(b'+') => (BinaryOperator::Add, (1, 2)),
143 | Some(b'-') => (BinaryOperator::Sub, (1, 2)),
144 | Some(x) if x.is_ascii_whitespace() => {
145 | continue;
146 | }
147 | _ => break,
148 | };
149 |
150 | if bp.0 < binding_power {
151 | break;
152 | }
153 |
154 | bytes.next();
155 | if op == BinaryOperator::Pow {
156 | bytes.next();
157 | }
158 |
159 | let rhs = ctx.run(|ctx| parse(ctx, bytes, bp.1)).await?;
160 |
161 | lhs = Expression::Binary {
162 | left: Box::new(lhs),
163 | op,
164 | right: Box::new(rhs),
165 | }
166 | }
167 |
168 | Ok(lhs)
169 | }
170 |
171 | async fn eval(ctx: &mut Stk, expr: &Expression) -> f64 {
172 | match expr {
173 | Expression::Number(x) => *x,
174 | Expression::Covered(ref x) => ctx.run(|ctx| eval(ctx, x)).await,
175 | Expression::Binary { left, op, right } => {
176 | let left = ctx.run(|ctx| eval(ctx, left)).await;
177 | let right = ctx.run(|ctx| eval(ctx, right)).await;
178 | match op {
179 | BinaryOperator::Pow => left.powf(right),
180 | BinaryOperator::Mul => left * right,
181 | BinaryOperator::Div => left / right,
182 | BinaryOperator::Add => left + right,
183 | BinaryOperator::Sub => left - right,
184 | }
185 | }
186 | Expression::Unary { op, expr } => {
187 | let expr = ctx.run(|ctx| eval(ctx, expr)).await;
188 | match op {
189 | UnaryOperator::Neg => -expr,
190 | UnaryOperator::Pos => expr,
191 | }
192 | }
193 | }
194 | }
195 |
196 | // A recursively defined simple calculater which can parse arbitrary depth expressions without
197 | // ever overflowing the stack.
198 | fn main() -> Result<(), Error> {
199 | let expr = std::env::args().skip(1).collect::>().join(" ");
200 | if expr.is_empty() {
201 | return Ok(());
202 | }
203 | let mut stack = reblessive::Stack::new();
204 | let mut tokens = Buffer(expr.as_bytes());
205 | let expr = stack.enter(|ctx| parse(ctx, &mut tokens, 0)).finish()?;
206 |
207 | eprintln!("EXPRESSION: {:#?}", expr);
208 |
209 | println!("{}", stack.enter(|ctx| eval(ctx, &expr)).finish());
210 |
211 | Ok(())
212 | }
213 |
--------------------------------------------------------------------------------
/examples/heavy_fibbo.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | mem::MaybeUninit,
3 | time::{Duration, Instant},
4 | };
5 |
6 | use reblessive::{Stack, Stk};
7 |
8 | async fn heavy_fibbo(ctx: &mut Stk, n: usize) -> usize {
9 | // An extra stack allocation to simulate a more complex function.
10 | let mut ballast: MaybeUninit<[u8; 1024 * 1024]> = std::mem::MaybeUninit::uninit();
11 | // Make sure the ballast isn't compiled out.
12 | std::hint::black_box(&mut ballast);
13 |
14 | match n {
15 | 0 => 1,
16 | 1 => 1,
17 | x => {
18 | ctx.run(move |ctx| heavy_fibbo(ctx, x - 1)).await
19 | + ctx.run(move |ctx| heavy_fibbo(ctx, x - 2)).await
20 | }
21 | }
22 | }
23 |
24 | fn main() {
25 | // Create a stack to run the function in.
26 | let mut stack = Stack::new();
27 |
28 | // run the function to completion on the stack.
29 | let res = stack.enter(|ctx| heavy_fibbo(ctx, 20)).finish();
30 | println!("result: {res}");
31 |
32 | assert_eq!(res, 10946);
33 |
34 | // Reblessive can also make any recursive function interuptable.
35 | let mut runner = stack.enter(|ctx| heavy_fibbo(ctx, 60));
36 |
37 | let start = Instant::now();
38 | loop {
39 | // run the function forward by a step.
40 | // If this returned Some than the function completed.
41 | if let Some(x) = runner.step() {
42 | println!("finished: {x}")
43 | }
44 | // We didn't complete the computation in time so we can just drop the runner and stop the
45 | // function.
46 | if start.elapsed() > Duration::from_secs(3) {
47 | println!("Timed out!");
48 | break;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/heavy_fibbo_base.rs:
--------------------------------------------------------------------------------
1 | use std::mem::MaybeUninit;
2 |
3 | fn heavy_fibbo(n: usize) -> usize {
4 | let mut ballast: MaybeUninit<[u8; 1024 * 1024]> = std::mem::MaybeUninit::uninit();
5 | std::hint::black_box(&mut ballast);
6 |
7 | match n {
8 | 0 => 1,
9 | 1 => 1,
10 | x => heavy_fibbo(x - 1) + heavy_fibbo(x - 2),
11 | }
12 | }
13 |
14 | fn main() {
15 | let res = heavy_fibbo(20);
16 |
17 | assert_eq!(res, 10946)
18 | }
19 |
--------------------------------------------------------------------------------
/src/allocator.rs:
--------------------------------------------------------------------------------
1 | use std::alloc::Layout;
2 |
3 | use crate::ptr::Owned;
4 |
5 | struct BlockHeader {
6 | previous: Option>,
7 | last: Owned,
8 | }
9 |
10 | /// A stack allocator, an allocator which is only able to free the most recent allocated value.
11 | ///
12 | /// Allocates increasingly larger and larger chunks of memory, freeing previous ones once they are
13 | /// empty, only keeping the most recent around.
14 | pub struct StackAllocator {
15 | block: Option>,
16 | top: Option>,
17 | }
18 |
19 | impl StackAllocator {
20 | pub const MINIMUM_ALIGN: usize = std::mem::align_of::