Merge branch 'master' into library-use-case

This commit is contained in:
Nicolas Luck
2023-12-04 20:06:14 +01:00
47 changed files with 1622 additions and 821 deletions

52
.github/actions/setup-rust/action.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: 'Setup Rust'
inputs:
rust-version:
required: true
type: string
targets:
required: true
type: string
components:
required: false
default:
cache-context:
required: true
type: string
runs:
using: "composite"
steps:
- uses: dtolnay/rust-toolchain@master
id: toolchain
with:
toolchain: ${{ inputs.rust-version }}
targets: ${{ inputs.targets }}
components: ${{ inputs.components }}
- name: Install i686 dependencies
if: "contains(inputs.targets,'i686')"
shell: bash
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libssl-dev:i386 gcc-multilib clang -y
echo "CC=clang" >> $GITHUB_ENV
echo "PKG_CONFIG_SYSROOT_DIR=/" >> $GITHUB_ENV
- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ inputs.cache-context }}_${{ inputs.targets }}_rustc-${{ steps.toolchain.outputs.cachekey }}_cargo-${{ hashFiles('**/Cargo.lock') }}
# Remove build artifacts for the current crate, since it will be rebuilt every
# run anyway, but keep dependency artifacts to cache them.
# Must be placed after actions/cache so its post step runs first.
- uses: pyTooling/Actions/with-post-step@v0.4.6
with:
main: bash ./.github/actions/setup-rust/cleanup.sh
post: bash ./.github/actions/setup-rust/cleanup.sh

13
.github/actions/setup-rust/cleanup.sh vendored Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
echo Cleanup workspace build artifacts and extra target output
# clean just the direct members of the current workspace, use cargo metadata to generalize to all rust projects
cargo clean -p `cargo metadata --no-deps --offline --format-version 1 | jq -r '[.workspace_members[]|split(" ")|.[0]]|join(" ")'`
# remove directories in /target/ that are not named `debug` or `release`
before=`du -s target | awk '{print $1}'`
find ./target -maxdepth 1 -type d ! -name debug ! -name release ! -name target -exec rm -r {} \;
after=`du -s target | awk '{print $1}'`
echo Deleted $(($before - $after)) bytes from target directory

View File

@@ -10,47 +10,26 @@ on:
- cron: '0 0 * * 3' # At 12:00 AM, only on Wednesday
workflow_dispatch:
permissions:
checks: write
jobs:
style:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
id: toolchain
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
toolchain: nightly
rust-version: nightly
targets: x86_64-unknown-linux-gnu
components: clippy, rustfmt
- run: cargo install cargo2junit --force
- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: style_rustc-${{ steps.toolchain.outputs.cachekey }}_cargo-${{ hashFiles('**/Cargo.lock') }}
cache-context: style
- name: Check formatting
run: cargo fmt --check
- name: Check clippy
run: cargo clippy --no-deps --all-targets
- name: Test and report
run: |
RUSTC_BOOTSTRAP=1 cargo test --all -- -Z unstable-options --format json --report-time | cargo2junit > cargo_test_results.xml
- name: Publish cargo test results artifact
uses: actions/upload-artifact@v3
with:
name: cargo-test-results
path: cargo_test_results.xml
- name: Publish cargo test summary
uses: EnricoMi/publish-unit-test-result-action/composite@master
with:
check_name: Cargo test summary
files: cargo_test_results.xml
fail_on: nothing
comment_mode: off
build-test:
runs-on: ${{ matrix.os }}
@@ -75,34 +54,19 @@ jobs:
shell: bash
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
id: toolchain
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
toolchain: ${{ matrix.rust-version }}
rust-version: ${{ matrix.rust-version }}
targets: ${{ matrix.target }}
- name: Install i686 dependencies
if: "contains(matrix.target,'i686')"
run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libssl-dev:i386 gcc-multilib clang -y
echo "CC=clang" >> $GITHUB_ENV
echo "PKG_CONFIG_SYSROOT_DIR=/" >> $GITHUB_ENV
- uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ matrix.os }}_${{ matrix.target }}_rustc-${{ steps.toolchain.outputs.cachekey }}_cargo-${{ hashFiles('**/Cargo.lock') }}
cache-context: ${{ matrix.os }}
# Build and test.
- name: Build library
run: cargo rustc --lib --target ${{ matrix.target }} ${{ matrix.args }} --verbose
- name: Test
run: cargo test --target ${{ matrix.target }} ${{ matrix.args }} --all --verbose || echo "::warning ::Tests failed"
continue-on-error: ${{ contains(matrix.target,'wasm32') }} # allow wasm builds to fail tests for now
run: cargo test --target ${{ matrix.target }} ${{ matrix.args }} --all --verbose
# On stable rust builds, build a binary and publish as a github actions
# artifact. These binaries could be useful for testing the pipeline but
@@ -166,6 +130,53 @@ jobs:
fail_on: nothing
comment_mode: off
report:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
rust-version: nightly
targets: x86_64-unknown-linux-gnu
cache-context: report
- run: |
cargo install cargo2junit --force
# cargo install iai-callgrind-runner --force --version `cargo metadata --format-version 1 | jq -r '.resolve.nodes[].id|split(" ")|select(.[0]=="iai-callgrind")|.[1]'`
cargo install iai-callgrind-runner --force --git https://github.com/iai-callgrind/iai-callgrind --rev c77bc3c83d7f4e976cc42d4597236a8db259e772
sudo apt install valgrind -y
- name: Test and report
run: |
RUSTC_BOOTSTRAP=1 cargo test --all -- -Z unstable-options --format json --report-time | cargo2junit > cargo_test_results.xml
- name: Publish cargo test results artifact
uses: actions/upload-artifact@v3
with:
name: cargo-test-results
path: cargo_test_results.xml
- name: Publish cargo test summary
uses: EnricoMi/publish-unit-test-result-action/composite@master
with:
check_name: Cargo test summary
files: cargo_test_results.xml
fail_on: nothing
comment_mode: off
- run: cargo build --all-targets --release
- run: cargo test --bench setup --release
- run: cargo bench --bench run_iai -- --save-summary=json
- run: cargo bench --bench run_criterion
- run: cargo bench --bench run_criterion -- --profile-time 60
- name: Publish benchmark results
uses: actions/upload-artifact@v3
with:
name: benchmark-results
path: |
target/criterion/*
target/iai/*
target/benchmark_inference_counts.json
# Publish binaries when building for a tag
release:
runs-on: ubuntu-20.04

541
Cargo.lock generated
View File

@@ -17,6 +17,19 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
dependencies = [
"cfg-if",
"getrandom",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.0.2"
@@ -41,12 +54,30 @@ dependencies = [
"libc",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstyle"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "assert_cmd"
version = "1.0.8"
@@ -100,6 +131,15 @@ version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.3"
@@ -213,6 +253,12 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
[[package]]
name = "byteorder"
version = "1.4.3"
@@ -225,6 +271,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.83"
@@ -254,6 +306,58 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "ciborium"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
[[package]]
name = "ciborium-ll"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
dependencies = [
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "clipboard-win"
version = "4.5.0"
@@ -275,16 +379,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "console_log"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be8aed40e4edbf4d3b4431ab260b63fdc40f5780a4766824329ea0f1eefe3c0f"
dependencies = [
"log",
"web-sys",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -301,6 +395,15 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cpp_demangle"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119"
dependencies = [
"cfg-if",
]
[[package]]
name = "cpu-time"
version = "1.0.0"
@@ -320,6 +423,75 @@ dependencies = [
"libc",
]
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossterm"
version = "0.20.0"
@@ -472,6 +644,15 @@ dependencies = [
"num-order",
]
[[package]]
name = "debugid"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
dependencies = [
"uuid",
]
[[package]]
name = "derive_deref"
version = "1.1.1"
@@ -563,6 +744,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.3"
@@ -611,6 +798,18 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "findshlibs"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64"
dependencies = [
"cc",
"lazy_static",
"libc",
"winapi",
]
[[package]]
name = "fnv"
version = "1.0.7"
@@ -827,13 +1026,19 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap",
"indexmap 1.9.3",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -990,6 +1195,35 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "iai-callgrind"
version = "0.8.0"
source = "git+https://github.com/iai-callgrind/iai-callgrind.git?rev=c77bc3c83d7f4e976cc42d4597236a8db259e772#c77bc3c83d7f4e976cc42d4597236a8db259e772"
dependencies = [
"bincode",
"iai-callgrind-macros",
"iai-callgrind-runner",
]
[[package]]
name = "iai-callgrind-macros"
version = "0.1.0"
source = "git+https://github.com/iai-callgrind/iai-callgrind.git?rev=c77bc3c83d7f4e976cc42d4597236a8db259e772#c77bc3c83d7f4e976cc42d4597236a8db259e772"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.37",
]
[[package]]
name = "iai-callgrind-runner"
version = "0.8.0"
source = "git+https://github.com/iai-callgrind/iai-callgrind.git?rev=c77bc3c83d7f4e976cc42d4597236a8db259e772#c77bc3c83d7f4e976cc42d4597236a8db259e772"
dependencies = [
"serde",
]
[[package]]
name = "iana-time-zone"
version = "0.1.57"
@@ -1033,6 +1267,34 @@ dependencies = [
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad227c3af19d4914570ad36d30409928b75967c298feb9ea1969db3a610bb14e"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
]
[[package]]
name = "inferno"
version = "0.11.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50453ec3a6555fad17b1cd1a80d16af5bc7cb35094f64e429fd46549018c6a3"
dependencies = [
"ahash",
"indexmap 2.0.1",
"is-terminal",
"itoa",
"log",
"num-format",
"once_cell",
"quick-xml",
"rgb",
"str_stack",
]
[[package]]
name = "instant"
version = "0.1.12"
@@ -1048,6 +1310,17 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
@@ -1103,7 +1376,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"arrayvec 0.5.2",
"bitflags 1.3.2",
"cfg-if",
"ryu",
@@ -1147,9 +1420,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
version = "0.4.7"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]]
name = "lock_api"
@@ -1217,6 +1490,24 @@ version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "memmap2"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a5a03cefb0d953ec0be133036f14e109412fa594edc2f77227249db66cc3ed"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.17"
@@ -1376,6 +1667,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "num-format"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3"
dependencies = [
"arrayvec 0.7.4",
"itoa",
]
[[package]]
name = "num-modular"
version = "0.6.1"
@@ -1393,9 +1694,9 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
]
@@ -1425,6 +1726,12 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "opaque-debug"
version = "0.2.3"
@@ -1433,9 +1740,9 @@ checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "openssl"
version = "0.10.57"
version = "0.10.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800"
dependencies = [
"bitflags 2.4.0",
"cfg-if",
@@ -1465,9 +1772,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.93"
version = "0.9.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f"
dependencies = [
"cc",
"libc",
@@ -1658,6 +1965,56 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "plotters"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
[[package]]
name = "plotters-svg"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
dependencies = [
"plotters-backend",
]
[[package]]
name = "pprof"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef5c97c51bd34c7e742402e216abdeb44d415fbe6ae41d56b114723e953711cb"
dependencies = [
"backtrace",
"cfg-if",
"criterion",
"findshlibs",
"inferno",
"libc",
"log",
"nix 0.26.4",
"once_cell",
"parking_lot 0.12.1",
"smallvec",
"symbolic-demangle",
"tempfile",
"thiserror",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -1697,6 +2054,30 @@ dependencies = [
"termtree",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.20+deprecated"
@@ -1712,6 +2093,15 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.33"
@@ -1767,6 +2157,26 @@ dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -1874,6 +2284,15 @@ dependencies = [
"winreg",
]
[[package]]
name = "rgb"
version = "0.8.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8"
dependencies = [
"bytemuck",
]
[[package]]
name = "ring"
version = "0.16.20"
@@ -1933,9 +2352,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustix"
version = "0.38.14"
version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.0",
"errno",
@@ -2042,8 +2461,8 @@ dependencies = [
"bytes",
"chrono",
"console_error_panic_hook",
"console_log",
"cpu-time",
"criterion",
"crossterm",
"crrl",
"ctrlc",
@@ -2056,7 +2475,9 @@ dependencies = [
"getrandom",
"git-version",
"hostname",
"indexmap",
"iai-callgrind",
"indexmap 1.9.3",
"js-sys",
"lazy_static",
"lexical",
"libc",
@@ -2068,6 +2489,7 @@ dependencies = [
"num-order",
"ordered-float",
"phf 0.9.0",
"pprof",
"predicates-core",
"proc-macro2",
"quote",
@@ -2351,6 +2773,12 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
@@ -2363,6 +2791,12 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "str_stack"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb"
[[package]]
name = "string_cache"
version = "0.8.7"
@@ -2420,6 +2854,29 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "symbolic-common"
version = "12.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "405af7bd5edd866cef462e22ef73f11cf9bf506c9d62824fef8364eb69d4d4ad"
dependencies = [
"debugid",
"memmap2",
"stable_deref_trait",
"uuid",
]
[[package]]
name = "symbolic-demangle"
version = "12.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bcd041ccfb77d9c70639efcd5b804b508ac7a273e9224d227379e225625daf9"
dependencies = [
"cpp_demangle",
"rustc-demangle",
"symbolic-common",
]
[[package]]
name = "syn"
version = "1.0.109"
@@ -2498,6 +2955,16 @@ dependencies = [
"syn 2.0.37",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@@ -2751,6 +3218,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
[[package]]
name = "vcpkg"
version = "0.2.15"
@@ -3056,3 +3529,23 @@ name = "xmlparser"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd"
[[package]]
name = "zerocopy"
version = "0.7.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
]

View File

@@ -85,19 +85,21 @@ tokio = { version = "1.28.2", features = ["full"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.10", features = ["js"] }
tokio = { version = "1.28.2", features = ["sync", "macros", "io-util", "rt", "time"] }
tokio = { version = "1.28.2", features = [
"sync",
"macros",
"io-util",
"rt",
"time",
] }
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
console_error_panic_hook = "0.1"
console_log = "1.0"
wasm-bindgen = "0.2.87"
wasm-bindgen-futures = "0.4"
serde-wasm-bindgen = "0.5"
web-sys = { version = "0.3", features = [
"Document",
"Window",
"Element",
]}
web-sys = { version = "0.3", features = ["Document", "Window", "Element", "Performance"] }
js-sys = "0.3"
[target.'cfg(target_os = "wasi")'.dependencies]
ring-wasi = { version = "0.16.25" }
@@ -110,6 +112,27 @@ assert_cmd = "1.0.3"
predicates-core = "1.0.2"
maplit = "1.0.2"
serial_test = "2.0.0"
iai-callgrind = { git = "https://github.com/iai-callgrind/iai-callgrind.git", rev = "c77bc3c83d7f4e976cc42d4597236a8db259e772" }
criterion = "0.5.1"
[target.'cfg(not(target_os = "windows"))'.dev-dependencies]
pprof = { version = "0.13.0", features = ["criterion", "flamegraph"] }
[patch.crates-io]
modular-bitfield = { git = "https://github.com/mthom/modular-bitfield" }
modular-bitfield = { git = "https://github.com/mthom/modular-bitfield" }
[profile.bench]
lto = true
opt-level = 3
[profile.release]
lto = true
opt-level = 3
[[bench]]
name = "run_criterion"
harness = false
[[bench]]
name = "run_iai"
harness = false

View File

@@ -5,13 +5,6 @@
X = "Scryer Prolog!".
```
``` =html
<div style="border: solid #00007f 3px;padding-left: 15px;padding-right: 15px;font-style: italic;background-color: #00007f30;">
<h4>Scryer Prolog Meetup 2023</h4>
<p>The first annual Scryer Prolog meetup is going to happen in Düsseldorf (Germany) on the 9th and 10th of November 2023. Join us to discover the present and future of Scryer Prolog! Participation is free, registration not required. <a href="https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2023/">More details here.</a></p>
</div>
```
![scryer](scryer.png){width=128 style=float:right;} [Scryer Prolog](https://github.com/mthom/scryer-prolog) is a free software ISO Prolog system intended to be an industrial
strength production environment *and* a testbed for bleeding edge research in
logic and constraint programming.
@@ -28,6 +21,7 @@ Some of the Scryer Prolog features are:
* [Cryptographical predicates](/crypto.html)
* [Foreign Function Interface](/ffi.html)
* WebAssembly support
* Usable as a library
* WAM based engine, cross-platform made in Rust
* _and more..._
@@ -80,4 +74,4 @@ an [issue](https://github.com/mthom/scryer-prolog/issues).
To get in touch with the Scryer Prolog community, participate in
[discussions](https://github.com/mthom/scryer-prolog/discussions)
or visit our #scryer IRC channel on [Libera](https://libera.chat)!
or visit our #scryer IRC channel on [Libera](https://libera.chat)!

View File

@@ -6,7 +6,10 @@ source industrial strength production environment that is also a
testbed for bleeding edge research in logic and constraint
programming, which is itself written in a high-level language.
As of July 2023, **Scryer Prolog passes all [syntactic conformity&nbsp;tests](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing)**.
**Scryer Prolog passes all tests** of
[syntactic&nbsp;conformity](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/conformity_testing),
[`variable_names/1`](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/variable_names) and
[`dif/2`](https://www.complang.tuwien.ac.at/ulrich/iso-prolog/dif).
The homepage of the project is: [**https://www.scryer.pl**](https://www.scryer.pl)

95
benches/README.md Normal file
View File

@@ -0,0 +1,95 @@
# About benches
The `benches` directory contains benchmarks that test scryer-prolog performance.
Benchmarks are run via two harnesses:
* `criterion` - criterion performs statistical analysis of benchmark runs and is
great for benchmarking locally.
* `iai-callgrind` - this runs the benchmark with callgrind, which is able to
precisely track the number of instructions executed during the run. This is
especially helpful in a public CI runner context where neighboring VMs can
cause a very high wall time variance. While instructions executed is only
correlated with the desired metric (wall time), this is a good tradeoff for CI
where that metric is unreliable.
Run benchmarks with the following commands:
```
cargo bench --bench run_criterion
# run a particular criterion benchmark
cargo bench --bench run_criterion -- <benchmark_name>
# run in profiling mode which outputs flamegraphs. Set profile time in seconds:
cargo bench --bench run_criterion -- --profile-time <time>
# to run iai, you need valgrind installed and to install iai-callgrind-runner
# at the same version as is in Cargo.toml:
cargo install iai-callgrind-runner --version 0.7.3
cargo bench --bench run_iai
```
For consistency, both runners -- `run_iai.rs` and `run_criterion.rs` -- import
the same setup code from `setup.rs`.
## Setup
`setup.rs` contains the setup code to run benchmarks. `fn prolog_benches()` at
the top of the file is where the benchmarks are defined.
Benchmarks are organized around running queries against a prolog module file.
Before a benchmark starts, `benchmark.setup()` is called which reads the module
file and initializes a new `scryer_prolog::machine::Machine`.
Each benchmark measurement is done by running a query against the machine. In
the case of criterion each query is run many times, in the case of iai it's run
once.
## Adding benchmarks
This design is meant to suppoort defining lots of benchmarks.
To add a new benchmark:
* Add a new file `benches/[module].pl` that contains setup prolog code. Import
libraries, define predicates, etc.
* Add a new section in `setup.rs::prolog_benchmarks()` that refers to to the
file and write a query to be benchmarked.
* If the query mutates the machine, then use `Strategy::Fresh` so the criterion
benchmark will recreate a new machine for each benchmark run, otherwise use
`Strategy::Reuse` which has lower overhead. (This is not used by the iai
benchmark because it only runs once anyway.)
Some tips:
* The goal of benchmarking is to know if a library or engine change improved
performance or not.
* Once a benchmark is defined and named, avoid changing it's definition. If a
benchmark needs to change to be more useful, give the new definition a new
name instead. This will prevent charts from showing wild changes in
performance just because the definition changed (see previous).
* Aim for queries to execute in less than 0.5s realtime. Longer runtimes make it
easier for humans to see big differences, but benchmarks either run 10x slower
(iai) or execute repeatedly to attain statistical significance (criterion) and
in both cases benchmarking queries that take longer than about 0.5s are
cumbersome to run.
* Consider that the library runtime actually parses the text output of the top
level. So don't use custom outputs or it will fail to parse. Also keep the
output small so it doesn't just benchmark the ouput parsing code.
* DO test the output of the benchmark run, we don't want to count broken
benchmarks.
## CI
Both benchmark harnesses are run in `.github/workflows/ci.yaml` in the `report`
job, and the results are published as build artifacts.
## Todo
- [ ] Currently, the execution time to load a module is not benchmarked. It
would be nice to have at least one benchmark for loading a module (probably a
big one).
- [ ] Write a new action that downloads the test and benchmark results
artifacts, plots them over time, and publishes a report to github pages.

41
benches/csv.pl Normal file

File diff suppressed because one or more lines are too long

130
benches/edges.pl Normal file
View File

@@ -0,0 +1,130 @@
:- use_module(library(clpb)).
:- use_module(library(assoc)).
:- use_module(library(lists)).
:- use_module(library(pairs)).
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Contiguous United States and DC as they appear in SGB:
http://www-cs-faculty.stanford.edu/~uno/sgb.html
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
edge(al, fl).
edge(al, ga).
edge(al, ms).
edge(al, tn).
edge(ar, la).
edge(ar, mo).
edge(ar, ms).
edge(ar, ok).
edge(ar, tn).
edge(ar, tx).
edge(az, ca).
edge(az, nm).
edge(az, nv).
edge(az, ut).
edge(ca, nv).
edge(ca, or).
edge(co, ks).
edge(co, ne).
edge(co, nm).
edge(co, ok).
edge(co, ut).
edge(co, wy).
edge(ct, ma).
edge(ct, ny).
edge(ct, ri).
edge(dc, md).
edge(dc, va).
edge(de, md).
edge(de, nj).
edge(de, pa).
edge(fl, ga).
edge(ga, nc).
edge(ga, sc).
edge(ga, tn).
edge(ia, il).
edge(ia, mn).
edge(ia, mo).
edge(ia, ne).
edge(ia, sd).
edge(ia, wi).
edge(id, mt).
edge(id, nv).
edge(id, or).
edge(id, ut).
edge(id, wa).
edge(id, wy).
edge(il, in).
edge(il, ky).
edge(il, mo).
edge(il, wi).
edge(in, ky).
edge(in, mi).
edge(in, oh).
edge(ks, mo).
edge(ks, ne).
edge(ks, ok).
edge(ky, mo).
edge(ky, oh).
edge(ky, tn).
edge(ky, va).
edge(ky, wv).
edge(la, ms).
edge(la, tx).
edge(ma, nh).
edge(ma, ny).
edge(ma, ri).
edge(ma, vt).
edge(md, pa).
edge(md, va).
edge(md, wv).
edge(me, nh).
edge(mi, oh).
edge(mi, wi).
edge(mn, nd).
edge(mn, sd).
edge(mn, wi).
edge(mo, ne).
edge(mo, ok).
edge(mo, tn).
edge(ms, tn).
edge(mt, nd).
edge(mt, sd).
edge(mt, wy).
edge(nc, sc).
edge(nc, tn).
edge(nc, va).
edge(nd, sd).
edge(ne, sd).
edge(ne, wy).
edge(nh, vt).
edge(nj, ny).
edge(nj, pa).
edge(nm, ok).
edge(nm, tx).
edge(nv, or).
edge(nv, ut).
edge(ny, pa).
edge(ny, vt).
edge(oh, pa).
edge(oh, wv).
edge(ok, tx).
edge(or, wa).
edge(pa, wv).
edge(sd, wy).
edge(tn, va).
edge(ut, wy).
edge(va, wv).
independent_set(G, *(NBs)) :-
findall(U-V, (edge(U, V),G@<U), Edges),
setof(U, V^(member(U-V, Edges);member(V-U, Edges)), Nodes),
pairs_keys_values(Pairs, Nodes, _),
list_to_assoc(Pairs, Assoc),
maplist(not_both(Assoc), Edges, NBs).
not_both(Assoc, U-V, ~BU + ~BV) :-
get_assoc(U, Assoc, BU),
get_assoc(V, Assoc, BV).
independent_set_count(G, Count) :- independent_set(G, Sat), sat_count(Sat, Count).

2
benches/numlist.pl Normal file
View File

@@ -0,0 +1,2 @@
:- use_module(library(between)).
run_numlist(Upper, Head) :- numlist(1, Upper, L), L = [Head|_].

36
benches/run_criterion.rs Normal file
View File

@@ -0,0 +1,36 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
#[cfg(not(target_os = "windows"))]
use pprof::criterion::{Output, PProfProfiler};
mod setup;
fn bench_criterion(c: &mut Criterion) {
for (&name, bench) in setup::prolog_benches().iter() {
match bench.strategy {
setup::Strategy::Fresh => c.bench_function(name, |b| {
b.iter_batched(|| bench.setup(), |mut r| r(), BatchSize::LargeInput)
}),
setup::Strategy::Reuse => c.bench_function(name, |b| b.iter(bench.setup())),
};
}
}
#[cfg(not(target_os = "windows"))]
fn config() -> Criterion {
Criterion::default()
.sample_size(20)
.with_profiler(PProfProfiler::new(100, Output::Flamegraph(None)))
}
#[cfg(target_os = "windows")]
fn config() -> Criterion {
Criterion::default().sample_size(20)
}
criterion_group!(
name = benches;
config = config();
targets = bench_criterion
);
criterion_main!(benches);

18
benches/run_iai.rs Normal file
View File

@@ -0,0 +1,18 @@
use iai_callgrind::{library_benchmark, library_benchmark_group, main};
use scryer_prolog::machine::parsed_results::QueryResolution;
mod setup;
#[library_benchmark]
#[bench::count_edges(setup::prolog_benches()["count_edges"].setup())]
#[bench::numlist(setup::prolog_benches()["numlist"].setup())]
#[bench::csv_codename(setup::prolog_benches()["csv_codename"].setup())]
fn bench(mut run: impl FnMut() -> QueryResolution) -> QueryResolution {
run()
}
library_benchmark_group!(
name = benches;
benchmarks = bench
);
main!(library_benchmark_groups = benches);

132
benches/setup.rs Normal file
View File

@@ -0,0 +1,132 @@
use std::{collections::BTreeMap, fs, path::Path};
use maplit::btreemap;
use scryer_prolog::machine::{
parsed_results::{QueryResolution, Value},
Machine,
};
pub fn prolog_benches() -> BTreeMap<&'static str, PrologBenchmark> {
[
(
"count_edges", // name of the benchmark
"benches/edges.pl", // name of the prolog module file to load. use the same file in multiple benchmarks
"independent_set_count(ky, Count).", // query to benchmark in the context of the loaded module. consider making the query adjustable to tune the run time to ~0.1s
Strategy::Reuse,
btreemap! { "Count" => Value::try_from("2869176".to_string()).unwrap() },
),
(
"numlist",
"benches/numlist.pl",
"run_numlist(1000000, Head).",
Strategy::Reuse,
btreemap! { "Head" => Value::try_from("1".to_string()).unwrap()},
),
(
"csv_codename",
"benches/csv.pl",
"get_codename(\"0020\",Name).",
Strategy::Reuse,
btreemap! { "Name" => Value::try_from("SPACE".to_string()).unwrap()},
),
]
.map(|b| {
(
b.0,
PrologBenchmark {
name: b.0,
filename: b.1,
query: b.2,
strategy: b.3,
bindings: b.4,
},
)
})
.into()
}
pub enum Strategy {
#[allow(dead_code)]
Fresh,
Reuse,
}
pub struct PrologBenchmark {
pub name: &'static str,
pub filename: &'static str,
pub query: &'static str,
pub strategy: Strategy,
pub bindings: BTreeMap<&'static str, Value>,
}
impl PrologBenchmark {
pub fn make_machine(&self) -> Machine {
let program = fs::read_to_string(self.filename).unwrap();
let module_name = Path::new(self.filename)
.file_stem()
.and_then(|s| s.to_str())
.unwrap();
let mut machine = Machine::new_lib();
machine.load_module_string(module_name, program);
machine
}
pub fn setup(&self) -> impl FnMut() -> QueryResolution {
let mut machine = self.make_machine();
let query = self.query;
move || {
use criterion::black_box;
black_box(machine.run_query(black_box(query.to_string()))).unwrap()
}
}
}
#[cfg(test)]
mod test {
#[test]
fn validate_benchmarks() {
use super::prolog_benches;
use scryer_prolog::machine::parsed_results::{QueryMatch, QueryResolution};
use std::{fmt::Write, fs};
struct BenchResult {
pub name: &'static str,
pub setup_inference_count: u64,
pub query_inference_count: u64,
}
let mut results: Vec<BenchResult> = vec![];
for (_, r) in prolog_benches() {
let mut machine = r.make_machine();
let setup_inference_count = machine.get_inference_count();
let result = machine.run_query(r.query.to_string()).unwrap();
let query_inference_count = machine.get_inference_count() - setup_inference_count;
let expected = QueryResolution::Matches(vec![QueryMatch::from(r.bindings.clone())]);
assert_eq!(result, expected, "validating benchmark {}", r.name);
results.push(BenchResult {
name: r.name,
setup_inference_count,
query_inference_count,
})
}
let mut json: String = Default::default();
json.push('[');
for r in results {
json.push('\n');
write!(
json,
r#"{{"name":"{}","setup_inference_count":{},"query_inference_count":{}}},"#,
r.name, r.setup_inference_count, r.query_inference_count
)
.unwrap();
}
json.pop(); // trailing comma
json.push_str("\n]");
fs::write("target/benchmark_inference_counts.json", json).expect("Unable to write file");
}
}

View File

@@ -329,6 +329,8 @@ enum SystemClauseType {
InstallSCCCleaner,
#[strum_discriminants(strum(props(Arity = "3", Name = "$install_inference_counter")))]
InstallInferenceCounter,
#[strum_discriminants(strum(props(Arity = "1", Name = "$inference_count")))]
InferenceCount,
#[strum_discriminants(strum(props(Arity = "1", Name = "$lh_length")))]
LiftedHeapLength,
#[strum_discriminants(strum(props(Arity = "3", Name = "$load_library_as_stream")))]
@@ -573,6 +575,8 @@ enum SystemClauseType {
ForeignCall,
#[strum_discriminants(strum(props(Arity = "2", Name = "$define_foreign_struct")))]
DefineForeignStruct,
#[strum_discriminants(strum(props(Arity = "2", Name = "$js_eval")))]
JsEval,
#[strum_discriminants(strum(props(Arity = "3", Name = "$predicate_defined")))]
PredicateDefined,
#[strum_discriminants(strum(props(Arity = "3", Name = "$strip_module")))]
@@ -1720,6 +1724,7 @@ fn generate_instruction_preface() -> TokenStream {
&Instruction::CallHeadIsDynamic |
&Instruction::CallInstallSCCCleaner |
&Instruction::CallInstallInferenceCounter |
&Instruction::CallInferenceCount |
&Instruction::CallLiftedHeapLength |
&Instruction::CallLoadLibraryAsStream |
&Instruction::CallModuleExists |
@@ -1774,6 +1779,7 @@ fn generate_instruction_preface() -> TokenStream {
&Instruction::CallLoadForeignLib |
&Instruction::CallForeignCall |
&Instruction::CallDefineForeignStruct |
&Instruction::CallJsEval |
&Instruction::CallPredicateDefined |
&Instruction::CallStripModule |
&Instruction::CallCurrentTime |
@@ -1954,6 +1960,7 @@ fn generate_instruction_preface() -> TokenStream {
&Instruction::ExecuteHeadIsDynamic |
&Instruction::ExecuteInstallSCCCleaner |
&Instruction::ExecuteInstallInferenceCounter |
&Instruction::ExecuteInferenceCount |
&Instruction::ExecuteLiftedHeapLength |
&Instruction::ExecuteLoadLibraryAsStream |
&Instruction::ExecuteModuleExists |
@@ -2008,6 +2015,7 @@ fn generate_instruction_preface() -> TokenStream {
&Instruction::ExecuteLoadForeignLib |
&Instruction::ExecuteForeignCall |
&Instruction::ExecuteDefineForeignStruct |
&Instruction::ExecuteJsEval |
&Instruction::ExecutePredicateDefined |
&Instruction::ExecuteStripModule |
&Instruction::ExecuteCurrentTime |

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 4.8 MiB

View File

View File

@@ -29,7 +29,7 @@ pub(crate) trait Allocator {
&mut self,
var_num: usize,
lvl: Level,
cell: &'a Cell<VarReg>,
cell: &Cell<VarReg>,
term_loc: GenContext,
code: &mut CodeDeque,
r: RegType,
@@ -42,7 +42,7 @@ pub(crate) trait Allocator {
&mut self,
var_num: usize,
lvl: Level,
cell: &'a Cell<VarReg>,
cell: &Cell<VarReg>,
context: GenContext,
code: &mut CodeDeque,
);

View File

@@ -10,6 +10,7 @@ use crate::parser::ast::*;
use crate::targets::*;
use crate::temp_v;
use crate::types::*;
use crate::variable_records::*;
use crate::instr;
use crate::machine::disjuncts::*;
@@ -60,6 +61,7 @@ impl BranchCodeStack {
marker: &mut DebrayAllocator,
) -> SubsumedBranchHits {
let mut subsumed_hits = SubsumedBranchHits::with_hasher(FxBuildHasher::default());
let mut propagated_var_nums = IndexSet::with_hasher(FxBuildHasher::default());
for idx in (self.stack.len() - depth..self.stack.len()).rev() {
let branch = &mut marker.branch_stack[idx];
@@ -85,9 +87,17 @@ impl BranchCodeStack {
}
}
if idx > self.stack.len() - depth {
propagated_var_nums.insert(var_num);
}
subsumed_hits.insert(var_num);
}
}
for var_num in propagated_var_nums.drain(..) {
marker.branch_stack[idx - 1].add_branch_occurrence(var_num);
}
}
subsumed_hits
@@ -277,7 +287,6 @@ impl DebrayAllocator {
code: &mut CodeDeque,
) -> RegType {
self.mark_var::<QueryInstruction>(var_num, Level::Shallow, vr, term_loc, code);
vr.get().norm()
}
@@ -296,7 +305,14 @@ impl DebrayAllocator {
self.mark_var_in_non_callable(var_num, term_loc, vr, code);
temp_v!(arg)
} else {
self.increment_running_count(var_num);
if let VarAlloc::Perm(_, PermVarAllocation::Pending) =
&self.var_data.records[var_num].allocation
{
self.mark_var_in_non_callable(var_num, term_loc, vr, code);
} else {
self.increment_running_count(var_num);
}
RegType::Perm(p)
}
}

View File

@@ -39,6 +39,19 @@ impl BranchOccurrences {
subsumed_hits: SubsumedBranchHits::with_hasher(FxBuildHasher::default()),
}
}
pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) {
debug_assert!(self.current_branch < self.num_branches);
let num_branches = self.num_branches;
let entry = self
.hits
.entry(var_num)
.or_insert_with(|| BitVec::repeat(false, num_branches));
entry.set(self.current_branch, true);
self.subsumed_hits.insert(var_num);
}
}
#[derive(Debug)]
@@ -92,17 +105,7 @@ impl BranchStack {
pub(crate) fn add_branch_occurrence(&mut self, var_num: usize) {
if let Some(occurrences) = self.last_mut() {
debug_assert!(occurrences.current_branch < occurrences.num_branches);
let num_branches = occurrences.num_branches;
let entry = occurrences
.hits
.entry(var_num)
.or_insert_with(|| BitVec::repeat(false, num_branches));
entry.set(occurrences.current_branch, true);
occurrences.subsumed_hits.insert(var_num);
occurrences.add_branch_occurrence(var_num);
}
}
@@ -166,30 +169,26 @@ impl DebrayAllocator {
for var_num in subsumed_hits {
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(_, ref mut allocation) => {
match allocation {
PermVarAllocation::Done {
shallow_safety,
deep_safety,
..
} => {
if !self
.branch_stack
.safety_unneeded_in_branch(shallow_safety, &branch_designator)
{
let branch_occurrences = self.branch_stack.last_mut().unwrap();
branch_occurrences.shallow_safety.insert(var_num);
}
if !self
.branch_stack
.safety_unneeded_in_branch(deep_safety, &branch_designator)
{
let branch_occurrences = self.branch_stack.last_mut().unwrap();
branch_occurrences.deep_safety.insert(var_num);
}
if let PermVarAllocation::Done {
shallow_safety,
deep_safety,
..
} = allocation
{
if !self
.branch_stack
.safety_unneeded_in_branch(shallow_safety, &branch_designator)
{
let branch_occurrences = self.branch_stack.last_mut().unwrap();
branch_occurrences.shallow_safety.insert(var_num);
}
_ => {
unreachable!();
if !self
.branch_stack
.safety_unneeded_in_branch(deep_safety, &branch_designator)
{
let branch_occurrences = self.branch_stack.last_mut().unwrap();
branch_occurrences.deep_safety.insert(var_num);
}
}
@@ -740,7 +739,7 @@ impl Allocator for DebrayAllocator {
&mut self,
var_num: usize,
lvl: Level,
cell: &'a Cell<VarReg>,
cell: &Cell<VarReg>,
term_loc: GenContext,
code: &mut CodeDeque,
) {
@@ -748,11 +747,11 @@ impl Allocator for DebrayAllocator {
RegType::Temp(0) => {
let o = self.alloc_reg_to_var::<Target>(var_num, lvl, term_loc, code);
cell.set(VarReg::Norm(RegType::Temp(o)));
(RegType::Temp(o), true)
}
RegType::Perm(0) => {
let p = self.alloc_perm_var(var_num, term_loc.chunk_num());
cell.set(VarReg::Norm(RegType::Perm(p)));
(RegType::Perm(p), true)
}
r @ RegType::Perm(_) => {
@@ -780,7 +779,7 @@ impl Allocator for DebrayAllocator {
&mut self,
var_num: usize,
lvl: Level,
cell: &'a Cell<VarReg>,
cell: &Cell<VarReg>,
term_loc: GenContext,
code: &mut CodeDeque,
r: RegType,
@@ -846,8 +845,21 @@ impl Allocator for DebrayAllocator {
fn mark_cut_var(&mut self, var_num: usize, chunk_num: usize) -> RegType {
match self.get_binding(var_num) {
RegType::Perm(0) | RegType::Temp(0) => {
RegType::Perm(self.alloc_perm_var(var_num, chunk_num))
RegType::Perm(0) => RegType::Perm(self.alloc_perm_var(var_num, chunk_num)),
RegType::Temp(0) => {
let t = self.alloc_reg_to_non_var();
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Temp {
temp_reg, safety, ..
} => {
*temp_reg = t;
*safety = VarSafetyStatus::GloballyUnneeded;
}
_ => unreachable!(),
};
RegType::Temp(t)
}
r => r,
}

View File

@@ -744,7 +744,11 @@ impl Number {
Number::Float(f) => Number::Float(OrderedFloat(f.signum())),
_ => {
if self.is_positive() {
Number::Fixnum(Fixnum::build_with(1))
if self.is_zero() {
Number::Fixnum(Fixnum::build_with(0))
} else {
Number::Fixnum(Fixnum::build_with(1))
}
} else if self.is_negative() {
Number::Fixnum(Fixnum::build_with(-1))
} else {
@@ -876,7 +880,7 @@ impl ClauseIndexInfo {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct PredicateInfo {
pub(crate) is_extensible: bool,
pub(crate) is_discontiguous: bool,
@@ -885,19 +889,6 @@ pub(crate) struct PredicateInfo {
pub(crate) has_clauses: bool,
}
impl Default for PredicateInfo {
#[inline]
fn default() -> Self {
PredicateInfo {
is_extensible: false,
is_discontiguous: false,
is_dynamic: false,
is_multifile: false,
has_clauses: false,
}
}
}
impl PredicateInfo {
#[inline]
pub(crate) fn compile_incrementally(&self) -> bool {

View File

@@ -1762,7 +1762,7 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> {
while let Some(loc_data) = self.state_stack.pop() {
match loc_data {
TokenOrRedirect::Atom(atom) => self.print_impromptu_atom(atom),
TokenOrRedirect::BarAsOp => append_str!(self, " | "),
TokenOrRedirect::BarAsOp => append_str!(self, "|"),
TokenOrRedirect::Char(c) => print_char!(self, self.quoted, c),
TokenOrRedirect::Op(atom, op) => {
self.print_op(&atom.as_str());

View File

@@ -51,7 +51,8 @@ use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn eval_code(s: &str) -> String {
use machine::mock_wam::*;
use web_sys::console;
console_error_panic_hook::set_once();
let mut wam = Machine::with_test_streams();
let bytes = wam.test_load_string(s);

View File

@@ -1173,8 +1173,13 @@ clause(H, B) :-
% The clause will be inserted at the beginning of the module.
asserta(Clause0) :-
loader:strip_subst_module(Clause0, user, Module, Clause),
iso_ext:asserta(Module, Clause).
asserta_(Module, Clause).
asserta_(Module, (Head :- Body)) :-
!,
'$asserta'(Module, Head, Body).
asserta_(Module, Fact) :-
'$asserta'(Module, Fact, true).
:- meta_predicate assertz(:).
@@ -1184,7 +1189,13 @@ asserta(Clause0) :-
% The clase will be inserted at the end of the module.
assertz(Clause0) :-
loader:strip_subst_module(Clause0, user, Module, Clause),
iso_ext:assertz(Module, Clause).
assertz_(Module, Clause).
assertz_(Module, (Head :- Body)) :-
!,
'$assertz'(Module, Head, Body).
assertz_(Module, Fact) :-
'$assertz'(Module, Fact, true).
:- meta_predicate retract(:).
@@ -1203,6 +1214,9 @@ retract(Clause0) :-
Body = true,
retract_module_clause(Head, Body, Module)
; Clause = (Head :- Body) ->
( var(Module) -> Module = user
; true
),
retract_module_clause(Head, Body, Module)
).

View File

@@ -7968,11 +7968,11 @@ coeff_var_term(C-V, T) :- ( C =:= 1 -> T = #V ; T = C * #V ).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#=(X, Y, T) :-
X #= Y #<==> B,
X #= Y #<==> #B,
zo_t(B, T).
#<(X, Y, T) :-
X #< Y #<==> B,
X #< Y #<==> #B,
zo_t(B, T).
zo_t(0, false).

View File

@@ -33,11 +33,13 @@ remove_goal([G0|G0s], Goal0, Goals) :-
vars_remove_goal([], _).
vars_remove_goal([Var|Vars], Goal0) :-
get_atts(Var, +dif(Goals0)),
remove_goal(Goals0, Goal0, Goals),
( Goals = [] ->
put_atts(Var, -dif(_))
; put_atts(Var, +dif(Goals))
( get_atts(Var, +dif(Goals0)) ->
remove_goal(Goals0, Goal0, Goals),
( Goals = [] ->
put_atts(Var, -dif(_))
; put_atts(Var, +dif(Goals))
)
; true
),
vars_remove_goal(Vars, Goal0).

View File

@@ -17,9 +17,7 @@ but they're not part of the ISO Prolog standard at the moment.
succ/2,
call_nth/2,
countall/2,
copy_term_nat/2,
asserta/2,
assertz/2]).
copy_term_nat/2]).
:- use_module(library(error), [can_be/2,
domain_error/3,
@@ -384,21 +382,3 @@ countall(Goal, N) :-
copy_term_nat(Source, Dest) :-
'$copy_term_without_attr_vars'(Source, Dest).
%% asserta(Module, Rule_Fact).
%
% Similar to `asserta/1` but allows specifying a Module
asserta(Module, (Head :- Body)) :-
!,
'$asserta'(Module, Head, Body).
asserta(Module, Fact) :-
'$asserta'(Module, Fact, true).
%% assertz(Module, Rule_Fact).
%
% Similar to `assertz/1` but allows specifying a Module
assertz(Module, (Head :- Body)) :-
!,
'$assertz'(Module, Head, Body).
assertz(Module, Fact) :-
'$assertz'(Module, Fact, true).

View File

@@ -37,6 +37,7 @@
atomic_si/1,
list_si/1,
character_si/1,
term_si/1,
chars_si/1,
dif_si/2]).
@@ -68,6 +69,11 @@ character_si(Ch) :-
atom(Ch),
atom_length(Ch,1).
term_si(Term) :-
( ground(Term) -> acyclic_term(Term)
; throw(error(instantiation_error, term_si/1))
).
chars_si(Chs0) :-
'$skip_max_list'(_,_, Chs0,Chs),
( nonvar(Chs) -> Chs == [] ; true ), % fails for infinite lists too

View File

@@ -96,7 +96,7 @@ sleep(T) :-
:- meta_predicate time(0).
:- dynamic(time_id/1).
:- dynamic(time_state/2).
:- dynamic(time_state/3).
time_next_id(N) :-
( retract(time_id(N0)) ->
@@ -111,9 +111,9 @@ time_next_id(N) :-
% Reports the execution time of Goal.
time(Goal) :-
'$cpu_now'(T0),
cputime_inferences(T0, I0),
time_next_id(ID),
setup_call_cleanup(asserta(time_state(ID, T0)),
setup_call_cleanup(asserta(time_state(ID, T0, I0)),
( call_cleanup(catch(Goal, E, (report_time(ID),throw(E))),
Det = true),
time_true(ID),
@@ -123,49 +123,72 @@ time(Goal) :-
; report_time(ID),
false
),
retract(time_state(ID, _))).
retract(time_state(ID, _, _))).
cputime_inferences(T, I) :-
'$cpu_now'(T),
'$inference_count'(I).
time_true(ID) :-
report_time(ID).
time_true(ID) :-
% on backtracking, update the stored CPU time for this ID
retract(time_state(ID, _)),
'$cpu_now'(T0),
asserta(time_state(ID, T0)),
retract(time_state(ID, _, _)),
cputime_inferences(T0, I0),
asserta(time_state(ID, T0, I0)),
false.
report_time(ID) :-
time_state(ID, T0),
'$cpu_now'(T),
time_state(ID, T0, I0),
cputime_inferences(T, I),
Time is T - T0,
Inferences0 is I - I0,
% we must subtract the number of inferences that time/1 itself takes;
% this may have to be adapted if the implementation changes,
% so that (for example) true/1 takes exactly 1 inference.
( bb_get('$answer_count', 0) ->
Inferences is Inferences0 - 60,
Pre = " ", Post = ""
; Pre = "", Post = " "
; Inferences is Inferences0 - 9,
Pre = "", Post = " "
),
format("~s% CPU time: ~3fs~n~s", [Pre,Time,Post]).
phrase((Pre,"% CPU time: ", format_("~3f", [Time]), "s, ",
format_("~U", [Inferences])," inference",s_if_necessary(Inferences),"\n",
Post), Cs),
format("~s", [Cs]).
s_if_necessary(Inferences) -->
{ compare(C, 1, Inferences) },
s_(C).
s_(=) --> "".
s_(<) --> "s".
s_(>) --> " (exception?)".
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
?- time((true;false)).
%@ % CPU time: 0.006s
%@ true
%@ ; % CPU time: 0.001s
%@ false.
% CPU time: 0.000s, 1 inference
true
; % CPU time: 0.000s, 0 inference (exception?)
false.
:- time(use_module(library(clpz))).
%@ % CPU time: 3.711s
%@ true.
% CPU time: 0.343s, 409_874 inferences
true.
:- time(use_module(library(lists))).
%@ % CPU time: 0.006s
%@ true.
% CPU time: 0.000s, 19 inferences
true.
?- time(member(X, "abc")).
%@ % CPU time: 0.005s
%@ X = a
%@ ; % CPU time: 0.000s
%@ X = b
%@ ; % CPU time: 0.000s
%@ X = c
%@ ; % CPU time: 0.000s
%@ false.
% CPU time: 0.000s, 1 inference
X = a
; % CPU time: 0.000s, 3 inferences
X = b
; % CPU time: 0.000s, 3 inferences
X = c.
?- time((repeat,false)).
% CPU time: 2.726s, 53_330_502 inferences
error('$interrupt_thrown',repl/0).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

29
src/lib/wasm.pl Normal file
View File

@@ -0,0 +1,29 @@
/** Predicates for the WebAssembly platform
This module contains predicates that are only available in
the WASM (WebAssembly) version of Scryer Prolog.
*/
:- module(wasm, [js_eval/2]).
:- use_module(library(error)).
%% js_eval(+JsCode, -Result).
%
% Executes a JavaScript snippet `JsCode` using the platform
% `eval` function. `Result` takes the return value of that code.
% Strings, booleans, numbers, null and undefined are directly mapped to Prolog.
% Arrays, objects, bigints, symbols and functions are not mapped.
% Instead, a `js_{type}` atom will be returned.
%
% Example (on a browser):
%
% ```
% ?- js_eval("prompt('What is your name?')", Name).
% % A prompt is showed, with a textbox.
% Name = "Whatever was written on the textbox".
% ```
js_eval(JsCode, Result) :-
must_be(chars, JsCode),
can_be(chars, Result),
'$js_eval'(JsCode, Result).

View File

@@ -348,7 +348,7 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
(Number::Fixnum(n1), Number::Integer(n2)) => {
let n1_i = n1.get_num();
if !(n1_i == 1 || n1_i == 0 || n1_i == -1) && n2.is_zero() {
if !(n1_i == 1 || n1_i == 0 || n1_i == -1) && n2.is_negative() {
let n = Number::Fixnum(n1);
Err(numerical_type_error(ValidType::Float, n, stub_gen))
} else {
@@ -359,7 +359,7 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
(Number::Integer(n1), Number::Fixnum(n2)) => {
let n2_i = n2.get_num();
if !(*n1 == Integer::from(1) || n1.is_zero() || *n1 == Integer::from(-1)) && n2_i < 0 {
if !(n1.is_one() || n1.is_zero() || n1.num_eq(&-1)) && n2_i < 0 {
let n = Number::Integer(n1);
Err(numerical_type_error(ValidType::Float, n, stub_gen))
} else {
@@ -368,9 +368,7 @@ pub(crate) fn int_pow(n1: Number, n2: Number, arena: &mut Arena) -> Result<Numbe
}
}
(Number::Integer(n1), Number::Integer(n2)) => {
if !(*n1 == Integer::from(1) || n1.is_zero() || *n1 == Integer::from(-1))
&& n2.is_zero()
{
if !(n1.is_one() || n1.is_zero() || n1.num_eq(&-1)) && n2.is_negative() {
let n = Number::Integer(n1);
Err(numerical_type_error(ValidType::Float, n, stub_gen))
} else {
@@ -711,11 +709,8 @@ pub(crate) fn shl(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
(Number::Fixnum(n1), Number::Integer(n2)) => {
let n1 = Integer::from(n1.get_num());
match (&*n2).try_into() as Result<u32, _> {
Ok(n2) => {
let n1: u64 = n1.try_into().unwrap();
Ok(Number::arena_from(n1 << n2, arena))
}
match (&*n2).try_into() as Result<usize, _> {
Ok(n2) => Ok(Number::arena_from(n1 << n2, arena)),
_ => Ok(Number::arena_from(n1 << usize::max_value(), arena)),
}
}
@@ -726,11 +721,8 @@ pub(crate) fn shl(n1: Number, n2: Number, arena: &mut Arena) -> Result<Number, M
arena,
)),
},
(Number::Integer(n1), Number::Integer(n2)) => match (&*n2).try_into() as Result<u32, _> {
Ok(n2) => {
let n1: u64 = (&*n1).try_into().unwrap();
Ok(Number::arena_from(Integer::from(n1 << n2), arena))
}
(Number::Integer(n1), Number::Integer(n2)) => match (&*n2).try_into() as Result<usize, _> {
Ok(n2) => Ok(Number::arena_from(Integer::from(&*n1 << n2), arena)),
_ => Ok(Number::arena_from(
Integer::from(&*n1 << usize::max_value()),
arena,

View File

@@ -18,6 +18,7 @@ pub trait CopierTarget: IndexMut<usize, Output = HeapCellValue> {
fn store(&self, value: HeapCellValue) -> HeapCellValue;
fn deref(&self, value: HeapCellValue) -> HeapCellValue;
fn push(&mut self, value: HeapCellValue);
fn push_attr_var_queue(&mut self, attr_var_loc: usize);
fn stack(&mut self) -> &mut Stack;
fn threshold(&self) -> usize;
}
@@ -73,7 +74,6 @@ impl<T: CopierTarget> CopyTermState<T> {
if h >= self.old_h {
*self.value_at_scan() = list_loc_as_cell!(h);
self.scan += 1;
return;
}
}
@@ -96,14 +96,19 @@ impl<T: CopierTarget> CopyTermState<T> {
.store(self.target.deref(heap_loc_as_cell!(addr + 1)));
if !cdr.is_var() {
// mark addr + 1 as a list back edge in the cdr of the list
self.trail_list_cell(addr + 1, threshold);
self.target[addr + 1].set_mark_bit(true);
self.target[addr + 1].set_forwarding_bit(true);
} else {
let car = self
.target
.store(self.target.deref(heap_loc_as_cell!(addr)));
if !car.is_var() {
// mark addr as a list back edge in the car of the list
self.trail_list_cell(addr, threshold);
self.target[addr].set_mark_bit(true);
}
}
@@ -178,6 +183,7 @@ impl<T: CopierTarget> CopyTermState<T> {
for (threshold, list_loc) in iter {
self.target[threshold] = list_loc_as_cell!(self.target.threshold());
self.target.push_attr_var_queue(threshold - 1);
self.copy_attr_var_list(list_loc);
}
}
@@ -263,6 +269,7 @@ impl<T: CopierTarget> CopyTermState<T> {
}
fn copy_var(&mut self, addr: HeapCellValue) {
let index = addr.get_value() as usize;
let rd = self.target.deref(addr);
let ra = self.target.store(rd);
@@ -271,7 +278,20 @@ impl<T: CopierTarget> CopyTermState<T> {
if h >= self.old_h {
*self.value_at_scan() = ra;
self.scan += 1;
return;
}
}
(HeapCellValueTag::Lis, h) => {
if h >= self.old_h && self.target[index].get_mark_bit() {
*self.value_at_scan() = heap_loc_as_cell!(
if ra.get_forwarding_bit() {
h + 1
} else {
h
}
);
self.scan += 1;
return;
}
}
@@ -356,12 +376,16 @@ impl<T: CopierTarget> CopyTermState<T> {
}
}
fn unwind_trail(&mut self) {
for (r, value) in self.trail.drain(0..) {
fn unwind_trail(mut self) {
for (r, value) in self.trail {
let index = r.get_value() as usize;
match r.get_tag() {
RefTag::AttrVar | RefTag::HeapCell => self.target[index] = value,
RefTag::AttrVar | RefTag::HeapCell => {
self.target[index] = value;
self.target[index].set_mark_bit(false);
self.target[index].set_forwarding_bit(false);
}
RefTag::StackCell => self.target.stack()[index] = value,
}
}

View File

@@ -663,12 +663,16 @@ impl VariableClassifier {
state_stack.last(),
Some(TraversalState::RemoveBranchNum)
) {
// check if the second-to-last element is a regular BuildDisjunct, as we don't
// want to add GetPrevLevel in case of a TrustMe.
matches!(
state_stack.iter().rev().nth(1),
Some(TraversalState::BuildDisjunct(..))
)
// check if the second-to-last element
// is a regular BuildDisjunct, as we
// don't want to add GetPrevLevel in
// case of a TrustMe.
match state_stack.iter().rev().nth(1) {
Some(&TraversalState::BuildDisjunct(preceding_len)) => {
preceding_len + 1 == build_stack.len()
}
_ => false,
}
} else {
false
};

View File

@@ -36,7 +36,7 @@ macro_rules! try_or_throw {
macro_rules! increment_call_count {
($s:expr) => {{
if !($s.increment_call_count_fn)(&mut $s) {
if !$s.increment_call_count() {
$s.backtrack();
continue;
}
@@ -208,6 +208,7 @@ impl MachineState {
l
}
(HeapCellValueTag::Fixnum |
HeapCellValueTag::CutPoint |
HeapCellValueTag::Char |
HeapCellValueTag::F64) => {
c
@@ -3675,6 +3676,16 @@ impl Machine {
try_or_throw!(self.machine_st, self.install_inference_counter());
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
&Instruction::CallInferenceCount => {
let global_count = self.machine_st.cwil.global_count.clone();
self.inference_count(self.machine_st.registers[1], global_count);
step_or_fail!(self, self.machine_st.p += 1);
}
&Instruction::ExecuteInferenceCount => {
let global_count = self.machine_st.cwil.global_count.clone();
self.inference_count(self.machine_st.registers[1], global_count);
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
&Instruction::CallLiftedHeapLength => {
self.lifted_heap_length();
step_or_fail!(self, self.machine_st.p += 1);
@@ -4128,6 +4139,14 @@ impl Machine {
try_or_throw!(self.machine_st, self.define_foreign_struct());
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
&Instruction::CallJsEval => {
try_or_throw!(self.machine_st, self.js_eval());
step_or_fail!(self, self.machine_st.p += 1);
}
&Instruction::ExecuteJsEval => {
try_or_throw!(self.machine_st, self.js_eval());
step_or_fail!(self, self.machine_st.p = self.machine_st.cp);
}
&Instruction::CallCurrentTime => {
self.current_time();
step_or_fail!(self, self.machine_st.p += 1);

View File

@@ -57,10 +57,11 @@ impl Machine {
or_frame.prelude.attr_var_queue_len = 0;
self.machine_st.b = stub_b;
self.machine_st.hb = self.machine_st.heap.len();
}
pub fn run_query(&mut self, query: String) -> QueryResult {
println!("Query: {}", query);
// println!("Query: {}", query);
// Parse the query so we can analyze and then call the term
let mut parser = Parser::new(
Stream::from_owned_string(query, &mut self.machine_st.arena),
@@ -87,6 +88,7 @@ impl Machine {
.expect("couldn't get code index")
.local()
.unwrap();
self.machine_st.b0 = self.machine_st.b;
let var_names: IndexMap<_, _> = term_write_result
.var_dict
@@ -192,7 +194,7 @@ impl Machine {
let outputter = printer.print();
let output: String = outputter.result();
println!("Result: {} = {}", var_key.to_string(), output);
// println!("Result: {} = {}", var_key.to_string(), output);
bindings.insert(var_key.to_string(), Value::try_from(output).expect("asdfs"));
}
@@ -444,10 +446,7 @@ mod tests {
}
// Check if the block is a query
if block.starts_with("query") {
// Extract the query from the block
let query = &block[5..];
if let Some(query) = block.strip_prefix("query") {
i += 1;
println!("query #{}: {}", i, query);
// Parse and execute the query
@@ -457,10 +456,7 @@ mod tests {
// Print the result
println!("{:?}", result);
} else if block.starts_with("consult") {
// Extract the code from the block
let code = &block[7..];
} else if let Some(code) = block.strip_prefix("consult") {
println!("load code: {}", code);
// Load the code into the machine

View File

@@ -148,9 +148,10 @@ impl<'a, LS: LoadState<'a>> Drop for Loader<'a, LS> {
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
pub enum CompilationTarget {
Module(Atom),
#[default]
User,
}
@@ -163,13 +164,6 @@ impl fmt::Display for CompilationTarget {
}
}
impl Default for CompilationTarget {
#[inline]
fn default() -> Self {
CompilationTarget::User
}
}
impl CompilationTarget {
#[inline]
pub(crate) fn module_name(&self) -> Atom {

View File

@@ -492,13 +492,22 @@ impl MachineState {
self.permission_error(Permission::Modify, atom!("static_module"), module)
}
SessionError::ExistenceError(err) => self.existence_error(err),
SessionError::ModuleDoesNotContainExport(..) => {
let error_atom = atom!("module_does_not_contain_claimed_export");
SessionError::ModuleDoesNotContainExport(module_name, key) => {
let functor_stub = functor_stub(key.0, key.1);
let stub = functor!(
atom!("module_does_not_contain_claimed_export"),
[
atom(module_name),
str(self.heap.len() + 4, 0)
],
[functor_stub]
);
self.permission_error(
Permission::Access,
atom!("private_procedure"),
functor!(error_atom),
stub,
)
}
SessionError::ModuleCannotImportSelf(module_name) => {

View File

@@ -96,7 +96,6 @@ pub struct MachineState {
pub(crate) unify_fn: fn(&mut MachineState),
pub(crate) bind_fn: fn(&mut MachineState, Ref, HeapCellValue),
pub(crate) run_cleaners_fn: fn(&mut Machine) -> bool,
pub(crate) increment_call_count_fn: fn(&mut MachineState) -> bool,
}
impl fmt::Debug for MachineState {
@@ -290,6 +289,11 @@ impl<'a> CopierTarget for CopyTerm<'a> {
self.state.heap.push(hcv);
}
#[inline(always)]
fn push_attr_var_queue(&mut self, attr_var_loc: usize) {
self.state.attr_var_init.attr_var_queue.push(attr_var_loc);
}
#[inline(always)]
fn store(&self, value: HeapCellValue) -> HeapCellValue {
self.state.store(value)
@@ -308,6 +312,7 @@ impl<'a> CopierTarget for CopyTerm<'a> {
#[derive(Debug)]
pub(super) struct CopyBallTerm<'a> {
attr_var_queue: &'a mut Vec<usize>,
stack: &'a mut Stack,
heap: &'a mut Heap,
heap_boundary: usize,
@@ -315,10 +320,16 @@ pub(super) struct CopyBallTerm<'a> {
}
impl<'a> CopyBallTerm<'a> {
pub(super) fn new(stack: &'a mut Stack, heap: &'a mut Heap, stub: &'a mut Heap) -> Self {
pub(super) fn new(
attr_var_queue: &'a mut Vec<usize>,
stack: &'a mut Stack,
heap: &'a mut Heap,
stub: &'a mut Heap,
) -> Self {
let hb = heap.len();
CopyBallTerm {
attr_var_queue,
stack,
heap,
heap_boundary: hb,
@@ -360,6 +371,11 @@ impl<'a> CopierTarget for CopyBallTerm<'a> {
self.stub.push(value);
}
#[inline(always)]
fn push_attr_var_queue(&mut self, attr_var_loc: usize) {
self.attr_var_queue.push(attr_var_loc);
}
fn store(&self, value: HeapCellValue) -> HeapCellValue {
read_heap_cell!(value,
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar, h) => {
@@ -417,15 +433,17 @@ impl MachineState {
return true;
}
self.cwil.global_count += 1;
if let Some(&(ref limit, block)) = self.cwil.limits.last() {
if self.cwil.count == *limit {
if self.cwil.local_count == *limit {
self.cwil.inference_limit_exceeded = true;
self.block = block;
self.unwind_stack();
return false;
} else {
self.cwil.count += 1;
self.cwil.local_count += 1;
}
}
@@ -967,7 +985,8 @@ impl MachineState {
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
pub(crate) struct CWIL {
count: Integer,
local_count: Integer,
pub(crate) global_count: Integer,
limits: Vec<(Integer, usize)>,
pub(crate) inference_limit_exceeded: bool,
}
@@ -975,22 +994,22 @@ pub(crate) struct CWIL {
impl CWIL {
pub(crate) fn new() -> Self {
CWIL {
count: Integer::from(0),
local_count: Integer::from(0),
global_count: Integer::from(0),
limits: vec![],
inference_limit_exceeded: false,
}
}
pub(crate) fn add_limit(&mut self, limit: usize, block: usize) -> &Integer {
let mut limit = Integer::from(limit);
limit += &self.count;
pub(crate) fn add_limit(&mut self, mut limit: Integer, block: usize) -> &Integer {
limit += &self.local_count;
match self.limits.last() {
Some((ref inner_limit, _)) if *inner_limit <= limit => {}
_ => self.limits.push((limit, block)),
};
}
&self.count
&self.local_count
}
#[inline(always)]
@@ -1001,12 +1020,12 @@ impl CWIL {
}
}
&self.count
&self.local_count
}
#[inline(always)]
pub(crate) fn reset(&mut self) {
self.count = Integer::from(0);
self.local_count = Integer::from(0);
self.limits.clear();
self.inference_limit_exceeded = false;
}

View File

@@ -60,7 +60,6 @@ impl MachineState {
unify_fn: MachineState::unify,
bind_fn: MachineState::bind,
run_cleaners_fn: |_| false,
increment_call_count_fn: |_| true,
}
}
@@ -335,7 +334,12 @@ impl MachineState {
self.ball.boundary = self.heap.len();
copy_term(
CopyBallTerm::new(&mut self.stack, &mut self.heap, &mut self.ball.stub),
CopyBallTerm::new(
&mut self.attr_var_init.attr_var_queue,
&mut self.stack,
&mut self.heap,
&mut self.ball.stub,
),
addr,
AttrVarPolicy::DeepCopy,
);

View File

@@ -159,6 +159,14 @@ impl<'a> CopierTarget for TermCopyingMockWAM<'a> {
self.wam.machine_st.heap.push(val);
}
fn push_attr_var_queue(&mut self, attr_var_loc: usize) {
self.wam
.machine_st
.attr_var_init
.attr_var_queue
.push(attr_var_loc);
}
fn stack(&mut self) -> &mut Stack {
&mut self.wam.machine_st.stack
}

View File

@@ -211,6 +211,15 @@ impl Machine {
)
}
pub fn get_inference_count(&mut self) -> u64 {
self.machine_st
.cwil
.global_count
.clone()
.try_into()
.unwrap()
}
pub fn throw_session_error(&mut self, err: SessionError, key: PredicateKey) {
let err = self.machine_st.session_error(err);
let stub = functor_stub(key.0, key.1);

View File

@@ -27,6 +27,7 @@ use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom, Write};
use std::mem;
use std::net::{Shutdown, TcpStream};
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::ptr;
#[cfg(feature = "tls")]
@@ -1837,42 +1838,55 @@ impl MachineState {
}
};
let file = match open_options.open(&*file_spec.as_str()) {
Ok(file) => file,
Err(err) => {
match err.kind() {
ErrorKind::NotFound => {
// 8.11.5.3j)
let stub = functor_stub(atom!("open"), 4);
let mut path = PathBuf::from(&*file_spec.as_str());
let err =
self.existence_error(ExistenceError::SourceSink(self[temp_v!(1)]));
loop {
let file = match open_options.open(&path) {
Ok(file) => file,
Err(err) => {
match err.kind() {
ErrorKind::NotFound => {
// 8.11.5.3j)
let stub = functor_stub(atom!("open"), 4);
return Err(self.error_form(err, stub));
let err =
self.existence_error(ExistenceError::SourceSink(self[temp_v!(1)]));
return Err(self.error_form(err, stub));
}
ErrorKind::PermissionDenied => {
// 8.11.5.3k)
return Err(self.open_permission_error(
self.registers[1],
atom!("open"),
4,
));
}
_ => {
// assume the OS is out of file descriptors.
let stub = functor_stub(atom!("open"), 4);
let err = self.resource_error(ResourceError::OutOfFiles);
return Err(self.error_form(err, stub));
}
}
ErrorKind::PermissionDenied => {
// 8.11.5.3k)
return Err(self.open_permission_error(
self.registers[1],
atom!("open"),
4,
));
}
_ => {
// assume the OS is out of file descriptors.
let stub = functor_stub(atom!("open"), 4);
let err = self.resource_error(ResourceError::OutOfFiles);
}
};
return Err(self.error_form(err, stub));
if path.extension().is_none() {
if let Some(metadata) = file.metadata().ok() {
if metadata.is_dir() {
path.set_extension("pl");
continue;
}
}
}
};
Ok(if is_input_file {
Stream::from_file_as_input(file_spec, file, &mut self.arena)
} else {
Stream::from_file_as_output(file_spec, file, in_append_mode, &mut self.arena)
})
return Ok(if is_input_file {
Stream::from_file_as_input(file_spec, file, &mut self.arena)
} else {
Stream::from_file_as_output(file_spec, file, in_append_mode, &mut self.arena)
});
}
}
}

View File

@@ -1,8 +1,7 @@
use crate::parser::ast::*;
use crate::parser::parser::*;
use dashu::integer::Sign;
use dashu::integer::UBig;
use dashu::integer::{Sign, UBig};
use lazy_static::lazy_static;
use num_order::NumOrd;
@@ -828,8 +827,12 @@ impl MachineState {
) -> usize {
let threshold = self.lifted_heap.len() - lh_offset;
let mut copy_ball_term =
CopyBallTerm::new(&mut self.stack, &mut self.heap, &mut self.lifted_heap);
let mut copy_ball_term = CopyBallTerm::new(
&mut self.attr_var_init.attr_var_queue,
&mut self.stack,
&mut self.heap,
&mut self.lifted_heap,
);
copy_ball_term.push(list_loc_as_cell!(threshold + 1));
copy_ball_term.push(heap_loc_as_cell!(threshold + 3));
@@ -4188,7 +4191,14 @@ impl Machine {
#[cfg(target_arch = "wasm32")]
#[inline(always)]
pub(crate) fn cpu_now(&mut self) {
// TODO
let millisecs = web_sys::window()
.expect("window global object should be available")
.performance()
.expect("performance property in window should be available")
.now();
let secs = float_alloc!(millisecs / 1000.0, self.machine_st.arena);
self.machine_st.unify_f64(secs, self.deref_register(1));
}
#[inline(always)]
@@ -4879,6 +4889,70 @@ impl Machine {
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
#[inline(always)]
pub(crate) fn js_eval(&mut self) -> CallResult {
unimplemented!()
}
#[cfg(target_arch = "wasm32")]
#[inline(always)]
pub(crate) fn js_eval(&mut self) -> CallResult {
let code = self.deref_register(1);
let result_reg = self.deref_register(2);
if let Some(code) = self.machine_st.value_to_str_like(code) {
match js_sys::eval(&code.as_str()) {
Ok(result) => self.unify_js_value(result, result_reg),
Err(result) => self.unify_js_value(result, result_reg),
};
return Ok(());
}
self.machine_st.fail = true;
Ok(())
}
#[cfg(target_arch = "wasm32")]
fn unify_js_value(&mut self, result: wasm_bindgen::JsValue, result_reg: HeapCellValue) {
match result.as_bool() {
Some(result) => match result {
true => self.machine_st.unify_atom(atom!("true"), result_reg),
false => self.machine_st.unify_atom(atom!("false"), result_reg),
},
None => match result.as_f64() {
Some(result) => {
let n = float_alloc!(result, self.machine_st.arena);
self.machine_st.unify_f64(n, result_reg);
}
None => match result.as_string() {
Some(result) => {
let result = AtomTable::build_with(&self.machine_st.atom_tbl, &result);
self.machine_st.unify_complete_string(result, result_reg);
}
None => {
if result.is_null() {
self.machine_st.unify_atom(atom!("null"), result_reg);
} else if result.is_undefined() {
self.machine_st.unify_atom(atom!("undefined"), result_reg);
} else if result.is_symbol() {
self.machine_st.unify_atom(atom!("js_symbol"), result_reg);
} else if result.is_object() {
self.machine_st.unify_atom(atom!("js_object"), result_reg);
} else if result.is_array() {
self.machine_st.unify_atom(atom!("js_array"), result_reg);
} else if result.is_function() {
self.machine_st.unify_atom(atom!("js_function"), result_reg);
} else if result.is_bigint() {
self.machine_st.unify_atom(atom!("js_bigint"), result_reg);
} else {
self.machine_st
.unify_atom(atom!("js_unknown_type"), result_reg);
}
}
},
},
}
}
#[inline(always)]
pub(crate) fn current_time(&mut self) {
let timestamp = self.systemtime_to_timestamp(SystemTime::now());
@@ -5516,11 +5590,8 @@ impl Machine {
let a2 = self.deref_register(2);
let n = match Number::try_from(a2) {
Ok(Number::Fixnum(bp)) => bp.get_num() as usize,
Ok(Number::Integer(n)) => {
let value: usize = (&*n).try_into().unwrap();
value
}
Ok(Number::Fixnum(bp)) => Integer::from(bp.get_num() as usize),
Ok(Number::Integer(n)) => (*n).clone(),
_ => {
let stub = functor_stub(atom!("call_with_inference_limit"), 3);
@@ -5531,21 +5602,24 @@ impl Machine {
let bp = cell_as_fixnum!(a1).get_num() as usize;
let a3 = self.deref_register(3);
let count = self.machine_st.cwil.add_limit(n, bp);
let result = count.try_into();
if let Ok(value) = result {
self.machine_st.unify_fixnum(Fixnum::build_with(value), a3);
} else {
let count = arena_alloc!(count.clone(), &mut self.machine_st.arena);
self.machine_st.unify_big_int(count, a3);
}
self.machine_st.increment_call_count_fn = MachineState::increment_call_count;
let count = self.machine_st.cwil.add_limit(n, bp).clone();
self.inference_count(a3, count);
Ok(())
}
#[inline(always)]
pub(crate) fn inference_count(&mut self, count_var: HeapCellValue, count: Integer) {
if let Some(value) = <&Integer as TryInto<i64>>::try_into(&count).ok() {
self.machine_st
.unify_fixnum(Fixnum::build_with(value), count_var);
} else {
let count = arena_alloc!(count, &mut self.machine_st.arena);
self.machine_st.unify_big_int(count, count_var);
}
}
#[inline(always)]
pub(crate) fn module_exists(&mut self) {
let module = self.deref_register(1);
@@ -5671,7 +5745,6 @@ impl Machine {
if bp == self.machine_st.b && self.machine_st.cwil.is_empty() {
self.machine_st.cwil.reset();
self.machine_st.increment_call_count_fn = |_| true;
}
}
@@ -6791,6 +6864,7 @@ impl Machine {
copy_term(
CopyBallTerm::new(
&mut self.machine_st.attr_var_init.attr_var_queue,
&mut self.machine_st.stack,
&mut self.machine_st.heap,
&mut ball.stub,

View File

@@ -327,8 +327,9 @@ impl DoubleQuotes {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Default)]
pub enum Unknown {
#[default]
Error,
Fail,
Warn,
@@ -348,13 +349,6 @@ impl Unknown {
}
}
impl Default for Unknown {
#[inline]
fn default() -> Self {
Unknown::Error
}
}
pub fn default_op_dir() -> OpDir {
let mut op_dir = OpDir::with_hasher(FxBuildHasher::default());

View File

@@ -207,6 +207,13 @@ test("scryer-prolog#2056",(
\+ E=[]
)).
% https://github.com/mthom/scryer-prolog/issues/2175
test("scryer-prolog#2175",(
dif(A,B),
A=_C*[],
A=[]*D*B,D=[]
)).
main :-
findall(test(Name, Goal), test(Name, Goal), Tests),
run_tests(Tests, Failed),

View File

@@ -452,4 +452,4 @@ print_exception_with_check(E) :-
% is expected to be printed instead.
; print_exception(E)
).

View File

@@ -783,10 +783,10 @@ test_217_181_290_317 :-
( op(1105,xfy,'|'),
read_from_chars("(a-->b,c|d).", T0),
writeq_term_to_chars(T0, C0),
C0 == "a-->b,c | d",
C0 == "a-->b,c|d",
read_from_chars("[(a|b)].", T1),
writeq_term_to_chars(T1, C1),
C1 == "[(a | b)]",
C1 == "[(a|b)]",
read_from_chars("[a,(b,c)|[]].", T2),
writeq_term_to_chars(T2, C2),
C2 == "[a,(b,c)]"