276 Commits

Author SHA1 Message Date
Mark Thom
4e7d9f9010 update README.md
Some checks failed
CI / style (push) Has been cancelled
CI / read-msrv (push) Has been cancelled
CI / build-test (--no-default-features --features=all-simple-cross, miri, true, ubuntu-22.04, nightly, s390x-unknown-linux-gnu, --no-run --no-default-features --features=all-simple-cross) (push) Has been cancelled
CI / build-test (--no-default-features, ubuntu-22.04, true, nightly, wasm32-unknown-unknown, --no-run --no-default-features, true) (push) Has been cancelled
CI / build-test (miri, true, ubuntu-22.04, nightly, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (true, macos-latest, true, stable, x86_64-apple-darwin) (push) Has been cancelled
CI / build-test (true, ubuntu-22.04, true, stable, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (true, windows-latest, true, stable, x86_64-pc-windows-msvc) (push) Has been cancelled
CI / build-test (ubuntu-22.04, ${{ needs.read-msrv.outputs.msrv }}, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, beta, x86_64-unknown-linux-gnu) (push) Has been cancelled
CI / build-test (ubuntu-22.04, true, stable, i686-unknown-linux-gnu) (push) Has been cancelled
CI / logtalk-test (push) Has been cancelled
CI / report (push) Has been cancelled
CI / release (push) Has been cancelled
Docker Publish / build (push) Has been cancelled
2026-07-21 23:51:19 -07:00
Mark Thom
d3edffb7fc update README.md 2026-07-21 23:48:56 -07:00
Mark Thom
295a642034 Merge pull request #3372 from triska/global_cardinality
FIXED: Correct global_cardinality/2 propagation with reified constraints.
2026-06-30 17:25:30 -07:00
Mark Thom
ae0999a3bb Merge pull request #3398 from Skgland/fix-3320
fix incorrect missing feature errors
2026-06-30 17:25:02 -07:00
Mark Thom
11fd39bbaf Merge pull request #3275 from abmclin/Issue-3223-migrate-to-Rust-edition-2024
Issue 3223 migrate to rust edition 2024
2026-06-30 17:22:27 -07:00
Skgland
eb06f19c32 fix incorrect missing feature errors
this was accidentally introduced in mthom/scryer-prolog#3320

fixes mthom/scryer-prolog#3397
2026-06-29 20:13:10 +02:00
Alexander McLin
ea88ca552d Issue 3223: reformat via cargo fmt 2026-06-16 21:18:49 -04:00
Alexander McLin
eabbf67e86 Issue 3223: add more unsafe scopes 2026-06-16 21:14:43 -04:00
Alexander McLin
7a636b0394 Issue 3223: trying again repair for the failing wasm32 build
Removed `mut` modifier completely.
2026-06-16 21:05:12 -04:00
Alexander McLin
71ea74a077 Issue 3223: repair for the failing wasm32 build
Removed `ref` modifier due to not being allowed when implicitly borrowing.
2026-06-16 21:05:12 -04:00
Alexander McLin
091af28368 Issue 3223: reformat via cargo fmt 2026-06-16 21:05:12 -04:00
Alexander McLin
fbc5345c9a Issue 3223: more unsafe scope refinements 2026-06-16 21:05:12 -04:00
Alexander McLin
9d49415449 Issue 3223: reformat via cargo fmt 2026-06-16 21:05:12 -04:00
Alexander McLin
83218bf0da Issue 3223: make unsafe scopes tighter 2026-06-16 21:05:12 -04:00
Alexander McLin
fcd6c3f127 Issue 3223: Second phase of migration to Rust Edition
Reformat via `cargo fmt`
2026-06-16 21:05:12 -04:00
Alexander McLin
efbddeaeee Issue 3223: First phase of migration to Rust Edition 2024
Update cargo dependencies
Apply cargo fix --edition
Change cargo.toml edition property to `2024`

^ Conflicts:
^	Cargo.lock
^	src/ffi.rs

^ Conflicts:
^	src/offset_table.rs
^	src/raw_block.rs
2026-06-16 20:37:52 -04:00
Mark Thom
daaab1cb37 Merge pull request #3331 from adri326/rawblock-safety
Prove safety of RawBlock and support multi-threaded usages
2026-06-11 00:38:04 -07:00
Markus Triska
9b73536cdd FIXED: Correct global_cardinality/2 propagation with reified constraints.
This issue was found by @david-sitsky in a Prolog formulation of an
interesting scheduling task:

    https://github.com/mthom/scryer-prolog/discussions/3341

See also the filed issue:

    https://github.com/mthom/scryer-prolog/issues/3369

Thank you a lot!

This issue needs more analysis to find out what exactly happened here,
whether other cases like it exist, and whether it can be addressed in
a better way. I would greatly appreciate any help!
2026-06-10 20:31:20 +02:00
Mark Thom
e12d3f14c1 Merge pull request #3363 from Skgland/adjust-discussion-3359-test
adjust regression test for discussion 3359
2026-06-05 13:55:21 -07:00
Mark Thom
ffb77473b2 Merge pull request #3364 from Skgland/finish-msrv-bump-cleanup
finish msrv bump cleanup
2026-06-05 13:54:49 -07:00
Skgland
49c0cd3733 remove unecessary use of LayzLock
AtomicBool::new const
2026-06-05 20:47:20 +02:00
Skgland
a236d00d7d fix lint warnings 2026-06-05 20:45:18 +02:00
Skgland
bb5a59170a cleanup after msrv bump 2026-06-05 20:44:38 +02:00
Skgland
2b757de73a adjust regression test for discussion 3359
- incorporate fix from 3a2d57db33 (commitcomment-187528793)
- mark as should panic rather than ignored, this ensures that the test will be updated should this get fixed
2026-06-05 20:29:50 +02:00
Mark Thom
03f796e9ff Merge pull request #3299 from danilp-id/interrupt_test
Test: SIGINT should be able to interrupt non-terminating goals on linux
2026-06-04 13:11:08 -07:00
Danil Platonov
d8be99b6ad fix merge 2026-06-04 12:25:54 -07:00
Danil Platonov
2a04025419 Merge branch 'master' into interrupt_test 2026-06-04 12:08:36 -07:00
Mark Thom
bc14e3aedd Merge pull request #3362 from danilp-id/socket_interrupt
Handle interrupts for socket_server_accept/4
2026-06-04 10:56:09 -07:00
Danil Platonov
bcb9dd0860 handle interrupts for tcp server 2026-06-04 02:41:11 -07:00
Mark Thom
18dd939159 Merge pull request #3352 from danilp-id/http_interrupt
Interrupts support for http server + other improvements
2026-06-03 13:36:21 -07:00
Mark Thom
1cbc95bd06 Merge pull request #3360 from Skgland/fix-discussion-3359
Fix discussion 3359
2026-06-03 13:35:56 -07:00
Skgland
d1e8ea4dd5 ignore added test on i686-unknown-linux-gnu
appears to have been broken even before d50d425099
so has a different cause that needs to be investigated
2026-06-03 22:05:20 +02:00
Skgland
068959bd07 fix discussion 3359 2026-06-03 22:05:20 +02:00
Skgland
3a2d57db33 add regression test for discussion 3359 2026-06-03 22:05:20 +02:00
Mark Thom
3ce16656ee Merge pull request #3357 from Skgland/fix-issue3346
ensure ParallelHeapIter pushes its stack in pairs and fix 3346
2026-06-02 22:19:21 -07:00
Danil Platonov
732f3ae10a use Notify instead of Sender 2026-06-02 00:49:00 -07:00
Skgland
a4993284cd fix parallel heap iter 2026-06-01 23:59:37 +02:00
Skgland
8aeb326e5c use a pair for ParallelHeapIter
to ensure it is pushed popped in pairs
2026-06-01 23:58:17 +02:00
Danil Platonov
304cf424ed unused code for unimplemented shutdown on timeout feature 2026-05-30 15:59:16 -07:00
Danil Platonov
3cd40419eb cargo fmt 2026-05-30 15:56:18 -07:00
Danil Platonov
f8f7b3b7e2 allow to stop and start http server at will with SIGINT, stop the server on uncaught throws, display timing info for each request, use setup_call_cleanup in more places to avoid potential leaks, when handler for a path doesn't have a matching case, throw a detailed error instead of silently continuing 2026-05-30 15:47:50 -07:00
Danil Platonov
39c850b4f4 Test that SIGINT interrupts non-terminating goals on unix.
Cleanup: use tokio::test instead of special test helpers

Added pty_exec.py helper script to run binaries with a pseudoterminal attached (bug won't trigger otherwise).

Tested CI to fail on all unixes (ubuntu+macos) with older rustyline, and pass with new one.
2026-05-30 11:45:57 -07:00
Mark Thom
8b6d68a4cd Merge pull request #3347 from drzv/im7-compat2
showterm.el: use new magick command by default, fallback to convert
2026-05-28 16:34:52 -07:00
Mark Thom
149d3932be Merge pull request #3272 from abmclin/windows_improve_notty_stdin_support
Windows improve notty stdin support to resolve issue 3264
2026-05-26 14:01:23 -06:00
drzv
36cbeb3d6c showterm.el: use new magick command by default, fallback to convert 2026-05-26 11:22:17 -04:00
drzv
875ae5a773 showterm.el: revert #3338 2026-05-26 08:10:24 -04:00
Alexander McLin
70d046499a Issue 3264: rebase and update dependencies 2026-05-25 18:24:24 -04:00
Alexander McLin
b6db1cde25 Issue 3264 add rationale comment & #[allow(...)]
Explain why no multibyte UTF-8 encoding support
Disable `bytes()` clippy warning about performance penalty due to unbuffered bytes
2026-05-25 18:13:02 -04:00
Alexander McLin
257209df75 Issue 3264: refactor supported_terminal
Use better code style
2026-05-25 18:13:02 -04:00
Alexander McLin
d5d514ec96 Issue 3264: reformat to conform to style guidelines 2026-05-25 18:13:01 -04:00
Alexander McLin
533594b6b6 Fixes issue 3264 by adding a fallback read mode when reading from non-tty stdin on Windows 2026-05-25 18:13:01 -04:00
Mark Thom
43b6afcdf3 Merge pull request #3342 from Skgland/pdl-pairs
ensure pdl is pushed/popped in pairs and reuse tabu_list allocation by moving it from a local variable into the machine state
2026-05-25 15:10:02 -06:00
Mark Thom
2c23f84808 Merge pull request #3310 from Skgland/process_wait-release
add `release(Bool)` option to `process_wait/3`
2026-05-25 15:08:37 -06:00
Mark Thom
ad13aa3801 Merge pull request #3320 from Skgland/all-pure-rust
add a feature for enabling all features that don't pull in non pure rust dependencies
2026-05-25 14:36:04 -06:00
Mark Thom
79597b63bf Merge pull request #3317 from Skgland/optimize-put_back_char
optimize put_back_char
2026-05-25 14:03:31 -06:00
Skgland
bdba7ba0f5 merge crypto-impure feature into feature crypto-full 2026-05-25 19:07:20 +02:00
Skgland
6c7d5e8278 more spelling fixes 2026-05-25 16:39:37 +02:00
Skgland
61ebcdb56e fix spelling 2026-05-24 23:55:19 +02:00
Skgland
f2195c2362 cleanup
- fix some warnings and mark others as expected
- make use of the msrv bump to 1.93.1 and cleanup compat and workarounds
2026-05-24 23:29:23 +02:00
Skgland
6150ca90d3 check individual features in CI 2026-05-24 23:00:27 +02:00
Skgland
938f588769 make crypto depending on ring optional 2026-05-24 23:00:27 +02:00
Mark Thom
61d94dceae Merge pull request #3290 from abmclin/bump-rustyline-version-18
bump `Rust` version to 1.93.1 and bump `rustyline` to version 18.0.0
2026-05-24 13:27:00 -06:00
Mark Thom
1411417a44 Merge pull request #3338 from drzv/master
Use make-process to call imagemagick in showterm.el
2026-05-24 13:22:33 -06:00
Mark Thom
609514b4b8 Merge pull request #3312 from Skgland/big-endian-miri
run miri for a big-endian target
2026-05-24 13:22:00 -06:00
Skgland
1fd9246b16 configure linker via env var insread of .cargo/config.toml
someone might already have a .cargo/config.toml
2026-05-24 19:08:23 +02:00
Skgland
19974e580b fix missing description
Github documentation states that it is a required field and my linter complained

https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#description
2026-05-24 19:02:23 +02:00
Skgland
550efb8416 add a feature for things that are simple to cross-compile 2026-05-24 10:39:20 +02:00
Skgland
e0c491e9f3 fix miri not being passed the target 2026-05-24 10:39:20 +02:00
Skgland
f16850e5f8 fix ci and setup cross-compilation 2026-05-24 10:39:20 +02:00
Skgland
ca0d4e9aac add miri CI for a big endian target 2026-05-24 10:39:20 +02:00
Skgland
6e50268efb fix spelling again 2026-05-24 10:30:30 +02:00
Skgland
54b3034905 remove out-dated comment and fix spelling/grammar 2026-05-24 10:25:24 +02:00
Mark Thom
cf9e667b50 Merge pull request #3311 from Skgland/cleanup2
some cleanup and fixes
2026-05-23 18:27:50 -06:00
Mark Thom
3f0b471227 Merge pull request #3333 from 0nkery/lazy-lock
replace lazy_static macro with std::sync::LazyLock
2026-05-23 09:51:22 -06:00
Mark Thom
bfca60aa86 Merge pull request #3345 from triska/meetups
link to Scryer Prolog Meetup 2026 page and its announcement
2026-05-23 09:51:06 -06:00
Skgland
6b9a291f4e fix spelling 2026-05-23 11:28:45 +02:00
Markus Triska
777637001f link to Scryer Prolog Meetup 2026 page and its announcement 2026-05-23 10:17:11 +02:00
Mark Thom
9fcd7776a9 Merge pull request #3336 from 0nkery/fix-between-doc
fix var mode in between/3 documentation
2026-05-22 23:04:05 -06:00
Mark Thom
4f166f50a8 Merge pull request #3334 from Skgland/stable-order
use BTree{Map,Set} to ensure a stable order based on the key/entry
2026-05-22 22:56:13 -06:00
Mark Thom
1547839c33 Merge pull request #3328 from Skgland/update-libffi
upgrade libffi dependency
2026-05-22 22:53:49 -06:00
Mark Thom
5a1423d0f0 Merge pull request #3316 from josd/master
Improve CharReader buffering
2026-05-22 22:52:27 -06:00
Mark Thom
3faa838ee2 Merge pull request #3323 from triska/VATmiraal
add link to VATmiraal, https://vatmiraal.be
2026-05-22 22:45:39 -06:00
Skgland
e4792429d3 reuse tabu list 2026-05-17 20:50:37 +02:00
Skgland
d50d425099 ensure pdl is pushed/popped in pairs 2026-05-17 19:48:52 +02:00
drzv
0df5f7e576 Call ImageMagick with make-process. Redirect stderr to separate buffer 2026-05-14 19:10:05 +00:00
Dmitrii Shlagov
b9f1443eb0 fix var mode in between/3 documentation 2026-05-14 16:25:33 +02:00
Skgland
ac18aed5a7 use BTree{Map,Set} to ensure a stable order based on the key/entry even if filesystem enumeration order changes
this should help with reproducibility
2026-05-14 01:02:19 +02:00
Dmitrii Shlagov
2c60e4183f roll back gitignore changes 2026-05-12 17:15:28 +02:00
Dmitry Shlagoff
2bbb6487ec remove redundant closure 2026-05-12 16:12:33 +02:00
Dmitry Shlagoff
095fa1227a replace lazy_static macro with std::sync::LazyLock 2026-05-12 15:40:38 +02:00
Emilie Burgun
f395d554a5 RawBlock: finish proof of safety and defragment after growing
After calling `grow()`, the new head would jump to `old_capacity` rather
than staying to the same offset.

In practice this only loses a few bytes at most.
2026-05-10 22:36:11 +02:00
Emilie Burgun
ad614b684c RawBlock: switch to Cell or AtomicPtr instead of UnsafeCell
This is the first step towards enabling multithreading on AtomTable.

For now RawBlock will default to using Cell, which yields a byte-
equivalent compiled output.

Also adds an `atomic` feature, which, when enabled, will make RawBlock
use an AtomicPtr instead, ensuring that it implements `Sync`.
2026-05-10 22:36:11 +02:00
Emilie Burgun
898b6b2b25 RawBlock: seal base and add Stack::index_dangling_or_frame
Direct accesses to `base` are replaced with dedicated methods with
explicit safety requirements.
2026-05-10 22:36:11 +02:00
Emilie Burgun
3c5818a040 RawBlock: seal most fields, replace top with a capacity field 2026-05-10 22:24:57 +02:00
Skgland
0c313a0849 upgrade libffi dependency 2026-05-08 22:36:41 +02:00
Alexander McLin
7c9075b4ee Bump Cargo.toml rust-version to 1.93.1
MSRV discussion settled on `1.93.1` to align with upcoming Debian stable releases
2026-05-05 09:58:05 -04:00
Markus Triska
4c14cbe39a explanation of product
See posting by @constraintAutomaton at:

    https://github.com/mthom/scryer-prolog/pull/3323#issuecomment-4365798844

Many thanks!
2026-05-03 11:00:38 +02:00
Markus Triska
afe744aa57 add link to VATmiraal, https://vatmiraal.be 2026-05-03 09:52:28 +02:00
Skgland
89dde3f370 un-ignore lorem_ipsum tests as they are no longer slow 2026-04-30 19:54:39 +02:00
Skgland
f8ddd78776 optimize CharReader 2026-04-30 19:46:40 +02:00
Skgland
327a677a7c use a function rather than a closure 2026-04-30 19:02:27 +02:00
Skgland
48a34aa2cc add a test with bad utf-8 2026-04-30 19:01:34 +02:00
Skgland
af54fb2aba optimize put_back_char
Always encode the char directly into the buffer.
Only shift the buffer content if there isn't enough room in the front.
2026-04-26 14:13:55 +02:00
josd
b2dad66270 Improve CharReader buffering 2026-04-26 13:42:41 +02:00
Skgland
eac7ff680c fix ArenaPtr payload offset logic
the old logic would be incorrect if the payload has higher alignment than the ArenaHeader i.e. when there is padding between the ArenaHeader and the Payload
2026-04-25 18:12:32 +02:00
Skgland
b4db85c8c3 don't erase ptr type early when construction a Cons HeapCellValue
rather than passing an address as usize pass the ArenaHeader pointer
similarly don't return a u8 ptr but use a ArenaHeader pointer instead

Don't convert the pointer to a ConsPtr by going through native endian
bytes in between.
We are exploiting the fact that the 3 least significant bytes are zero
for pointer to types of alignment 8 and we expect these to line up with
the f, m, and tag field at the end of the ConsPtr struct, but using
native endiannes for this would only work on big endian systems.
2026-04-25 18:12:31 +02:00
Skgland
6765921666 fix unused import warning on windows 2026-04-25 16:19:58 +02:00
Skgland
ff293a56e6 fix large enum variant size difference warning of PermVarAllocation
by wrapping BranchNumber in an Arc.
PermVarAllocation::Done had size 208 and is now down to 32.
A Box rather than an Arc would be smaller, but it looks like BranchNumber/BranchDesignator are clones a bunch so I expect it to be beneficial to reduce allocations both of the Box itself as well as its content.
2026-04-25 16:19:53 +02:00
Skgland
ab2b652c84 fix linux tests on 1.85
old rust versions didn't print the pid
2026-04-25 15:19:41 +02:00
Skgland
83add732e0 adjust test expectations 2026-04-25 14:43:52 +02:00
Skgland
f8c4679850 add process_wait/3 release(Bool) option
make process_wait/3 release the Process by default
2026-04-25 14:35:20 +02:00
Skgland
3da2b8fba2 add tests 2026-04-25 14:35:07 +02:00
Skgland
f7dc1d72f6 remove unused HeapCellValueView
it is identical to HeapCellValueTag
2026-04-22 22:16:36 +02:00
Mark Thom
8dffd72db5 replace tabs with spaces 2026-04-21 16:22:38 -07:00
Mark Thom
703a6dddd1 dereference compare_term_test args down from stack (#3286, #3295) 2026-04-21 16:02:55 -07:00
Mark Thom
5ea262e1d3 replace tabs with spaces 2026-04-21 15:02:14 -07:00
Mark Thom
010b00f349 take Lis index from rd in copy_var (#3298) 2026-04-21 14:31:53 -07:00
Mark Thom
974722bc1c Merge pull request #3269 from no382001/quads
fix library paths and module qualification in quadtests.pl
2026-04-18 18:20:03 -06:00
Mark Thom
695389bda1 Merge pull request #3293 from danilp-id/http_server_test
http_server test + unignore http_open_hanging
2026-04-18 18:17:18 -06:00
Mark Thom
9cf0d8b484 Merge pull request #3274 from no382001/issue3262
fix read/1 on non-TTY stdin blocking until newline
2026-04-18 18:16:53 -06:00
Mark Thom
381b4f2857 Merge pull request #3291 from Skgland/update-actions
Update actions
2026-04-18 18:16:00 -06:00
Danil Platonov
35aa4d9048 cargo fmt 2026-04-12 19:32:40 -07:00
Danil Platonov
8e2dcd905b pass scryer path through stream instead of env var 2026-04-12 19:29:39 -07:00
Danil Platonov
081f3c0827 http server test, and also unignore http client test 2026-04-11 08:59:01 -07:00
Skgland
8bbb484f94 update pyTooling/Actions/with-post-step@v4.1.0 -> pyTooling/Actions/with-post-step@v7.7.0 2026-04-11 00:06:13 +02:00
Skgland
717c3c2db8 update actions/cache@v4.3.0 -> actions/cache@v5.0.4 2026-04-10 23:59:59 +02:00
Skgland
0aa6abe517 pin 3rd-party actions
following githubs recommendations[^3rd-p-a] to use full-length commit SHA for 3rd-party actions

setup dependabot to help keep dependencies up-to-date

[^3rd-p-a]: https://docs.github.com/en/actions/reference/security/secure-use#using-third-party-actions
2026-04-10 23:52:43 +02:00
Skgland
4a9f690c16 update docker/build-push-action@v6 -> docker/build-push-action@v7 2026-04-10 23:22:38 +02:00
Skgland
e3d020ea74 update docker/metadata-action@v5 -> docker/metadata-action@v6 2026-04-10 23:22:38 +02:00
Skgland
82378c6338 update docker/login-action@v3 -> docker/login-action@v4 2026-04-10 23:22:38 +02:00
Skgland
f456c98c5b update docker/setup-buildx-action@v3 -> docker/setup-buildx-action@v4 2026-04-10 23:22:37 +02:00
Skgland
4db325e4b8 update actions/download-artifact@v4 -> actions/download-artifact@v8 2026-04-10 23:22:37 +02:00
Skgland
8f33eaf7d1 update actions/upload-artifact@v4 -> actions/upload-artifact@v7 2026-04-10 23:22:37 +02:00
Skgland
eba16a2213 update actions/checkout@v4 -> actions/checkout@v6 2026-04-10 23:22:37 +02:00
Mark Thom
87f97c7e5c Merge pull request #3276 from Skgland/cleanup
remove unused file
2026-04-08 22:18:32 -06:00
Alexander McLin
2fec440ba6 bump rustyline to version 18.0.0
Fixes regression discovered on Linux after upgrading `rustyline` to 17.0.2; `SIGINT` not handled as expected, breaking ability to interrupt nontermination in `scryer`.

This was due to a change in `rustyline`'s `SIGINT` behavior since version 16 and corrected in version 18.
2026-04-05 20:43:38 -04:00
Mark Thom
3e2f3ecfdb Merge pull request #3273 from no382001/issue2914
fix panic in current_prolog_flag/2 when arguments are the same variable
2026-04-04 13:06:11 -06:00
Mark Thom
2b6b11e6be Merge pull request #3271 from abmclin/update_rustyline
Resolves issue 2694 by updating rustyline to 17.0.2
2026-04-04 12:28:35 -06:00
Mark Thom
7da2ba2015 Merge pull request #3266 from Skgland/perf-issue-3265
perf improvements for 3265
2026-04-04 12:02:29 -06:00
Skgland
bb9c1cf5a2 remove unused file 2026-04-03 00:16:59 +02:00
no382001
28f8da5ce9 restructure based on suggestion
Co-authored-by: Bennet Bleßmann <3877590+Skgland@users.noreply.github.com>
2026-03-31 21:14:54 +02:00
no382001
c065b52778 fix read/1 on non-TTY stdin blocking until newline #3262 2026-03-31 15:52:27 +02:00
no382001
6a10c6d6a5 fix panic in current_prolog_flag/2 when arguments are the same variable 2026-03-31 12:01:00 +02:00
Alexander McLin
0623e8e7df Resolves issue 2694 by updating rustyline to 17.0.2 2026-03-29 14:49:51 -04:00
Skgland
3c9c7ade8d use checked_add to prevent silent overflow 2026-03-27 22:00:54 +01:00
no382001
13c85c74db fix library paths and module qualification in quadtests.pl 2026-03-27 21:27:15 +01:00
Skgland
5fc21241d6 hint to the compiler/branch-predictor that
we expect p to be in bound

if we are suffiently certain that p can never be oob
unsafe get_unchecked could be used to
eliminate bounds checking all together
which should improve performance further.
2026-03-21 19:01:52 +01:00
Skgland
833c3bd77f run rustfmt 2026-03-21 14:24:13 +01:00
Skgland
153f04b72a adjust interrupt handling
we used to spend ~5.8% of the time on getting the next value of the 0..INSTRUCTIONS_PER_INTERRUPT_POLL iterator

increment on Wrapping<u8> now only takes ~0.58 of the time
2026-03-21 14:20:11 +01:00
Skgland
8aab887388 switch from Integer to u128
- u128 is only 16 bytes instead of 24
- incrementing u128 does not involve heap allocations
- u128 should be sufficent
 it would take more than 2 sextilion years to overflow if we would be incrementing it every tick at 5GHz
   i.e. 2^128-1 / 5 GHz / 60 / 60 / 24 / 356 > 2 sextilion

before this ~3.2% of the execution time of the program in https://github.com/mthom/scryer-prolog/issues/3265#issuecomment-4103176469 was spend in the increment_call_count function, after this change it's down to 0.3%
2026-03-21 13:36:40 +01:00
Mark Thom
79a9b950cb Merge pull request #3243 from danilp-id/master
fix http stream not being closed fully
2026-03-19 23:36:58 -07:00
Mark Thom
fba2854f21 Merge pull request #3257 from triska/include
ISO: Implement the include/1 directive
2026-03-17 23:58:04 -07:00
Markus Triska
1c9fd1501a retain error context (load/1) when using :- D. 2026-03-16 07:36:55 +01:00
Mark Thom
b0b89a0aca Merge pull request #3258 from no382001/fix_load_xml/3
load_xml was returning a single term instead of a list of nodes
2026-03-14 22:46:03 -07:00
no382001
025da8315b load_xml was returning a single term instead of a list of nodes 2026-03-10 12:20:34 +01:00
Mark Thom
8002b9a78b Merge pull request #3234 from fusiongyro/patch-1
Fixed a typo in the documentation for atom_codes/2
2026-03-09 22:09:40 -07:00
Mark Thom
02f86587b5 Merge pull request #3232 from triska/not_si
ADDED: not_si/1
2026-03-09 22:09:09 -07:00
Mark Thom
4d144ea819 Merge pull request #3252 from Skgland/towards-a-crate-without-syn@1
replace custom modular_bitfields fork with update upstream
2026-03-09 22:08:28 -07:00
Mark Thom
02c927bb83 Merge pull request #3241 from Skgland/parse-error-improvements
change `ParserError` type
2026-03-09 22:07:03 -07:00
Mark Thom
4a8388a6e9 Merge pull request #3189 from Skgland/fix-function_casts_as_integer
fix `function_casts_as_integer` warning
2026-03-09 22:06:33 -07:00
Mark Thom
7bf014cdf5 Merge pull request #3260 from thierrymarianne/dealing-with-invalid-utf8-string
handle invalid UTF-8 string
2026-03-09 20:56:04 -07:00
Markus Triska
b41563e976 ENHANCED: support quads in included files
Suggested by @dcnorris, many thanks!

    https://github.com/mthom/scryer-prolog/issues/634#issuecomment-4003451420
2026-03-07 09:37:30 +01:00
Thierry Marianne
ead2fccdbb add documentation
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-05 09:03:31 +01:00
Thierry Marianne
817c584993 revise indentation of raw string
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 15:06:27 +01:00
Thierry M.
e00112b92f use C-string literal
Co-authored-by: Bennet Bleßmann <3877590+Skgland@users.noreply.github.com>
2026-03-04 15:01:23 +01:00
Thierry Marianne
4bd0ff0fcb cargo fmt fixes
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 14:04:47 +01:00
Thierry Marianne
ed59aa05b0 apply suggestion
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 14:01:17 +01:00
Thierry Marianne
16ed1040d2 move test to tests/scryer/ffi.rs
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 14:01:17 +01:00
Thierry M.
fa21ff4813 use CStr .to_str() method
Co-authored-by: Bennet Bleßmann <3877590+Skgland@users.noreply.github.com>
2026-03-04 14:00:44 +01:00
Thierry M.
891f7fc892 use C-string literal
Co-authored-by: Bennet Bleßmann <3877590+Skgland@users.noreply.github.com>
2026-03-04 13:59:53 +01:00
Thierry Marianne
fae9ca1bb9 fix utf-8 panic
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 12:45:58 +01:00
Thierry Marianne
ac71cff338 add failing test highlighting panic on invalid UTF-8 cstr
Signed-off-by: Thierry Marianne <thierry@marianne.io>
2026-03-04 12:45:48 +01:00
Markus Triska
8f39bee463 ISO: Implement the include/1 directive
Quoting from the standard:

    7.4.2.7 include/1

    If F is an implementation defined ground term designating
    a Prolog text unit, then Prolog text P1 which contains
    a directive include(F) is identical to a Prolog text P2
    obtained by replacing the directive include(F) in P1 by
    the Prolog text denoted by F.

Example:

    :- include("hello.pl").

This addresses #583 and #634.
2026-02-28 14:49:13 +01:00
Skgland
2dc904aa73 run apt update before apt install
https://github.com/orgs/community/discussions/145882#discussioncomment-11447924
2026-02-20 23:00:45 +01:00
Skgland
0ceab41b39 fix warning in CI about using apt in a script 2026-02-20 22:57:44 +01:00
Skgland
2a79d6fc40 run cargo fmt 2026-02-20 22:52:25 +01:00
Skgland
49e49b425d fix clippy lint warning 2026-02-20 22:47:08 +01:00
Skgland
7d0d61d789 remove no longer necessary allows 2026-02-20 22:45:09 +01:00
Skgland
5ef5a3f30a replace custom modular_bitfields fork with update upstream
part of #2468
2026-02-20 22:40:33 +01:00
Danil Platonov
337d9c5583 move drop to impl Drop 2026-01-26 04:57:28 -08:00
Danil Platonov
26523139a8 fix http stream not being closed fully 2026-01-25 03:16:37 -08:00
Skgland
65aadb3b88 reduce visibility to pub(crate) to prevent accidentally exposing 2026-01-24 04:20:13 +01:00
Skgland
dfad71bc6e fix some lint warnings 2026-01-24 04:13:26 +01:00
Skgland
d8c6fa1fe5 don't expose lexical error and provide location 2026-01-24 04:08:41 +01:00
Skgland
18b476af28 make ParserError a struct with an enum kind field 2026-01-24 04:08:40 +01:00
Skgland
bd233fedfd add location to InvalidSingleQuotedCharacter 2026-01-24 04:08:40 +01:00
Skgland
083546442d make match exhaustive 2026-01-24 04:08:40 +01:00
Skgland
3343188756 replace pairs of usize with location struct 2026-01-24 04:08:40 +01:00
Daniel K Lyons
ed36aa7313 Fixed a typo in the documentation for atom_codes/2 2026-01-20 09:54:18 -07:00
Markus Triska
0dc94ecc0b ADDED: not_si/1
For a recent example that would benefit from this, see "Prolog Basics
Explained with Pokémon" by @alexpetros:

    https://unplannedobsolescence.com/blog/prolog-basics-pokemon/

Thank you a lot!
2026-01-18 09:58:31 +01:00
Mark Thom
453a88f03e Merge pull request #3211 from mthom/setof_correction_and_opt
Correct and optimize setof/3 and bagof/3
2026-01-14 22:07:10 -07:00
Mark Thom
c79fd74331 remove unnecessary extra work in findall_with_existential/5 2026-01-14 20:39:48 -08:00
Mark Thom
e446b13376 remove hashbrown crate 2026-01-14 20:39:48 -08:00
Mark Thom
a83f412251 replace sort_without_dedup/2 with keysort/2 2026-01-14 20:39:48 -08:00
Mark Thom
1a8c4f9b03 cargo fmt fixes 2026-01-14 20:39:48 -08:00
Mark Thom
c2e1ded852 remove variant_hashing.rs and related instructions 2026-01-14 20:39:48 -08:00
Mark Thom
70220687f4 find variant terms using just sort/2 and (==)/2 2026-01-14 20:39:48 -08:00
Mark Thom
69a367d1c9 do not retain attributes in solutions of findall (#3020) 2026-01-14 20:39:48 -08:00
Mark Thom
e2bdf59c81 fix cargo fmt 2026-01-14 20:39:48 -08:00
Mark Thom
6284aa3a3f add variant_hash and is_non_variant to fix setof/3, bagof/3 2026-01-14 20:39:48 -08:00
Mark Thom
29cd80510b replace compare_term_test with parallel iterator, add is_not_variant 2026-01-14 20:39:45 -08:00
Mark Thom
9089f9ddb4 use branch numbers to detect branch subsumption 2026-01-14 20:34:06 -08:00
Mark Thom
16dc10ee96 assert rational(3) as true in tests/builtins.pl 2026-01-14 20:34:06 -08:00
Mark Thom
4e8f338870 Merge pull request #3222 from Skgland/reduce-instructions_template
move static parts of instructions template directly into instructions module, fix clippy and typos
2026-01-12 23:22:44 -07:00
Skgland
b14e363d5e run rustfmt to fix formatting 2026-01-10 18:31:40 +01:00
Bennet Bleßmann
87f89545bb restore accidentally removed comment 2026-01-10 18:30:09 +01:00
Skgland
5064760b1e fix spelling 2026-01-10 18:13:59 +01:00
Skgland
eca4262be6 fix clippy lints 2026-01-10 17:52:34 +01:00
Skgland
73c26bed2a extaract the static part of the instructions template directly into the instructions module
This way goto source doesn't end up in a generated file for those parts and they can be edited directly.
I have way too often accidentally edited the generated file.
2026-01-10 17:48:07 +01:00
Mark Thom
172b6a6f8f Merge pull request #3207 from Skgland/quote-fix-unquote-segv
"fix" segv from #3198, #3199, and #3205
2025-12-17 14:11:21 -07:00
Skgland
129cca53ed throw error(resource_error(memory), []) when the list length exceeds usize. 2025-12-10 00:04:24 +01:00
Mark Thom
cd1ed106ed Merge pull request #3177 from triska/use_phrase_to_file
use more recently available phrase_[to|from]_file/3 in example
2025-12-08 19:23:52 -07:00
Skgland
00aba96185 remove overengineered RecursionGuard 2025-12-08 22:27:24 +01:00
Skgland
616f071cd9 fix another integer overflow 2025-12-08 19:59:43 +01:00
Skgland
580572aec6 don't calculate panic message eagerly 2025-12-07 23:06:09 +01:00
Skgland
47e908bf76 handle overflown in Heap::with_cell_capacity 2025-12-07 22:59:00 +01:00
Skgland
11901b5fde handle oob heap index calculation 2025-12-07 21:45:13 +01:00
Skgland
d63b0a192c use checked multiplication in heap_index! macro 2025-12-07 21:31:24 +01:00
Skgland
2fc08dde1e prevent multiply with overflow resulting in odd errors/segv 2025-12-07 21:27:24 +01:00
Skgland
193cfabc66 detect recursive throw_resource_error and panic instead of causing a segmentation fault 2025-12-07 21:08:17 +01:00
Skgland
7c3ffd0e87 deny function_casts_as_integer warnings 2025-12-04 23:35:35 +01:00
Skgland
797a8f8611 fix function_casts_as_integer warning
Comparing addresses of function pointers is brittle.

Functions may be duplicated resulting in function pointers to the same function to compare !=.
Functions may be merged/de-duplicated resulting in function pointers to different function to compare ==.

The later shouldn't be relevant here as the function differ in behavior, but mentioning it for completeness.
2025-12-04 23:35:23 +01:00
Mark Thom
f570e566f8 fix backtrack_on_resource_error around choice instructions 2025-12-03 18:13:00 -08:00
Mark Thom
96a9dfcc1b Merge pull request #3185 from mthom/install_verify_attr_opt
Optimize `verify_attr` by removing the need to scan instructions
2025-12-03 19:10:46 -07:00
Mark Thom
6fdcd17f32 Merge pull request #3164 from Skgland/alloc_errors2
handle machine heap/stack allocation error
2025-12-03 19:10:22 -07:00
Mark Thom
652e824e59 Merge pull request #3147 from jjtolton/error-termination-flag
Add -t flag for custom toplevel (replaces --halt-on-error)
2025-12-02 00:24:46 -07:00
Mark Thom
f595eaee2e Merge pull request #3179 from triska/meetups
update meetups
2025-12-01 23:55:46 -07:00
Mark Thom
9ddeeed683 Merge pull request #3180 from triska/foldl_6
ADDED: foldl/6, addressing #2973
2025-12-01 23:55:34 -07:00
Mark Thom
b287f0aedb corrections to arithmetic expression compilation 2025-11-27 21:03:56 -08:00
Mark Thom
3b019fca1d corrections to dispatch loop 2025-11-27 21:03:22 -08:00
Mark Thom
4bd16b8a2e revise InstallVerifyAttrs to remove need for predicate scanning (#3175) 2025-11-25 23:17:51 -08:00
Mark Thom
f2e044e5b8 remove interms field from MachineState 2025-11-24 22:19:36 -08:00
Markus Triska
1cd55d8131 ADDED: foldl/6, addressing #2973 2025-11-23 11:13:57 +01:00
Markus Triska
14265118f1 update meetups 2025-11-23 10:29:31 +01:00
Markus Triska
b66824ffce use more recently available phrase_[to|from]_file/3 in example 2025-11-23 09:42:14 +01:00
Skgland
6ec8c9064f fix pstr_iter_tests test 2025-11-20 23:08:46 +01:00
Skgland
d4f2f7ba2b remove RESOURCE_ERROR_OFFSET_INIT
With it when using multiple Machine in one process only the first would store the pre-allocated error.
Instead Heap.resource_err_loc is now Option<NonZero<usize>> instead of usize using None for uninitialized.
The cell at index 0 should alredy be used by a runtime reserved interstitial cell that is allocated prior. So requiring the offset to be non zero should be fine.
2025-11-20 21:44:29 +01:00
Skgland
a05dc79505 fix unecessary parens in macro 2025-11-20 21:24:47 +01:00
Skgland
e90c813528 return an AllocError instead of panicing in Heap::with_cell_capacity 2025-11-20 21:13:17 +01:00
Skgland
516848e214 wrap resource_error into an error/2 functor and don't wrap it into a syntax_error functor 2025-11-20 01:22:18 +01:00
Skgland
6063783e9d propagate AllocErrro instead of unwrapping 2025-11-20 00:19:24 +01:00
Skgland
e9e97fe82a handle machine heap/stack allocation error 2025-11-19 22:17:24 +01:00
Skgland
5686b69013 stub a clippy config
with things we need to get rid of replace if we want to eliminate panics due to oom
2025-11-19 19:23:42 +01:00
Skgland
248b05c992 use as_bytes().to_vec() instead of bytes().collect()
copying a slice into a vec should be easier to optimize by the complier than collecting a byte iteration into a vec
2025-11-19 19:23:41 +01:00
Skgland
a283c8bdc2 remove some uncessary allocations/copies 2025-11-19 19:23:41 +01:00
Skgland
6b5ec3fab8 ignore incompatible msrv in compat module 2025-11-19 19:23:41 +01:00
Skgland
a0b4162d7a improve count_to_letter_code
- reserve the complete required length at the beginning to reduce reallocations
- use u8 instead of char so that we can re-use the allocation for the string
2025-11-19 19:23:41 +01:00
Skgland
119761ad10 ignore unused_parens warning
parens are generated by external macro
2025-11-18 21:42:00 +01:00
Skgland
bf8651db29 remove two unecessary clones 2025-11-18 21:29:22 +01:00
J.J. Tolton
11d0215963 Fix -t flag help text to reflect it accepts any goal
The -t flag is not limited to arity 0 predicates - it accepts any
goal including goals with arguments (e.g., -t 'halt(1)'). Updated
the help text to remove the incorrect "(arity 0 predicate)" constraint.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 10:55:49 -05:00
J.J. Tolton
16fd7a8d51 Move -t and -g flags before filenames in tests
Per maintainer feedback, switches must come before files to follow
the convention: switches before files are Scryer-specific, switches
after files are application-specific.
2025-11-09 13:04:59 -05:00
J.J. Tolton
5357ecf8e3 Remove redundant unit test file
The custom_toplevel.pl unit tests were trivial and didn't actually test
the functionality. All real testing is done via comprehensive CLI tests
in tests/scryer/cli/src_tests/custom_toplevel.md
2025-11-09 12:56:47 -05:00
J.J. Tolton
e7c288f8f0 Move g_caused_exception/2 dynamic directive to toplevel.pl
- Add dynamic directive in toplevel.pl with other module-level directives
- Update test files to reference it as '':g_caused_exception/2
- Remove redundant dynamic directives from test files
- All tests passing
2025-11-09 12:44:48 -05:00
J.J. Tolton
95abc4017e Update tests to use format/2 instead of write/1 2025-11-09 12:44:48 -05:00
J.J. Tolton
b91ce76052 Add comprehensive tests for g_caused_exception/2
Following TESTING_GUIDE.md, added tests at layers 2 and 3:

Layer 2 - Prolog Integration Tests (src/tests/custom_toplevel.pl):
- Test that g_caused_exception/2 is not asserted when no exception occurs
- Test that g_caused_exception/2 can be checked from custom toplevel
- Added check_for_exception/0 helper predicate for testing

Layer 3 - CLI Tests (tests/scryer/cli/src_tests/custom_toplevel.md):
- Test g_caused_exception/2 with exception thrown
- Test g_caused_exception/2 with no exception
- Test g_caused_exception/2 with error/2 terms
- Added test helper predicates in fixtures/toplevel_test_helper.pl

All tests pass successfully.

Co-Authored-By: J.J.'s Robot <jjtolton@gmail.com>
2025-11-09 12:44:48 -05:00
J.J. Tolton
617a551a56 Add g_caused_exception/2 for custom toplevel error handling
When a goal throws an exception during initialization (-g flag), the
system now asserts g_caused_exception(Goal, Exception) in the user
module. This allows custom toplevels (-t flag) to check if an error
occurred and handle it appropriately.

Example usage:
  scryer-prolog -g "throw(error)" -t check_error

Where check_error can be:
  :- dynamic(g_caused_exception/2).

  check_error :-
      (   g_caused_exception(_, E) ->
          write('Error: '), write(E), nl, halt(1)
      ;   halt(0)
      ).

This enables scripts to use custom toplevels for sophisticated error
handling and exit code logic.

Addresses: https://github.com/mthom/scryer-prolog/pull/3147#issuecomment-3503875719

Co-Authored-By: J.J.'s Robot <jjtolton@gmail.com>
2025-11-09 12:44:48 -05:00
J.J. Tolton
31247545c3 Fix bug where -t argument was processed as filename
Fixed issue where `scryer-prolog -t halt` would try to load "halt.pl"
as a file instead of just using halt as the custom toplevel.

The bug was caused by an extra clause `delegate_task([], []).` that
would return control to the calling context instead of continuing to
start_toplevel. This caused the argument processing in delegate_task
to continue and treat the already-consumed toplevel argument as a
filename.

Removing this clause ensures that delegate_task([], Goals0) always
proceeds to load initialization files and start the toplevel, fixing
the double-processing bug.

Co-Authored-By: J.J.'s Robot <jjtolton@gmail.com>
2025-11-09 12:44:48 -05:00
J.J. Tolton
15d112485c Add comprehensive tests for -t custom toplevel flag
- Create Prolog integration tests in src/tests/custom_toplevel.pl
- Add CLI test configuration in tests/scryer/cli/src_tests/custom_toplevel_tests.toml
- Tests verify:
  * -t halt terminates after initialization
  * Custom toplevels can be user-defined predicates
  * Toplevel receives control after initialization completes
  * Default behavior is REPL when no -t specified
- All tests pass successfully

Following TESTING_GUIDE.md three-layer testing approach:
- Layer 2: Prolog integration tests with test_framework
- Layer 3: CLI snapshot tests with .toml configuration

Co-Authored-By: J.J.'s Robot <noreply@example.com>
2025-11-09 12:44:48 -05:00
J.J. Tolton
768f7ce9a7 -t custom toplevel option
- Add -t FLAG to specify custom toplevel (arity 0 predicate)
- Default toplevel is 'repl' if -t is not specified
- Using `-t halt` achieves original goal of guaranteed termination
- Custom toplevels enable flexible exit strategies (e.g., server mode)
- Update help text to document -t flag

Examples:
  scryer-prolog -t halt program.pl    # Exits after execution
  scryer-prolog -t my_repl program.pl # Custom REPL
  scryer-prolog program.pl            # Default REPL

Co-Authored-By: J.J.'s Robot <noreply@example.com>
2025-11-09 12:44:37 -05:00
Mark Thom
e4d9692535 Merge pull request #3120 from aarroyoc/master
Delete test page from learning section
2025-10-20 19:46:26 -07:00
Mark Thom
f050e75d81 Merge pull request #3124 from bakaq/mark-partial-string-tail
Fix marking of partial string tail in iteration
2025-10-20 19:46:11 -07:00
Mark Thom
3dcb42af3c Merge pull request #3117 from aarroyoc/update-releases-0100
Update web to show 0.10.0 releases
2025-10-20 19:45:19 -07:00
bakaq
fdcd92db08 Fix marking of partial string tail in iteration 2025-10-14 16:59:50 -03:00
Adrián Arroyo Calle
c82f1ccde1 Delete test page from learning section 2025-10-11 15:24:07 +02:00
Adrián Arroyo Calle
2b85a307ae Update web to show 0.10.0 releases 2025-10-07 22:16:40 +02:00
Mark Thom
d7d5dfe059 Merge pull request #3108 from Skgland/issue-3073
emit a more appropriate error when passing the wrong argument count when constructing an ffi struct with the wrong argument count
2025-10-02 23:43:13 -07:00
Mark Thom
109796122e Merge pull request #3111 from Skgland/ffi-non-fixed-sized-integers
add support for non-fixed-width integers types for ffi
2025-10-02 23:42:59 -07:00
Mark Thom
476c1718f9 Merge pull request #3109 from Skgland/issue-3107
move CI  job release from retired ubuntu-20.04 image to ubuntu-22.04
2025-09-29 20:47:55 -07:00
Skgland
f8b9944f5c update documentation 2025-09-28 19:50:21 +02:00
Skgland
6e7dbfc75e consistently treat bool as i8 2025-09-28 18:29:20 +02:00
Skgland
1add5a9a75 add ffi types for non-fixed-sized integers 2025-09-28 18:17:35 +02:00
Skgland
777e6de9ee fix missing quotes 2025-09-28 13:43:09 +02:00
Skgland
2443af57d0 fixup quad 2025-09-27 23:14:14 +02:00
Skgland
ed03d5b1b4 move CI job release from retired ubuntu-20.04 image to ubuntu-22.04
the new image is the same as all other ubuntu jobs
2025-09-27 22:54:45 +02:00
Skgland
9376bc6369 fix mthom/scryer-prolog#3073 2025-09-27 22:48:05 +02:00
Skgland
2f43c3ff40 add regression test for issue 3073 2025-09-27 22:05:51 +02:00
115 changed files with 9023 additions and 7236 deletions

View File

@@ -1,4 +1,7 @@
name: 'Setup Rust'
description: |
Setup the rust toolchain and environment for the selected toolchain
inputs:
rust-version:
required: true
@@ -16,7 +19,7 @@ inputs:
runs:
using: "composite"
steps:
- uses: dtolnay/rust-toolchain@master
- uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # master
id: toolchain
with:
toolchain: ${{ inputs.rust-version }}
@@ -33,7 +36,15 @@ runs:
echo "CC=clang" >> $GITHUB_ENV
echo "PKG_CONFIG_SYSROOT_DIR=/" >> $GITHUB_ENV
- uses: actions/cache@v4
- name: Install s390x cross-compilation toolchain
if: ${{ matrix.target == 's390x-unknown-linux-gnu' }}
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y gcc-s390x-linux-gnu
echo "CARGO_TARGET_S390X_UNKNOWN_LINUX_GNU_LINKER=s390x-linux-gnu-gcc" >> $GITHUB_ENV
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: |
~/.cargo/bin/
@@ -46,7 +57,7 @@ runs:
# 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@v4.1.0
- uses: pyTooling/Actions/with-post-step@41aed3e559cbf69bfdb46abb25d7c535900ed5b6 # v7.7.0
with:
main: bash ./.github/actions/setup-rust/cleanup.sh
post: bash ./.github/actions/setup-rust/cleanup.sh

View File

@@ -4,7 +4,7 @@ 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(" ")'`
cargo clean --workspace
# remove directories in /target/ that are not named `debug` or `release`
before=`du -s target | awk '{print $1}'`

52
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
version: 2
updates:
# Enable version updates for cargo
- package-ecosystem: "cargo"
# Look for `Cargo.toml` and `Cargo.lock` files in the `root` directory
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: "7"
groups:
cargo-incompatible:
applies-to: version-updates
# TODO: use incompatible update-type once available
# see issue https://github.com/dependabot/dependabot-core/issues/9681
update-types:
- "major"
- "minor" # pre-1.0 dependencies
cargo-compatible:
applies-to: version-updates
# TODO: use compatible update-type once available
# see issue https://github.com/dependabot/dependabot-core/issues/9681
update-types:
- "patch"
ignore:
# ignore all cargo updates for now while dependabot does not respect msrv/rust-version
# see issue https://github.com/dependabot/dependabot-core/issues/5423
- dependency-name: "*"
# Enable version updates for Docker
- package-ecosystem: "docker"
# Look for a `Dockerfile` in the `root` directory
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: "7"
# Enable version updates for GitHub Actions
- package-ecosystem: "github-actions"
# Workflow files stored in the default location of `.github/workflows`
# You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: "7"
ignore:
# these actions doesn't have proper version tags
- dependency-name: "dtolnay/rust-toolchain"
- dependency-name: "logtalk-actions/setup-logtalk"

12
.github/workflows/check_features.sh vendored Executable file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
echo "Checking all feature at once"
cargo check -q --all-targets --all-features "$@"
features=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name = "scryer-prolog") | .features | keys | join(" ")')
for feature in ${features} ; do
echo "Checking feature ${feature} in isolation"
cargo check -q --all-targets --no-default-features --features=${feature} "$@"
done

View File

@@ -19,7 +19,7 @@ jobs:
style:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
@@ -39,7 +39,7 @@ jobs:
outputs:
msrv: ${{ steps.read-declared-msrv.outputs.msrv }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- id: read-declared-msrv
name: Read msrv from Cargo.toml rust_version field
run: echo "msrv=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name = "scryer-prolog") | ."rust_version"')" >> "$GITHUB_OUTPUT"
@@ -53,26 +53,30 @@ jobs:
matrix:
include:
# operating systems
- { os: windows-latest, rust-version: stable, target: 'x86_64-pc-windows-msvc', publish: true }
- { os: macos-latest, rust-version: stable, target: 'x86_64-apple-darwin', publish: true }
- { os: windows-latest, rust-version: stable, target: 'x86_64-pc-windows-msvc', publish: true, check-features: true }
- { os: macos-latest, rust-version: stable, target: 'x86_64-apple-darwin', publish: true, check-features: true }
# architectures
- { os: ubuntu-22.04, rust-version: stable, target: 'x86_64-unknown-linux-gnu', publish: true }
- { os: ubuntu-22.04, rust-version: stable, target: 'x86_64-unknown-linux-gnu', publish: true, check-features: true }
- { os: ubuntu-22.04, rust-version: stable, target: 'i686-unknown-linux-gnu', publish: true }
# FIXME(issue #2138): run wasm tests, failing to run since https://github.com/mthom/scryer-prolog/pull/2137 removed wasm-pack
- { os: ubuntu-22.04, rust-version: nightly, target: 'wasm32-unknown-unknown', publish: true, args: '--no-default-features' , test-args: '--no-run --no-default-features', use_swap: true }
# Cargo.toml rust-version
- { os: ubuntu-22.04, rust-version: "${{ needs.read-msrv.outputs.msrv }}" , target: 'x86_64-unknown-linux-gnu'}
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu'}
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu', miri: true, components: "miri"}
- { os: ubuntu-22.04, rust-version: "${{ needs.read-msrv.outputs.msrv }}" , target: 'x86_64-unknown-linux-gnu' }
- { os: ubuntu-22.04, rust-version: beta, target: 'x86_64-unknown-linux-gnu' }
- { os: ubuntu-22.04, rust-version: nightly, target: 'x86_64-unknown-linux-gnu', miri: true, components: "miri" }
# run miri for a big-endian target, with all features that are simple to get cross-compiled
- { os: ubuntu-22.04, rust-version: nightly, target: 's390x-unknown-linux-gnu', miri: true, components: "miri", args: '--no-default-features --features=all-simple-cross', test-args: '--no-run --no-default-features --features=all-simple-cross' }
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actionhippie/swap-space@v1
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actionhippie/swap-space@0cffa893f224708cfb6b011690d8ba819d69c10f # v1.1.0
if: matrix.use_swap
with:
size: 10G
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
@@ -84,12 +88,17 @@ jobs:
# Build and test.
- name: Build library
run: cargo build --all-targets --target ${{ matrix.target }} ${{ matrix.args }} --verbose
- name: Test
run: cargo test --target ${{ matrix.target }} ${{ matrix.test-args }} --all
- name: Check features
if: matrix.check-features
run: bash ./.github/workflows/check_features.sh --target ${{ matrix.target }}
- name: Check miri
if: matrix.miri
run: cargo miri test
run: cargo miri test --target ${{ matrix.target }} ${{ matrix.args }}
# On stable rust builds, build a binary and publish as a github actions
# artifact. These binaries could be useful for testing the pipeline but
@@ -110,7 +119,7 @@ jobs:
- name: Publish release binary artifact
if: matrix.publish
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
path: |
target/${{ matrix.target }}/release/scryer-prolog*
@@ -123,14 +132,14 @@ jobs:
needs: [build-test]
steps:
# Download prebuilt ubuntu binary from build-test job, setup logtalk
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
with:
name: scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu
- run: |
chmod +x release/scryer-prolog
echo "$PWD/release" >> "$GITHUB_PATH"
- name: Install Logtalk
uses: logtalk-actions/setup-logtalk@master
uses: logtalk-actions/setup-logtalk@4ea002fe3037199afcf1c6c91bf1f57de0f995e6 # master
with:
logtalk-version: "3.70.0"
logtalk-tool-dependencies: false
@@ -147,17 +156,17 @@ jobs:
|| echo "::warning ::logtalk compliance suite failed"
# -u "https://github.com/LogtalkDotOrg/logtalk3/tree/$LOGTALK_GIT_HASH/tests/prolog/" \
- name: Publish Logtalk test logs
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: logtalk-test-logs
path: '${{ env.LOGTALKUSER }}/tests/prolog/logtalk_tester_logs'
- name: Publish Logtalk test results artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: logtalk-test-results
path: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
- name: Publish Logtalk test summary
uses: EnricoMi/publish-unit-test-result-action/composite@master
uses: EnricoMi/publish-unit-test-result-action/composite@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2.23.0
with:
check_name: Logtalk test summary
files: '${{ env.LOGTALKUSER }}/tests/prolog/**/*.xml'
@@ -167,7 +176,7 @@ jobs:
report:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
@@ -180,18 +189,19 @@ jobs:
version=`yq -ptoml -oj -r '.package[] | select(.name == "iai-callgrind") | .version' Cargo.lock`
echo installing iai-callgrind "$version"
cargo install iai-callgrind-runner --force --version "$version"
sudo apt install valgrind -y
sudo apt-get update -y
sudo apt-get 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@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: cargo-test-results
path: cargo_test_results.xml
- name: Publish cargo test summary
uses: EnricoMi/publish-unit-test-result-action/composite@master
uses: EnricoMi/publish-unit-test-result-action/composite@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2.23.0
with:
check_name: Cargo test summary
files: cargo_test_results.xml
@@ -205,7 +215,7 @@ jobs:
- run: cargo bench --bench run_criterion -- --profile-time 60
- name: Publish benchmark results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: benchmark-results
path: |
@@ -215,11 +225,11 @@ jobs:
# Publish binaries when building for a tag
release:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
needs: [build-test]
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1
- name: Zip binaries for release
run: |
zip scryer-prolog_macos-latest.zip ./scryer-prolog_macos-latest_x86_64-apple-darwin/scryer-prolog
@@ -228,7 +238,7 @@ jobs:
zip scryer-prolog_windows-latest.zip ./scryer-prolog_windows-latest_x86_64-pc-windows-msvc/scryer-prolog.exe
zip scryer-prolog_wasm32.zip ./scryer-prolog_ubuntu-22.04_wasm32-unknown-unknown/scryer-prolog.wasm
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
with:
files: |
scryer-prolog_macos-latest.zip

View File

@@ -14,17 +14,17 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
# https://github.com/docker/setup-buildx-action
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
# Login against Docker registry
- name: Log into registry
# https://github.com/docker/login-action
uses: docker/login-action@v3
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -35,7 +35,7 @@ jobs:
- name: Extract Docker metadata
id: meta
# https://github.com/docker/metadata-action
uses: docker/metadata-action@v5
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/scryer-prolog
tags: |
@@ -47,7 +47,7 @@ jobs:
- name: Build and push Docker image
id: build-and-push
# https://github.com/docker/build-push-action
uses: docker/build-push-action@v6
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
# v4 adds SLSA Provenance attestation which is
# - unsupported by AWS Lambda
# - limited support by Google Cloud Run

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@ target/
.direnv/
__pycache__
*.pyc

1576
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
name = "scryer-prolog"
version = "0.10.0"
authors = ["Mark Thom <markjordanthom@gmail.com>"]
edition = "2021"
edition = "2024"
description = "A modern Prolog implementation written mostly in Rust."
readme = "README.md"
repository = "https://github.com/mthom/scryer-prolog"
@@ -11,28 +11,39 @@ keywords = ["prolog", "prolog-interpreter", "prolog-system"]
categories = ["command-line-utilities"]
build = "build/main.rs"
# Remember to check CI
rust-version = "1.85"
rust-version = "1.93.1"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["ffi", "repl", "hostname", "tls", "http", "crypto-full"]
default = ["all-simple-cross", "tls", "http"]
# activates all features that depend on no non pure-rust dependencies
#
# currently does not include
# ffi due to libffi
# tls, http due to openssl
# crypto-full due to ring
all-pure = ["repl", "hostname"]
# enables all features that are simple to get working for cross-compliation
# currently all but tls, http as those depend on openssl
all-simple-cross = ["all-pure", "ffi", "crypto-full"]
ffi = ["dep:libffi"]
repl = ["dep:crossterm", "dep:ctrlc", "dep:rustyline"]
hostname = ["dep:hostname"]
tls = ["dep:native-tls"]
http = ["dep:warp", "dep:reqwest"]
crypto-full = []
# crypto function that require non pure-rust dependencies
crypto-full = ["dep:ring"]
[lints.clippy]
collapsible_match = "allow"
[lints.rust]
unexpected_cfgs = { level = "deny", check-cfg = [
'cfg(rust_version, values("1.87.0"))',
] }
unexpected_cfgs = "deny"
function_casts_as_integer = "deny"
[build-dependencies]
indexmap = "2.3.0"
proc-macro2 = "1.0.86"
quote = "1.0.36"
strum = "0.26"
@@ -40,7 +51,6 @@ strum_macros = "0.26"
syn = { version = "2.0.72", features = ['full', 'visit', 'extra-traits'] }
to-syn-value = "0.1.1"
to-syn-value_derive = "0.1.1"
version_check = "0.9.5"
walkdir = "2"
[dependencies]
@@ -61,17 +71,18 @@ futures = "0.3"
fxhash = "0.2.1"
git-version = "0.3.9"
indexmap = "2.3.0"
lazy_static = "1.5.0"
lexical = "7.0.4"
libc = "0.2.155"
libloading = "0.8"
scryer-modular-bitfield = "0.11.4"
modular-bitfield = "0.13.1"
num-order = { version = "1.2.0" }
ordered-float = "5.0.0"
phf = { version = "0.11", features = ["macros"] }
puruspe = "0.4.1"
rand = "0.8.5"
ring = { version = "0.17.8", features = ["wasm32_unknown_unknown_js"] }
ring = { version = "0.17.8", features = [
"wasm32_unknown_unknown_js",
], optional = true }
ripemd = "0.1.3"
roxmltree = "0.20.0"
ryu = "1.0.18"
@@ -93,12 +104,12 @@ parking_lot = "0.12.4"
crossterm = { version = "0.28.1", optional = true }
ctrlc = { version = "3.4.4", optional = true }
hostname = { version = "0.4.0", optional = true }
libffi = { version = "4.0.0", optional = true }
libffi = { version = "5.1.0", optional = true }
native-tls = { version = "0.2.12", optional = true }
# the version requirement of reqwest is kept low for compatibility with old deno versions
# that pin reqwest to 0.11.20
reqwest = { version = "0.11.0", optional = true }
rustyline = { version = "14.0.0", optional = true }
rustyline = { version = "18.0.0", optional = true }
tokio = { version = "1.39.2", features = ["full"] }
warp = { version = "0.3.7", features = ["tls"], optional = true }

View File

@@ -55,14 +55,13 @@ the builtin Prolog modules and libraries in Scryer, check the documentation site
## Downloads
The latest version of Scryer Prolog is *0.9.4*. And it's already useful for lots of tasks.
The latest version of Scryer Prolog is *0.10.0*. And it's already useful for lots of tasks.
| Windows (64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_windows-latest_x86_64-pc-windows-msvc.zip) |
| macOS (Intel) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_macos-11_x86_64-apple-darwin.zip) |
| macOS (ARM) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog-macos-arm.zip) |
| Linux (Ubuntu 20.04, 64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_ubuntu-20.04_x86_64-unknown-linux-gnu.zip) |
| Linux (Ubuntu 22.04, 64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu.zip) |
| Linux (Ubuntu 22.04, 32 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-094/scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu.zip) |
| Windows (64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_windows-latest_x86_64-pc-windows-msvc.zip) |
| macOS (Intel) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_macos-11_x86_64-apple-darwin.zip) |
| macOS (ARM) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog--0.10.0.arm64_tahoe.bottle.tar.gz) |
| Linux (Ubuntu 22.04, 64 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_ubuntu-22.04_x86_64-unknown-linux-gnu.zip) |
| Linux (Ubuntu 22.04, 32 bits) | [Download](https://scryerprologrelease.blob.core.windows.net/release-0100/scryer-prolog_ubuntu-22.04_i686-unknown-linux-gnu.zip) |
Scryer Prolog can also be compiled from source, instructions are on the [GitHub README](https://github.com/mthom/scryer-prolog). It runs on Linux, macOS and Windows. Other operating systems may work but they're not regularly tested.

View File

@@ -73,9 +73,10 @@ Extend Scryer Prolog to include the following, among other features:
- [ ] Inlining all built-ins and system call instructions.
- [x] Greatly reducing the number of instructions used to compile disjunctives.
- [x] Storing short atoms to heap cells without writing them to the atom table.
- [ ] Configurable JIT/on-demand indexing over all arguments
(documented in "[Demand-Driven Indexing of Prolog Clauses](https://user.it.uu.se/~kostis/Papers/iclp07.pdf)"). (_in progress_)
- [ ] A compacting garbage collector satisfying the five properties of
"[Precise Garbage Collection in Prolog](https://www.complang.tuwien.ac.at/ulrich/papers/PDF/2008-ciclops.pdf)." (_in progress_)
- [ ] Mode declarations.
## Phase 3
@@ -103,8 +104,6 @@ Gustafson's book "[The End of Error](http://www.johngustafson.net/unums.html)."
3. Add concurrent tables to manage shared references to atoms and
strings.
4. Add some form of JIT predicate indexing.
## Installing Scryer Prolog
### Binaries
@@ -159,9 +158,9 @@ during the installation of the rust toolchain.
#### From Crates.io [![Crates.io Version](https://img.shields.io/crates/v/scryer-prolog)](https://crates.io/crates/scryer-prolog) ![Crates.io MSRV](https://img.shields.io/crates/msrv/scryer-prolog)
> [!NOTE]
> The lates crates.io release can be significantly behind the version available in the git repository
> The latest crates.io release can be significantly behind the version available in the git repository
> The crates.io badge in this sections title is a link to the crates.io page.
> The msrv badge in the section title referece to the minimum rust toolchain version required to compile the latest crates.io release
> The msrv badge in the section title references the minimum rust toolchain version required to compile the latest crates.io release
`scryer-prolog` is also release on crates.io and can be installed with
@@ -880,6 +879,8 @@ Successful existing applications of Scryer Prolog include:
trial&nbsp;design, described in [*An Executable Specification of
Oncology Dose-Escalation Protocols with&nbsp;Prolog*](https://arxiv.org/abs/2402.08334)
and culminating in&nbsp;[**DEDUCTION**](https://codeberg.org/dcnorris/DEDUCTION)
- the core tax engine for VAT reasoning and compliance of the
Belgian&nbsp;company [VATmiraal](https://vatmiraal.be/)
- semantic reasoning and queries in [AD4M](https://github.com/coasys/ad4m),
an agent-centric distributed application meta-ontology.
@@ -900,9 +901,11 @@ in&nbsp;person.
and [discussion](https://github.com/mthom/scryer-prolog/discussions/2160).
- [Scryer Prolog Meetup 2024](https://www.digitalaustria.gv.at/wissenswertes/events/scryerprologmeetup2024)
in Vienna, Austria. Its [announcement and discussion](https://github.com/mthom/scryer-prolog/discussions/2377).
- **Save the date:** The [Scryer Prolog Meetup 2025](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2025/)
will take place on Nov.&nbsp;13th and 14th&nbsp;2025 in Düsseldorf, Germany.
Its [announcement](https://github.com/mthom/scryer-prolog/discussions/2948).
- [Scryer Prolog Meetup 2025](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2025/)
in Düsseldorf, Germany. Its [announcement and discussion](https://github.com/mthom/scryer-prolog/discussions/2948).
- **Attend, present and represent:** The [Scryer Prolog Meetup 2026](https://www.digitalaustria.gv.at/wissenswertes/events/scryerprologmeetup2026.html)
will take place on Oct.&nbsp;24th and 25th&nbsp;2026 in Vienna, Austria.
Its [announcement](https://github.com/mthom/scryer-prolog/discussions/3327).
## Support and discussions

35
_typos.toml Normal file
View File

@@ -0,0 +1,35 @@
# config for https://github.com/crate-ci/typos
[default]
# example from https://github.com/crate-ci/typos/blob/master/docs/reference.md#example-configurations
extend-ignore-re = [
"(#|//)\\s*spellchecker:ignore-next-line\\n.*"
]
# correct word key to value
# can be used to ignore a typo by adding an entry <typo> = "<typo>"
[default.extend-words]
# correct identifier key to value
# can be used to ignore a typo by adding an entry <typo> = "<typo>"
[default.extend-identifiers]
interm = "interm"
IntermReg = "IntermReg"
[type.rust]
extend-glob = [ "*.rs" ]
[type.rust.extend-identifiers]
consts = "consts" # std::{f32,f64}::consts
[type.prolog]
extend-glob = ["*.pl"]
check-file = false
[type.stdout]
extend-glob = ["*.stdout"]
check-file = false
[files]
extend-exclude = ["lib_integration_test_commands.txt"]

View File

@@ -49,7 +49,7 @@ once.
## Adding benchmarks
This design is meant to suppoort defining lots of benchmarks.
This design is meant to support defining lots of benchmarks.
To add a new benchmark:
@@ -77,7 +77,7 @@ Some tips:
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.
output small so it doesn't just benchmark the output parsing code.
* DO test the output of the benchmark run, we don't want to count broken
benchmarks.

View File

@@ -1,5 +1,5 @@
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
#[cfg(not(target_os = "windows"))]

View File

@@ -70,7 +70,7 @@ impl PrologBenchmark {
}
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
pub fn setup(&self) -> impl FnMut() -> Vec<LeafAnswer> {
pub fn setup(&self) -> impl FnMut() -> Vec<LeafAnswer> + use<> {
let mut machine = self.make_machine();
let query = self.query;
move || {

File diff suppressed because it is too large Load Diff

View File

@@ -4,20 +4,22 @@ mod static_string_indexing;
use instructions_template::generate_instructions_rs;
use static_string_indexing::index_static_strings;
use std::collections::BTreeMap;
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::MAIN_SEPARATOR_STR;
use std::path::Path;
use std::path::PathBuf;
use std::path::MAIN_SEPARATOR_STR;
use std::process::{Command, Stdio};
fn find_prolog_files(path_prefix: &str, current_dir: &Path) -> Vec<(String, PathBuf)> {
let mut libraries = vec![];
// use a BTreeMap to get a stable order independent of fs enumeration order
let mut libraries = BTreeMap::new();
let entries = match current_dir.read_dir() {
Ok(entries) => entries,
Err(_) => return libraries,
Err(_) => return vec![],
};
for entry in entries.filter_map(Result::ok).map(|e| e.path()) {
@@ -34,19 +36,15 @@ fn find_prolog_files(path_prefix: &str, current_dir: &Path) -> Vec<(String, Path
let name = entry.file_stem().unwrap().to_str().unwrap();
let lib_name = format!("{path_prefix}{name}");
libraries.push((lib_name, entry));
libraries.insert(lib_name, entry);
}
}
}
libraries
libraries.into_iter().collect()
}
fn main() {
if version_check::is_min_version("1.87.0").unwrap_or(false) {
println!(r#"cargo:rustc-cfg=rust_version="1.87.0""#);
}
let has_rustfmt = Command::new("rustfmt")
.arg("--version")
.stdin(Stdio::inherit())

View File

@@ -1,18 +1,18 @@
use std::collections::BTreeSet;
use proc_macro2::TokenStream;
use syn::parse::*;
use syn::visit::*;
use syn::*;
use indexmap::IndexSet;
struct StaticStrVisitor {
static_strs: IndexSet<String>,
static_strs: BTreeSet<String>,
}
impl StaticStrVisitor {
fn new() -> Self {
Self {
static_strs: IndexSet::new(),
static_strs: BTreeSet::new(),
}
}
}
@@ -166,9 +166,9 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
visitor.visit_file(&syntax)
}
let mut static_str_keys = vec![];
let mut static_strs = vec![];
let mut static_str_indices = vec![];
let static_str_keys: Vec<_> = visitor.static_strs.iter().collect();
let mut static_strs = Vec::with_capacity(static_str_keys.len());
let mut static_str_indices = Vec::with_capacity(static_str_keys.len());
let indices: Vec<u64> = visitor
.static_strs
@@ -176,8 +176,6 @@ pub fn index_static_strings(instruction_rs_path: &std::path::Path) -> TokenStrea
.map(|string| {
let index = static_string_index(string, static_strs.len());
static_str_keys.push(string);
if index & 1 == 1 {
index
} else {

26
clippy.toml Normal file
View File

@@ -0,0 +1,26 @@
disallowed-macros = [
# https://rust-lang.github.io/rust-clippy/master/#disallowed_macros
# list of macros that may panic on allocation failure e.g.
# "std::vec",
]
disallowed-methods = [
# https://rust-lang.github.io/rust-clippy/master/#disallowed_method
# list of methods that may panic on allocation failure
# though not including things that can be used correctly by reversing ahead of time (i.e. std::vec::Vec::try_reserve + std::iter::Extend::extend ).
# "std::iter::Iter::collect",
# { path = "std::vec::Vec::with_capacity", replacement = "std::vec::Vec::new + std::vec::Vec::try_reserve" },
# { path = "std::string::String::with_capacity", replacement = "std::string::String::new + std::string::String::try_reserve" },
]
disallowed-types = [
# https://rust-lang.github.io/rust-clippy/master/#disallowed_types
# list of types that can't be used without risking a panic due to allocation failure
# { path = "std::collections::BTreeMap", reason = "unlike Vec and HashMap BTreeMap cannot reserve capacity ahead of time (i.e. try_reserve) making it unusable without risk of oom panic"},
]

View File

@@ -6,8 +6,7 @@ omit(["ops_and_meta_predicates.pl", "tabling"]).
learn_pages_source_folder("learn").
learn_pages_categories(["First steps", "Tutorials"]).
learn_pages([
page("Let's play Brisca", "Tutorials", "lets-play-brisca.dj"),
page("Test page", "First steps", "test-page.dj")
page("Let's play Brisca", "Tutorials", "lets-play-brisca.dj")
]).
copy_file("logo/scryer.png", "scryer.png").
copy_file("learn/Spanish_deck_Fournier.jpg", "learn/Spanish_deck_Fournier.jpg").

View File

@@ -23,7 +23,7 @@ The rules for knowing which players takes the round are the following:
First, we need to decide a representation of our cards. Coming from another languages we can think that a good representation might be a class or a struct, with two fields, one for the number and the other for the suite, but Prolog doesn't have objects. We can use a list with two elements. But lists are better when we're dealing with variable length data. We could also use a compound term. This is the right choice if our fields are fixed.
A compound term is defined by an atom, followed by the data itself enclosed by parenthesis and separated by comma. Like this: `card(oros, 4)`. Yes, very similar to predicates. In fact the only difference is how we use them, because they're the same. If we pass a compund term in the first level of a query, or inside a call/N, Prolog will treat it as code instead of data. This is one the the examples of Prolog being a homoiconic language.
A compound term is defined by an atom, followed by the data itself enclosed by parenthesis and separated by comma. Like this: `card(oros, 4)`. Yes, very similar to predicates. In fact the only difference is how we use them, because they're the same. If we pass a compound term in the first level of a query, or inside a call/N, Prolog will treat it as code instead of data. This is one the the examples of Prolog being a homoiconic language.
We can go further, Prolog is very flexible and we can define custom operators easily if we want. Those are also compound terms, but with a different syntax. There's an operator already defined that is very useful for us: the dash. We can just join two pieces of data with a dash, and they'll be together in the same structure. This is usually called "pair".
@@ -98,7 +98,7 @@ cards_score_(X) -->
cards_score_(X1).
```
In DCGs, to match an item of the sequence, we use brackets. We use braces to introduce normal Prolog code. Calling other DCGs (in this case, the same, as it's a recursive one), it's just calling it again. Notice in this code that we are doing the addition of X0 and X1 when we still don't know the value of X1. This would be an error in the traditional arithmethic system of Prolog, but it's valid with clpz. clpz allows us to have a more declarative arithmethic, at least with integers.
In DCGs, to match an item of the sequence, we use brackets. We use braces to introduce normal Prolog code. Calling other DCGs (in this case, the same, as it's a recursive one), it's just calling it again. Notice in this code that we are doing the addition of X0 and X1 when we still don't know the value of X1. This would be an error in the traditional arithmetic system of Prolog, but it's valid with clpz. clpz allows us to have a more declarative arithmetic, at least with integers.
Now we can try this code using `phrase/2` which is needed to jump to a DCG.
@@ -276,7 +276,7 @@ Let's ask ourselves what is a procedure. It's a sequence. And we have already se
The basic idea however is having _explicit_ states. The predicates that we're going to write will take an state (a view of the world at a certain point) and will give us the next state.
Let's define the state first. In a game of Brisca we have players. Each player has the three cards he can choose to put (less if we're running out of cards) and the cards he has got from winning rounds. Aditionally we have a stock, a trump suite and the order to play, which is usually from the player who won the last round and going to the right. We could store the data in a list, with different compound terms:
Let's define the state first. In a game of Brisca we have players. Each player has the three cards he can choose to put (less if we're running out of cards) and the cards he has got from winning rounds. Additionally we have a stock, a trump suite and the order to play, which is usually from the player who won the last round and going to the right. We could store the data in a list, with different compound terms:
```
[players([player(Name, PlayableCards, WonCards), player(Name, PlayableCards, WonCards), ...]), stock(Cards), trump(Trump)]

View File

@@ -1,3 +0,0 @@
# Test page
This is a page about Scryer Prolog

View File

@@ -1,7 +1,6 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
#[cfg(feature = "http")]
use crate::http::{HttpListener, HttpResponse};
use crate::machine::heap::AllocError;
use crate::machine::loader::LiveLoadState;
use crate::machine::streams::*;
use crate::offset_table::*;
@@ -14,16 +13,16 @@ use ordered_float::OrderedFloat;
use std::fmt;
use std::fmt::Debug;
use std::hash::{Hash, Hasher};
use std::io::PipeReader;
use std::io::PipeWriter;
use std::mem;
use std::mem::ManuallyDrop;
use std::net::TcpListener;
use std::ops::{Deref, DerefMut};
use std::process::Child;
use std::ptr;
use std::ptr::addr_of_mut;
use std::ptr::NonNull;
use crate::machine::streams::{PipeReader, PipeWriter};
use std::ptr::addr_of_mut;
macro_rules! arena_alloc {
($e:expr, $arena:expr) => {{
@@ -33,9 +32,7 @@ macro_rules! arena_alloc {
}
macro_rules! float_alloc {
($e:expr, $arena:expr) => {{
$arena.f64_tbl.build_with(OrderedFloat($e))
}};
($e:expr, $arena:expr) => {{ $arena.f64_tbl.build_with(OrderedFloat($e)) }};
}
pub fn header_offset_from_payload<T: ?Sized + ArenaAllocated>() -> usize
@@ -50,7 +47,7 @@ where
payload_offset - header_offset
}
#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq)]
#[derive(Specifier, Copy, Clone, Debug, PartialEq)]
#[bits = 7]
pub enum ArenaHeaderTag {
Integer = 0b10,
@@ -283,9 +280,14 @@ pub trait ArenaAllocated {
where
Self::Payload: Sized,
{
TypedArenaPtr(NonNull::new_unchecked(
ptr.payload_offset().cast_mut().cast::<Self::Payload>(),
))
unsafe {
TypedArenaPtr(NonNull::new_unchecked(
ptr.get_ptr()
.byte_add(Self::header_offset_from_payload())
.cast_mut()
.cast::<Self::Payload>(),
))
}
}
#[allow(clippy::missing_safety_doc)]
@@ -443,7 +445,7 @@ impl<T: ?Sized + ArenaAllocated> TypedAllocSlab<T> {
pub fn to_untyped(self: Box<Self>) -> (TypedArenaPtr<T>, UntypedArenaSlab) {
let raw_box = Box::into_raw(self);
// safety: the pointer from Box::into_raw fullfills addr_of_mut's saftey requirements
// safety: the pointer from Box::into_raw fulfills addr_of_mut's safety requirements
let payload_ptr = unsafe { addr_of_mut!((*raw_box).payload) };
(
@@ -485,12 +487,12 @@ unsafe impl Sync for Arena {}
#[allow(clippy::new_without_default)]
impl Arena {
#[inline]
pub fn new() -> Self {
Arena {
pub fn new() -> Result<Self, AllocError> {
Ok(Arena {
base: None,
f64_tbl: F64Table::new(),
code_index_tbl: CodeIndexTable::new(),
}
f64_tbl: F64Table::new()?,
code_index_tbl: CodeIndexTable::new()?,
})
}
}
@@ -500,82 +502,83 @@ unsafe fn drop_slab_in_place(value: NonNull<AllocSlab>, tag: ArenaHeaderTag) {
<$payload as ArenaAllocated>::dealloc($value.cast::<TypedAllocSlab<$payload>>())
};
}
match tag {
ArenaHeaderTag::Integer => {
drop_typed_slab_in_place!(Integer, value);
}
ArenaHeaderTag::Rational => {
drop_typed_slab_in_place!(Rational, value);
}
ArenaHeaderTag::InputFileStream => {
drop_typed_slab_in_place!(InputFileStream, value);
}
ArenaHeaderTag::OutputFileStream => {
drop_typed_slab_in_place!(OutputFileStream, value);
}
ArenaHeaderTag::NamedTcpStream => {
drop_typed_slab_in_place!(NamedTcpStream, value);
}
ArenaHeaderTag::NamedTlsStream => {
#[cfg(feature = "tls")]
drop_typed_slab_in_place!(NamedTlsStream, value);
}
ArenaHeaderTag::HttpReadStream => {
#[cfg(feature = "http")]
drop_typed_slab_in_place!(HttpReadStream, value);
}
ArenaHeaderTag::HttpWriteStream => {
#[cfg(feature = "http")]
drop_typed_slab_in_place!(HttpWriteStream, value);
}
ArenaHeaderTag::ReadlineStream => {
drop_typed_slab_in_place!(ReadlineStream, value);
}
ArenaHeaderTag::StaticStringStream => {
drop_typed_slab_in_place!(StaticStringStream, value);
}
ArenaHeaderTag::ByteStream => {
drop_typed_slab_in_place!(ByteStream, value);
}
ArenaHeaderTag::CallbackStream => {
drop_typed_slab_in_place!(CallbackStream, value);
}
ArenaHeaderTag::InputChannelStream => {
drop_typed_slab_in_place!(InputChannelStream, value);
}
ArenaHeaderTag::LiveLoadState | ArenaHeaderTag::InactiveLoadState => {
drop_typed_slab_in_place!(LiveLoadState, value);
}
ArenaHeaderTag::Dropped => {}
ArenaHeaderTag::TcpListener => {
drop_typed_slab_in_place!(TcpListener, value);
}
ArenaHeaderTag::HttpListener => {
#[cfg(feature = "http")]
drop_typed_slab_in_place!(HttpListener, value);
}
ArenaHeaderTag::HttpResponse => {
#[cfg(feature = "http")]
drop_typed_slab_in_place!(HttpResponse, value);
}
ArenaHeaderTag::StandardOutputStream => {
drop_typed_slab_in_place!(StandardOutputStream, value);
}
ArenaHeaderTag::StandardErrorStream => {
drop_typed_slab_in_place!(StandardErrorStream, value);
}
ArenaHeaderTag::PipeReader => {
drop_typed_slab_in_place!(PipeReader, value);
}
ArenaHeaderTag::PipeWriter => {
drop_typed_slab_in_place!(PipeWriter, value);
}
ArenaHeaderTag::ChildProcess => {
drop_typed_slab_in_place!(Child, value);
}
ArenaHeaderTag::NullStream => {
unreachable!("NullStream is never arena allocated!");
unsafe {
match tag {
ArenaHeaderTag::Integer => {
drop_typed_slab_in_place!(Integer, value);
}
ArenaHeaderTag::Rational => {
drop_typed_slab_in_place!(Rational, value);
}
ArenaHeaderTag::InputFileStream => {
drop_typed_slab_in_place!(InputFileStream, value);
}
ArenaHeaderTag::OutputFileStream => {
drop_typed_slab_in_place!(OutputFileStream, value);
}
ArenaHeaderTag::NamedTcpStream => {
drop_typed_slab_in_place!(NamedTcpStream, value);
}
ArenaHeaderTag::NamedTlsStream => {
#[cfg(feature = "tls")]
drop_typed_slab_in_place!(NamedTlsStream, value);
}
ArenaHeaderTag::HttpReadStream => {
#[cfg(feature = "http")]
drop_typed_slab_in_place!(HttpReadStream, value);
}
ArenaHeaderTag::HttpWriteStream => {
#[cfg(feature = "http")]
drop_typed_slab_in_place!(HttpWriteStream, value);
}
ArenaHeaderTag::ReadlineStream => {
drop_typed_slab_in_place!(ReadlineStream, value);
}
ArenaHeaderTag::StaticStringStream => {
drop_typed_slab_in_place!(StaticStringStream, value);
}
ArenaHeaderTag::ByteStream => {
drop_typed_slab_in_place!(ByteStream, value);
}
ArenaHeaderTag::CallbackStream => {
drop_typed_slab_in_place!(CallbackStream, value);
}
ArenaHeaderTag::InputChannelStream => {
drop_typed_slab_in_place!(InputChannelStream, value);
}
ArenaHeaderTag::LiveLoadState | ArenaHeaderTag::InactiveLoadState => {
drop_typed_slab_in_place!(LiveLoadState, value);
}
ArenaHeaderTag::Dropped => {}
ArenaHeaderTag::TcpListener => {
drop_typed_slab_in_place!(TcpListener, value);
}
ArenaHeaderTag::HttpListener => {
#[cfg(feature = "http")]
drop_typed_slab_in_place!(HttpListener, value);
}
ArenaHeaderTag::HttpResponse => {
#[cfg(feature = "http")]
drop_typed_slab_in_place!(HttpResponse, value);
}
ArenaHeaderTag::StandardOutputStream => {
drop_typed_slab_in_place!(StandardOutputStream, value);
}
ArenaHeaderTag::StandardErrorStream => {
drop_typed_slab_in_place!(StandardErrorStream, value);
}
ArenaHeaderTag::PipeReader => {
drop_typed_slab_in_place!(PipeReader, value);
}
ArenaHeaderTag::PipeWriter => {
drop_typed_slab_in_place!(PipeWriter, value);
}
ArenaHeaderTag::ChildProcess => {
drop_typed_slab_in_place!(Child, value);
}
ArenaHeaderTag::NullStream => {
unreachable!("NullStream is never arena allocated!");
}
}
}
}
@@ -634,16 +637,22 @@ mod tests {
#[test]
fn heap_cell_value_const_cast() {
let mut wam = MockWAM::new();
#[cfg(target_pointer_width = "32")]
let const_value = HeapCellValue::from(ConsPtr::build_with(
std::ptr::without_provenance(0x0000_0431),
ConsPtrMaskTag::Cons,
));
assert_eq!(ConsPtr::NICHE_SHIFT, 0);
#[cfg(not(target_pointer_width = "32"))]
assert_eq!(ConsPtr::NICHE_SHIFT, 3);
#[cfg(target_pointer_width = "32")]
let dummy_ptr: *const ArenaHeader = std::ptr::without_provenance(0x0000_0438);
#[cfg(target_pointer_width = "64")]
let const_value = HeapCellValue::from(ConsPtr::build_with(
std::ptr::without_provenance(0x0000_5555_ff00_0431),
ConsPtrMaskTag::Cons,
));
let dummy_ptr: *const ArenaHeader = std::ptr::without_provenance(0x0000_5555_ff00_0438);
assert!(dummy_ptr.is_aligned());
let const_value = HeapCellValue::from_arena_header_ptr(dummy_ptr);
match const_value.to_untyped_arena_ptr() {
Some(arena_ptr) => {
@@ -658,8 +667,7 @@ mod tests {
}
let stream = Stream::from_static_string("test", &mut wam.machine_st.arena);
let stream_cell =
HeapCellValue::from(ConsPtr::build_with(stream.as_ptr(), ConsPtrMaskTag::Cons));
let stream_cell = HeapCellValue::from_arena_header_ptr(stream.as_ptr());
match stream_cell.to_untyped_arena_ptr() {
Some(arena_ptr) => {
@@ -728,7 +736,7 @@ mod tests {
Some(untyped_arena_ptr) => {
assert_eq!(
Some(big_rat_ptr.header_ptr()),
Some(untyped_arena_ptr.into()),
Some(untyped_arena_ptr.get_ptr()),
);
}
None => {

View File

@@ -1,5 +1,3 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
use crate::allocator::*;
use crate::arena::*;
use crate::atom_table::*;
@@ -22,7 +20,7 @@ use num_order::NumOrd;
use ordered_float::{Float, OrderedFloat};
use std::cell::Cell;
use std::cmp::{max, min, Ordering};
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::f64;
use std::num::FpCategory;
@@ -31,21 +29,11 @@ use std::vec::Vec;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ArithmeticTerm {
IntermReg(usize),
Reg(RegType),
Interm(usize),
Number(Number),
}
impl ArithmeticTerm {
pub(crate) fn interm_or(&self, interm: usize) -> usize {
if let &ArithmeticTerm::Interm(interm) = self {
interm
} else {
interm
}
}
}
impl Default for ArithmeticTerm {
fn default() -> Self {
ArithmeticTerm::Number(Number::default())
@@ -57,7 +45,7 @@ pub(crate) struct ArithInstructionIterator<'a> {
state_stack: Vec<TermIterState<'a>>,
}
pub(crate) type ArithCont = (CodeDeque, Option<ArithmeticTerm>);
pub(crate) type ArithCont = (CodeDeque, ArithmeticTerm);
impl<'a> ArithInstructionIterator<'a> {
fn push_subterm(&mut self, lvl: Level, term: &'a Term) {
@@ -76,7 +64,7 @@ impl<'a> ArithInstructionIterator<'a> {
return Err(ArithmeticError::NonEvaluableFunctor(
Literal::Atom(atom!(".")),
2,
))
));
}
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Shallow, cell, var_ptr.clone()),
};
@@ -90,7 +78,7 @@ impl<'a> ArithInstructionIterator<'a> {
#[derive(Debug)]
pub(crate) enum ArithTermRef<'a> {
Literal(Literal),
Op(Atom, usize), // name, arity.
Op(Level, &'a Cell<RegType>, Atom, usize), // name, arity.
Var(Level, &'a Cell<VarReg>, VarPtr),
}
@@ -105,7 +93,7 @@ impl<'a> Iterator for ArithInstructionIterator<'a> {
let arity = subterms.len();
if child_num == arity {
return Some(Ok(ArithTermRef::Op(name, arity)));
return Some(Ok(ArithTermRef::Op(lvl, cell, name, arity)));
} else {
self.state_stack.push(TermIterState::Clause(
lvl,
@@ -139,7 +127,6 @@ impl<'a> Iterator for ArithInstructionIterator<'a> {
pub(crate) struct ArithmeticEvaluator<'a> {
marker: &'a mut DebrayAllocator,
interm: Vec<ArithmeticTerm>,
interm_c: usize,
}
pub(crate) trait ArithmeticTermIter<'a> {
@@ -180,11 +167,10 @@ fn push_literal(interm: &mut Vec<ArithmeticTerm>, c: &Literal) -> Result<(), Ari
}
impl<'a> ArithmeticEvaluator<'a> {
pub(crate) fn new(marker: &'a mut DebrayAllocator, target_int: usize) -> Self {
pub(crate) fn new(marker: &'a mut DebrayAllocator) -> Self {
ArithmeticEvaluator {
marker,
interm: Vec::new(),
interm_c: target_int,
}
}
@@ -252,56 +238,37 @@ impl<'a> ArithmeticEvaluator<'a> {
}
}
fn incr_interm(&mut self) -> usize {
let temp = self.interm_c;
self.interm.push(ArithmeticTerm::Interm(temp));
self.interm_c += 1;
temp
fn try_add_to_free_list(&mut self, a1: ArithmeticTerm) {
if let ArithmeticTerm::IntermReg(t) = a1 {
self.marker.add_reg_to_free_list(RegType::Temp(t));
}
}
fn instr_from_clause(
&mut self,
name: Atom,
arity: usize,
arg: usize,
) -> Result<Instruction, ArithmeticError> {
match arity {
1 => {
let a1 = self.interm.pop().unwrap();
let ninterm = if a1.interm_or(0) == 0 {
self.incr_interm()
} else {
self.interm.push(a1);
a1.interm_or(0)
};
self.interm.push(ArithmeticTerm::IntermReg(arg));
self.try_add_to_free_list(a1);
self.get_unary_instr(name, a1, ninterm)
self.get_unary_instr(name, a1, arg)
}
2 => {
let a2 = self.interm.pop().unwrap();
let a1 = self.interm.pop().unwrap();
let min_interm = min(a1.interm_or(0), a2.interm_or(0));
self.interm.push(ArithmeticTerm::IntermReg(arg));
let ninterm = if min_interm == 0 {
let max_interm = max(a1.interm_or(0), a2.interm_or(0));
self.try_add_to_free_list(a1);
self.try_add_to_free_list(a2);
if max_interm == 0 {
self.incr_interm()
} else {
self.interm.push(ArithmeticTerm::Interm(max_interm));
self.interm_c = max_interm + 1;
max_interm
}
} else {
self.interm.push(ArithmeticTerm::Interm(min_interm));
self.interm_c = min_interm + 1;
min_interm
};
self.get_binary_instr(name, a1, a2, ninterm)
self.get_binary_instr(name, a1, a2, arg)
}
_ => Err(ArithmeticError::NonEvaluableFunctor(
Literal::Atom(name),
@@ -346,13 +313,20 @@ impl<'a> ArithmeticEvaluator<'a> {
self.interm.push(ArithmeticTerm::Reg(r));
}
ArithTermRef::Op(name, arity) => {
code.push_back(self.instr_from_clause(name, arity)?);
ArithTermRef::Op(lvl, cell, name, arity) => {
self.marker
.mark_non_var::<QueryInstruction>(lvl, term_loc, cell, &mut code);
if let RegType::Temp(t) = cell.get() {
code.push_back(self.instr_from_clause(name, arity, t)?);
} else {
unreachable!()
}
}
}
}
Ok((code, self.interm.pop()))
Ok((code, self.interm.pop().unwrap()))
}
}
@@ -387,7 +361,7 @@ pub(crate) fn rnd_i(n: &'_ Number, arena: &mut Arena) -> Result<Number, EvalErro
)))
}
}
Number::Rational(ref r) => {
Number::Rational(r) => {
let floor = r.floor();
if let Ok(value) = Fixnum::build_with_checked(&floor) {
@@ -410,9 +384,9 @@ impl From<Fixnum> for Integer {
pub(crate) fn rnd_f(n: &Number) -> f64 {
match n {
&Number::Fixnum(n) => n.get_num() as f64,
Number::Integer(ref n) => n.to_f64().value(),
Number::Integer(n) => n.to_f64().value(),
&Number::Float(OrderedFloat(f)) => f,
Number::Rational(ref r) => r.to_f64().value(),
Number::Rational(r) => r.to_f64().value(),
}
}
@@ -540,33 +514,23 @@ impl PartialEq for Number {
fn eq(&self, rhs: &Self) -> bool {
match (self, rhs) {
(&Number::Fixnum(n1), &Number::Fixnum(n2)) => n1.eq(&n2),
(&Number::Fixnum(n1), Number::Integer(ref n2)) => n1.get_num().num_eq(&**n2),
(Number::Integer(ref n1), &Number::Fixnum(n2)) => n1.num_eq(&n2.get_num()),
(&Number::Fixnum(n1), Number::Rational(ref n2)) => {
(&Number::Fixnum(n1), Number::Integer(n2)) => n1.get_num().num_eq(&**n2),
(Number::Integer(n1), &Number::Fixnum(n2)) => n1.num_eq(&n2.get_num()),
(&Number::Fixnum(n1), Number::Rational(n2)) => {
Integer::from(n1.get_num()).num_eq(&**n2)
}
(Number::Rational(ref n1), &Number::Fixnum(n2)) => {
n1.num_eq(&Integer::from(n2.get_num()))
}
(Number::Rational(n1), &Number::Fixnum(n2)) => n1.num_eq(&Integer::from(n2.get_num())),
(&Number::Fixnum(n1), &Number::Float(n2)) => OrderedFloat(n1.get_num() as f64).eq(&n2),
(&Number::Float(n1), &Number::Fixnum(n2)) => n1.eq(&OrderedFloat(n2.get_num() as f64)),
(Number::Integer(ref n1), Number::Integer(ref n2)) => n1.eq(n2),
(Number::Integer(ref n1), Number::Float(n2)) => {
OrderedFloat(n1.to_f64().value()).eq(n2)
}
(&Number::Float(n1), Number::Integer(ref n2)) => {
n1.eq(&OrderedFloat(n2.to_f64().value()))
}
(Number::Integer(ref n1), Number::Rational(ref n2)) => n1.num_eq(&**n2),
(Number::Rational(ref n1), Number::Integer(ref n2)) => n1.num_eq(&**n2),
(Number::Rational(ref n1), &Number::Float(n2)) => {
OrderedFloat(n1.to_f64().value()).eq(&n2)
}
(&Number::Float(n1), Number::Rational(ref n2)) => {
n1.eq(&OrderedFloat(n2.to_f64().value()))
}
(Number::Integer(n1), Number::Integer(n2)) => n1.eq(n2),
(Number::Integer(n1), Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).eq(n2),
(&Number::Float(n1), Number::Integer(n2)) => n1.eq(&OrderedFloat(n2.to_f64().value())),
(Number::Integer(n1), Number::Rational(n2)) => n1.num_eq(&**n2),
(Number::Rational(n1), Number::Integer(n2)) => n1.num_eq(&**n2),
(Number::Rational(n1), &Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).eq(&n2),
(&Number::Float(n1), Number::Rational(n2)) => n1.eq(&OrderedFloat(n2.to_f64().value())),
(&Number::Float(f1), &Number::Float(f2)) => f1.eq(&f2),
(Number::Rational(ref r1), Number::Rational(ref r2)) => r1.eq(r2),
(Number::Rational(r1), Number::Rational(r2)) => r1.eq(r2),
}
}
}
@@ -633,9 +597,7 @@ impl Ord for Number {
(&Number::Float(n1), &Number::Fixnum(n2)) => n1.cmp(&OrderedFloat(n2.get_num() as f64)),
(&Number::Integer(n1), &Number::Integer(n2)) => (*n1).cmp(&*n2),
(&Number::Integer(n1), Number::Float(n2)) => OrderedFloat(n1.to_f64().value()).cmp(n2),
(&Number::Float(n1), Number::Integer(ref n2)) => {
n1.cmp(&OrderedFloat(n2.to_f64().value()))
}
(&Number::Float(n1), Number::Integer(n2)) => n1.cmp(&OrderedFloat(n2.to_f64().value())),
(&Number::Integer(n1), &Number::Rational(n2)) => {
(*n1).num_partial_cmp(&*n2).unwrap_or(Ordering::Less)
}

View File

@@ -1,5 +1,4 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
use crate::machine::heap::AllocError;
use crate::parser::ast::MAX_ARITY;
use crate::raw_block::*;
use crate::types::*;
@@ -15,13 +14,13 @@ use std::sync::Mutex;
use std::sync::RwLock;
use std::sync::Weak;
use arcu::Rcu;
use arcu::atomic::Arcu;
use arcu::epoch_counters::GlobalEpochCounterPool;
use arcu::rcu_ref::RcuRef;
use arcu::Rcu;
use indexmap::IndexSet;
use scryer_modular_bitfield::prelude::*;
use modular_bitfield::prelude::*;
#[bitfield]
#[repr(u64)]
@@ -307,8 +306,7 @@ impl Atom {
AtomTableRef::try_map(atom_table.inner.read(), |buf| unsafe {
let ptr = buf
.block
.base
.add(self.flat_index() as usize - STRINGS.len());
.get_unchecked(self.flat_index() as usize - STRINGS.len());
// TODO use std::ptr::from_raw_parts instead when feature ptr_metadata is stable rust-lang/rust#81513
let atom_data = &*(std::ptr::slice_from_raw_parts(ptr, 0) as *const AtomData);
let len = atom_data.header.len();
@@ -346,11 +344,7 @@ impl Atom {
let c1 = it.next();
let c2 = it.next();
if c2.is_none() {
c1
} else {
None
}
if c2.is_none() { c1 } else { None }
}
#[inline]
@@ -390,9 +384,11 @@ impl Atom {
}
unsafe fn write_to_ptr(string: &str, ptr: *mut u8) {
ptr::write(ptr as *mut _, AtomHeader::build_with(string.len() as u64));
let str_ptr = ptr.add(mem::size_of::<AtomHeader>());
ptr::copy_nonoverlapping(string.as_ptr(), str_ptr, string.len());
unsafe {
ptr::write(ptr as *mut _, AtomHeader::build_with(string.len() as u64));
let str_ptr = ptr.add(mem::size_of::<AtomHeader>());
ptr::copy_nonoverlapping(string.as_ptr(), str_ptr, string.len());
}
}
impl PartialOrd for Atom {
@@ -436,21 +432,21 @@ impl InnerAtomTable {
impl AtomTable {
#[inline]
pub fn new() -> Arc<Self> {
pub fn new() -> Result<Arc<Self>, AllocError> {
let upgraded = global_atom_table().read().unwrap().upgrade();
// don't inline upgraded, otherwise temporary will be dropped too late in case of None
if let Some(atom_table) = upgraded {
atom_table
Ok(atom_table)
} else {
let mut guard = global_atom_table().write().unwrap();
// try to upgrade again in case we lost the race on the write lock
if let Some(atom_table) = guard.upgrade() {
atom_table
Ok(atom_table)
} else {
let atom_table = Arc::new(Self {
inner: Arcu::new(
InnerAtomTable {
block: RawBlock::new(),
block: RawBlock::new()?,
table: Arcu::new(IndexSet::new(), GlobalEpochCounterPool),
},
GlobalEpochCounterPool,
@@ -458,11 +454,16 @@ impl AtomTable {
update: Mutex::new(()),
});
*guard = Arc::downgrade(&atom_table);
atom_table
Ok(atom_table)
}
}
}
#[inline]
pub fn retrieve() -> Arc<Self> {
global_atom_table().read().unwrap().upgrade().unwrap()
}
pub fn active_table(&self) -> RcuRef<IndexSet<Atom>, IndexSet<Atom>> {
self.inner.read().table.read()
}
@@ -516,12 +517,13 @@ impl AtomTable {
}
};
let ptr_base = block_epoch.block.base.addr();
// SAFETY: `len_ptr` was obtained from `block_epoch.block.alloc()`
let len_offset = block_epoch.block.get_offset(len_ptr);
write_to_ptr(string, len_ptr);
let atom = AtomCell::new()
.with_name((STRINGS.len() + len_ptr.addr() - ptr_base) as u64)
.with_name((STRINGS.len() + len_offset) as u64)
.with_arity(0)
.with_f(false)
.with_m(false)
@@ -533,7 +535,7 @@ impl AtomTable {
table.insert(atom);
block_epoch.table.replace(table);
// expicit drop to ensure we don't accidentally drop it early
// explicit drop to ensure we don't accidentally drop it early
drop(update_guard);
return atom;

View File

@@ -145,7 +145,7 @@ impl BranchCodeStack {
settings.trust_me()
});
combined_code.extend(code.into_iter());
combined_code.extend(code);
}
}
@@ -345,10 +345,10 @@ impl<'a> AddToFreeList<'a, QueryInstruction> for CodeGenerator {
fn structure_cell(term: &Term) -> Option<&Cell<RegType>> {
match term {
&Term::Cons(ref cell, ..)
| &Term::Clause(ref cell, ..)
| Term::PartialString(ref cell, ..)
| Term::CompleteString(ref cell, ..) => Some(cell),
Term::Cons(cell, ..)
| Term::Clause(cell, ..)
| Term::PartialString(cell, ..)
| Term::CompleteString(cell, ..) => Some(cell),
_ => None,
}
}
@@ -401,18 +401,18 @@ impl CodeGenerator {
&Term::AnonVar => {
Self::add_or_increment_void_instr::<Target>(target);
}
&Term::Cons(ref cell, ..)
| &Term::Clause(ref cell, ..)
| Term::PartialString(ref cell, ..)
| Term::CompleteString(ref cell, ..) => {
Term::Cons(cell, ..)
| Term::Clause(cell, ..)
| Term::PartialString(cell, ..)
| Term::CompleteString(cell, ..) => {
self.marker
.mark_non_var::<Target>(Level::Deep, term_loc, cell, target);
target.push_back(Target::clause_arg_to_instr(cell.get()));
}
Term::Literal(_, ref constant) => {
Term::Literal(_, constant) => {
target.push_back(Target::constant_subterm(*constant));
}
Term::Var(ref cell, ref var_ptr) => {
Term::Var(cell, var_ptr) => {
self.deep_var_instr::<Target>(
cell,
var_ptr.to_var_num().unwrap(),
@@ -560,27 +560,19 @@ impl CodeGenerator {
&InlinedClauseType::CompareNumber(mut cmp) => {
self.marker.reset_arg(2);
let (mut lcode, at_1) = self.compile_arith_expr(&terms[0], 1, term_loc, 1)?;
if !matches!(terms[0], Term::Var(..)) {
self.marker.advance_arg();
}
let (mut rcode, at_2) = self.compile_arith_expr(&terms[1], 2, term_loc, 2)?;
let (mut lcode, at_1) = self.compile_arith_expr(&terms[0], term_loc, 1)?;
let (mut rcode, at_2) = self.compile_arith_expr(&terms[1], term_loc, 2)?;
code.append(&mut lcode);
code.append(&mut rcode);
let at_1 = at_1.unwrap_or(interm!(1));
let at_2 = at_2.unwrap_or(interm!(2));
compare_number_instr!(cmp, at_1, at_2)
}
InlinedClauseType::IsAtom(..) => match &terms[0] {
Term::Literal(_, Literal::Atom(..)) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
Term::Var(vr, name) => {
self.marker.reset_arg(1);
let r = self.marker.mark_non_callable(
@@ -608,7 +600,7 @@ impl CodeGenerator {
Term::Literal(..) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
Term::Var(vr, name) => {
self.marker.reset_arg(1);
let r = self.marker.mark_non_callable(
@@ -629,7 +621,7 @@ impl CodeGenerator {
| Term::CompleteString(..) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
Term::Var(vr, name) => {
self.marker.reset_arg(1);
let r = self.marker.mark_non_callable(
@@ -647,7 +639,10 @@ impl CodeGenerator {
}
},
InlinedClauseType::IsRational(..) => match terms[0] {
Term::Literal(_, Literal::Rational(_)) => {
Term::Literal(
_,
Literal::Rational(_) | Literal::Fixnum(_) | Literal::Integer(_),
) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
@@ -687,10 +682,13 @@ impl CodeGenerator {
}
},
InlinedClauseType::IsNumber(..) => match terms[0] {
Term::Literal(_, Literal::F64(..))
| Term::Literal(_, Literal::Rational(_))
| Term::Literal(_, Literal::Integer(_))
| Term::Literal(_, Literal::Fixnum(_)) => {
Term::Literal(
_,
Literal::F64(..)
| Literal::Rational(_)
| Literal::Integer(_)
| Literal::Fixnum(_),
) => {
instr!("$succeed")
}
Term::Var(ref vr, ref name) => {
@@ -735,7 +733,7 @@ impl CodeGenerator {
Term::Literal(_, Literal::Integer(_)) | Term::Literal(_, Literal::Fixnum(_)) => {
instr!("$succeed")
}
Term::Var(ref vr, name) => {
Term::Var(vr, name) => {
self.marker.reset_arg(1);
let r = self.marker.mark_non_callable(
@@ -787,11 +785,10 @@ impl CodeGenerator {
fn compile_arith_expr(
&mut self,
term: &Term,
target_int: usize,
term_loc: GenContext,
arg: usize,
) -> Result<ArithCont, ArithmeticError> {
let mut evaluator = ArithmeticEvaluator::new(&mut self.marker, target_int);
let mut evaluator = ArithmeticEvaluator::new(&mut self.marker);
evaluator.compile_is(term, term_loc, arg)
}
@@ -804,7 +801,7 @@ impl CodeGenerator {
) -> Result<(), CompilationError> {
macro_rules! compile_expr {
($self:expr, $terms:expr, $term_loc:expr, $code:expr) => {{
let (acode, at) = $self.compile_arith_expr($terms, 1, $term_loc, 2)?;
let (acode, at) = $self.compile_arith_expr($terms, $term_loc, 2)?;
$code.extend(acode.into_iter());
at
}};
@@ -831,7 +828,7 @@ impl CodeGenerator {
self.marker
.mark_anon_var::<QueryInstruction>(Level::Shallow, term_loc, code);
if let Term::Var(ref vr, ref var) = &terms[1] {
if let Term::Var(vr, var) = &terms[1] {
let var_num = var.to_var_num().unwrap();
// if var is an anonymous variable, insert
@@ -859,16 +856,18 @@ impl CodeGenerator {
}
}
Term::Literal(
_,
ref cell,
c @ Literal::Integer(_)
| c @ Literal::F64(..)
| c @ Literal::Rational(_)
| c @ Literal::Fixnum(_),
) => {
let v = HeapCellValue::from(c);
code.push_back(instr!("put_constant", Level::Shallow, v, temp_v!(1)));
self.marker.advance_arg();
self.marker
.mark_non_var::<QueryInstruction>(Level::Shallow, term_loc, cell, code);
code.push_back(instr!("put_constant", Level::Shallow, v, temp_v!(1)));
compile_expr!(self, &terms[1], term_loc, code)
}
_ => {
@@ -877,7 +876,6 @@ impl CodeGenerator {
}
};
let at = at.unwrap_or(interm!(1));
self.add_call(code, instr!("is", temp_v!(1), at), call_policy);
Ok(())
}
@@ -996,20 +994,27 @@ impl CodeGenerator {
self.marker.in_tail_position = false;
self.marker.reset_contents();
}
ClauseItem::FirstBranch(num_branches) => {
ClauseItem::FirstBranch {
branch_num,
num_branches,
} => {
branch_code_stack.add_new_branch_stack();
branch_code_stack.add_new_branch();
self.marker.branch_stack.add_branch_stack(num_branches);
self.marker
.branch_stack
.add_branch_stack(branch_num.clone(), num_branches);
self.marker.add_branch();
}
ClauseItem::NextBranch => {
ClauseItem::NextBranch { branch_num } => {
branch_code_stack.add_new_branch();
self.marker.add_branch();
self.marker.branch_stack.incr_current_branch();
self.marker
.branch_stack
.incr_current_branch(branch_num.clone());
}
ClauseItem::BranchEnd(depth) => {
ClauseItem::BranchEnd { depth } => {
if !clause_iter.in_tail_position() {
let subsumed_hits =
branch_code_stack.push_missing_vars(depth, &mut self.marker);
@@ -1231,7 +1236,7 @@ impl CodeGenerator {
}
self.skeleton.clauses.push_back(clause_index_info);
code.extend(clause_code.into_iter());
code.extend(clause_code);
}
let index_code = if clauses_len > 1 || self.settings.is_extensible {
@@ -1308,7 +1313,7 @@ impl CodeGenerator {
}
}
code.extend(code_segment.into_iter());
code.extend(code_segment);
}
Ok(code)

View File

@@ -1,6 +1,6 @@
use crate::allocator::*;
use crate::codegen::SubsumedBranchHits;
use crate::forms::{GenContext, Level};
use crate::forms::{BranchNumber, GenContext, Level};
use crate::instructions::*;
use crate::machine::disjuncts::VarData;
use crate::parser::ast::*;
@@ -15,6 +15,7 @@ use indexmap::IndexMap;
use std::cell::Cell;
use std::collections::VecDeque;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
pub type BranchHits = IndexMap<usize, BitVec, FxBuildHasher>; // key: var_num, value: branch arm occurrences.
@@ -24,24 +25,26 @@ pub struct BranchOccurrences {
pub shallow_safety: BitSet<usize>, // unset means safe, set means unsafe (after the branch merge)
pub deep_safety: BitSet<usize>,
pub num_branches: usize,
pub current_branch: usize,
pub current_branch_idx: usize,
pub current_branch_num: Arc<BranchNumber>,
pub subsumed_hits: SubsumedBranchHits,
}
impl BranchOccurrences {
fn new(num_branches: usize) -> Self {
fn new(current_branch_num: Arc<BranchNumber>, num_branches: usize) -> Self {
Self {
hits: BranchHits::with_hasher(FxBuildHasher::default()),
shallow_safety: BitSet::default(),
deep_safety: BitSet::default(),
num_branches,
current_branch: 0,
current_branch_idx: 0,
current_branch_num,
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);
debug_assert!(self.current_branch_idx < self.num_branches);
let num_branches = self.num_branches;
let entry = self
@@ -49,7 +52,7 @@ impl BranchOccurrences {
.entry(var_num)
.or_insert_with(|| BitVec::repeat(false, num_branches));
entry.set(self.current_branch, true);
entry.set(self.current_branch_idx, true);
self.subsumed_hits.insert(var_num);
}
}
@@ -76,19 +79,6 @@ impl DerefMut for BranchStack {
}
impl BranchStack {
fn branch_subsumes(&self, branch: &BranchDesignator, sub_branch: &BranchDesignator) -> bool {
if branch.branch_stack_num < sub_branch.branch_stack_num {
if branch.branch_stack_num == 0 {
true
} else {
let idx = branch.branch_stack_num - 1;
self[idx].current_branch == branch.branch_num
}
} else {
branch == sub_branch
}
}
fn safety_unneeded_in_branch(
&self,
safety: &VarSafetyStatus,
@@ -96,9 +86,9 @@ impl BranchStack {
) -> bool {
match safety {
VarSafetyStatus::Needed => false,
VarSafetyStatus::LocallyUnneeded(planter_branch) => {
self.branch_subsumes(planter_branch, branch)
}
VarSafetyStatus::LocallyUnneeded(planter_branch) => planter_branch
.branch_num
.has_as_subbranch(&branch.branch_num),
VarSafetyStatus::GloballyUnneeded => true,
}
}
@@ -109,27 +99,24 @@ impl BranchStack {
}
}
pub(crate) fn add_branch_stack(&mut self, num_branches: usize) {
self.push(BranchOccurrences::new(num_branches));
pub(crate) fn add_branch_stack(&mut self, branch_num: Arc<BranchNumber>, num_branches: usize) {
self.push(BranchOccurrences::new(branch_num, num_branches));
}
pub(crate) fn current_branch_designator(&self) -> BranchDesignator {
let branch_stack_num = self.len();
let branch_num = self
.last()
.map(|occurrences| occurrences.current_branch)
.unwrap_or(0);
.map(|occurrences| occurrences.current_branch_num.clone())
.unwrap_or_default();
BranchDesignator {
branch_stack_num,
branch_num,
}
BranchDesignator { branch_num }
}
#[inline]
pub(crate) fn incr_current_branch(&mut self) {
pub(crate) fn incr_current_branch(&mut self, branch_num: Arc<BranchNumber>) {
let branch_occurrences = self.last_mut().unwrap();
branch_occurrences.current_branch += 1;
branch_occurrences.current_branch_idx += 1;
branch_occurrences.current_branch_num = branch_num;
}
#[inline]
@@ -171,7 +158,7 @@ impl DebrayAllocator {
for var_num in subsumed_hits {
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(_, ref mut allocation) => {
VarAlloc::Perm(_, allocation) => {
if let PermVarAllocation::Done {
shallow_safety,
deep_safety,
@@ -215,7 +202,7 @@ impl DebrayAllocator {
},
);
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
let (deep_safety, shallow_safety) = match self.branch_stack.last_mut() {
Some(latest_branch) => {
@@ -235,12 +222,12 @@ impl DebrayAllocator {
VarAlloc::Perm(_, allocation) => {
let shallow_safety = VarSafetyStatus::needed_if(
shallow_safety.contains(var_num),
branch_designator,
&branch_designator,
);
let deep_safety = VarSafetyStatus::needed_if(
deep_safety.contains(var_num),
branch_designator,
&branch_designator,
);
if running_count < num_occurrences {
@@ -520,7 +507,7 @@ impl DebrayAllocator {
}
pub(crate) fn mark_safe_var_unconditionally(&mut self, var_num: usize) {
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
@@ -531,11 +518,11 @@ impl DebrayAllocator {
..
},
) => {
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
*deep_safety = VarSafetyStatus::unneeded(&branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(&branch_designator);
}
VarAlloc::Temp { safety, .. } => {
*safety = VarSafetyStatus::unneeded(branch_designator);
*safety = VarSafetyStatus::unneeded(&branch_designator);
}
_ => {
unreachable!()
@@ -544,7 +531,7 @@ impl DebrayAllocator {
}
fn mark_safe_var(&mut self, var_num: usize, lvl: Level, term_loc: GenContext) {
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
@@ -557,15 +544,14 @@ impl DebrayAllocator {
) => {
// GetVariable in head chunk is considered safe.
if lvl == Level::Deep {
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
*deep_safety = VarSafetyStatus::unneeded(&branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(&branch_designator);
} else if term_loc == GenContext::Head {
*shallow_safety = VarSafetyStatus::GloballyUnneeded;
} else if let Some(&temp_var_num) = self.shallow_temp_mappings.get(&self.arg_c) {
match &mut self.var_data.records[temp_var_num].allocation {
VarAlloc::Temp {
ref mut to_perm_var_num,
..
to_perm_var_num, ..
} => {
*to_perm_var_num = Some(var_num);
}
@@ -573,7 +559,7 @@ impl DebrayAllocator {
}
}
}
VarAlloc::Temp { ref mut safety, .. } => {
VarAlloc::Temp { safety, .. } => {
*safety = VarSafetyStatus::GloballyUnneeded;
}
_ => {
@@ -588,16 +574,10 @@ impl DebrayAllocator {
r: RegType,
arg_c: usize,
) -> Instruction {
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
_,
PermVarAllocation::Done {
ref mut shallow_safety,
..
},
) => {
VarAlloc::Perm(_, PermVarAllocation::Done { shallow_safety, .. }) => {
if !self.in_tail_position
|| self
.branch_stack
@@ -605,7 +585,7 @@ impl DebrayAllocator {
{
Target::argument_to_value(r, arg_c)
} else {
*shallow_safety = VarSafetyStatus::unneeded(branch_designator);
*shallow_safety = VarSafetyStatus::unneeded(&branch_designator);
Target::unsafe_argument_to_value(r, arg_c)
}
}
@@ -624,34 +604,28 @@ impl DebrayAllocator {
var_num: usize,
r: RegType,
) -> Instruction {
let branch_designator = self.branch_stack.current_branch_designator();
let branch_designator = Arc::new(self.branch_stack.current_branch_designator());
match &mut self.var_data.records[var_num].allocation {
VarAlloc::Perm(
_,
PermVarAllocation::Done {
ref mut deep_safety,
..
},
) => {
VarAlloc::Perm(_, PermVarAllocation::Done { deep_safety, .. }) => {
if self
.branch_stack
.safety_unneeded_in_branch(deep_safety, &branch_designator)
{
Target::subterm_to_value(r)
} else {
*deep_safety = VarSafetyStatus::unneeded(branch_designator);
*deep_safety = VarSafetyStatus::unneeded(&branch_designator);
Target::unsafe_subterm_to_value(r)
}
}
VarAlloc::Temp { ref mut safety, .. } => {
VarAlloc::Temp { safety, .. } => {
if self
.branch_stack
.safety_unneeded_in_branch(safety, &branch_designator)
{
Target::subterm_to_value(r)
} else {
*safety = VarSafetyStatus::unneeded(branch_designator);
*safety = VarSafetyStatus::unneeded(&branch_designator);
Target::unsafe_subterm_to_value(r)
}
}
@@ -887,7 +861,7 @@ impl Allocator for DebrayAllocator {
self.arity = args.len();
for (idx, arg) in args.iter().enumerate() {
if let Term::Var(_, ref var) = arg {
if let Term::Var(_, var) = arg {
let var_num = var.to_var_num().unwrap();
let r = self.get_binding(var_num);

View File

@@ -1,5 +1,7 @@
:- use_module(library(charsio)).
:- use_module(library(lists)).
:- use_module(library(pio)).
:- use_module(library(dcgs)).
:- initialization(unit_test).
@@ -10,22 +12,12 @@ unit_test :-
Cs = "a£\x2124\".
write_f :-
open('x.txt', write, Stream, [type(binary)]),
F = put_byte(Stream),
chars_utf8bytes("£\x2124\\x2764\\x1F496\\n", Bs),
maplist(F, Bs),
close(Stream).
get_bytes(Stream, Res) :- get_bytes(Stream, [], Res).
get_bytes(Stream, Acc, Res) :-
get_byte(Stream, B),
(B =:= -1 ->
reverse(Acc, Res)
; get_bytes(Stream, [B|Acc], Res)).
maplist(char_code, Cs, Bs),
phrase_to_file(Cs, "x.txt", [type(binary)]).
read_f :-
open('x.txt', read, Stream, [type(binary)]),
get_bytes(Stream, Bs),
chars_utf8bytes(Cs, Bs),
write(Cs),
close(Stream).
phrase_from_file(seq(Cs), "x.txt", [type(binary)]),
maplist(char_code, Cs, Bs),
chars_utf8bytes(Chars, Bs),
write(Chars).

View File

@@ -26,17 +26,16 @@ use crate::machine::machine_errors::DomainErrorType;
use crate::parser::ast::{Fixnum, MightNotFitInFixnum};
use dashu::Integer;
use libffi::middle::{Arg, Cif, CodePtr, Type};
use libffi::middle::{Arg, Cif, CodePtr, Ret, Type};
use libloading::{Library, Symbol};
use ordered_float::OrderedFloat;
use std::alloc::{self, Layout};
use std::collections::HashMap;
use std::error::Error;
use std::ffi::{c_char, c_void, CStr, CString};
use std::ffi::{CStr, CString, c_char, c_void};
use std::fmt::Debug;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::ptr::NonNull;
pub struct FunctionDefinition {
@@ -55,7 +54,9 @@ pub struct FunctionImpl {
impl FunctionImpl {
unsafe fn call_void(&self, args: &[Arg], _: &mut Arena) -> Result<Value, FfiError> {
self.cif.call::<()>(self.code_ptr, args);
unsafe {
self.cif.call_return_into(self.code_ptr, args, Ret::void());
}
Ok(Value::Number(Number::Fixnum(Fixnum::build_with(0))))
}
@@ -64,7 +65,7 @@ impl FunctionImpl {
Integer: From<T>,
T: Copy + TryInto<i64> + MightNotFitInFixnum,
{
let n = self.cif.call::<T>(self.code_ptr, args);
let n = unsafe { self.cif.call::<T>(self.code_ptr, args) };
Ok(Value::Number(fixnum!(Number, n, arena)))
}
@@ -72,7 +73,7 @@ impl FunctionImpl {
where
T: Into<f64>,
{
let n = self.cif.call::<T>(self.code_ptr, args);
let n = unsafe { self.cif.call::<T>(self.code_ptr, args) };
Ok(Value::Number(Number::Float(OrderedFloat(n.into()))))
}
@@ -114,12 +115,8 @@ impl FunctionImpl {
let alloc = FfiStruct::new(layout, FfiAllocator::Rust)?;
unsafe {
libffi::raw::ffi_call(
self.cif.as_raw_ptr(),
Some(*self.code_ptr.as_safe_fun()),
alloc.ptr.as_ptr(),
args.as_ptr() as *mut *mut c_void,
)
self.cif
.call_return_into(self.code_ptr, args, alloc.as_ret())
};
let struct_val =
@@ -152,7 +149,7 @@ impl FunctionImpl {
FfiType::Ptr => FunctionImpl::call_ptr,
FfiType::CStr => FunctionImpl::call_cstr,
FfiType::Struct(name) => {
return unsafe { self.call_struct(name, args, arena, structs_table) }
return unsafe { self.call_struct(name, args, arena, structs_table) };
}
};
unsafe { call_fn(self, args, arena) }
@@ -180,10 +177,17 @@ impl StructImpl {
fn build(
&self,
name: Atom,
structs_table: &HashMap<Atom, StructImpl>,
struct_args: &mut [Value],
) -> Result<FfiStruct, FfiError> {
let args = ArgValue::build_args(struct_args, &self.fields, structs_table)?;
let args = ArgValue::build_args(
name,
ArgCountMismatchKind::Struct,
struct_args,
&self.fields,
structs_table,
)?;
let alloc = FfiStruct::new(self.layout()?, FfiAllocator::Rust)?;
@@ -200,7 +204,9 @@ impl StructImpl {
.extend(Layout::new::<T>())
.map_err(|_| FfiError::LayoutError)?;
*layout = new_layout;
ptr.byte_offset(offset as isize).cast::<T>().write(val);
unsafe {
ptr.byte_offset(offset as isize).cast::<T>().write(val);
}
Ok(())
}
@@ -261,7 +267,7 @@ impl StructImpl {
.extend(Layout::new::<T>())
.map_err(|_| FfiError::LayoutError)?;
*layout = new_layout;
let n = std::ptr::read::<T>(ptr.byte_offset(offset as isize).cast());
let n = unsafe { std::ptr::read::<T>(ptr.byte_offset(offset as isize).cast()) };
Ok(n)
}
@@ -274,7 +280,7 @@ impl StructImpl {
T: Copy + TryInto<i64> + MightNotFitInFixnum,
Integer: From<T>,
{
let n = read_primitive::<T>(ptr, layout)?;
let n = unsafe { read_primitive::<T>(ptr, layout)? };
Ok(Value::Number(fixnum!(Number, n, arena)))
}
@@ -285,7 +291,7 @@ impl StructImpl {
where
T: Into<f64>,
{
let n = read_primitive::<T>(ptr, layout)?;
let n = unsafe { read_primitive::<T>(ptr, layout)? };
Ok(Value::Number(Number::Float(OrderedFloat(n.into()))))
}
@@ -329,7 +335,9 @@ impl StructImpl {
substruct_type.read(field_ptr, *substruct, struct_table, arena)?;
Ok(struct_val)
}
FfiType::Void => return Err(FfiError::VoidArgumentType),
FfiType::Void => {
return Err(FfiError::UnsupportedArgumentType(Some(atom!("void"))));
}
};
returns.push(val?);
}
@@ -338,48 +346,6 @@ impl StructImpl {
}
}
struct PointerArgs<'a, 'val> {
memory: Vec<Arg>,
phantom: PhantomData<&'a mut ArgValue<'val>>,
}
impl<'args, 'val> PointerArgs<'args, 'val> {
fn new(args: &'args [ArgValue<'val>]) -> Self {
let args = args
.iter()
.map(|arg| match arg {
ArgValue::U8(a) => libffi::middle::arg(a),
ArgValue::I8(a) => libffi::middle::arg(a),
ArgValue::U16(a) => libffi::middle::arg(a),
ArgValue::I16(a) => libffi::middle::arg(a),
ArgValue::U32(a) => libffi::middle::arg(a),
ArgValue::I32(a) => libffi::middle::arg(a),
ArgValue::U64(a) => libffi::middle::arg(a),
ArgValue::I64(a) => libffi::middle::arg(a),
ArgValue::F32(a) => libffi::middle::arg(a),
ArgValue::F64(a) => libffi::middle::arg(a),
ArgValue::Ptr(ptr, _) => Arg::new(ptr),
ArgValue::Struct(s) => unsafe {
std::mem::transmute::<*mut c_void, Arg>(s.ptr.as_ptr())
},
})
.collect();
PointerArgs {
memory: args,
phantom: PhantomData,
}
}
}
impl Deref for PointerArgs<'_, '_> {
type Target = [Arg];
fn deref(&self) -> &Self::Target {
&self.memory
}
}
#[derive(Debug, Clone, Copy)]
enum FfiType {
Void,
@@ -399,9 +365,49 @@ enum FfiType {
Struct(Atom),
}
trait ToFfiType {
const TYPE: FfiType;
}
macro_rules! impl_to_ffi_type {
($($t:ty => $v:ident);*$(;)?) => {
$(
impl ToFfiType for $t {
const TYPE: FfiType = FfiType::$v;
}
)*
};
}
impl_to_ffi_type!(
u8 => U8;
i8 => I8;
u16 => U16;
i16 => I16;
u32 => U32;
i32 => I32;
u64 => U64;
i64 => I64;
f32 => F32;
f64 => F64;
);
impl FfiType {
fn from_atom(atom: &Atom) -> Self {
match atom {
atom!("char") => <core::ffi::c_char as ToFfiType>::TYPE,
atom!("uchar") => <core::ffi::c_uchar as ToFfiType>::TYPE,
atom!("schar") => <core::ffi::c_schar as ToFfiType>::TYPE,
atom!("short") => <core::ffi::c_short as ToFfiType>::TYPE,
atom!("ushort") => <core::ffi::c_ushort as ToFfiType>::TYPE,
atom!("int") => <core::ffi::c_int as ToFfiType>::TYPE,
atom!("uint") => <core::ffi::c_uint as ToFfiType>::TYPE,
atom!("long") => <core::ffi::c_long as ToFfiType>::TYPE,
atom!("ulong") => <core::ffi::c_ulong as ToFfiType>::TYPE,
atom!("longlong") => <core::ffi::c_longlong as ToFfiType>::TYPE,
atom!("ulonglong") => <core::ffi::c_ulonglong as ToFfiType>::TYPE,
atom!("float") => <core::ffi::c_float as ToFfiType>::TYPE,
atom!("double") => <core::ffi::c_double as ToFfiType>::TYPE,
atom!("sint64") | atom!("i64") => Self::I64,
atom!("sint32") | atom!("i32") => Self::I32,
atom!("sint16") | atom!("i16") => Self::I16,
@@ -490,19 +496,30 @@ impl<'val> ArgValue<'val> {
return Err(FfiError::StructNotFound(*arg_type_name));
};
Ok(Self::Struct(struct_type.build(structs_table, args)?))
Ok(Self::Struct(struct_type.build(
*arg_type_name,
structs_table,
args,
)?))
}
FfiType::Void => Err(FfiError::VoidArgumentType),
FfiType::Void => Err(FfiError::UnsupportedArgumentType(Some(atom!("void")))),
}
}
fn build_args(
name: Atom,
kind: ArgCountMismatchKind,
args: &'val mut [Value],
types: &[FfiType],
structs_table: &HashMap<Atom, StructImpl>,
) -> Result<Vec<Self>, FfiError> {
if types.len() != args.len() {
return Err(FfiError::ArgCountMismatch);
return Err(FfiError::ArgCountMismatch {
name,
kind,
expected: types.len(),
got: args.len(),
});
}
args.iter_mut()
@@ -510,6 +527,28 @@ impl<'val> ArgValue<'val> {
.map(|(arg, arg_type)| ArgValue::new(arg, arg_type, structs_table))
.collect::<Result<Vec<_>, _>>()
}
fn as_arg<'res, 'this: 'res>(&'this self) -> Arg<'res>
where
'val: 'res,
{
match self {
ArgValue::U8(a) => libffi::middle::arg(a),
ArgValue::I8(a) => libffi::middle::arg(a),
ArgValue::U16(a) => libffi::middle::arg(a),
ArgValue::I16(a) => libffi::middle::arg(a),
ArgValue::U32(a) => libffi::middle::arg(a),
ArgValue::I32(a) => libffi::middle::arg(a),
ArgValue::U64(a) => libffi::middle::arg(a),
ArgValue::I64(a) => libffi::middle::arg(a),
ArgValue::F32(a) => libffi::middle::arg(a),
ArgValue::F64(a) => libffi::middle::arg(a),
ArgValue::Ptr(ptr, _) => Arg::new(ptr),
ArgValue::Struct(s) => unsafe {
std::mem::transmute::<*mut c_void, Arg>(s.ptr.as_ptr())
},
}
}
}
struct FfiStruct {
@@ -570,6 +609,10 @@ impl FfiStruct {
allocator,
})
}
fn as_ret(&self) -> libffi::middle::Ret<'_> {
unsafe { std::mem::transmute::<*mut c_void, Ret>(self.ptr.as_ptr()) }
}
}
impl Drop for FfiStruct {
@@ -659,11 +702,17 @@ impl ForeignFunctionTable {
let fn_impl = self
.table
.get(&fn_name)
.ok_or(FfiError::FunctionNotFound(fn_name))?;
.ok_or(FfiError::FunctionNotFound(fn_name, args.len()))?;
let args = ArgValue::build_args(&mut args, &fn_impl.args, &self.structs)?;
let args = ArgValue::build_args(
fn_name,
ArgCountMismatchKind::Function,
&mut args,
&fn_impl.args,
&self.structs,
)?;
let args = PointerArgs::new(&args);
let args: Vec<_> = args.iter().map(|v| v.as_arg()).collect();
fn_impl.call(&args, arena, &self.structs)
}
@@ -691,9 +740,9 @@ impl ForeignFunctionTable {
}
match FfiType::from_atom(&kind) {
FfiType::Void => Err(FfiError::VoidArgumentType),
FfiType::Void => Err(FfiError::UnsupportedArgumentType(Some(atom!("void")))),
FfiType::Bool => {
let val = args.as_int::<u8>()?;
let val = args.as_int::<i8>()?;
let init = match val {
0 => false,
1 => true,
@@ -720,7 +769,7 @@ impl ForeignFunctionTable {
let (_, args) = args.as_struct()?;
let ffi_struct = struct_impl.build(&self.structs, args)?;
let ffi_struct = struct_impl.build(kind, &self.structs, args)?;
let ptr = ManuallyDrop::new(ffi_struct).ptr;
@@ -744,7 +793,7 @@ impl ForeignFunctionTable {
T: Copy + TryInto<i64> + MightNotFitInFixnum,
Integer: From<T>,
{
let n = ptr.cast::<T>().read();
let n = unsafe { ptr.cast::<T>().read() };
Value::Number(fixnum!(Number, n, arena))
}
@@ -755,9 +804,9 @@ impl ForeignFunctionTable {
};
match FfiType::from_atom(&kind) {
FfiType::Void => Err(FfiError::VoidArgumentType),
FfiType::Bool | FfiType::U8 => Ok(unsafe { read_int::<u8>(ptr, arena) }),
FfiType::I8 => Ok(unsafe { read_int::<i8>(ptr, arena) }),
FfiType::Void => Err(FfiError::UnsupportedArgumentType(Some(atom!("void")))),
FfiType::U8 => Ok(unsafe { read_int::<u8>(ptr, arena) }),
FfiType::Bool | FfiType::I8 => Ok(unsafe { read_int::<i8>(ptr, arena) }),
FfiType::U16 => Ok(unsafe { read_int::<u16>(ptr, arena) }),
FfiType::I16 => Ok(unsafe { read_int::<i16>(ptr, arena) }),
FfiType::U32 => Ok(unsafe { read_int::<u32>(ptr, arena) }),
@@ -805,7 +854,7 @@ impl ForeignFunctionTable {
};
match FfiType::from_atom(&kind) {
FfiType::Void => return Err(FfiError::VoidArgumentType),
FfiType::Void => return Err(FfiError::UnsupportedArgumentType(Some(atom!("void")))),
FfiType::Bool => deallocate_primitive::<bool>(allocator, ptr),
FfiType::U8 => deallocate_primitive::<u8>(allocator, ptr),
FfiType::I8 => deallocate_primitive::<i8>(allocator, ptr),
@@ -879,7 +928,7 @@ impl Value {
fn as_ptr(&mut self) -> Result<*mut c_void, FfiError> {
match self {
Value::CString(ref mut cstr) => Ok(cstr.as_ptr().cast_mut().cast()),
Value::CString(cstr) => Ok(cstr.as_ptr().cast_mut().cast()),
Value::Number(Number::Fixnum(fixnum)) => Ok(std::ptr::with_exposed_provenance_mut(
fixnum.get_num() as usize,
)),
@@ -902,20 +951,37 @@ impl Value {
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FfiError {
ValueCast(Atom, Atom),
ValueOutOfRange(DomainErrorType, Value),
VoidArgumentType,
FunctionNotFound(Atom),
UnsupportedArgumentType(Option<Atom>),
FunctionNotFound(Atom, usize),
StructNotFound(Atom),
ArgCountMismatch,
ArgCountMismatch {
name: Atom, // ffi function or struct
kind: ArgCountMismatchKind,
#[allow(dead_code, reason = "will be used by PR 3173")]
expected: usize,
got: usize,
},
AllocationFailed,
// LayoutError should never occour
// LayoutError should never occur
LayoutError,
UnsupportedTypedef,
UnsupportedAbi,
CStrFieldType,
NullPtr,
#[doc(hidden)]
#[non_exhaustive]
Other,
}
#[derive(Debug)]
pub(crate) enum ArgCountMismatchKind {
Function,
Struct,
}
impl std::fmt::Display for FfiError {
@@ -931,6 +997,8 @@ impl From<libffi::low::Error> for FfiError {
match value {
libffi::low::Error::Typedef => FfiError::UnsupportedTypedef,
libffi::low::Error::Abi => FfiError::UnsupportedAbi,
libffi::low::Error::ArgType => FfiError::UnsupportedArgumentType(None),
_ => FfiError::Other,
}
}
}

View File

@@ -18,11 +18,14 @@ use indexmap::{IndexMap, IndexSet};
use ordered_float::OrderedFloat;
use std::cell::Cell;
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::{AddAssign, Deref, DerefMut};
use std::path::PathBuf;
use std::sync::Arc;
pub type PredicateKey = (Atom, usize); // name, arity.
@@ -42,12 +45,6 @@ impl AppendOrPrepend {
}
}
#[derive(Debug, Clone, Copy)]
pub enum VarComparison {
Indistinct,
Distinct,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Level {
Deep,
@@ -128,10 +125,82 @@ impl ChunkType {
}
}
#[derive(Debug, Clone)] //, PartialOrd, PartialEq, Eq, Hash)]
pub(crate) struct BranchNumber {
pub(crate) branch_num: Rational,
pub(crate) delta: Rational,
}
impl Default for BranchNumber {
fn default() -> Self {
Self {
branch_num: Rational::from(0),
delta: Rational::from(1u64 << 31),
}
}
}
impl PartialEq<BranchNumber> for BranchNumber {
#[inline]
fn eq(&self, rhs: &BranchNumber) -> bool {
self.branch_num == rhs.branch_num
}
}
impl Eq for BranchNumber {}
impl Hash for BranchNumber {
#[inline(always)]
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.branch_num.hash(hasher)
}
}
impl PartialOrd<BranchNumber> for BranchNumber {
#[inline]
fn partial_cmp(&self, rhs: &BranchNumber) -> Option<Ordering> {
self.branch_num.partial_cmp(&rhs.branch_num)
}
}
impl BranchNumber {
pub(crate) fn has_as_subbranch(&self, other: &Self) -> bool {
other.delta <= self.delta
&& other.branch_num >= self.branch_num
&& other.branch_num < &self.branch_num + &self.delta
}
pub(crate) fn split(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone() + &self.delta / Rational::from(2),
delta: &self.delta / Rational::from(4),
}
}
pub(crate) fn incr_by_delta(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone() + &self.delta,
delta: self.delta.clone(),
}
}
pub(crate) fn halve_delta(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone(),
delta: &self.delta / Rational::from(2),
}
}
}
#[derive(Debug)]
pub enum ChunkedTerms {
Branch(Vec<VecDeque<ChunkedTerms>>),
Chunk { terms: VecDeque<QueryTerm> },
Branch {
branch_nums: Vec<Arc<BranchNumber>>,
arms: Vec<VecDeque<ChunkedTerms>>,
},
Chunk {
terms: VecDeque<QueryTerm>,
},
}
#[derive(Debug)]
@@ -165,21 +234,22 @@ impl ChunkedTermVec {
}
pub fn reserve_branch(&mut self, capacity: usize) {
self.chunk_vec
.push_back(ChunkedTerms::Branch(Vec::with_capacity(capacity)));
self.chunk_vec.push_back(ChunkedTerms::Branch {
branch_nums: Vec::with_capacity(capacity),
arms: Vec::with_capacity(capacity),
});
}
#[inline]
pub fn add_chunk(&mut self) {
let chunk = ChunkedTerms::Chunk {
self.chunk_vec.push_back(ChunkedTerms::Chunk {
terms: VecDeque::from(vec![]),
};
self.chunk_vec.push_back(chunk);
});
}
pub fn push_chunk_term(&mut self, term: QueryTerm) {
match self.chunk_vec.back_mut() {
Some(ChunkedTerms::Branch(_)) => {
Some(ChunkedTerms::Branch { .. }) => {
let chunk = ChunkedTerms::Chunk {
terms: VecDeque::from(vec![term]),
};
@@ -319,15 +389,15 @@ impl ClauseInfo for Rule {
impl ClauseInfo for PredicateClause {
fn name(&self) -> Option<Atom> {
match self {
PredicateClause::Fact(ref term, ..) => term.head.name(),
PredicateClause::Rule(ref rule, ..) => rule.name(),
PredicateClause::Fact(term, ..) => term.head.name(),
PredicateClause::Rule(rule, ..) => rule.name(),
}
}
fn arity(&self) -> usize {
match self {
PredicateClause::Fact(ref term, ..) => term.head.arity(),
PredicateClause::Rule(ref rule, ..) => rule.arity(),
PredicateClause::Fact(term, ..) => term.head.arity(),
PredicateClause::Rule(rule, ..) => rule.arity(),
}
}
}
@@ -512,7 +582,7 @@ pub(crate) fn fetch_op_spec_from_existing(
op_desc: Option<OpDesc>,
op_dir: &OpDir,
) -> Option<OpDesc> {
if let Some(ref op_desc) = &op_desc {
if let Some(op_desc) = &op_desc {
if op_desc.arity() != arity {
/* it's possible to extend operator functors with
* additional terms. When that happens,
@@ -773,9 +843,9 @@ impl Number {
pub(crate) fn is_positive(&self) -> bool {
match self {
Number::Fixnum(n) => n.get_num() > 0,
Number::Integer(ref n) => n.is_positive(),
Number::Integer(n) => n.is_positive(),
Number::Float(f) => f.is_sign_positive(),
Number::Rational(ref r) => r.is_positive(),
Number::Rational(r) => r.is_positive(),
}
}
@@ -783,9 +853,9 @@ impl Number {
pub(crate) fn is_negative(&self) -> bool {
match self {
Number::Fixnum(n) => n.get_num() < 0,
Number::Integer(ref n) => n.is_negative(),
Number::Integer(n) => n.is_negative(),
&Number::Float(OrderedFloat(f)) => f.is_sign_negative() && f != -0f64,
Number::Rational(ref r) => r.is_negative(),
Number::Rational(r) => r.is_negative(),
}
}
@@ -793,9 +863,9 @@ impl Number {
pub(crate) fn is_zero(&self) -> bool {
match self {
Number::Fixnum(n) => n.get_num() == 0,
Number::Integer(ref n) => n.is_zero(),
Number::Integer(n) => n.is_zero(),
&Number::Float(OrderedFloat(f)) => f == 0.0 || f == -0.0,
Number::Rational(ref r) => r.is_zero(),
Number::Rational(r) => r.is_zero(),
}
}
@@ -850,9 +920,9 @@ impl OptArgIndexKey {
#[inline]
pub(crate) fn set_switch_on_term_loc(&mut self, value: usize) {
match self {
OptArgIndexKey::Literal(_, ref mut loc, ..)
| OptArgIndexKey::Structure(_, ref mut loc, ..)
| OptArgIndexKey::List(_, ref mut loc) => {
OptArgIndexKey::Literal(_, loc, ..)
| OptArgIndexKey::Structure(_, loc, ..)
| OptArgIndexKey::List(_, loc) => {
*loc = value;
}
OptArgIndexKey::None => {}
@@ -864,9 +934,9 @@ impl AddAssign<usize> for OptArgIndexKey {
#[inline]
fn add_assign(&mut self, n: usize) {
match self {
OptArgIndexKey::Literal(_, ref mut o, ..)
| OptArgIndexKey::List(_, ref mut o)
| OptArgIndexKey::Structure(_, ref mut o, ..) => {
OptArgIndexKey::Literal(_, o, ..)
| OptArgIndexKey::List(_, o)
| OptArgIndexKey::Structure(_, o, ..) => {
*o += n;
}
OptArgIndexKey::None => {}

View File

@@ -230,9 +230,9 @@ pub(crate) fn variadic_functor(
#[allow(unused_parens)]
mod tests {
use super::*;
use FunctorElement::*;
use indexmap::indexmap;
use std::string::String;
use FunctorElement::*;
#[test]
fn basic_terms() {
@@ -551,7 +551,7 @@ mod tests {
#[test]
fn inlined_atoms() {
let atom_table = AtomTable::new();
let atom_table = AtomTable::new().unwrap();
let inlined = AtomTable::build_with(&atom_table, "inline");
assert!(inlined.is_inlined());

View File

@@ -1,20 +1,348 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
use crate::arena::Arena;
use crate::forms::Number;
#[cfg(test)]
pub(crate) use crate::machine::gc::StacklessPreOrderHeapIter;
use crate::atom_table::*;
use crate::machine::cycle_detection::*;
use crate::machine::heap::*;
use crate::machine::machine_indices::TermOrderCategory;
use crate::machine::stack::*;
use crate::parser::lexer::MachineState;
use crate::types::*;
use core::marker::PhantomData;
use scryer_modular_bitfield::prelude::*;
use fxhash::FxBuildHasher;
use indexmap::IndexSet;
use modular_bitfield::prelude::*;
use std::cmp::Ordering;
use std::ops::Deref;
use std::vec::Vec;
// iterate through the subterms of a pair of terms
// for as long as they structurally agree, reporting
// the earliest (pre-order) difference before returning
// None's and pairs of variables as the Iterator Item.
pub struct ParallelHeapIter<'a> {
stack: Vec<(HeapCellValue, HeapCellValue)>,
heap: &'a Heap,
arena: &'a Arena,
tabu_list: IndexSet<(usize, usize), FxBuildHasher>,
}
impl<'a> ParallelHeapIter<'a> {
pub fn from(machine_st: &'a MachineState, h1: HeapCellValue, h2: HeapCellValue) -> Self {
Self {
stack: vec![(h1, h2)],
heap: &machine_st.heap,
arena: &machine_st.arena,
tabu_list: IndexSet::with_hasher(FxBuildHasher::new()),
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
pub enum TermPair {
Vars(usize, usize),
Less(HeapCellValue, HeapCellValue),
Greater(HeapCellValue, HeapCellValue),
Unordered(HeapCellValue, HeapCellValue),
}
impl ParallelHeapIter<'_> {
#[inline]
fn parallel_cmp<Cmp: Ord>(
&mut self,
v1: Cmp,
v2: Cmp,
h1: HeapCellValue,
h2: HeapCellValue,
) -> Option<TermPair> {
match v1.cmp(&v2) {
Ordering::Greater => {
self.stack.clear();
Some(TermPair::Greater(h1, h2))
}
Ordering::Less => {
self.stack.clear();
Some(TermPair::Less(h1, h2))
}
Ordering::Equal => None,
}
}
}
macro_rules! some_or_return {
($e:expr) => {
if let Some(x) = $e {
return Some(x);
}
};
}
impl Iterator for ParallelHeapIter<'_> {
type Item = TermPair;
fn next(&mut self) -> Option<Self::Item> {
use crate::offset_table::F64Offset;
while let Some((s1, s2)) = self.stack.pop() {
let s1 = heap_bound_deref(self.heap, s1);
let s2 = heap_bound_deref(self.heap, s2);
let v1 = heap_bound_store(self.heap, s1);
let v2 = heap_bound_store(self.heap, s2);
let order_cat_v1 = v1.order_category(self.heap);
let order_cat_v2 = v2.order_category(self.heap);
some_or_return!(self.parallel_cmp(order_cat_v1, order_cat_v2, v1, v2));
match order_cat_v1 {
Some(TermOrderCategory::Variable) => {
let v1 = v1.get_value() as usize;
let v2 = v2.get_value() as usize;
return Some(TermPair::Vars(v1, v2));
}
Some(TermOrderCategory::FloatingPoint) => {
let v1_offset = cell_as_f64_offset!(v1);
let v2_offset = cell_as_f64_offset!(v2);
let v1_f64 = self.arena.f64_tbl.get_entry(v1_offset);
let v2_f64 = self.arena.f64_tbl.get_entry(v2_offset);
some_or_return!(self.parallel_cmp(v1_f64, v2_f64, v1, v2));
}
Some(TermOrderCategory::Integer) => {
let v1_int = Number::try_from((v1, &self.arena.f64_tbl)).unwrap();
let v2_int = Number::try_from((v2, &self.arena.f64_tbl)).unwrap();
some_or_return!(self.parallel_cmp(v1_int, v2_int, v1, v2));
}
Some(TermOrderCategory::Atom) => {
read_heap_cell!(v1,
(HeapCellValueTag::Atom, (n1, _a1)) => {
read_heap_cell!(v2,
(HeapCellValueTag::Atom, (n2, _a2)) => {
some_or_return!(self.parallel_cmp(n1, n2, v1, v2));
}
(HeapCellValueTag::Str, s) => {
let n2 = cell_as_atom_cell!(self.heap[s])
.get_name();
some_or_return!(self.parallel_cmp(n1, n2, v1, v2));
}
_ => {
unreachable!();
}
)
}
(HeapCellValueTag::Str, s) => {
let n1 = cell_as_atom_cell!(self.heap[s])
.get_name();
read_heap_cell!(v2,
(HeapCellValueTag::Atom, (n2, _a2)) => {
some_or_return!(self.parallel_cmp(n1, n2, v1, v2));
}
(HeapCellValueTag::Str, s) => {
let n2 = cell_as_atom_cell!(self.heap[s])
.get_name();
some_or_return!(self.parallel_cmp(n1, n2, v1, v2));
}
_ => {
unreachable!();
}
)
}
_ => {
unreachable!()
}
)
}
Some(TermOrderCategory::Compound) => {
read_heap_cell!(v1,
(HeapCellValueTag::Lis, l1) => {
read_heap_cell!(v2,
(HeapCellValueTag::PStrLoc, l2) => {
if self.tabu_list.contains(&(l1, l2)) {
continue;
}
self.tabu_list.insert((l1, l2));
// like the action of partial_string_to_stack here but the
// ordering of stack pushes is (crucially for comparison
// correctness) different.
let (c, succ_cell) = self.heap.last_str_char_and_tail(l2);
self.stack.push((heap_loc_as_cell!(l1 + 1), succ_cell));
self.stack.push((heap_loc_as_cell!(l1), char_as_cell!(c)));
}
(HeapCellValueTag::Lis, l2) => {
if self.tabu_list.contains(&(l1, l2)) {
continue;
}
self.tabu_list.insert((l1, l2));
self.stack.push((self.heap[l1 + 1], self.heap[l2 + 1]));
self.stack.push((self.heap[l1], self.heap[l2]));
}
(HeapCellValueTag::Str, s2) => {
if self.tabu_list.contains(&(l1, s2)) {
continue;
}
let (n2, a2) = cell_as_atom_cell!(self.heap[s2])
.get_name_and_arity();
some_or_return!(self.parallel_cmp((2, atom!(".")), (a2, n2), v1, v2));
self.tabu_list.insert((l1, s2));
self.stack.push((self.heap[l1 + 1], self.heap[s2 + 2]));
self.stack.push((self.heap[l1], self.heap[s2 + 1]));
}
_ => {
unreachable!();
}
)
}
(HeapCellValueTag::PStrLoc, l1) => {
read_heap_cell!(v2,
(HeapCellValueTag::PStrLoc, l2) => {
if self.tabu_list.contains(&(l1, l2)) {
continue;
}
match self.heap.compare_pstr_segments(l1, l2) {
PStrSegmentCmpResult::Continue(v1, v2) => {
self.tabu_list.insert((l1, l2));
self.stack.push((v1.offset_by(l1), v2.offset_by(l2)));
}
PStrSegmentCmpResult::Less => {
self.stack.clear();
return Some(TermPair::Less(v1, v2));
}
PStrSegmentCmpResult::Greater => {
self.stack.clear();
return Some(TermPair::Greater(v1, v2));
}
}
}
(HeapCellValueTag::Lis, l2) => {
if self.tabu_list.contains(&(l1, l2)) {
continue;
}
self.tabu_list.insert((l1, l2));
let (c, succ_cell) = self.heap.last_str_char_and_tail(l1);
self.stack.push((succ_cell, heap_loc_as_cell!(l2 + 1)));
self.stack.push((char_as_cell!(c), heap_loc_as_cell!(l2)));
}
(HeapCellValueTag::Str, s2) => {
if self.tabu_list.contains(&(l1, s2)) {
continue;
}
self.tabu_list.insert((l1, s2));
let (n2, a2) = cell_as_atom_cell!(self.heap[s2])
.get_name_and_arity();
some_or_return!(self.parallel_cmp((2, atom!(".")), (a2, n2), v1, v2));
let (c, succ_cell) = self.heap.last_str_char_and_tail(l1);
self.stack.push((succ_cell, heap_loc_as_cell!(s2+2)));
self.stack.push((char_as_cell!(c), heap_loc_as_cell!(s2+1)));
}
_ => {
unreachable!()
}
);
}
(HeapCellValueTag::Str, s1) => {
read_heap_cell!(v2,
(HeapCellValueTag::Str, s2) => {
if self.tabu_list.contains(&(s1, s2)) {
continue;
}
let (n1, a1) = cell_as_atom_cell!(self.heap[s1])
.get_name_and_arity();
let (n2, a2) = cell_as_atom_cell!(self.heap[s2])
.get_name_and_arity();
some_or_return!(self.parallel_cmp((a1, n1), (a2, n2), v1, v2));
self.tabu_list.insert((s1, s2));
for idx in (1 .. a1+1).rev() {
self.stack.push((self.heap[s1+idx], self.heap[s2+idx]));
}
}
(HeapCellValueTag::Lis, l2) => {
if self.tabu_list.contains(&(s1, l2)) {
continue;
}
let (n1, a1) = cell_as_atom_cell!(self.heap[s1])
.get_name_and_arity();
some_or_return!(self.parallel_cmp((a1, n1), (2, atom!(".")), v1, v2));
self.stack.push((self.heap[s1+1], self.heap[l2]));
self.stack.push((self.heap[s1+2], self.heap[l2+1]));
}
(HeapCellValueTag::PStrLoc, l2) => {
if self.tabu_list.contains(&(s1, l2)) {
continue;
}
let (n1, a1) = cell_as_atom_cell!(self.heap[s1])
.get_name_and_arity();
some_or_return!(self.parallel_cmp((a1, n1), (2, atom!(".")), v1, v2));
self.tabu_list.insert((s1, l2));
let (c, succ_cell) = self.heap.last_str_char_and_tail(l2);
self.stack.push((heap_loc_as_cell!(s1+2), succ_cell));
self.stack.push((heap_loc_as_cell!(s1+1), char_as_cell!(c)));
}
_ => {
unreachable!()
}
)
}
_ => {
unreachable!()
}
);
}
None => {
return Some(TermPair::Unordered(v1, v2));
}
}
}
None
}
}
#[inline(always)]
pub fn eager_stackful_preorder_iter(
heap: &mut Heap,
@@ -34,7 +362,7 @@ pub struct EagerStackfulPreOrderHeapIter<'a> {
start_value: HeapCellValue,
iter_stack: Vec<HeapCellValue>,
mark_phase: bool,
heap: &'a mut Heap,
pub heap: &'a mut Heap,
}
impl<'a> Drop for EagerStackfulPreOrderHeapIter<'a> {
@@ -118,8 +446,8 @@ impl<'a> EagerStackfulPreOrderHeapIter<'a> {
(HeapCellValueTag::PStrLoc, h) => {
let tail_idx = self.heap.scan_slice_to_str(h).tail_idx;
self.heap[tail_idx].set_mark_bit(self.mark_phase);
self.iter_stack.push(self.heap[tail_idx]);
self.heap[tail_idx].set_mark_bit(self.mark_phase);
}
_ => {
}
@@ -141,7 +469,7 @@ impl<'a> Iterator for EagerStackfulPreOrderHeapIter<'a> {
}
}
#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Specifier, Clone, Copy, Debug, PartialEq, Eq)]
#[bits = 2]
enum IterStackLocTag {
Iterable,
@@ -149,7 +477,7 @@ enum IterStackLocTag {
PendingMark,
}
#[derive(BitfieldSpecifier, Clone, Copy, Debug, PartialEq, Eq)]
#[derive(Specifier, Clone, Copy, Debug, PartialEq, Eq)]
#[bits = 1]
pub enum HeapOrStackTag {
Heap,

View File

@@ -3,7 +3,7 @@ use crate::atom_table::*;
use crate::parser::ast::*;
use crate::parser::dashu::base::RemEuclid;
use crate::parser::dashu::integer::Sign;
use crate::parser::dashu::{ibig, Integer, Rational};
use crate::parser::dashu::{Integer, Rational, ibig};
use crate::forms::*;
use crate::heap_iter::*;
@@ -387,7 +387,7 @@ fn negated_op_needs_bracketing(
op_dir: &OpDir,
op: &Option<DirectedOp>,
) -> bool {
if let Some(ref op) = op {
if let Some(op) = op {
op.is_negative_sign()
&& iter.leftmost_leaf_has_property(op_dir, |addr| {
match Number::try_from((addr, f64_tbl)) {
@@ -1452,7 +1452,7 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> {
});
for op in &[op, parent_op] {
if let Some(ref op) = &op {
if let Some(op) = &op {
if op.is_left()
&& (op.is_prefix() || requires_space(&op.as_atom().as_str(), "("))
{

View File

@@ -1,10 +1,12 @@
use bytes::{buf::Reader, Bytes};
use bytes::{Bytes, buf::Reader};
use std::sync::{Arc, Condvar, Mutex};
use tokio::sync::Notify;
use warp::http;
pub struct HttpListener {
pub incoming: std::sync::mpsc::Receiver<HttpRequest>,
pub warp_shutdown: Arc<Notify>,
}
pub struct HttpRequest {

View File

@@ -167,7 +167,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
.push(IndexingLine::IndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(ref mut constants)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(constants)) => {
constants.insert(
constant,
IndexingCodePtr::Internal(indexing_code_len - self.offset),
@@ -196,7 +196,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(ref mut constants)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(constants)) => {
constants.insert(
constant,
IndexingCodePtr::Internal(indexing_code_len - self.offset),
@@ -210,22 +210,22 @@ impl<'a> IndexingCodeMergingPtr<'a> {
fn extend_indexed_choice(&mut self, index: usize) {
match &mut self.indexing_code[self.offset] {
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs)
IndexingLine::IndexedChoice(indexed_choice_instrs)
if self.append_or_prepend.is_append() =>
{
uncap_choice_seq_with_trust(indexed_choice_instrs.make_contiguous());
indexed_choice_instrs.push_back(IndexedChoiceInstruction::Trust(index));
}
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::IndexedChoice(indexed_choice_instrs) => {
uncap_choice_seq_with_try(indexed_choice_instrs.make_contiguous());
indexed_choice_instrs.push_front(IndexedChoiceInstruction::Try(index));
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs)
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs)
if self.append_or_prepend.is_append() =>
{
indexed_choice_instrs.push_back(index);
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs) => {
indexed_choice_instrs.push_front(index);
}
_ => {
@@ -244,7 +244,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
let indexing_code_len = self.indexing_code.len();
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, ref mut c, ..)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, c, ..)) => {
match *c {
IndexingCodePtr::Fail if self.is_dynamic => {
*c = IndexingCodePtr::DynamicExternal(index);
@@ -322,7 +322,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
let indexing_code_len = self.indexing_code.len();
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, ref mut c, ..)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, c, ..)) => {
match *c {
IndexingCodePtr::Fail if self.is_dynamic => {
*c = IndexingCodePtr::DynamicExternal(index);
@@ -445,7 +445,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
.push(IndexingLine::IndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures)) => {
structures.insert(
key,
IndexingCodePtr::Internal(indexing_code_len - self.offset),
@@ -474,7 +474,7 @@ impl<'a> IndexingCodeMergingPtr<'a> {
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures)) => {
structures.insert(
key,
IndexingCodePtr::Internal(indexing_code_len - self.offset),
@@ -491,33 +491,29 @@ impl<'a> IndexingCodeMergingPtr<'a> {
let indexing_code_len = self.indexing_code.len();
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
_,
_,
_,
ref mut s,
)) => match *s {
IndexingCodePtr::Fail if self.is_dynamic => {
*s = IndexingCodePtr::DynamicExternal(index);
break;
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, _, s)) => {
match *s {
IndexingCodePtr::Fail if self.is_dynamic => {
*s = IndexingCodePtr::DynamicExternal(index);
break;
}
IndexingCodePtr::Fail => {
*s = IndexingCodePtr::External(index);
break;
}
IndexingCodePtr::DynamicExternal(o) => {
*s = IndexingCodePtr::Internal(indexing_code_len - self.offset);
self.internalize_structure(IndexingCodePtr::DynamicExternal(o));
}
IndexingCodePtr::External(o) => {
*s = IndexingCodePtr::Internal(indexing_code_len - self.offset);
self.internalize_structure(IndexingCodePtr::External(o));
}
IndexingCodePtr::Internal(o) => {
self.offset += o;
}
}
IndexingCodePtr::Fail => {
*s = IndexingCodePtr::External(index);
break;
}
IndexingCodePtr::DynamicExternal(o) => {
*s = IndexingCodePtr::Internal(indexing_code_len - self.offset);
self.internalize_structure(IndexingCodePtr::DynamicExternal(o));
}
IndexingCodePtr::External(o) => {
*s = IndexingCodePtr::Internal(indexing_code_len - self.offset);
self.internalize_structure(IndexingCodePtr::External(o));
}
IndexingCodePtr::Internal(o) => {
self.offset += o;
}
},
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures)) => {
match structures.get(&key).cloned() {
None | Some(IndexingCodePtr::Fail) if self.is_dynamic => {
@@ -559,52 +555,50 @@ impl<'a> IndexingCodeMergingPtr<'a> {
let indexing_code_len = self.indexing_code.len();
match &mut self.indexing_code[self.offset] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, ref mut l, _)) => {
match *l {
IndexingCodePtr::Fail if self.is_dynamic => {
*l = IndexingCodePtr::DynamicExternal(index);
}
IndexingCodePtr::Fail => {
*l = IndexingCodePtr::External(index);
}
IndexingCodePtr::DynamicExternal(o) => {
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
let third_level_index = if self.append_or_prepend.is_append() {
vec![o, index].into()
} else {
vec![index, o].into()
};
self.indexing_code
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
}
IndexingCodePtr::External(o) => {
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
let third_level_index = if self.append_or_prepend.is_append() {
vec![
IndexedChoiceInstruction::Try(o),
IndexedChoiceInstruction::Trust(index),
]
.into()
} else {
vec![
IndexedChoiceInstruction::Try(index),
IndexedChoiceInstruction::Trust(o),
]
.into()
};
self.indexing_code
.push(IndexingLine::IndexedChoice(third_level_index));
}
IndexingCodePtr::Internal(o) => {
self.offset += o;
self.extend_indexed_choice(index);
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, l, _)) => match *l {
IndexingCodePtr::Fail if self.is_dynamic => {
*l = IndexingCodePtr::DynamicExternal(index);
}
}
IndexingCodePtr::Fail => {
*l = IndexingCodePtr::External(index);
}
IndexingCodePtr::DynamicExternal(o) => {
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
let third_level_index = if self.append_or_prepend.is_append() {
vec![o, index].into()
} else {
vec![index, o].into()
};
self.indexing_code
.push(IndexingLine::DynamicIndexedChoice(third_level_index));
}
IndexingCodePtr::External(o) => {
*l = IndexingCodePtr::Internal(indexing_code_len - self.offset);
let third_level_index = if self.append_or_prepend.is_append() {
vec![
IndexedChoiceInstruction::Try(o),
IndexedChoiceInstruction::Trust(index),
]
.into()
} else {
vec![
IndexedChoiceInstruction::Try(index),
IndexedChoiceInstruction::Trust(o),
]
.into()
};
self.indexing_code
.push(IndexingLine::IndexedChoice(third_level_index));
}
IndexingCodePtr::Internal(o) => {
self.offset += o;
self.extend_indexed_choice(index);
}
},
_ => {
unreachable!()
}
@@ -632,7 +626,7 @@ pub(crate) fn merge_clause_index(
);
match &opt_arg_index_key {
OptArgIndexKey::Literal(_, index_loc, constant, ref overlapping_constants) => {
OptArgIndexKey::Literal(_, index_loc, constant, overlapping_constants) => {
let offset = new_clause_loc - index_loc + 1;
merging_ptr.index_constant(HeapCellValue::from(*constant), offset);
@@ -676,20 +670,18 @@ pub(crate) fn remove_constant_indices(
let iter = once(&constant).chain(overlapping_constants.iter());
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, ref mut c, ..)) => {
match *c {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*c = IndexingCodePtr::Fail;
return;
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, c, ..)) => match *c {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*c = IndexingCodePtr::Fail;
return;
}
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
},
_ => {
unreachable!()
}
@@ -700,9 +692,7 @@ pub(crate) fn remove_constant_indices(
for constant in iter.map(|l| HeapCellValue::from(*l)) {
loop {
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
ref mut constants,
)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(constants)) => {
constants_index = index;
match constants.get(&constant).cloned() {
@@ -720,7 +710,7 @@ pub(crate) fn remove_constant_indices(
}
}
}
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::IndexedChoice(indexed_choice_instrs) => {
StaticCodeIndices::remove_instruction_with_offset(
indexed_choice_instrs,
offset,
@@ -734,13 +724,13 @@ pub(crate) fn remove_constant_indices(
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
_,
ref mut c,
c,
..,
)) => {
*c = ext;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
ref mut constants,
constants,
)) => {
constants.insert(constant, ext);
}
@@ -753,7 +743,7 @@ pub(crate) fn remove_constant_indices(
break;
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs) => {
DynamicCodeIndices::remove_instruction_with_offset(
indexed_choice_instrs,
offset,
@@ -767,13 +757,13 @@ pub(crate) fn remove_constant_indices(
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
_,
ref mut c,
c,
..,
)) => {
*c = ext;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(
ref mut constants,
constants,
)) => {
constants.insert(constant, ext);
}
@@ -794,11 +784,11 @@ pub(crate) fn remove_constant_indices(
}
match &indexing_code[constants_index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(ref constants))
IndexingLine::Indexing(IndexingInstruction::SwitchOnConstant(constants))
if constants.is_empty() =>
{
match &mut indexing_code[0] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, ref mut c, ..)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, c, ..)) => {
*c = IndexingCodePtr::Fail;
}
_ => {
@@ -819,20 +809,18 @@ pub(crate) fn remove_structure_index(
let mut index = 0;
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, _, ref mut s)) => {
match *s {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*s = IndexingCodePtr::Fail;
return;
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, _, s)) => match *s {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*s = IndexingCodePtr::Fail;
return;
}
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
},
_ => {
unreachable!()
}
@@ -842,7 +830,7 @@ pub(crate) fn remove_structure_index(
loop {
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref mut structures)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures)) => {
structures_index = index;
match structures.get(&(name, arity)).cloned() {
@@ -859,7 +847,7 @@ pub(crate) fn remove_structure_index(
}
}
}
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::IndexedChoice(indexed_choice_instrs) => {
StaticCodeIndices::remove_instruction_with_offset(indexed_choice_instrs, offset);
if indexed_choice_instrs.len() == 1 {
@@ -872,12 +860,12 @@ pub(crate) fn remove_structure_index(
_,
_,
_,
ref mut s,
s,
)) => {
*s = ext;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(
ref mut structures,
structures,
)) => {
structures.insert((name, arity), ext);
}
@@ -890,7 +878,7 @@ pub(crate) fn remove_structure_index(
break;
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs) => {
DynamicCodeIndices::remove_instruction_with_offset(indexed_choice_instrs, offset);
if indexed_choice_instrs.len() == 1 {
@@ -903,12 +891,12 @@ pub(crate) fn remove_structure_index(
_,
_,
_,
ref mut s,
s,
)) => {
*s = ext;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(
ref mut structures,
structures,
)) => {
structures.insert((name, arity), ext);
}
@@ -928,17 +916,11 @@ pub(crate) fn remove_structure_index(
}
match &indexing_code[structures_index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(ref structures))
IndexingLine::Indexing(IndexingInstruction::SwitchOnStructure(structures))
if structures.is_empty() =>
{
match &mut indexing_code[0] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
_,
_,
_,
ref mut s,
)) => {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, _, s)) => {
*s = IndexingCodePtr::Fail;
}
_ => {
@@ -954,27 +936,25 @@ pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usiz
let mut index = 0;
match &mut indexing_code[index] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, ref mut l, _)) => {
match *l {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*l = IndexingCodePtr::Fail;
return;
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, _, _, l, _)) => match *l {
IndexingCodePtr::DynamicExternal(_) | IndexingCodePtr::External(_) => {
*l = IndexingCodePtr::Fail;
return;
}
}
IndexingCodePtr::Internal(o) => {
index += o;
}
IndexingCodePtr::Fail => {
return;
}
},
_ => {
unreachable!()
}
}
match &mut indexing_code[index] {
IndexingLine::IndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::IndexedChoice(indexed_choice_instrs) => {
StaticCodeIndices::remove_instruction_with_offset(indexed_choice_instrs, offset);
if indexed_choice_instrs.len() == 1 {
@@ -986,7 +966,7 @@ pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usiz
_,
_,
_,
ref mut l,
l,
_,
)) => {
*l = ext;
@@ -998,7 +978,7 @@ pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usiz
}
}
}
IndexingLine::DynamicIndexedChoice(ref mut indexed_choice_instrs) => {
IndexingLine::DynamicIndexedChoice(indexed_choice_instrs) => {
DynamicCodeIndices::remove_instruction_with_offset(indexed_choice_instrs, offset);
if indexed_choice_instrs.len() == 1 {
@@ -1010,7 +990,7 @@ pub(crate) fn remove_list_index(indexing_code: &mut [IndexingLine], offset: usiz
_,
_,
_,
ref mut l,
l,
_,
)) => {
*l = ext;
@@ -1034,7 +1014,7 @@ pub(crate) fn remove_index(
clause_loc: usize,
) {
match opt_arg_index_key {
OptArgIndexKey::Literal(_, _, constant, ref overlapping_constants) => {
OptArgIndexKey::Literal(_, _, constant, overlapping_constants) => {
remove_constant_indices(*constant, *overlapping_constants, indexing_code, clause_loc);
}
OptArgIndexKey::Structure(_, _, name, arity) => {
@@ -1328,9 +1308,7 @@ impl Indexer for DynamicCodeIndices {
for (key, code) in indices.into_iter() {
if code.len() > 1 {
index_locs.insert(key, IndexingCodePtr::Internal(prelude.len() + 1));
prelude.push_back(IndexingLine::DynamicIndexedChoice(
code.into_iter().collect(),
));
prelude.push_back(IndexingLine::DynamicIndexedChoice(code));
} else if let Some(i) = code.front() {
index_locs.insert(key, IndexingCodePtr::DynamicExternal(*i));
}
@@ -1369,9 +1347,7 @@ impl Indexer for DynamicCodeIndices {
) -> IndexingCodePtr {
if lists.len() > 1 {
let lists = std::mem::take(lists);
prelude.push_back(IndexingLine::DynamicIndexedChoice(
lists.into_iter().collect(),
));
prelude.push_back(IndexingLine::DynamicIndexedChoice(lists));
IndexingCodePtr::Internal(1)
} else {
lists
@@ -1529,11 +1505,11 @@ impl<I: Indexer> CodeOffsets<I> {
&mut prelude,
);
if let IndexingCodePtr::Internal(ref mut i) = &mut str_loc {
if let IndexingCodePtr::Internal(i) = &mut str_loc {
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
}
if let IndexingCodePtr::Internal(ref mut i) = &mut lst_loc {
if let IndexingCodePtr::Internal(i) = &mut lst_loc {
*i += emitted_switch_on_constant as usize; // con_loc.is_internal() as usize;
*i += emitted_switch_on_structure as usize; // str_loc.is_internal() as usize;
}
@@ -1548,6 +1524,6 @@ impl<I: Indexer> CodeOffsets<I> {
str_loc,
)));
prelude.into_iter().collect()
prelude.into()
}
}

1386
src/instructions.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@ use std::cell::Cell;
use std::collections::VecDeque;
use std::iter::*;
use std::rc::Rc;
use std::sync::Arc;
use std::vec::Vec;
#[allow(clippy::borrowed_box)]
@@ -89,7 +90,7 @@ impl<'a> QueryIterator<'a> {
| Term::CompleteString(..) => {
return QueryIterator {
state_stack: vec![],
}
};
}
Term::Clause(r, name, terms) => TermIterState::Clause(Level::Root, 0, r, *name, terms),
Term::Var(cell, var_ptr) => TermIterState::Var(Level::Root, cell, var_ptr.clone()),
@@ -102,11 +103,11 @@ impl<'a> QueryIterator<'a> {
fn extend_state(&mut self, lvl: Level, term: &'a QueryTerm) {
match term {
QueryTerm::Clause(ref cell, ClauseType::CallN(_), ref terms, _) => {
QueryTerm::Clause(cell, ClauseType::CallN(_), terms, _) => {
self.state_stack
.push(TermIterState::Clause(lvl, 1, cell, atom!("$call"), terms));
}
QueryTerm::Clause(ref cell, ref ct, ref terms, _) => {
QueryTerm::Clause(cell, ct, terms, _) => {
self.state_stack
.push(TermIterState::Clause(lvl, 0, cell, ct.name(), terms));
}
@@ -142,7 +143,7 @@ impl<'a> Iterator for QueryIterator<'a> {
return match lvl {
Level::Root => None,
lvl => Some(TermRef::Clause(lvl, cell, name, child_terms)),
}
};
}
};
} else {
@@ -292,7 +293,7 @@ impl<'a> Iterator for FactIterator<'a> {
return Some(TermRef::CompleteString(lvl, cell, atom));
}
TermIterState::Literal(lvl, cell, constant) => {
return Some(TermRef::Literal(lvl, cell, constant))
return Some(TermRef::Literal(lvl, cell, constant));
}
TermIterState::Var(lvl, cell, var_ptr) => {
return Some(TermRef::Var(lvl, cell, var_ptr));
@@ -319,15 +320,28 @@ pub(crate) fn breadth_first_iter(
#[derive(Debug, Copy, Clone)]
enum ClauseIteratorState<'a> {
RemainingChunks(&'a VecDeque<ChunkedTerms>, usize),
RemainingBranches(&'a Vec<VecDeque<ChunkedTerms>>, usize),
RemainingBranches(
&'a Vec<Arc<BranchNumber>>,
&'a Vec<VecDeque<ChunkedTerms>>,
usize,
),
}
#[derive(Debug, Clone)]
pub(crate) enum ClauseItem<'a> {
FirstBranch(usize),
NextBranch,
BranchEnd(usize),
Chunk { terms: &'a VecDeque<QueryTerm> },
FirstBranch {
branch_num: &'a Arc<BranchNumber>,
num_branches: usize,
},
NextBranch {
branch_num: &'a Arc<BranchNumber>,
},
BranchEnd {
depth: usize,
},
Chunk {
terms: &'a VecDeque<QueryTerm>,
},
}
#[derive(Debug)]
@@ -338,8 +352,8 @@ pub(crate) struct ClauseIterator<'a> {
fn state_from_chunked_terms(chunk_vec: &VecDeque<ChunkedTerms>) -> ClauseIteratorState<'_> {
if chunk_vec.len() == 1 {
if let Some(ChunkedTerms::Branch(ref branches)) = chunk_vec.front() {
return ClauseIteratorState::RemainingBranches(branches, 0);
if let Some(ChunkedTerms::Branch { branch_nums, arms }) = chunk_vec.front() {
return ClauseIteratorState::RemainingBranches(branch_nums, arms, 0);
}
}
@@ -370,7 +384,9 @@ impl<'a> ClauseIterator<'a> {
while let Some(state) = self.state_stack.pop() {
match state {
ClauseIteratorState::RemainingBranches(terms, focus) if terms.len() == focus => {
ClauseIteratorState::RemainingBranches(_branch_nums, terms, focus)
if terms.len() == focus =>
{
depth += 1;
}
_ => {
@@ -399,11 +415,11 @@ impl<'a> Iterator for ClauseIterator<'a> {
}
match &chunks[focus] {
ChunkedTerms::Branch(branches) => {
ChunkedTerms::Branch { branch_nums, arms } => {
self.state_stack
.push(ClauseIteratorState::RemainingBranches(branches, 0));
.push(ClauseIteratorState::RemainingBranches(branch_nums, arms, 0));
}
ChunkedTerms::Chunk { ref terms } => {
ChunkedTerms::Chunk { terms } => {
return Some(ClauseItem::Chunk { terms });
}
}
@@ -411,11 +427,15 @@ impl<'a> Iterator for ClauseIterator<'a> {
ClauseIteratorState::RemainingChunks(chunks, focus) => {
debug_assert_eq!(chunks.len(), focus);
}
ClauseIteratorState::RemainingBranches(branches, focus)
ClauseIteratorState::RemainingBranches(branch_nums, branches, focus)
if focus < branches.len() =>
{
self.state_stack
.push(ClauseIteratorState::RemainingBranches(branches, focus + 1));
.push(ClauseIteratorState::RemainingBranches(
branch_nums,
branches,
focus + 1,
));
let state = state_from_chunked_terms(&branches[focus]);
if let ClauseIteratorState::RemainingChunks(..) = &state {
@@ -425,14 +445,21 @@ impl<'a> Iterator for ClauseIterator<'a> {
self.state_stack.push(state);
return if focus == 0 {
Some(ClauseItem::FirstBranch(branches.len()))
Some(ClauseItem::FirstBranch {
branch_num: &branch_nums[0],
num_branches: branches.len(),
})
} else {
Some(ClauseItem::NextBranch)
Some(ClauseItem::NextBranch {
branch_num: &branch_nums[focus],
})
};
}
ClauseIteratorState::RemainingBranches(branches, focus) => {
ClauseIteratorState::RemainingBranches(_branch_nums, branches, focus) => {
debug_assert_eq!(branches.len(), focus);
return Some(ClauseItem::BranchEnd(self.branch_end_depth()));
return Some(ClauseItem::BranchEnd {
depth: self.branch_end_depth(),
});
}
}
}

View File

@@ -29,10 +29,10 @@ pub(crate) mod heap_print;
mod http;
mod indexing;
mod variable_records;
#[macro_use]
pub(crate) mod instructions {
include!(concat!(env!("OUT_DIR"), "/instructions.rs"));
}
pub(crate) mod instructions;
mod iterators;
pub(crate) mod machine;
mod raw_block;
@@ -43,9 +43,9 @@ mod targets;
pub(crate) mod types;
// Re-exports
pub use machine::Machine;
pub use machine::config::*;
pub use machine::lib_machine::*;
pub use machine::Machine;
#[cfg(target_arch = "wasm32")]
pub mod wasm;
@@ -54,6 +54,8 @@ pub mod wasm;
/// The entry point for the Scryer Prolog CLI.
pub fn run_binary() -> std::process::ExitCode {
use crate::atom_table::Atom;
#[cfg(feature = "repl")]
use crate::machine::INTERRUPT;
#[cfg(feature = "repl")]

View File

@@ -59,7 +59,7 @@ get_attrs_var_check(Module) -->
!,
'$get_attr_list'(Var, Ls),
nonvar(Ls),
atts:'$copy_attr_list'(Ls, Module, Attr))].
atts:'$copy_attr_list'(Ls, Module, Attr))].
put_attrs(Name/Arity, Module) -->
put_attr(Name, Arity, Module),

View File

@@ -12,7 +12,7 @@ integers that may also be interesting.
:- use_module(library(lists), [length/2]).
:- use_module(library(error)).
%% between(+Lower, +Upper, -X).
%% between(+Lower, +Upper, ?X).
%
% Given Lower and Upper are both integer numbers, true iff X is an integer so that _Lower =< X =< Upper_.
% Can be used both to check if X is between Lower and Upper or to generate an integer between

View File

@@ -944,65 +944,6 @@ findall(Template, Goal, Solutions0, Solutions1) :-
builtins:findall_cleanup(LhLength, Error)
).
:- non_counted_backtracking set_difference/3.
set_difference([X|Xs], [Y|Ys], Zs) :-
X == Y, !, set_difference(Xs, [Y|Ys], Zs).
set_difference([X|Xs], [Y|Ys], [X|Zs]) :-
X @< Y, !, set_difference(Xs, [Y|Ys], Zs).
set_difference([X|Xs], [Y|Ys], Zs) :-
X @> Y, !, set_difference([X|Xs], Ys, Zs).
set_difference([], _, []) :- !.
set_difference(Xs, [], Xs).
% variant/2 checks whether X is a variant of Y per the definition in
% 7.1.6.1 of the ISO standard.
:- non_counted_backtracking variant/4.
variant(X,Y,VPs,VPs0) :-
( var(X) ->
var(Y),
VPs = [X-Y|VPs0]
; var(Y) ->
false
; X =.. [FX | XArgs],
Y =.. [FX | YArgs],
lists:foldl('$call'(builtins:variant), XArgs, YArgs, VPs, VPs0)
).
:- non_counted_backtracking variant/2.
singleton([_]).
variant(X, Y) :-
variant(X,Y, VPs, []),
keysort(VPs, SVPs),
pairs:group_pairs_by_key(SVPs, SVPKs),
pairs:pairs_values(SVPKs, Vals),
lists:maplist('$call'(builtins:term_variables), Vals, Vs),
lists:maplist('$call'(builtins:singleton), Vs),
term_variables(Vs, YVars),
lists:length(SVPKs, N),
lists:length(YVars, N).
:- non_counted_backtracking group_by_variant/4.
group_by_variant([V2-S2 | Pairs], V1-S1, [S2 | Solutions], Pairs0) :-
variant(V1, V2),
!,
V1 = V2,
group_by_variant(Pairs, V2-S2, Solutions, Pairs0).
group_by_variant(Pairs, _, [], Pairs).
:- non_counted_backtracking group_by_variants/2.
group_by_variants([V-S|Pairs], [V-Solution|Solutions]) :-
group_by_variant([V-S|Pairs], V-S, Solution, Pairs0),
group_by_variants(Pairs0, Solutions).
group_by_variants([], []).
:- non_counted_backtracking iterate_variants/3.
@@ -1035,9 +976,7 @@ findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses)
( Goal1 = _ ^ _ ) ->
rightmost_power(Goal1, Goal2, ExistentialVars0),
term_variables(ExistentialVars0, ExistentialVars),
sort(Witnesses0, Witnesses1),
sort(ExistentialVars, ExistentialVars1),
set_difference(Witnesses1, ExistentialVars1, Witnesses),
lists:append(Witnesses0, Witnesses, ExistentialVars),
expand_goal(M:Goal2, M, Goal3),
findall(Witnesses-Template, Goal3, PairedSolutions)
; Witnesses = Witnesses0,
@@ -1045,6 +984,34 @@ findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses)
).
:- non_counted_backtracking split_by_variant/4.
:- non_counted_backtracking split_by_variant/3.
:- non_counted_backtracking unify_variant_variables/2.
split_by_variant([V2-S2 | Pairs], V1-S1, Solutions, Rest) :-
( V1 == V2 ->
Solutions = [S2 | Solutions1],
split_by_variant(Pairs, V1-S1, Solutions1, Rest)
; Solutions = [],
Rest = [V2-S2 | Pairs]
).
split_by_variant([], _, [], []).
split_by_variant([V-S|Pairs], Ws, Solutions) :-
split_by_variant(Pairs, V-S, Solutions0, Rest),
( Rest == [] -> V = Ws, Solutions = [S|Solutions0]
; V = Ws, Solutions = [S|Solutions0]
; split_by_variant(Rest, Ws, Solutions)
).
unify_variant_variables([], _Dict).
unify_variant_variables([V-_S|Pairs], Dict) :-
term_variables(V, VVars),
lists:append(VVars, _, Dict),
unify_variant_variables(Pairs, Dict).
:- meta_predicate(bagof(?, 0, ?)).
:- non_counted_backtracking bagof/3.
@@ -1074,22 +1041,10 @@ bagof(Template, Goal, Solution) :-
term_variables(Goal, GoalVars),
term_variables(TemplateVars+GoalVars, TGVs),
lists:append(TemplateVars, Witnesses0, TGVs),
findall_with_existential(Template, Goal, PairedSolutions0, Witnesses0, Witnesses),
keysort(PairedSolutions0, PairedSolutions),
group_by_variants(PairedSolutions, GroupedSolutions),
iterate_variants(GroupedSolutions, Witnesses, Solution).
:- non_counted_backtracking iterate_variants_and_sort/3.
iterate_variants_and_sort([V-Solution0|GroupSolutions], V, Solution) :-
sort(Solution0, Solution1),
Solution1 = Solution,
( GroupSolutions == [] -> !
; true
).
iterate_variants_and_sort([_|GroupSolutions], Ws, Solution) :-
iterate_variants_and_sort(GroupSolutions, Ws, Solution).
findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses),
unify_variant_variables(PairedSolutions, _Dict),
keysort(PairedSolutions, PairedSolutions1),
split_by_variant(PairedSolutions1, Witnesses, Solution).
:- meta_predicate(setof(?, 0, ?)).
@@ -1112,10 +1067,10 @@ setof(Template, Goal, Solution) :-
term_variables(Goal, GoalVars),
term_variables(TemplateVars+GoalVars, TGVs),
lists:append(TemplateVars, Witnesses0, TGVs),
findall_with_existential(Template, Goal, PairedSolutions0, Witnesses0, Witnesses),
'$keysort_with_constant_var_ordering'(PairedSolutions0, PairedSolutions), % see 7.2.1
group_by_variants(PairedSolutions, GroupedSolutions),
iterate_variants_and_sort(GroupedSolutions, Witnesses, Solution).
findall_with_existential(Template, Goal, PairedSolutions, Witnesses0, Witnesses),
unify_variant_variables(PairedSolutions, _Dict),
sort(PairedSolutions, PairedSolutions1),
split_by_variant(PairedSolutions1, Witnesses, Solution).
% Clause retrieval and information.
@@ -1551,7 +1506,7 @@ atom_chars(Atom, List) :-
%% atom_codes(?Atom, ?Codes).
%
% Relates an atom with a string in codes representation. It can be used to convert
% between atoms and strings. However, codes is not the default representation of double quoutes
% between atoms and strings. However, codes is not the default representation of double quoted
% strings in Scryer Prolog. Examples:
%
% ```

View File

@@ -3,7 +3,7 @@
Author: Markus Triska
E-mail: triska@metalevel.at
WWW: https://www.metalevel.at
Copyright (C): 2016-2024 Markus Triska
Copyright (C): 2016-2026 Markus Triska
This library provides CLP():
@@ -6844,6 +6844,22 @@ gcc_global(Vs, KNs) -->
% the propagator of tuples_in/2). Hence: We need this only if
% an example shows it, ideally found by a systematic search
% that can be used to test the implementation.
%
% UPDATE: A case that requires gcc_check//1 was recently found,
% see: https://github.com/mthom/scryer-prolog/issues/3369.
%
% We need to study carefully what exactly happens in this case.
% The example involves reified constraints, which post constraints
% in their propagators. The example also involves multiple queues,
% because not all reified constraints use variables_same_queue/1
% (for instance (#==>)/2 does not), and variables_same_queue/1 would
% in fact need changes to handle cases where variables no longer
% have a clpz attribute attached. Such cases arise for variables
% involved in entailed reified constraints.
gcc_check(KNs), % see comment above.
do_queue, % is this needed? this (also) needs more analysis.
{ with_local_attributes(Vs,
(gcc_arcs(KNs, S, Vals),
variables_with_num_occurrences(Vs, VNs),

View File

@@ -11,34 +11,75 @@ The main predicate is `use_foreign_module/2`. It takes a library name (which dep
operating system could be a `.so`, `.dylib` or `.dll` file). and a list of functions. Each
function is defined by its name, a list of the type of the arguments, and the return argument.
Types available are: `sint8`/`i8`, `uint8`/`u8`, `sint16`/`i16`, `uint16`/`u16`, `sint32`/`i32`, `uint32`/`u32`, `sint64`/`i64`,
`uint64`/`u64`, `f32`, `f64`, `cstr`, `void`, `bool`, `ptr` and custom structs, which can be defined
with `foreign_struct/2`.
After that, each function on the lists maps to a predicate created in the ffi module which
are used to call the native code.
The predicate takes the functor name after the function name. Then, the arguments are the input
arguments followed by a return argument. However, functions with return type `void` or `bool`
don't have that return argument. Predicates with `void` always succeed and `bool` predicates depend
on the return value on the native side.
For each function in the list a predicate of the same name is generated in the ffi module which
can then be used to call the native code.
The predicates arguments are the input arguments of the foreign function and depending on the return type an extra argument for the return value.
Functions with return type `void` or `bool` don't have this extra argument.
Predicates for functions with return type `void` always succeed.
Predicates for functions with retun type `bool` succeed iff the return value is 1.
```
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN, -ReturnArg). % for all return types except void and bool
ffi:FUNCTION_NAME(+InputArg1, ..., +InputArgN). % for void and bool
```
## Notes regarding cstr
## Available types are
### Basic C Types
[C Types Reference](https://en.cppreference.com/w/c/language/types.html)
- `void`,
- `char`, `uchar`, `schar`
- `short`, `ushort`
- `int`, `uint`
- `long`, `ulong`,
- `longlong`, `ulonglong`,
- `float`, `double`
### Fixed Width Integer Types
[C Fixed With Integer Types Reference](https://en.cppreference.com/w/c/types/integer.html)
- `sint8`/`i8`, `uint8`/`u8`,
- `sint16`/`i16`, `uint16`/`u16`,
- `sint32`/`i32`, `uint32`/`u32`,
- `sint64`/`i64`, `uint64`/`u64`,
### Fixed Width Floating-Point Types
[C++ Fixed Width Floating-Point Types Reference](https://en.cppreference.com/w/cpp/types/floating-point.html)
- `f32`, `f64`
### Other Types
- `cstr`,
- `ptr`,
- `bool` and,
- custom structs, which can be defined with `foreign_struct/2`.
### Notes regarding bool
- Not necessarily compatible with the fundamental C type bool.
- Same as `i8` but only values 0 and 1 are valid values.
### Notes regarding cstr
- When using `cstr` as an argument type the string will be deallocated once the function returns.
- When using `cstr` as a return type the string will be copied and won't be deallocated.
- When an ffi function returns bytes that are not a valid utf8-string the bytes will be turned into a list of `codes` (integers)
instead of a string (list of `chars`). Note: passing a list of `codes` is not accepted in argument position.
- In argument position you can also pass a pointer directly instead of a string,
e.g. to pass a null-pointer one can provide the integer 0 as the argument.
- In return position a null-pointer will be returned as the integer 0
## Example
For example, let's see how to define a function from the [raylib](https://www.raylib.com/) library.
```
?- use_foreign_module("./libraylib.so", ['InitWindow'([sint32, sint32, cstr], void)]).
?- use_foreign_module("./libraylib.so", ['InitWindow'([int, int, cstr], void)]).
```
This creates a `'InitWindow'` predicate under the ffi module. Now, we can call it:
@@ -143,7 +184,7 @@ allocate(Allocator, Type, Args, Ptr) :-
%
% Read a value of Type from the pointer Ptr and unify the read value with Value
%
% For type cstr take read a nul-terminated utf-8 string starting at Ptr.
% For type cstr read a nul-terminated utf-8 string starting at Ptr.
%
read_ptr(Type, Ptr, Value) :-
must_be(atom, Type),
@@ -163,7 +204,7 @@ deallocate(Allocator, Type, Ptr) :-
%% array_type(+ElemType, +Len, -ArrayType)
%
% unify the ffi type for an array of lenth Len with element type ElemType with ArrayType
% unify the ffi type for an array of length Len with element type ElemType with ArrayType
%
array_type(ElemType, Len, ArrayType) :-
(Len =< 0 -> domain_error(greater_than_zero, Len, array_type/3); true),
@@ -183,7 +224,7 @@ array_type(ElemType, Len, ArrayType) :-
% Allocate the Locals, evaluate the Goal and deallocate the Locals.
% The Locals will also be cleandup when Goal fails or throws an error.
%
% Locals is a list of local variable definitions let(-Ptr, +Type, +Args).
% Locals is a list of local variable definitions `let(-Ptr, +Type, +Args)`.
% Ptr will be unified with the pointer to the local of Type initialized with Args.
%
with_locals(Locals, Goal) :-

View File

@@ -112,12 +112,29 @@ module_qualification(M, H0, H) :-
H0 =.. [Method, Path, Goal],
H =.. [Method, Path, M:Goal].
http_listen__(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit) :-
'$http_listen'(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit).
http_listen_stop_(HttpListener) :-
'$http_listen_stop'(HttpListener).
http_accept_(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle) :-
'$http_accept'(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle).
http_answer_(ResponseHandle, Code, Headers, ResponseStream) :-
'$http_answer'(ResponseHandle, Code, Headers, ResponseStream).
http_listen_(Port, Handlers, Options) :-
parse_options(Options, TLSKey, TLSCert, ContentLengthLimit),
phrase(format_("0.0.0.0:~d", [Port]), Addr),
'$http_listen'(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit),!,
format("Listening at ~s\n", [Addr]),
http_loop(HttpListener, Handlers).
setup_call_cleanup(
(
http_listen__(Addr, HttpListener, TLSKey, TLSCert, ContentLengthLimit),
format("Listening at http://~s\n", [Addr])
),
http_loop(HttpListener, Handlers),
http_listen_stop_(HttpListener)
).
parse_options(Options, TLSKey, TLSCert, ContentLengthLimit) :-
member_option_default(tls_key, Options, "", TLSKey),
@@ -132,36 +149,55 @@ member_option_default(Key, List, Default, Default) :-
X =.. [Key, _],
\+ member(X, List).
http_loop(HttpListener, Handlers) :-
'$http_accept'(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle),
current_time(Time),
phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString),
format("~s ~w ~s\n", [TimeString, RequestMethod, RequestPath]),
maplist(map_header_kv, RequestHeaders, RequestHeadersKV),
phrase(parse_queries(RequestQueries), RequestQuery),
(
match_handler(Handlers, RequestMethod, RequestPath, Handler) ->
(
HttpRequest = http_request(RequestHeadersKV, stream(RequestStream), RequestQueries),
HttpResponse = http_response(_, _, _),
(call(Handler, HttpRequest, HttpResponse) ->
send_response(ResponseHandle, HttpResponse)
; (
'$http_answer'(ResponseHandle, 500, [], ResponseStream),
call_cleanup(format(ResponseStream, "Internal Server Error", []), close(ResponseStream)))
)
)
; (
'$http_answer'(ResponseHandle, 404, [], ResponseStream),
call_cleanup(format(ResponseStream, "Not Found", []), close(ResponseStream)))
),
time((
http_accept_(HttpListener, RequestMethod, RequestPath, RequestHeaders, RequestQuery, RequestStream, ResponseHandle),
current_time(Time),
phrase(format_time("%Y-%m-%d (%H:%M:%S)", Time), TimeString),
format("~s ~w ~s", [TimeString, RequestMethod, RequestPath]),
maplist(map_header_kv, RequestHeaders, RequestHeadersKV),
phrase(parse_queries(RequestQueries), RequestQuery),
(
match_handler(Handlers, RequestMethod, RequestPath, Handler) ->
(
HttpRequest = http_request(RequestHeadersKV, stream(RequestStream), RequestQueries),
HttpResponse = http_response(_, _, _),
catch(
(call(Handler, HttpRequest, HttpResponse) ->
send_response(ResponseHandle, HttpResponse)
;
setup_call_cleanup(
http_answer_(ResponseHandle, 500, [], ResponseStream),
format(ResponseStream, "Internal Server Error", []),
close(ResponseStream)
),
throw(handler_not_available(Handler, RequestMethod, RequestPath, RequestQuery, RequestHeaders))
),
HandlerError,
(
setup_call_cleanup(
http_answer_(ResponseHandle, 500, [], ResponseStream),
format(ResponseStream, "Internal Server Error", []),
close(ResponseStream)
),
throw(HandlerError)
)
)
)
;
setup_call_cleanup(
http_answer_(ResponseHandle, 404, [], ResponseStream),
format(ResponseStream, "Not Found", []),
close(ResponseStream)
)
)
)),
http_loop(HttpListener, Handlers).
send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream0),
http_answer_(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream0),
open(stream(ResponseStream0), write, ResponseStream, [type(text)]),
catch(
call_cleanup(format(ResponseStream, "~s", [ResponseText]),close(ResponseStream)),
@@ -172,9 +208,9 @@ send_response(ResponseHandle, http_response(StatusCode0, text(ResponseText), Res
send_response(ResponseHandle, http_response(StatusCode0, bytes(ResponseBytes), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
http_answer_(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
catch(
call_cleanup(format(ResponseStream, "~s", [ResponseBytes]),close(ResponseStream)),
call_cleanup(format(ResponseStream, "~s", [ResponseBytes]),close(ResponseStream)),
error(existence_error(stream, _), _),
true
).
@@ -182,7 +218,7 @@ send_response(ResponseHandle, http_response(StatusCode0, bytes(ResponseBytes), R
send_response(ResponseHandle, http_response(StatusCode0, file(Filename), ResponseHeaders0)) :-
default(StatusCode0, 200, StatusCode),
maplist(map_header_kv_2, ResponseHeaders, ResponseHeaders0),
'$http_answer'(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
http_answer_(ResponseHandle, StatusCode, ResponseHeaders, ResponseStream),
catch(
call_cleanup(
setup_call_cleanup(

View File

@@ -2,7 +2,7 @@
List manipulation predicates
*/
:- module(lists, [member/2, select/3, append/2, append/3, foldl/4, foldl/5,
:- module(lists, [member/2, select/3, append/2, append/3, foldl/4, foldl/5, foldl/6,
memberchk/2, reverse/2, length/2, maplist/2,
maplist/3, maplist/4, maplist/5, maplist/6,
maplist/7, maplist/8, maplist/9, same_length/2, nth0/3, nth0/4, nth1/3, nth1/4,
@@ -314,6 +314,16 @@ foldl(G_4, [X|Xs], [Y|Ys], A0, A) :-
call(G_4, X, Y, A0, A1),
foldl(G_4, Xs, Ys, A1, A).
%% foldl(+Goal, ?Ls0, ?Ls1, ?Ls2, +A0, ?A).
%
% Like `foldl/4`, with 2 additional lists.
foldl(_, [], [], [], A, A).
foldl(G_5, [X|Xs], [Y|Ys], [Z|Zs], A0, A) :-
call(G_5, X, Y, Z, A0, A1),
foldl(G_5, Xs, Ys, Zs, A1, A).
%% transpose(?Ls, ?Ts).
%
% If Ls is a list of lists, Ts contains the transposition

View File

@@ -1,5 +1,7 @@
% Efforts toward literate tests with quads
:- module(quadtests, [check_module_quads/2]).
:- use_module(library(iso_ext)).
:- use_module(library(pio)).
:- use_module(library(lists)).
@@ -10,9 +12,8 @@
:- use_module(library(lambda)).
:- use_module(library(error)).
:- use_module(library(time)).
:- use_module(testutils).
:- use_module(special_functions).
:- use_module(library('numerics/testutils')).
:- use_module(library('numerics/special_functions')).
portray_term(Stream) :-
read_term(Stream, Term, []),
@@ -39,7 +40,7 @@ check_module_quads(Module, Quads) :-
zip(Qs, ADs, Quads),
length(Qs, NQ),
format("% Checking ~d quads ..~n", [NQ]),
maplist(check_qu_ad, Qs, ADs).
maplist(check_qu_ad(Module), Qs, ADs).
read_quads(Module, Quads) :-
module_terms(Module, Terms),
@@ -70,6 +71,7 @@ term_type(Term-_, Type) :-
( Term = (?- _) -> Type = query
; Term = (_,_) -> Type = answer_description
; Term = (_;_) -> Type = answer_description
; Term = (_ = _) -> Type = answer_description
; Term == true -> Type = answer_description
; Term == false -> Type = answer_description
; Type = clause
@@ -114,22 +116,22 @@ zip([], [], []).
Xs = [1,2,3], Ys = [4,5,6].
% 3. Demonstrate checking 1 quad, the top two elements of a QAs list.
check_qu_ad(Q-QVN, A-AVN) :-
check_qu_ad(Module, Q-QVN, A-AVN) :-
Q = ?-(G),
phrase(portray_clause_(Q), LitQ), % NB: LitQ terminates w/ newline
format("% CHECKING.. ",[]),
( A == true -> call(G)
; A == false -> ( call(G) -> false
( A == true -> call(Module:G)
; A == false -> ( call(Module:G) -> false
; true
)
; phrase(unconj(A), As) ->
( length(As, N),
n_answers(N, A, AVN, ADs),
n_answers(N, G, QVN, Answers),
n_answers(N, Module:G, QVN, Answers),
maplist(contains, ADs, Answers)
)
; % Otherwise, we have the ',' case of a solitary answer
call(G),
call(Module:G),
call(A),
QVN == AVN
),
@@ -141,7 +143,7 @@ contains(AD, Answer) :- append(Answer, _, AD).
?- contains(['Xs'=[C],'L'=1,'_A'=C,'_B'=D], ['Xs'=[A],'L'=1]).
C = A.
?- check_qu_ad((?-length(_F,_G))-['Xs'=_F,'L'=_G],(_H=[],_I=0;_H=[_J],_I=1;_H=[_J,_K],_I=2;...)-['Xs'=_H,'L'=_I,'_A'=_J,'_B'=_K]).
?- check_qu_ad(quadtests, (?-length(_F,_G))-['Xs'=_F,'L'=_G],(_H=[],_I=0;_H=[_J],_I=1;_H=[_J,_K],_I=2;...)-['Xs'=_H,'L'=_I,'_A'=_J,'_B'=_K]).
% CHECKING.. (?-length(A,B)).
_F = [_A,_B], _G = 2, _H = [_J,_K], _I = 2.
@@ -185,3 +187,4 @@ n_answers_(N, G, VN, ADs) :-
?- n_answers(3, length(Xs, L), ('Xs'=Xs,'Len'=L), ADs).
Xs = [_A,_B], L = 2, ADs = [('Xs'=[],'Len'=0),('Xs'=[_C],'Len'=1),('Xs'=[_D,_E],'Len'=2)].

View File

@@ -95,10 +95,12 @@ process_wait(Process, Status) :- call_with_error_context(process_wait(Process, S
% `Options` is a a list of the following options
%
% * timeout(Timeout) supported values for `Timeout` are 0 or `infinite`
% * release(Bool) supported values for `Bool` are `true` or `false`
%
% Each options may be specified at most once, when an option is not specified the following defaults apply:
%
% - timeout(infinite)
% - release(true)
%
process_wait(Process, Status, Options) :- call_with_error_context(process_wait_(Process, Status, Options), predicate-process_wait/3).
@@ -106,17 +108,29 @@ process_wait_(Process, Status, Options) :-
valid_process(Process),
check_options(
[
option([timeout], valid_timeout, timeout(infinite), timeout(Timeout))
option([timeout], valid_timeout, timeout(infinite), timeout(Timeout)),
option([release], valid_release, release(true), release(Release))
],
Options,
process_wait_option
),
'$process_wait'(Process, Exit, Timeout),
((true = Release) -> '$process_release'(Process) ; true),
Exit = Status.
valid_timeout(timeout(infinite)).
valid_timeout(timeout(0)).
valid_release(release(Arg)) :-
( var(Arg) -> instantiation_error([])
; valid_bool(Arg) -> true
; domain_error(boolean, Arg, [])
).
valid_bool(true).
valid_bool(false).
%% process_kill(+Process).
%
@@ -141,8 +155,7 @@ process_release(Process) :- call_with_error_context(process_release_(Process), p
process_release_(Process) :-
valid_process(Process),
process_wait(Process, _),
'$process_release'(Process).
process_wait(Process, _).
must_be_known_options(Valid, Options, Domain) :- call_with_error_context(must_be_known_options_(Valid, [], Options, Domain),predicate-must_be_known_options/3).

View File

@@ -40,6 +40,7 @@
term_si/1,
chars_si/1,
dif_si/2,
not_si/1,
when_si/2]).
:- use_module(library(lists)).
@@ -100,6 +101,17 @@ dif_si(X, Y) :-
; throw(error(instantiation_error,dif_si/2))
).
%% not_si(+Goal).
%
% True if Goal is not provable. Instantiation error if Goal is not
% ground.
:- meta_predicate(not_si(0)).
not_si(Goal) :-
term_si(Goal),
\+ Goal.
:- meta_predicate(when_si(+, 0)).
%% when_si(Condition, Goal).

View File

@@ -1,4 +1,3 @@
use std::collections::BTreeSet;
use std::env;
#[derive(Debug)]
@@ -8,9 +7,8 @@ pub struct MachineArgs {
impl MachineArgs {
pub fn new() -> Self {
let args: BTreeSet<String> = env::args().collect();
Self {
add_history: !args.contains("--no-add-history"),
add_history: env::args().all(|arg| arg != "--no-add-history"),
}
}
}

View File

@@ -1,6 +1,6 @@
use dashu::base::{Abs, Gcd, Signed, UnsignedAbs};
use dashu::integer::fast_div::ConstDivisor;
use dashu::integer::IBig;
use dashu::integer::fast_div::ConstDivisor;
use divrem::*;
use num_order::NumOrd;
@@ -21,7 +21,6 @@ use ordered_float::{Float, OrderedFloat};
use std::cmp;
use std::convert::TryFrom;
use std::f64;
use std::mem;
macro_rules! try_numeric_result {
($e: expr, $stub_gen: expr) => {
@@ -1120,23 +1119,18 @@ pub(crate) fn bitwise_complement(n1: Number, arena: &mut Arena) -> Result<Number
impl MachineState {
#[inline]
pub fn get_number(&mut self, at: &ArithmeticTerm) -> Result<Number, MachineStub> {
match at {
&ArithmeticTerm::Reg(r) => {
let value = self.store(self.deref(self[r]));
let value = match at {
&ArithmeticTerm::Reg(r) => self.store(self.deref(self[r])),
&ArithmeticTerm::IntermReg(i) => self.registers[i],
ArithmeticTerm::Number(n) => return Ok(*n),
};
match Number::try_from((value, &self.arena.f64_tbl)) {
Ok(n) => Ok(n),
Err(_) => {
self.heap[0] = value;
self.arith_eval_by_metacall(0)
}
}
match Number::try_from((value, &self.arena.f64_tbl)) {
Ok(n) => Ok(n),
Err(_) => {
self.heap[0] = value;
self.arith_eval_by_metacall(0)
}
&ArithmeticTerm::Interm(i) => Ok(mem::replace(
&mut self.interms[i - 1],
Number::Fixnum(Fixnum::build_with(0)),
)),
ArithmeticTerm::Number(n) => Ok(*n),
}
}
@@ -1158,6 +1152,7 @@ impl MachineState {
term_loc: usize,
) -> Result<Number, MachineStub> {
let stub_gen = || functor_stub(atom!("is"), 2);
let mut interms = vec![];
let mut iter =
stackful_post_order_iter::<NonListElider>(&mut self.heap, &mut self.stack, term_loc);
@@ -1194,38 +1189,38 @@ impl MachineState {
read_heap_cell!(value,
(HeapCellValueTag::Atom, (name, arity)) => {
if arity == 2 {
let a2 = self.interms.pop().unwrap();
let a1 = self.interms.pop().unwrap();
let a2 = interms.pop().unwrap();
let a1 = interms.pop().unwrap();
match name {
atom!("+") => self.interms.push(drop_iter_on_err!(
atom!("+") => interms.push(drop_iter_on_err!(
self,
iter,
try_numeric_result!(add(a1, a2, &mut self.arena), stub_gen)
)),
atom!("-") => self.interms.push(drop_iter_on_err!(
atom!("-") => interms.push(drop_iter_on_err!(
self,
iter,
try_numeric_result!(sub(a1, a2, &mut self.arena), stub_gen)
)),
atom!("*") => self.interms.push(drop_iter_on_err!(
atom!("*") => interms.push(drop_iter_on_err!(
self,
iter,
try_numeric_result!(mul(a1, a2, &mut self.arena), stub_gen)
)),
atom!("/") => self.interms.push(
atom!("/") => interms.push(
drop_iter_on_err!(self, iter, div(a1, a2))
),
atom!("**") => self.interms.push(
atom!("**") => interms.push(
drop_iter_on_err!(self, iter, pow(a1, a2, atom!("is")))
),
atom!("^") => self.interms.push(
atom!("^") => interms.push(
drop_iter_on_err!(self, iter, int_pow(a1, a2, &mut self.arena))
),
atom!("max") => self.interms.push(
atom!("max") => interms.push(
drop_iter_on_err!(self, iter, max(a1, a2))
),
atom!("min") => self.interms.push(
atom!("min") => interms.push(
drop_iter_on_err!(self, iter, min(a1, a2))
),
atom!("rdiv") => {
@@ -1246,39 +1241,39 @@ impl MachineState {
&mut self.arena
);
self.interms.push(Number::Rational(result));
interms.push(Number::Rational(result));
}
atom!("//") => self.interms.push(
atom!("//") => interms.push(
drop_iter_on_err!(self, iter, idiv(a1, a2, &mut self.arena))
),
atom!("div") => self.interms.push(
atom!("div") => interms.push(
drop_iter_on_err!(self, iter, int_floor_div(a1, a2, &mut self.arena))
),
atom!(">>") => self.interms.push(
atom!(">>") => interms.push(
drop_iter_on_err!(self, iter, shr(a1, a2, &mut self.arena))
),
atom!("<<") => self.interms.push(
atom!("<<") => interms.push(
drop_iter_on_err!(self, iter, shl(a1, a2, &mut self.arena))
),
atom!("/\\") => self.interms.push(
atom!("/\\") => interms.push(
drop_iter_on_err!(self, iter, and(a1, a2, &mut self.arena))
),
atom!("\\/") => self.interms.push(
atom!("\\/") => interms.push(
drop_iter_on_err!(self, iter, or(a1, a2, &mut self.arena))
),
atom!("xor") => self.interms.push(
atom!("xor") => interms.push(
drop_iter_on_err!(self, iter, xor(a1, a2, &mut self.arena))
),
atom!("mod") => self.interms.push(
atom!("mod") => interms.push(
drop_iter_on_err!(self, iter, modulus(a1, a2, &mut self.arena))
),
atom!("rem") => self.interms.push(
atom!("rem") => interms.push(
drop_iter_on_err!(self, iter, remainder(a1, a2, &mut self.arena))
),
atom!("atan2") => self.interms.push(Number::Float(OrderedFloat(
atom!("atan2") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, atan2(a1, a2))
))),
atom!("gcd") => self.interms.push(
atom!("gcd") => interms.push(
drop_iter_on_err!(self, iter, gcd(a1, a2, &mut self.arena))
),
_ => {
@@ -1294,56 +1289,56 @@ impl MachineState {
continue;
} else if arity == 1 {
let a1 = self.interms.pop().unwrap();
let a1 = interms.pop().unwrap();
match name {
atom!("-") => self.interms.push(neg(a1, &mut self.arena)),
atom!("+") => self.interms.push(a1),
atom!("cos") => self.interms.push(Number::Float(OrderedFloat(
atom!("-") => interms.push(neg(a1, &mut self.arena)),
atom!("+") => interms.push(a1),
atom!("cos") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, cos(a1))
))),
atom!("sin") => self.interms.push(Number::Float(OrderedFloat(
atom!("sin") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, sin(a1))
))),
atom!("tan") => self.interms.push(Number::Float(OrderedFloat(
atom!("tan") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, tan(a1))
))),
atom!("float_fractional_part") => self.interms.push(Number::Float(OrderedFloat(
atom!("float_fractional_part") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, float_fractional_part(a1))
))),
atom!("float_integer_part") => self.interms.push(Number::Float(OrderedFloat(
atom!("float_integer_part") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, float_integer_part(a1))
))),
atom!("sqrt") => self.interms.push(Number::Float(OrderedFloat(
atom!("sqrt") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, sqrt(a1))
))),
atom!("log") => self.interms.push(Number::Float(OrderedFloat(
atom!("log") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, log(a1))
))),
atom!("exp") => self.interms.push(Number::Float(OrderedFloat(
atom!("exp") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, exp(a1))
))),
atom!("acos") => self.interms.push(Number::Float(OrderedFloat(
atom!("acos") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, acos(a1))
))),
atom!("asin") => self.interms.push(Number::Float(OrderedFloat(
atom!("asin") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, asin(a1))
))),
atom!("atan") => self.interms.push(Number::Float(OrderedFloat(
atom!("atan") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, atan(a1))
))),
atom!("abs") => self.interms.push(abs(a1, &mut self.arena)),
atom!("float") => self.interms.push(Number::Float(OrderedFloat(
atom!("abs") => interms.push(abs(a1, &mut self.arena)),
atom!("float") => interms.push(Number::Float(OrderedFloat(
drop_iter_on_err!(self, iter, float(a1))
))),
atom!("truncate") => self.interms.push(truncate(a1, &mut self.arena)),
atom!("round") => self.interms.push(drop_iter_on_err!(self, iter, round(a1, &mut self.arena))),
atom!("ceiling") => self.interms.push(ceiling(a1, &mut self.arena)),
atom!("floor") => self.interms.push(floor(a1, &mut self.arena)),
atom!("\\") => self.interms.push(
atom!("truncate") => interms.push(truncate(a1, &mut self.arena)),
atom!("round") => interms.push(drop_iter_on_err!(self, iter, round(a1, &mut self.arena))),
atom!("ceiling") => interms.push(ceiling(a1, &mut self.arena)),
atom!("floor") => interms.push(floor(a1, &mut self.arena)),
atom!("\\") => interms.push(
drop_iter_on_err!(self, iter, bitwise_complement(a1, &mut self.arena))
),
atom!("sign") => self.interms.push(a1.sign()),
atom!("sign") => interms.push(a1.sign()),
_ => {
let evaluable_stub = functor_stub(name, 1);
std::mem::drop(iter);
@@ -1362,15 +1357,15 @@ impl MachineState {
} else if arity == 0 {
match name {
atom!("pi") => {
self.interms.push(Number::Float(OrderedFloat(f64::consts::PI)));
interms.push(Number::Float(OrderedFloat(f64::consts::PI)));
continue;
}
atom!("e") => {
self.interms.push(Number::Float(OrderedFloat(f64::consts::E)));
interms.push(Number::Float(OrderedFloat(f64::consts::E)));
continue;
}
atom!("epsilon") => {
self.interms.push(Number::Float(OrderedFloat(f64::EPSILON)));
interms.push(Number::Float(OrderedFloat(f64::EPSILON)));
continue;
}
_ => {
@@ -1386,19 +1381,19 @@ impl MachineState {
return Err(self.error_form(evaluable_error, stub));
}
(HeapCellValueTag::Fixnum, n) => {
self.interms.push(Number::Fixnum(n));
interms.push(Number::Fixnum(n));
}
(HeapCellValueTag::F64Offset, offset) => {
let fl = self.arena.f64_tbl.get_entry(offset);
self.interms.push(Number::Float(fl));
interms.push(Number::Float(fl));
}
(HeapCellValueTag::Cons, ptr) => {
match_untyped_arena_ptr!(ptr,
(ArenaHeaderTag::Integer, n) => {
self.interms.push(Number::Integer(n));
interms.push(Number::Integer(n));
}
(ArenaHeaderTag::Rational, r) => {
self.interms.push(Number::Rational(r));
interms.push(Number::Rational(r));
}
_ => {
std::mem::drop(iter);
@@ -1429,7 +1424,7 @@ impl MachineState {
)
}
Ok(self.interms.pop().unwrap())
Ok(interms.pop().unwrap())
}
}

View File

@@ -15,7 +15,6 @@ pub(super) struct AttrVarInitializer {
pub(super) bindings: Bindings,
pub(super) p: usize,
pub(super) cp: usize,
// pub(super) instigating_p: usize,
pub(super) verify_attrs_loc: usize,
}
@@ -41,27 +40,29 @@ impl MachineState {
pub(super) fn push_attr_var_binding(&mut self, h: usize, addr: HeapCellValue) {
if self.attr_var_init.bindings.is_empty() {
// save self.p and self.cp and ensure that the next
// instruction is InstallVerifyAttrInterrupt.
// instruction is RunVerifyAttrInterrupt.
self.attr_var_init.p = self.p;
self.attr_var_init.cp = self.cp;
self.p = INSTALL_VERIFY_ATTR_INTERRUPT - 1;
self.cp = INSTALL_VERIFY_ATTR_INTERRUPT;
self.p = VERIFY_ATTR_INTERRUPT_LOC - 1;
self.cp = VERIFY_ATTR_INTERRUPT_LOC;
}
debug_assert_eq!(self.heap[h].get_tag(), HeapCellValueTag::AttrVar);
self.attr_var_init.bindings.push((h, addr));
}
fn populate_var_and_value_lists(&mut self) -> Result<(HeapCellValue, HeapCellValue), usize> {
fn populate_var_and_value_lists(
&mut self,
) -> Result<(HeapCellValue, HeapCellValue), AllocError> {
let size = self.attr_var_init.bindings.len();
let iter = self
.attr_var_init
.bindings
.iter()
.map(|(ref h, _)| attr_var_as_cell!(*h));
.map(|(h, _)| attr_var_as_cell!(*h));
let var_list_addr = sized_iter_to_heap_list(&mut self.heap, size, iter)?;
let iter = self.attr_var_init.bindings.drain(0..).map(|(_, ref v)| *v);
@@ -70,7 +71,7 @@ impl MachineState {
Ok((var_list_addr, value_list_addr))
}
fn verify_attributes(&mut self) -> Result<(), usize> {
fn verify_attributes(&mut self) -> Result<(), AllocError> {
for (h, _) in &self.attr_var_init.bindings {
self.heap[*h] = attr_var_as_cell!(*h);
}
@@ -102,21 +103,24 @@ impl MachineState {
.collect()
};
attr_vars.sort_unstable_by(|a1, a2| {
compare_term_test!(self, *a1, *a2).unwrap_or(Ordering::Less)
});
attr_vars
.sort_unstable_by(|a1, a2| self.compare_term_test(*a1, *a2).unwrap_or(Ordering::Less));
attr_vars.dedup();
attr_vars
}
pub(super) fn verify_attr_interrupt(&mut self, p: usize, arity: usize) -> Result<(), usize> {
self.allocate(arity + 3);
pub(super) fn verify_attr_interrupt(
&mut self,
p: usize,
arity: usize,
) -> Result<(), AllocError> {
self.allocate(arity + 3)?;
let e = self.e;
let and_frame = self.stack.index_and_frame_mut(e);
for i in 1..arity + 1 {
for i in 1..=arity {
and_frame[i] = self.registers[i];
}

View File

@@ -80,19 +80,19 @@ fn derelictize_try_me_else(
) -> Option<usize> {
match &mut code[index] {
Instruction::DynamicElse(_, _, NextOrFail::Next(0)) => None,
Instruction::DynamicElse(_, _, NextOrFail::Next(ref mut o)) => {
Instruction::DynamicElse(_, _, NextOrFail::Next(o)) => {
retraction_info.push_record(RetractionRecord::ReplacedDynamicElseOffset(index, *o));
Some(mem::replace(o, 0))
}
Instruction::DynamicInternalElse(_, _, NextOrFail::Next(0)) => None,
Instruction::DynamicInternalElse(_, _, NextOrFail::Next(ref mut o)) => {
Instruction::DynamicInternalElse(_, _, NextOrFail::Next(o)) => {
retraction_info.push_record(RetractionRecord::ReplacedDynamicElseOffset(index, *o));
Some(mem::replace(o, 0))
}
Instruction::DynamicElse(_, _, NextOrFail::Fail(_))
| Instruction::DynamicInternalElse(_, _, NextOrFail::Fail(_)) => None,
Instruction::TryMeElse(0) => None,
Instruction::TryMeElse(ref mut o) => {
Instruction::TryMeElse(o) => {
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(index, *o));
Some(mem::replace(o, 0))
}
@@ -275,7 +275,7 @@ fn merge_indexed_subsequences(
.unwrap(),
);
if let Instruction::TryMeElse(ref mut o) = &mut code[inner_try_me_else_loc] {
if let Instruction::TryMeElse(o) = &mut code[inner_try_me_else_loc] {
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(
inner_try_me_else_loc,
*o,
@@ -324,7 +324,7 @@ fn merge_indexed_subsequences(
);
}
None => {
if let Instruction::TryMeElse(ref mut o) = &mut code[outer_threaded_choice_instr_loc] {
if let Instruction::TryMeElse(o) = &mut code[outer_threaded_choice_instr_loc] {
retraction_info
.push_record(RetractionRecord::ModifiedTryMeElse(inner_trust_me_loc, *o));
@@ -472,7 +472,7 @@ fn set_switch_var_offset(
let target_indexing_line = code[index_loc].to_indexing_line_mut().unwrap();
let old_v = match &mut target_indexing_line[0] {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, ref mut v, ..)) => match *v {
IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, v, ..)) => match *v {
IndexingCodePtr::DynamicExternal(_) => {
mem::replace(v, IndexingCodePtr::DynamicExternal(offset))
}
@@ -497,7 +497,7 @@ fn internalize_choice_instr_at(
match &mut code[instr_loc] {
Instruction::DynamicElse(_, _, NextOrFail::Fail(_))
| Instruction::DynamicInternalElse(_, _, NextOrFail::Fail(_)) => {}
Instruction::DynamicElse(_, _, ref mut o @ NextOrFail::Next(0)) => {
Instruction::DynamicElse(_, _, o @ NextOrFail::Next(0)) => {
retraction_info.push_record(RetractionRecord::ReplacedDynamicElseOffset(instr_loc, 0));
*o = NextOrFail::Fail(0);
}
@@ -516,7 +516,7 @@ fn internalize_choice_instr_at(
}
}
}
Instruction::DynamicInternalElse(_, _, ref mut o @ NextOrFail::Next(0)) => {
Instruction::DynamicInternalElse(_, _, o @ NextOrFail::Next(0)) => {
retraction_info.push_record(RetractionRecord::ReplacedDynamicElseOffset(instr_loc, 0));
*o = NextOrFail::Fail(0);
}
@@ -564,16 +564,14 @@ fn thread_choice_instr_at_to(
) {
loop {
match &mut code[instr_loc] {
Instruction::TryMeElse(ref mut o) | Instruction::RetryMeElse(ref mut o)
if target_loc >= instr_loc =>
{
Instruction::TryMeElse(o) | Instruction::RetryMeElse(o) if target_loc >= instr_loc => {
retraction_info.push_record(RetractionRecord::ReplacedChoiceOffset(instr_loc, *o));
*o = target_loc - instr_loc;
return;
}
Instruction::DynamicElse(_, _, NextOrFail::Next(ref mut o))
| Instruction::DynamicInternalElse(_, _, NextOrFail::Next(ref mut o))
Instruction::DynamicElse(_, _, NextOrFail::Next(o))
| Instruction::DynamicInternalElse(_, _, NextOrFail::Next(o))
if target_loc >= instr_loc =>
{
retraction_info
@@ -588,7 +586,7 @@ fn thread_choice_instr_at_to(
Instruction::TryMeElse(o) | Instruction::RetryMeElse(o) => {
instr_loc += *o;
}
Instruction::RevJmpBy(ref mut o) if instr_loc >= target_loc => {
Instruction::RevJmpBy(o) if instr_loc >= target_loc => {
retraction_info.push_record(RetractionRecord::ModifiedRevJmpBy(instr_loc, *o));
*o = instr_loc - target_loc;
@@ -631,7 +629,7 @@ fn thread_choice_instr_at_to(
Instruction::DynamicInternalElse(_, _, NextOrFail::Fail(o)) if *o > 0 => {
instr_loc += *o;
}
Instruction::TrustMe(ref mut o) if target_loc >= instr_loc => {
Instruction::TrustMe(o) if target_loc >= instr_loc => {
retraction_info.push_record(
RetractionRecord::AppendedTrustMe(instr_loc, *o, false),
//choice_instr.is_default()),
@@ -657,7 +655,7 @@ fn remove_non_leading_clause(
retraction_info: &mut RetractionInfo,
) -> Option<IndexPtr> {
match &mut code[non_indexed_choice_instr_loc] {
Instruction::RetryMeElse(ref mut o) => {
Instruction::RetryMeElse(o) => {
let o = *o;
thread_choice_instr_at_to(
@@ -680,7 +678,7 @@ fn remove_non_leading_clause(
None
}
Instruction::TryMeElse(ref mut o) => {
Instruction::TryMeElse(o) => {
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(
preceding_choice_instr_loc,
*o,
@@ -730,7 +728,7 @@ fn remove_leading_unindexed_clause(
retraction_info: &mut RetractionInfo,
) -> Option<IndexPtr> {
match &mut code[non_indexed_choice_instr_loc] {
Instruction::TryMeElse(ref mut o) => {
Instruction::TryMeElse(o) => {
if *o > 0 {
retraction_info.push_record(RetractionRecord::ModifiedTryMeElse(
non_indexed_choice_instr_loc,
@@ -935,10 +933,10 @@ fn prepend_compiled_clause(
let prepend_queue_len = prepend_queue.len();
match &mut prepend_queue[1] {
Instruction::TryMeElse(ref mut o) if *o == 0 => {
Instruction::TryMeElse(o) if *o == 0 => {
*o = prepend_queue_len - 2;
}
Instruction::DynamicInternalElse(_, _, ref mut o @ NextOrFail::Next(0)) => {
Instruction::DynamicInternalElse(_, _, o @ NextOrFail::Next(0)) => {
*o = NextOrFail::Fail(prepend_queue_len - 2);
}
_ => {
@@ -1632,8 +1630,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
};
match &mut self.wam_prelude.code[clause_loc] {
Instruction::DynamicElse(_, ref mut d, _)
| Instruction::DynamicInternalElse(_, ref mut d, _) => {
Instruction::DynamicElse(_, d, _) | Instruction::DynamicInternalElse(_, d, _) => {
*d = Death::Finite(LS::machine_st(&mut self.payload).global_clock);
}
_ => unreachable!(),

View File

@@ -1,14 +1,14 @@
use std::borrow::Cow;
use std::io::Write;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::mpsc::{Receiver, Sender, channel};
use rand::{rngs::StdRng, SeedableRng};
use rand::{SeedableRng, rngs::StdRng};
use crate::Machine;
use super::{
bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module, Arena, Atom,
Callback, CompilationTarget, IndexStore, ListingSource, MachineArgs, MachineState, Stream,
Arena, Atom, Callback, CompilationTarget, IndexStore, ListingSource, MachineArgs, MachineState,
Stream, bootstrapping_compile, current_dir, import_builtin_impls, libraries, load_module,
};
#[derive(Default)]

View File

@@ -7,14 +7,14 @@ use crate::machine::heap::*;
use crate::machine::stack::*;
use crate::types::*;
use scryer_modular_bitfield::specifiers::*;
use scryer_modular_bitfield::*;
use modular_bitfield::specifiers::*;
use modular_bitfield::*;
use std::collections::BTreeMap;
use std::mem;
use std::ops::{IndexMut, Range};
#[derive(BitfieldSpecifier, Copy, Clone, Debug)]
#[derive(Specifier, Copy, Clone, Debug)]
#[bits = 6]
enum TrailRefTag {
HeapCell = 0b001011,
@@ -81,16 +81,16 @@ pub trait CopierTarget: IndexMut<usize, Output = HeapCellValue> {
fn threshold(&self) -> usize;
// returns the tail location of the pstr on success
fn as_slice_from<'a>(&'a self, from: usize) -> Box<dyn Iterator<Item = u8> + 'a>;
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, usize>;
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize>;
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), usize>;
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, AllocError>;
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, AllocError>;
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), AllocError>;
}
pub(crate) fn copy_term<T: CopierTarget>(
target: T,
addr: HeapCellValue,
attr_var_policy: AttrVarPolicy,
) -> Result<usize, usize> {
) -> Result<usize, AllocError> {
let mut copy_term_state = CopyTermState::new(target, attr_var_policy);
let old_threshold = copy_term_state.target.threshold();
@@ -147,7 +147,7 @@ impl<T: CopierTarget> CopyTermState<T> {
self.trail.push((TrailRef::heap_cell(addr), trail_item));
}
fn copy_list(&mut self, addr: usize) -> Result<(), usize> {
fn copy_list(&mut self, addr: usize) -> Result<(), AllocError> {
for offset in 0..2 {
read_heap_cell!(self.target[addr + offset],
(HeapCellValueTag::Lis, h) => {
@@ -192,7 +192,7 @@ impl<T: CopierTarget> CopyTermState<T> {
Ok(())
}
fn copy_partial_string(&mut self, pstr_loc: usize) -> Result<(), usize> {
fn copy_partial_string(&mut self, pstr_loc: usize) -> Result<(), AllocError> {
match self.pstr_loc_locs.range_mut(..=pstr_loc).next_back() {
Some((
_prev_pstr_loc,
@@ -247,11 +247,12 @@ impl<T: CopierTarget> CopyTermState<T> {
self.trail
.push((TrailRef::pstr_loc(pstr_loc_idx), old_cell));
let old_tail_idx = if (pstr_loc + offset + 1) % Heap::heap_cell_alignment() == 0 {
cell_index!(pstr_loc + offset) + 2
} else {
cell_index!(pstr_loc + offset) + 1
};
let old_tail_idx =
if (pstr_loc + offset + 1).is_multiple_of(Heap::heap_cell_alignment()) {
cell_index!(pstr_loc + offset) + 2
} else {
cell_index!(pstr_loc + offset) + 1
};
let tail_cell = self.target[old_tail_idx];
@@ -279,7 +280,7 @@ impl<T: CopierTarget> CopyTermState<T> {
Ok(())
}
fn copy_attr_var_lists(&mut self) -> Result<(), usize> {
fn copy_attr_var_lists(&mut self) -> Result<(), AllocError> {
while !self.attr_var_list_locs.is_empty() {
let mut list_loc_vec = std::mem::take(&mut self.attr_var_list_locs);
@@ -298,13 +299,13 @@ impl<T: CopierTarget> CopyTermState<T> {
* structure which is ensured by this function and not at all by
* the vanilla copier.
*/
fn copy_attr_var_list(&mut self, mut list_addr: HeapCellValue) -> Result<(), usize> {
fn copy_attr_var_list(&mut self, mut list_addr: HeapCellValue) -> Result<(), AllocError> {
while let HeapCellValueTag::Lis = list_addr.get_tag() {
let threshold = self.target.threshold();
let heap_loc = list_addr.get_value() as usize;
let str_loc = self.target[heap_loc].get_value() as usize;
let str_cell = self.target[str_loc];
let mut writer = self.target.reserve(3).unwrap();
let mut writer = self.target.reserve(3)?;
writer.write_with(|section| {
section.push_cell(heap_loc_as_cell!(threshold + 2));
@@ -328,7 +329,11 @@ impl<T: CopierTarget> CopyTermState<T> {
Ok(())
}
fn reinstantiate_var(&mut self, addr: HeapCellValue, frontier: usize) -> Result<(), usize> {
fn reinstantiate_var(
&mut self,
addr: HeapCellValue,
frontier: usize,
) -> Result<(), AllocError> {
read_heap_cell!(addr,
(HeapCellValueTag::Var, h) => {
self.target[frontier] = heap_loc_as_cell!(frontier);
@@ -379,8 +384,7 @@ impl<T: CopierTarget> CopyTermState<T> {
Ok(())
}
fn copy_var(&mut self, addr: HeapCellValue) -> Result<(), usize> {
let index = addr.get_value() as usize;
fn copy_var(&mut self, addr: HeapCellValue) -> Result<(), AllocError> {
let rd = self.target.deref(addr);
let ra = self.target.store(rd);
@@ -393,6 +397,7 @@ impl<T: CopierTarget> CopyTermState<T> {
}
}
(HeapCellValueTag::Lis, h) => {
let index = rd.get_value() as usize;
if h >= self.old_h && self.target[index].get_mark_bit() {
*self.value_at_scan() = heap_loc_as_cell!(
if ra.get_forwarding_bit() {
@@ -419,7 +424,7 @@ impl<T: CopierTarget> CopyTermState<T> {
Ok(())
}
fn copy_structure(&mut self, addr: usize) -> Result<(), usize> {
fn copy_structure(&mut self, addr: usize) -> Result<(), AllocError> {
read_heap_cell!(self.target[addr],
(HeapCellValueTag::Atom, (_name, arity)) => {
let threshold = self.target.threshold();
@@ -429,7 +434,7 @@ impl<T: CopierTarget> CopyTermState<T> {
let str_cell = if get_structure_index(index_cell).is_some() {
// copy the index pointer trailing this
// inlined or expanded goal.
let mut writer = self.target.reserve(1).unwrap();
let mut writer = self.target.reserve(1)?;
writer.write_with(|section| {
section.push_cell(index_cell);
@@ -462,7 +467,7 @@ impl<T: CopierTarget> CopyTermState<T> {
Ok(())
}
fn copy_term_impl(&mut self, addr: HeapCellValue) -> Result<(), usize> {
fn copy_term_impl(&mut self, addr: HeapCellValue) -> Result<(), AllocError> {
self.scan = self.target.threshold();
let mut writer = self.target.reserve(1)?;
@@ -501,7 +506,7 @@ impl<T: CopierTarget> CopyTermState<T> {
Ok(())
}
fn copy_pstrs(&mut self) -> Result<(), usize> {
fn copy_pstrs(&mut self) -> Result<(), AllocError> {
while let Some((least_pstr_loc, pstr_data)) = self.pstr_loc_locs.pop_first() {
let threshold = heap_index!(self.target.threshold());

View File

@@ -6,78 +6,16 @@ use crate::machine::loader::*;
use crate::machine::machine_errors::CompilationError;
use crate::machine::preprocessor::*;
use crate::parser::ast::*;
use crate::parser::dashu::Rational;
use crate::variable_records::*;
use dashu::Integer;
use indexmap::{IndexMap, IndexSet};
use std::cell::Cell;
use std::cmp::Ordering;
use std::collections::VecDeque;
use std::hash::{Hash, Hasher};
use std::hash::Hash;
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone)] //, PartialOrd, PartialEq, Eq, Hash)]
pub struct BranchNumber {
branch_num: Rational,
delta: Rational,
}
impl Default for BranchNumber {
fn default() -> Self {
Self {
branch_num: Rational::from(1u64 << 63),
delta: Rational::from(1),
}
}
}
impl PartialEq<BranchNumber> for BranchNumber {
#[inline]
fn eq(&self, rhs: &BranchNumber) -> bool {
self.branch_num == rhs.branch_num
}
}
impl Eq for BranchNumber {}
impl Hash for BranchNumber {
#[inline(always)]
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.branch_num.hash(hasher)
}
}
impl PartialOrd<BranchNumber> for BranchNumber {
#[inline]
fn partial_cmp(&self, rhs: &BranchNumber) -> Option<Ordering> {
self.branch_num.partial_cmp(&rhs.branch_num)
}
}
impl BranchNumber {
fn split(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone() + &self.delta / Rational::from(2),
delta: &self.delta / Rational::from(4),
}
}
fn incr_by_delta(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone() + &self.delta,
delta: self.delta.clone(),
}
}
fn halve_delta(&self) -> BranchNumber {
BranchNumber {
branch_num: self.branch_num.clone(),
delta: &self.delta / Rational::from(2),
}
}
}
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct VarInfo {
@@ -97,12 +35,12 @@ pub struct ChunkInfo {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct BranchInfo {
branch_num: BranchNumber,
branch_num: Arc<BranchNumber>,
chunks: Vec<ChunkInfo>,
}
impl BranchInfo {
fn new(branch_num: BranchNumber) -> Self {
fn new(branch_num: Arc<BranchNumber>) -> Self {
Self {
branch_num,
chunks: vec![],
@@ -131,7 +69,7 @@ impl DerefMut for BranchMap {
}
}
type RootSet = IndexSet<BranchNumber>;
type RootSet = IndexSet<Arc<BranchNumber>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClassifyInfo {
@@ -140,12 +78,13 @@ pub struct ClassifyInfo {
}
enum TraversalState {
// construct a QueryTerm::Branch with number of disjuncts, reset
// the chunk type to that of the chunk preceding the disjunct and the chunk_num.
// pop the latest branch number from the root set and use it to construct a QueryTerm::Branch
// with number of disjuncts, reset the chunk type to that of the chunk preceding the disjunct
// and the chunk_num.
BuildDisjunct(usize),
BuildFinalDisjunct(usize),
// add the last disjunct to a QueryTerm::Branch, continuing from
// where it leaves off.
BuildFinalDisjunct(usize),
Fail,
GetCutPoint { var_num: usize, prev_b: bool },
Cut { var_num: usize, is_global: bool },
@@ -154,15 +93,14 @@ enum TraversalState {
Term(Term),
OverrideGlobalCutVar(usize),
ResetGlobalCutVarOverride(Option<usize>),
RemoveBranchNum, // pop the current_branch_num and from the root set.
AddBranchNum(BranchNumber), // set current_branch_num, add it to the root set
RepBranchNum(BranchNumber), // replace current_branch_num and the latest in the root set
AddBranchNum(Arc<BranchNumber>), // set current_branch_num, add it to the root set
RepBranchNum(Arc<BranchNumber>), // replace current_branch_num and the latest in the root set
}
#[derive(Debug)]
pub struct VariableClassifier {
call_policy: CallPolicy,
current_branch_num: BranchNumber,
current_branch_num: Arc<BranchNumber>,
current_chunk_num: usize,
current_chunk_type: ChunkType,
branch_map: BranchMap,
@@ -199,7 +137,7 @@ impl VarData {
VarAlloc::Perm(0, PermVarAllocation::Pending);
match build_stack.front_mut() {
Some(ChunkedTerms::Branch(_)) => {
Some(ChunkedTerms::Branch { .. }) => {
build_stack.push_front(ChunkedTerms::Chunk {
terms: VecDeque::from(vec![term]),
});
@@ -219,24 +157,33 @@ pub type ClassifyFactResult = (Term, VarData);
pub type ClassifyRuleResult = (Term, ChunkedTermVec, VarData);
fn merge_branch_seq(branches: impl Iterator<Item = BranchInfo>) -> BranchInfo {
let mut branch_info = BranchInfo::new(BranchNumber::default());
let mut branch_info = BranchInfo::new(Arc::new(BranchNumber::default()));
for mut branch in branches {
branch_info.branch_num = branch.branch_num;
branch_info.chunks.append(&mut branch.chunks);
}
branch_info.branch_num.delta = branch_info.branch_num.delta * Integer::from(2);
branch_info.branch_num.branch_num -= &branch_info.branch_num.delta;
let new_delta = branch_info.branch_num.delta.clone() * Integer::from(2);
branch_info.branch_num = Arc::new(BranchNumber {
branch_num: branch_info.branch_num.branch_num.clone() - &new_delta,
delta: new_delta,
});
branch_info
}
fn flatten_into_disjunct(build_stack: &mut ChunkedTermVec, preceding_len: usize) {
fn flatten_into_disjunct(
build_stack: &mut ChunkedTermVec,
branch_num: Arc<BranchNumber>,
preceding_len: usize,
) {
let branch_vec = build_stack.drain(preceding_len + 1..).collect();
if let ChunkedTerms::Branch(ref mut disjuncts) = &mut build_stack[preceding_len] {
disjuncts.push(branch_vec);
if let ChunkedTerms::Branch { branch_nums, arms } = &mut build_stack[preceding_len] {
branch_nums.push(branch_num);
arms.push(branch_vec);
} else {
unreachable!();
}
@@ -246,7 +193,7 @@ impl VariableClassifier {
pub fn new(call_policy: CallPolicy) -> Self {
Self {
call_policy,
current_branch_num: BranchNumber::default(),
current_branch_num: Arc::new(BranchNumber::default()),
current_chunk_num: 0,
current_chunk_type: ChunkType::Head,
branch_map: BranchMap(BranchMapInt::new()),
@@ -477,9 +424,6 @@ impl VariableClassifier {
self.root_set.insert(branch_num.clone());
self.current_branch_num = branch_num;
}
TraversalState::RemoveBranchNum => {
self.root_set.pop();
}
TraversalState::RepBranchNum(branch_num) => {
self.root_set.pop();
self.root_set.insert(branch_num.clone());
@@ -488,14 +432,10 @@ impl VariableClassifier {
TraversalState::ResetCallPolicy(call_policy) => {
self.call_policy = call_policy;
}
TraversalState::BuildDisjunct(preceding_len) => {
flatten_into_disjunct(&mut build_stack, preceding_len);
self.current_chunk_type = ChunkType::Mid;
self.current_chunk_num += 1;
}
TraversalState::BuildFinalDisjunct(preceding_len) => {
flatten_into_disjunct(&mut build_stack, preceding_len);
TraversalState::BuildDisjunct(preceding_len)
| TraversalState::BuildFinalDisjunct(preceding_len) => {
let branch_num = self.root_set.pop().unwrap();
flatten_into_disjunct(&mut build_stack, branch_num, preceding_len);
self.current_chunk_type = ChunkType::Mid;
self.current_chunk_num += 1;
@@ -607,9 +547,9 @@ impl VariableClassifier {
let tail = terms.pop().unwrap();
let head = terms.pop().unwrap();
let first_branch_num = self.current_branch_num.split();
let first_branch_num = Arc::new(self.current_branch_num.split());
let branches: Vec<_> = std::iter::once(head)
.chain(unfold_by_str(tail, atom!(";")).into_iter())
.chain(unfold_by_str(tail, atom!(";")))
.collect();
let mut branch_numbers = vec![first_branch_num];
@@ -618,25 +558,24 @@ impl VariableClassifier {
let succ_branch_number = branch_numbers[idx - 1].incr_by_delta();
branch_numbers.push(if idx + 1 < branches.len() {
succ_branch_number.split()
Arc::new(succ_branch_number.split())
} else {
succ_branch_number
Arc::new(succ_branch_number)
});
}
let build_stack_len = build_stack.len();
build_stack.reserve_branch(branches.len());
state_stack.push(TraversalState::RepBranchNum(
state_stack.push(TraversalState::RepBranchNum(Arc::new(
self.current_branch_num.halve_delta(),
));
)));
let iter = branches.into_iter().zip(branch_numbers.into_iter());
let iter = branches.into_iter().zip(branch_numbers);
let final_disjunct_loc = state_stack.len();
for (term, branch_num) in iter.rev() {
state_stack.push(TraversalState::BuildDisjunct(build_stack_len));
state_stack.push(TraversalState::RemoveBranchNum);
state_stack.push(TraversalState::Term(term));
state_stack.push(TraversalState::AddBranchNum(branch_num));
}
@@ -655,23 +594,18 @@ impl VariableClassifier {
let then_term = terms.pop().unwrap();
let if_term = terms.pop().unwrap();
let prev_b = if matches!(
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.
match state_stack.iter().rev().nth(1) {
Some(&TraversalState::BuildDisjunct(preceding_len)) => {
preceding_len + 1 == build_stack.len()
}
_ => false,
}
} else {
false
};
let prev_b =
if let Some(&TraversalState::BuildDisjunct(preceding_len)) =
state_stack.last()
{
// check if the second-to-last element
// is a regular BuildDisjunct, as we
// don't want to add GetPrevLevel in
// case of a TrustMe.
preceding_len + 1 == build_stack.len()
} else {
false
};
state_stack.push(TraversalState::Term(then_term));
state_stack.push(TraversalState::Cut {
@@ -690,14 +624,21 @@ impl VariableClassifier {
let not_term = terms.pop().unwrap();
let build_stack_len = build_stack.len();
let first_branch_num = Arc::new(self.current_branch_num.split());
let second_branch_num = Arc::new(first_branch_num.incr_by_delta());
build_stack.reserve_branch(2);
state_stack.push(TraversalState::RepBranchNum(Arc::new(
self.current_branch_num.halve_delta(),
)));
state_stack.push(TraversalState::BuildFinalDisjunct(build_stack_len));
state_stack.push(TraversalState::Term(Term::Clause(
Cell::default(),
atom!("$succeed"),
vec![],
)));
state_stack.push(TraversalState::AddBranchNum(second_branch_num));
state_stack.push(TraversalState::BuildDisjunct(build_stack_len));
state_stack.push(TraversalState::Fail);
state_stack.push(TraversalState::CutPrev(self.var_num));
@@ -710,6 +651,7 @@ impl VariableClassifier {
var_num: self.var_num,
prev_b: false,
});
state_stack.push(TraversalState::AddBranchNum(first_branch_num));
self.current_chunk_type = ChunkType::Mid;
self.current_chunk_num += 1;

File diff suppressed because it is too large Load Diff

View File

@@ -690,8 +690,7 @@ mod tests {
// term is: [a, <stream ptr>]
let stream = Stream::from_static_string("test", &mut wam.machine_st.arena);
let stream_cell =
HeapCellValue::from(ConsPtr::build_with(stream.as_ptr(), ConsPtrMaskTag::Cons));
let stream_cell = HeapCellValue::from_arena_header_ptr(stream.as_ptr());
let mut writer = wam.machine_st.heap.reserve(16).unwrap();

View File

@@ -5,16 +5,25 @@ use crate::types::*;
use std::alloc;
use std::convert::TryFrom;
use std::num::NonZero;
use std::ops::{Bound, Index, IndexMut, Range, RangeBounds};
use std::ptr;
use std::sync::Once;
const ALIGN: usize = Heap::heap_cell_alignment();
#[derive(Debug, Clone)]
pub struct AllocError;
impl AllocError {
pub(crate) fn resource_error_offset(&self, heap: &mut Heap) -> usize {
heap.resource_error_offset()
}
}
#[derive(Debug)]
pub struct Heap {
inner: InnerHeap,
resource_err_loc: usize,
resource_err_loc: Option<NonZero<usize>>,
}
impl Drop for Heap {
@@ -63,12 +72,15 @@ impl InnerHeap {
"Allocation too large. We should probably GC (TODO)"
);
let new_ptr = if self.byte_cap == 0 {
alloc::alloc(new_layout)
} else {
let old_layout =
alloc::Layout::from_size_align(self.byte_cap, size_of::<HeapCellValue>()).unwrap();
alloc::realloc(self.ptr, old_layout, new_layout.size())
let new_ptr = unsafe {
if self.byte_cap == 0 {
alloc::alloc(new_layout)
} else {
let old_layout =
alloc::Layout::from_size_align(self.byte_cap, size_of::<HeapCellValue>())
.unwrap();
alloc::realloc(self.ptr, old_layout, new_layout.size())
}
};
if !new_ptr.is_null() {
@@ -85,8 +97,6 @@ impl InnerHeap {
unsafe impl Send for Heap {}
unsafe impl Sync for Heap {}
static RESOURCE_ERROR_OFFSET_INIT: Once = Once::new();
#[derive(Debug)]
pub struct HeapStringScan<'a> {
pub string: &'a str,
@@ -99,7 +109,8 @@ unsafe fn scan_slice_to_str(heap_slice: &[u8]) -> HeapStringScan<'_> {
.iter()
.position(|b| *b == 0u8)
.unwrap_or(heap_slice.len());
let zero_byte_addr = heap_slice.as_ptr().add(string_len);
let zero_byte_addr = unsafe { heap_slice.as_ptr().add(string_len) };
let sentinel_len = pstr_sentinel_length(zero_byte_addr.addr());
let tail_idx = cell_index!(
@@ -110,7 +121,7 @@ unsafe fn scan_slice_to_str(heap_slice: &[u8]) -> HeapStringScan<'_> {
let str_slice = &heap_slice[..string_len];
HeapStringScan {
string: std::str::from_utf8_unchecked(str_slice),
string: unsafe { std::str::from_utf8_unchecked(str_slice) },
tail_idx,
}
}
@@ -132,7 +143,7 @@ unsafe fn scan_slice_to_str_from_start(heap_slice: &[u8]) -> HeapStringScan<'_>
let str_slice = &heap_slice[..string_len];
HeapStringScan {
string: std::str::from_utf8_unchecked(str_slice),
string: unsafe { std::str::from_utf8_unchecked(str_slice) },
tail_idx,
}
}
@@ -268,30 +279,32 @@ impl ReservedHeapSection {
src.as_ptr(),
self.heap_ptr.add(heap_index!(self.heap_cell_len)),
str_byte_len,
);
)
};
let zero_region_idx = heap_index!(self.heap_cell_len) + str_byte_len;
let align_offset = pstr_sentinel_length(zero_region_idx);
let zero_region_idx = heap_index!(self.heap_cell_len) + str_byte_len;
let align_offset = pstr_sentinel_length(zero_region_idx);
ptr::write_bytes(self.heap_ptr.add(zero_region_idx), 0u8, align_offset);
unsafe { ptr::write_bytes(self.heap_ptr.add(zero_region_idx), 0u8, align_offset) };
cells_written = if align_offset == 1 {
cells_written = if align_offset == 1 {
unsafe {
ptr::write_bytes(
self.heap_ptr.add(zero_region_idx + 1),
0u8,
size_of::<HeapCellValue>(),
);
// ensure there are at least two bytes in the boundary
// buffer separating the string data from the tail
// cell
cell_index!(src.len() + align_offset + size_of::<HeapCellValue>())
} else {
cell_index!(src.len() + align_offset)
)
};
self.heap_cell_len += cells_written;
}
// ensure there are at least two bytes in the boundary
// buffer separating the string data from the tail
// cell
cell_index!(src.len() + align_offset + size_of::<HeapCellValue>())
} else {
cell_index!(src.len() + align_offset)
};
self.heap_cell_len += cells_written;
cells_written
}
@@ -438,16 +451,12 @@ impl Index<usize> for ReservedHeapSection {
/// Computes the number of bytes required to pad a string of length `chunk_len`
/// with zeroes, such that `chunk_len + pstr_sentinel_length(chunk_len)` is a
/// multiple of `Heap::heap_cell_alignement()`.
/// multiple of `Heap::heap_cell_alignment()`.
fn pstr_sentinel_length(chunk_len: usize) -> usize {
let res = chunk_len.next_multiple_of(ALIGN) - chunk_len;
// No bytes available in last chunk
if res == 0 {
ALIGN
} else {
res
}
if res == 0 { ALIGN } else { res }
}
#[must_use]
@@ -563,14 +572,14 @@ impl Heap {
byte_len: 0,
byte_cap: 0,
},
resource_err_loc: 0,
resource_err_loc: None,
}
}
// takes a heap index, returns a cell index
#[inline]
pub const fn pstr_tail_idx(pstr_zero_byte_loc: usize) -> usize {
if (pstr_zero_byte_loc + 1) % Heap::heap_cell_alignment() == 0 {
if (pstr_zero_byte_loc + 1).is_multiple_of(Heap::heap_cell_alignment()) {
cell_index!(pstr_zero_byte_loc) + 2
} else {
cell_index!(pstr_zero_byte_loc) + 1
@@ -579,18 +588,20 @@ impl Heap {
#[inline(always)]
unsafe fn grow(&mut self) -> bool {
self.inner.grow()
unsafe { self.inner.grow() }
}
#[inline]
fn resource_error_offset(&self) -> usize {
self.resource_err_loc
.expect("`error(resource_error(memory), [])` should be stored at the start of the heap")
.get()
}
pub(crate) fn with_cell_capacity(cap: usize) -> Result<Self, usize> {
pub(crate) fn with_cell_capacity(cap: usize) -> Result<Self, AllocError> {
let ptr = unsafe {
let layout = alloc::Layout::from_size_align(
cap * size_of::<HeapCellValue>(),
heap_index_checked!(cap).ok_or(AllocError)?,
size_of::<HeapCellValue>(),
)
.unwrap();
@@ -598,7 +609,7 @@ impl Heap {
};
if ptr.is_null() {
panic!("could not allocate {} bytes for heap!", heap_index!(cap))
Err(AllocError)
} else {
Ok(Self {
inner: InnerHeap {
@@ -607,26 +618,24 @@ impl Heap {
byte_cap: heap_index!(cap),
},
// pstr_vec: bitvec![],
resource_err_loc: 0,
resource_err_loc: None,
})
}
}
pub fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize> {
pub fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, AllocError> {
let section;
let len = heap_index!(num_cells);
let len = heap_index_checked!(num_cells).ok_or(AllocError)?;
loop {
unsafe {
if self.free_space() >= len {
section = ReservedHeapSection {
heap_ptr: self.inner.ptr,
heap_cell_len: self.cell_len(),
};
break;
} else if !self.grow() {
return Err(self.resource_error_offset());
}
if self.free_space() >= len {
section = ReservedHeapSection {
heap_ptr: self.inner.ptr,
heap_cell_len: self.cell_len(),
};
break;
} else if unsafe { !self.grow() } {
return Err(AllocError);
}
}
@@ -649,7 +658,7 @@ impl Heap {
}
}
pub(crate) fn append(&mut self, other_heap: &impl SizedHeap) -> Result<(), usize> {
pub(crate) fn append(&mut self, other_heap: &impl SizedHeap) -> Result<(), AllocError> {
let other_len = heap_index!(other_heap.cell_len());
loop {
@@ -665,7 +674,7 @@ impl Heap {
self.inner.byte_len += heap_index!(other_heap.cell_len());
break;
} else if unsafe { !self.grow() } {
return Err(self.resource_error_offset());
return Err(AllocError);
}
}
@@ -678,26 +687,26 @@ impl Heap {
}
pub(crate) fn clear(&mut self) {
unsafe {
let layout =
alloc::Layout::from_size_align(self.inner.byte_cap, size_of::<HeapCellValue>())
.unwrap();
alloc::dealloc(self.inner.ptr, layout);
}
self.inner.ptr = ptr::null_mut();
self.inner.byte_len = 0;
self.inner.byte_cap = 0;
*self = Heap::new();
}
pub(crate) fn store_resource_error(&mut self) {
RESOURCE_ERROR_OFFSET_INIT.call_once(move || {
let stub = functor!(atom!("resource_error"), [atom_as_cell((atom!("memory")))]);
self.resource_err_loc = cell_index!(self.inner.byte_len);
if self.resource_err_loc.is_none() {
let stub = functor!(
atom!("error"),
[
functor((atom!("resource_error")), [atom_as_cell((atom!("memory")))]),
atom_as_cell((atom!("[]")))
]
);
self.resource_err_loc = Some(NonZero::new(cell_index!(self.inner.byte_len)).expect(
"index 0 should already be taken by an interstitial cell reserved by the runtime",
));
let mut writer = Heap::functor_writer(stub);
writer(self).unwrap();
});
}
}
#[inline]
@@ -742,10 +751,10 @@ impl Heap {
// either succeed & return nothing or fail & return an offset into
// the heap to a pre-allocated resource error
pub(crate) fn push_cell(&mut self, cell: HeapCellValue) -> Result<(), usize> {
pub(crate) fn push_cell(&mut self, cell: HeapCellValue) -> Result<(), AllocError> {
unsafe {
if self.inner.byte_len == self.inner.byte_cap && !self.grow() {
return Err(self.resource_error_offset());
return Err(AllocError);
}
// SAFETY:
@@ -779,7 +788,7 @@ impl Heap {
Range { start, end }
}
pub fn allocate_pstr(&mut self, src: &str) -> Result<HeapCellValue, usize> {
pub fn allocate_pstr(&mut self, src: &str) -> Result<HeapCellValue, AllocError> {
let size_in_heap = Self::compute_pstr_size(src);
let mut writer = self.reserve(size_in_heap)?;
let HeapSectionWriteResult { result, .. } =
@@ -794,7 +803,7 @@ impl Heap {
// note that allocate_cstr emits a tail cell to the string (completing it with the empty list)
// unlike any version of allocate_pstr.
pub fn allocate_cstr(&mut self, src: &str) -> Result<HeapCellValue, usize> {
pub fn allocate_cstr(&mut self, src: &str) -> Result<HeapCellValue, AllocError> {
let size_in_heap = Self::compute_pstr_size(src);
let mut writer = self.reserve(size_in_heap + 1)?;
let HeapSectionWriteResult { result, .. } =
@@ -849,15 +858,15 @@ impl Heap {
// copies only the string, not its tail. returns the cell index of
// the tail location
pub(crate) fn copy_pstr_within(&mut self, pstr_loc: usize) -> Result<usize, usize> {
pub(crate) fn copy_pstr_within(&mut self, pstr_loc: usize) -> Result<usize, AllocError> {
let HeapStringScan { string, tail_idx } = self.scan_slice_to_str(pstr_loc);
let s_len = string.len();
let align_offset = pstr_sentinel_length(s_len);
let copy_size = s_len + align_offset;
unsafe {
loop {
loop {
unsafe {
if self.free_space() >= copy_size {
let slice =
std::slice::from_raw_parts_mut(self.inner.ptr, self.inner.byte_len + s_len);
@@ -884,7 +893,7 @@ impl Heap {
break;
} else if !self.grow() {
return Err(self.resource_error_offset());
return Err(AllocError);
}
}
}
@@ -893,7 +902,10 @@ impl Heap {
}
// src is a cell-indexed range.
pub(crate) fn copy_slice_to_end<R: RangeBounds<usize>>(&mut self, src: R) -> Result<(), usize> {
pub(crate) fn copy_slice_to_end<R: RangeBounds<usize>>(
&mut self,
src: R,
) -> Result<(), AllocError> {
let range = self.slice_range(src);
let len = range.end - range.start;
@@ -911,7 +923,7 @@ impl Heap {
break;
} else if !self.grow() {
return Err(self.resource_error_offset());
return Err(AllocError);
}
}
}
@@ -970,7 +982,7 @@ impl Heap {
pub(crate) fn functor_writer(
functor: Vec<FunctorElement>,
) -> impl FnMut(&mut Heap) -> Result<HeapCellValue, usize> {
) -> impl FnMut(&mut Heap) -> Result<HeapCellValue, AllocError> {
let size = Heap::compute_functor_byte_size(&functor);
let mut functor_writer = ReservedHeapSection::functor_writer(functor);
@@ -1133,10 +1145,12 @@ pub fn sized_iter_to_heap_list<SrcT: Into<HeapCellValue>>(
heap: &mut Heap,
size: usize,
values: impl Iterator<Item = SrcT>,
) -> Result<HeapCellValue, usize> {
) -> Result<HeapCellValue, AllocError> {
if size > 0 {
let h = heap.cell_len();
let mut writer = heap.reserve(1 + 2 * size)?;
// not using checked_add for 1 + as the result of multiplying by 2 will be even and the largest representable usize is odd,
// so the addition cannot overflow
let mut writer = heap.reserve(1 + size.checked_mul(2).ok_or(AllocError)?)?;
writer.write_with(|section| {
for (idx, value) in values.enumerate() {

View File

@@ -3,22 +3,23 @@ use std::collections::BTreeMap;
use std::rc::Rc;
use crate::atom_table;
use crate::heap_iter::{stackful_post_order_iter, NonListElider};
use crate::heap_iter::{NonListElider, stackful_post_order_iter};
use crate::machine::heap::AllocError;
use crate::machine::machine_indices::VarKey;
use crate::machine::mock_wam::CompositeOpDir;
use crate::machine::{
ArenaHeaderTag, Fixnum, Number, BREAK_FROM_DISPATCH_LOOP_LOC, LIB_QUERY_SUCCESS,
ArenaHeaderTag, BREAK_FROM_DISPATCH_LOOP_LOC, Fixnum, LIB_QUERY_SUCCESS, Number,
};
use crate::offset_table::*;
use crate::parser::ast::{Var, VarPtr};
use crate::parser::parser::{Parser, Tokens};
use crate::read::{write_term_to_heap, TermWriteResult};
use crate::read::{TermWriteResult, write_term_to_heap};
use crate::types::UntypedArenaPtr;
use dashu::{Integer, Rational};
use indexmap::IndexMap;
use super::{streams::Stream, Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine};
use super::{Atom, AtomCell, HeapCellValue, HeapCellValueTag, Machine, streams::Stream};
#[cfg(test)]
mod tests;
@@ -128,9 +129,11 @@ impl Term {
pub fn try_conjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
let mut iter = value.into_iter();
iter.next().map(|first| {
Term::try_conjunction(iter)
.map(|rest| Term::compound(",", [first.clone(), rest]))
.unwrap_or(first)
if let Some(rest) = Term::try_conjunction(iter) {
Term::compound(",", [first, rest])
} else {
first
}
})
}
@@ -143,9 +146,11 @@ impl Term {
pub fn try_disjunction(value: impl IntoIterator<Item = Term>) -> Option<Self> {
let mut iter = value.into_iter();
iter.next().map(|first| {
Term::try_disjunction(iter)
.map(|rest| Term::compound(";", [first.clone(), rest]))
.unwrap_or(first)
if let Some(rest) = Term::try_disjunction(iter) {
Term::compound(";", [first, rest])
} else {
first
}
})
}
}
@@ -155,9 +160,14 @@ impl Term {
fn count_to_letter_code(mut count: usize) -> String {
let mut letters = Vec::new();
// +2 rather than +1 to account for the _ at the end
let length = count.checked_ilog(26).unwrap_or(0) as usize + 2;
letters.reserve(length);
loop {
let letter_idx = (count % 26) as u32;
letters.push(char::from_u32('A' as u32 + letter_idx).unwrap());
let letter_idx = (count % 26) as u8;
letters.push(b'A' + letter_idx);
count /= 26;
if count == 0 {
@@ -165,7 +175,15 @@ fn count_to_letter_code(mut count: usize) -> String {
}
}
letters.into_iter().chain("_".chars()).rev().collect()
letters.push(b'_');
debug_assert_eq!(length, letters.len());
letters.reverse();
// Safety: we only push ascii chars A-Z and _
// an ascii only byte sequence is always valid utf-8
unsafe { String::from_utf8_unchecked(letters) }
}
impl Term {
@@ -207,14 +225,14 @@ impl Term {
let list = match tail {
Term::Atom(atom) if atom == "[]" => match head {
Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
Term::Atom(ref a) if a.chars().count() == 1 => {
// Handle lists of char as strings
Term::String(a.to_string())
}
_ => Term::List(vec![head]),
},
Term::List(elems) if elems.is_empty() => match head {
Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
Term::Atom(ref a) if a.chars().count() == 1 => {
// Handle lists of char as strings
Term::String(a.to_string())
},
@@ -225,7 +243,7 @@ impl Term {
Term::List(elems)
},
Term::String(mut elems) => match head {
Term::Atom(ref a) if a.chars().collect::<Vec<_>>().len() == 1 => {
Term::Atom(ref a) if a.chars().count() == 1 => {
// Handle lists of char as strings
elems.insert(0, a.chars().next().unwrap());
Term::String(elems)
@@ -397,7 +415,7 @@ pub struct QueryState<'a> {
impl Drop for QueryState<'_> {
fn drop(&mut self) {
// FIXME: This may be wrong if the iterator is not fully consumend, but from testing it
// FIXME: This may be wrong if the iterator is not fully consumed, but from testing it
// seems fine. Is this really ok?
self.machine.trust_me();
}
@@ -428,14 +446,15 @@ impl Iterator for QueryState<'_> {
// contained in self.machine_st.ball.
let h = machine.machine_st.heap.cell_len();
if let Err(resource_err_loc) = machine
if let Err(err) = machine
.machine_st
.heap
.append(&machine.machine_st.ball.stub)
{
let resource_error_offset = err.resource_error_offset(&mut machine.machine_st.heap);
return Some(Err(Term::from_heapcell(
machine,
machine.machine_st.heap[resource_err_loc],
machine.machine_st.heap[resource_error_offset],
&mut IndexMap::new(),
)));
}
@@ -536,11 +555,11 @@ impl Machine {
self.run_module_predicate(atom!("loader"), (atom!("consult_stream"), 2));
}
pub(crate) fn allocate_stub_choice_point(&mut self) {
pub(crate) fn allocate_stub_choice_point(&mut self) -> Result<(), AllocError> {
// NOTE: create a choice point to terminate the dispatch_loop
// if an exception is thrown.
let stub_b = self.machine_st.stack.allocate_or_frame(0);
let stub_b = self.machine_st.stack.allocate_or_frame(0)?;
let or_frame = self.machine_st.stack.index_or_frame_mut(stub_b);
or_frame.prelude.num_cells = 0;
@@ -558,6 +577,8 @@ impl Machine {
self.machine_st.b = stub_b;
self.machine_st.hb = self.machine_st.heap.cell_len();
self.machine_st.block = stub_b;
Ok(())
}
/// Runs a query.
@@ -571,7 +592,8 @@ impl Machine {
.read_term(&op_dir, Tokens::Default)
.expect("Failed to parse query");
self.allocate_stub_choice_point();
self.allocate_stub_choice_point()
.expect("failed to allocate stub choice point");
// Write parsed term to heap
let term_write_result = write_term_to_heap(&term, &mut self.machine_st.heap)
@@ -619,3 +641,11 @@ impl Machine {
}
}
}
#[test]
fn test_count_to_letter_code() {
for idx in 0..1000 {
// ensure the debug assert doesn't trigger
count_to_letter_code(idx);
}
}

View File

@@ -3,7 +3,7 @@ use crate::MachineBuilder;
#[test]
#[cfg_attr(miri, ignore = "it takes too long to run")]
fn programatic_query() {
fn programmatic_query() {
let mut machine = MachineBuilder::default().build();
machine.load_module_string(

View File

@@ -34,7 +34,7 @@ pub(super) fn set_code_index<'a, LS: LoadState<'a>>(
RetractionRecord::ReplacedUserPredicate(key, replaced)
}
}
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
if IndexPtrTag::Undefined == code_idx_ptr.tag() {
*code_idx_ptr = code_ptr;
RetractionRecord::AddedModulePredicate(*module_name, key)
@@ -98,7 +98,7 @@ pub(super) fn add_op_decl(
CompilationTarget::User => {
retraction_info.push_record(RetractionRecord::ReplacedUserOp(*op_decl, op_desc));
}
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
retraction_info.push_record(RetractionRecord::ReplacedModuleOp(
*module_name,
*op_decl,
@@ -110,7 +110,7 @@ pub(super) fn add_op_decl(
CompilationTarget::User => {
retraction_info.push_record(RetractionRecord::AddedUserOp(*op_decl));
}
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
retraction_info
.push_record(RetractionRecord::AddedModuleOp(*module_name, *op_decl));
}
@@ -167,7 +167,7 @@ pub(super) fn import_module_exports<'a, LS: LoadState<'a>>(
));
}
}
ModuleExport::OpDecl(ref op_decl) => {
ModuleExport::OpDecl(op_decl) => {
add_op_decl(
&mut payload.retraction_info,
compilation_target,
@@ -221,7 +221,7 @@ fn import_module_exports_into_module<'a, LS: LoadState<'a>>(
));
}
}
ModuleExport::OpDecl(ref op_decl) => {
ModuleExport::OpDecl(op_decl) => {
add_op_decl_as_module_export::<LS>(payload, op_dir, wam_op_dir, op_decl);
}
}
@@ -276,7 +276,7 @@ fn import_qualified_module_exports<'a, LS: LoadState<'a>>(
));
}
}
ModuleExport::OpDecl(ref op_decl) => {
ModuleExport::OpDecl(op_decl) => {
add_op_decl(
&mut payload.retraction_info,
compilation_target,
@@ -336,7 +336,7 @@ fn import_qualified_module_exports_into_module<'a, LS: LoadState<'a>>(
));
}
}
ModuleExport::OpDecl(ref op_decl) => {
ModuleExport::OpDecl(op_decl) => {
add_op_decl_as_module_export::<LS>(payload, op_dir, wam_op_dir, op_decl);
}
}
@@ -545,7 +545,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
) {
for export in removed_module.module_decl.exports.iter() {
match export {
ModuleExport::PredicateKey(ref key) => {
ModuleExport::PredicateKey(key) => {
if let (Some(module_code_idx), Some(target_code_idx)) = (
removed_module.code_dir.get(key).cloned(),
code_dir.get_mut(key).cloned(),
@@ -1050,7 +1050,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
);
for export in &module.module_decl.exports {
if let ModuleExport::OpDecl(ref op_decl) = export {
if let ModuleExport::OpDecl(op_decl) = export {
add_op_decl_as_module_export::<LS>(
&mut self.payload,
&mut module.op_dir,
@@ -1084,7 +1084,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
&mut self.wam_prelude.indices.meta_predicates,
)?;
}
CompilationTarget::Module(ref defining_module_name) => {
CompilationTarget::Module(defining_module_name) => {
match self
.wam_prelude
.indices
@@ -1137,7 +1137,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
&exports,
&mut self.wam_prelude,
),
CompilationTarget::Module(ref defining_module_name) => {
CompilationTarget::Module(defining_module_name) => {
match self
.wam_prelude
.indices

View File

@@ -160,7 +160,7 @@ impl fmt::Display for CompilationTarget {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CompilationTarget::User => write!(f, "user"),
CompilationTarget::Module(ref module_name) => write!(f, "{}", module_name.as_str()),
CompilationTarget::Module(module_name) => write!(f, "{}", module_name.as_str()),
}
}
}
@@ -819,11 +819,8 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
if let Instruction::IndexingCode(ref mut indexing_code) =
self.wam_prelude.code[index_loc]
{
if let IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(
_,
ref mut v,
..,
)) = &mut indexing_code[0]
if let IndexingLine::Indexing(IndexingInstruction::SwitchOnTerm(_, v, ..)) =
&mut indexing_code[0]
{
*v = old_v;
}
@@ -1110,7 +1107,7 @@ impl<'a, LS: LoadState<'a>> Loader<'a, LS> {
}
}
}
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
match self.wam_prelude.indices.modules.get_mut(module_name) {
Some(ref mut module) => match module.extensible_predicates.get_mut(&key) {
Some(ref mut skeleton) => {
@@ -1364,14 +1361,12 @@ impl<'a> MachinePreludeView<'a> {
) -> CompositeOpDir<'_, '_> {
match compilation_target {
CompilationTarget::User => CompositeOpDir::new(&self.indices.op_dir, None),
CompilationTarget::Module(ref module_name) => {
match self.indices.modules.get(module_name) {
Some(module) => CompositeOpDir::new(&self.indices.op_dir, Some(&module.op_dir)),
None => {
unreachable!()
}
CompilationTarget::Module(module_name) => match self.indices.modules.get(module_name) {
Some(module) => CompositeOpDir::new(&self.indices.op_dir, Some(&module.op_dir)),
None => {
unreachable!()
}
}
},
}
}
}
@@ -1504,9 +1499,10 @@ impl Machine {
}
pub(crate) fn load_compiled_library(&mut self) -> CallResult {
let library = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let library = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
if let Some(module) = self.indices.modules.get(&library) {
if let ListingSource::DynamicallyGenerated = module.listing_src {
@@ -1537,9 +1533,10 @@ impl Machine {
}
pub(crate) fn declare_module(&mut self) -> CallResult {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let mut loader = self.loader_from_heap_evacuable(temp_v!(3));
@@ -1649,9 +1646,10 @@ impl Machine {
}
pub(crate) fn add_goal_expansion_clause(&mut self) -> CallResult {
let target_module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let target_module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let mut loader = self.loader_from_heap_evacuable(temp_v!(3));
@@ -1744,9 +1742,10 @@ impl Machine {
&mut self,
r: RegType,
) -> Loader<'_, LiveLoadAndMachineState<'_>> {
let mut load_state = cell_as_load_state_payload!(self
.machine_st
.store(self.machine_st.deref(self.machine_st[r])));
let mut load_state = cell_as_load_state_payload!(
self.machine_st
.store(self.machine_st.deref(self.machine_st[r]))
);
load_state.set_tag(ArenaHeaderTag::LiveLoadState);
@@ -1842,9 +1841,7 @@ impl Machine {
let err = self.machine_st.permission_error(
Permission::Modify,
atom!("static_procedure"),
functor_stub(atom!(":"), 2)
.into_iter()
.collect::<MachineStub>(),
functor_stub(atom!(":"), 2),
);
self.machine_st
@@ -2100,9 +2097,10 @@ impl Machine {
}
pub(crate) fn abolish_clause(&mut self) -> CallResult {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st
@@ -2216,9 +2214,10 @@ impl Machine {
_ => unreachable!(),
};
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[4])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[4]))
);
let compilation_target = match module_name {
atom!("user") => CompilationTarget::User,
@@ -2271,9 +2270,10 @@ impl Machine {
}
pub(crate) fn is_consistent_with_term_queue(&mut self) -> CallResult {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st
@@ -2311,9 +2311,10 @@ impl Machine {
}
pub(crate) fn remove_module_exports(&mut self) -> CallResult {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let mut loader = self.loader_from_heap_evacuable(temp_v!(2));
@@ -2339,9 +2340,10 @@ impl Machine {
}
pub(crate) fn meta_predicate_property(&mut self) {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let (predicate_name, arity) = self
.machine_st
@@ -2361,8 +2363,8 @@ impl Machine {
let mut writer = match self.machine_st.heap.reserve(3 + meta_specs.len()) {
Ok(writer) => writer,
Err(err_loc) => {
self.machine_st.throw_resource_error(err_loc);
Err(err) => {
self.machine_st.throw_resource_error(err);
return;
}
};
@@ -2376,7 +2378,7 @@ impl Machine {
MetaSpec::Plus => atom_as_cell!(atom!("-")),
MetaSpec::Either => atom_as_cell!(atom!("?")),
MetaSpec::Colon => atom_as_cell!(atom!(":")),
MetaSpec::RequiresExpansionWithArgument(ref arg_num) => {
MetaSpec::RequiresExpansionWithArgument(arg_num) => {
fixnum_as_cell!(/* FIXME this is not safe */ unsafe {
Fixnum::build_with_unchecked(*arg_num as i64)
})
@@ -2403,9 +2405,10 @@ impl Machine {
}
pub(crate) fn dynamic_property(&mut self) {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st
@@ -2430,9 +2433,10 @@ impl Machine {
}
pub(crate) fn multifile_property(&mut self) {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st
@@ -2457,9 +2461,10 @@ impl Machine {
}
pub(crate) fn discontiguous_property(&mut self) {
let module_name = cell_as_atom!(self
.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1])));
let module_name = cell_as_atom!(
self.machine_st
.store(self.machine_st.deref(self.machine_st.registers[1]))
);
let key = self
.machine_st

View File

@@ -6,6 +6,7 @@ use crate::parser::ast::*;
use crate::ffi::{self, FfiError};
use crate::forms::*;
use crate::functor_macro::*;
use crate::machine::heap::AllocError;
use crate::machine::heap::*;
use crate::machine::loader::CompilationTarget;
use crate::machine::machine_state::*;
@@ -19,7 +20,7 @@ pub type MachineStubGen = Box<dyn Fn(&mut MachineState) -> MachineStub>;
#[derive(Debug)]
pub(crate) struct MachineError {
stub: MachineStub,
location: Option<(usize, usize)>, // line_num, col_num
location: Option<Location>,
}
// from 7.12.2 b) of 13211-1:1995
@@ -75,7 +76,6 @@ impl ValidType {
#[derive(Debug, Clone, Copy)]
pub(crate) enum ResourceError {
FiniteMemory(HeapCellValue),
OutOfFiles,
}
@@ -329,12 +329,6 @@ impl MachineState {
pub(super) fn resource_error(err: ResourceError) -> MachineError {
let stub = match err {
ResourceError::FiniteMemory(size_requested) => {
functor!(
atom!("resource_error"),
[atom_as_cell((atom!("finite_memory"))), cell(size_requested)]
)
}
ResourceError::OutOfFiles => {
functor!(
atom!("resource_error"),
@@ -447,10 +441,26 @@ impl MachineState {
location: None,
}
}
ExistenceError::FfiFunction(atom) => {
ExistenceError::FfiFunction(name, arity) => {
let culprit = functor!(atom!("/"), [atom_as_cell(name), fixnum(arity)]);
let stub = functor!(
atom!("existence_error"),
[atom_as_cell((atom!("ffi_function"))), atom_as_cell(atom)]
[atom_as_cell((atom!("ffi_function"))), functor(culprit)]
);
MachineError {
stub,
location: None,
}
}
ExistenceError::FfiStructConstructor(name, arity) => {
let culprit = functor!(atom!("/"), [atom_as_cell(name), fixnum(arity)]);
let stub = functor!(
atom!("existence_error"),
[
atom_as_cell((atom!("ffi_struct_constructor"))),
functor(culprit)
]
);
MachineError {
@@ -629,6 +639,19 @@ impl MachineState {
return self.directive_error(err);
}
if let CompilationError::FiniteMemoryInHeap(err) = err {
// err.resource_error_offset() should be the address of the error/2 functor in the pre-allocated term error(resource_error(memory), [])
let err_loc = err.resource_error_offset(&mut self.heap);
let stub = vec![FunctorElement::AbsoluteCell(
// err_loc + 1 should be the functors first argument which should be a str cell pointing at the resource_error/1 functor
self.heap[err_loc + 1],
)];
return MachineError {
stub,
location: None,
};
}
let location = err.line_and_col_num();
let stub = err.as_functor();
@@ -688,13 +711,25 @@ impl MachineState {
}
}
FfiError::ValueOutOfRange(domain, culprit) => self.domain_error(domain, culprit),
FfiError::FunctionNotFound(name) => {
self.existence_error(ExistenceError::FfiFunction(name))
FfiError::FunctionNotFound(name, arity) => {
self.existence_error(ExistenceError::FfiFunction(name, arity))
}
FfiError::StructNotFound(name) => {
self.existence_error(ExistenceError::FfiStructType(name))
}
FfiError::ArgCountMismatch => self.unreachable_error(),
FfiError::ArgCountMismatch {
name,
kind,
expected: _,
got,
} => match kind {
ffi::ArgCountMismatchKind::Function => {
self.existence_error(ExistenceError::FfiFunction(name, got))
}
ffi::ArgCountMismatchKind::Struct => {
self.existence_error(ExistenceError::FfiStructConstructor(name, got))
}
},
FfiError::AllocationFailed => MachineError {
stub: functor!(atom!("resource_error"), [atom_as_cell((atom!("heap")))]),
location: None,
@@ -702,10 +737,13 @@ impl MachineState {
FfiError::LayoutError => self.representation_error(RepFlag::FfiLayout),
FfiError::UnsupportedTypedef => self.representation_error(RepFlag::FfiLayout),
FfiError::UnsupportedAbi => self.representation_error(RepFlag::FfiAbi),
FfiError::VoidArgumentType => self.domain_error(
FfiError::UnsupportedArgumentType(None) => self.domain_error(
DomainErrorType::FfiArgumentType,
atom_as_cell!(atom!("void")),
atom_as_cell!(atom!("unknown")),
),
FfiError::UnsupportedArgumentType(Some(kind)) => {
self.domain_error(DomainErrorType::FfiArgumentType, atom_as_cell!(kind))
}
FfiError::CStrFieldType => self.domain_error(
DomainErrorType::NonCStrFfiArgumentType,
atom_as_cell!(atom!("cstr")),
@@ -714,18 +752,20 @@ impl MachineState {
DomainErrorType::NonNullPtr,
fixnum_as_cell!(Fixnum::build_with(0)),
),
FfiError::Other => self.unreachable_error(),
}
}
pub(super) fn error_form(&mut self, err: MachineError, src: MachineStub) -> MachineStub {
if let Some((line_num, _col_num)) = err.location {
if let Some(location) = err.location {
let line = location.line();
functor!(
atom!("error"),
[
functor((err.stub)),
functor(
(atom!(":")),
[functor(src), number(line_num, (&mut self.arena))]
[functor(src), number(line, (&mut self.arena))]
)
]
)
@@ -735,10 +775,19 @@ impl MachineState {
}
// throw an error pre-allocated in the heap
pub(super) fn throw_resource_error(&mut self, err_loc: usize) {
self.registers[1] = str_loc_as_cell!(err_loc);
pub(super) fn throw_resource_error(&mut self, err: AllocError) {
if self.throwing_resource_error {
panic!(
"attempted to throw `error(resource_error(memory), [])` while attempting to throw `error(resource_error(memory), [])`"
);
}
self.throwing_resource_error = true;
self.registers[1] = str_loc_as_cell!(err.resource_error_offset(&mut self.heap));
self.set_ball();
self.unwind_stack();
self.throwing_resource_error = false;
}
pub(super) fn throw_exception(&mut self, err: MachineStub) {
@@ -749,8 +798,8 @@ impl MachineState {
self.registers[1] = match writer(&mut self.heap) {
Ok(loc) => loc,
Err(resource_err_loc) => {
self.throw_resource_error(resource_err_loc);
Err(err) => {
self.throw_resource_error(err);
return;
}
};
@@ -774,7 +823,13 @@ pub enum CompilationError {
InvalidRuleHead,
InvalidUseModuleDecl,
InvalidModuleResolution(Atom),
FiniteMemoryInHeap(usize),
FiniteMemoryInHeap(AllocError),
}
impl From<AllocError> for CompilationError {
fn from(value: AllocError) -> Self {
Self::FiniteMemoryInHeap(value)
}
}
#[derive(Debug)]
@@ -805,9 +860,9 @@ impl From<ParserError> for CompilationError {
}
impl CompilationError {
pub(crate) fn line_and_col_num(&self) -> Option<(usize, usize)> {
pub(crate) fn line_and_col_num(&self) -> Option<Location> {
match self {
CompilationError::ParserError(err) => err.line_and_col_num(),
CompilationError::ParserError(err) => err.location(),
_ => None,
}
}
@@ -847,11 +902,11 @@ impl CompilationError {
CompilationError::InvalidUseModuleDecl => {
functor!(atom!("invalid_use_module_declaration"))
}
CompilationError::ParserError(ref err) => {
CompilationError::ParserError(err) => {
functor!(err.as_atom())
}
CompilationError::FiniteMemoryInHeap(h) => {
vec![FunctorElement::AbsoluteCell(str_loc_as_cell!(*h))]
CompilationError::FiniteMemoryInHeap(_) => {
functor!(atom!("resource_error"))
}
}
}
@@ -884,6 +939,7 @@ impl Permission {
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(not(feature = "ffi"), expect(dead_code))]
pub(crate) enum DomainErrorType {
IOMode,
NotLessThanZero,
@@ -934,6 +990,7 @@ impl DomainErrorType {
// from 7.12.2 f) of 13211-1:1995
#[derive(Debug, Clone, Copy)]
#[cfg_attr(not(feature = "ffi"), expect(dead_code))]
pub(crate) enum RepFlag {
Character,
CharacterCode,
@@ -1125,6 +1182,7 @@ impl MachineState {
}
#[derive(Debug)]
#[cfg_attr(not(feature = "ffi"), expect(dead_code))]
pub enum ExistenceError {
Module(Atom),
ModuleSource(ModuleSource),
@@ -1137,7 +1195,8 @@ pub enum ExistenceError {
SourceSink(HeapCellValue),
Stream(HeapCellValue),
Process(HeapCellValue),
FfiFunction(Atom),
FfiFunction(Atom, usize),
FfiStructConstructor(Atom, usize),
FfiStructType(Atom),
}

View File

@@ -1,20 +1,18 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
use crate::parser::ast::*;
use crate::atom_table::*;
use crate::forms::*;
use crate::machine::ClauseType;
use crate::machine::MachineStubGen;
use crate::machine::loader::*;
use crate::machine::machine_state::*;
use crate::machine::streams::{Stream, StreamOptions};
use crate::machine::ClauseType;
use crate::machine::MachineStubGen;
use crate::offset_table::*;
use fxhash::FxBuildHasher;
use indexmap::{IndexMap, IndexSet};
use scryer_modular_bitfield::specifiers::*;
use scryer_modular_bitfield::{bitfield, BitfieldSpecifier};
use modular_bitfield::specifiers::*;
use modular_bitfield::{Specifier, bitfield};
use std::cmp::Ordering;
use std::collections::BTreeSet;
@@ -65,7 +63,7 @@ impl PartialOrd<Ref> for HeapCellValue {
}
}
#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq)]
#[derive(Specifier, Copy, Clone, Debug, PartialEq)]
#[bits = 7]
pub enum IndexPtrTag {
DynamicUndefined = 0b1000101, // a predicate, declared as dynamic, whose location in code is as yet undefined.
@@ -294,7 +292,7 @@ impl IndexStore {
) -> Option<&mut PredicateSkeleton> {
match compilation_target {
CompilationTarget::User => self.extensible_predicates.get_mut(key),
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
if let Some(module) = self.modules.get_mut(module_name) {
module.extensible_predicates.get_mut(key)
} else {
@@ -311,7 +309,7 @@ impl IndexStore {
) -> Option<&PredicateSkeleton> {
match compilation_target {
CompilationTarget::User => self.extensible_predicates.get(key),
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
if let Some(module) = self.modules.get(module_name) {
module.extensible_predicates.get(key)
} else {
@@ -382,7 +380,7 @@ impl IndexStore {
) -> Option<PredicateSkeleton> {
match compilation_target {
CompilationTarget::User => self.extensible_predicates.swap_remove(key),
CompilationTarget::Module(ref module_name) => {
CompilationTarget::Module(module_name) => {
if let Some(module) = self.modules.get_mut(module_name) {
module.extensible_predicates.swap_remove(key)
} else {
@@ -415,7 +413,7 @@ impl IndexStore {
) -> Option<&Vec<MetaSpec>> {
match compilation_target {
CompilationTarget::User => self.meta_predicates.get(&(name, arity)),
CompilationTarget::Module(ref module_name) => match self.modules.get(module_name) {
CompilationTarget::Module(module_name) => match self.modules.get(module_name) {
Some(module) => module
.meta_predicates
.get(&(name, arity))

View File

@@ -3,21 +3,22 @@ use crate::atom_table::*;
use crate::forms::*;
use crate::heap_iter::*;
use crate::heap_print::*;
use crate::machine::Machine;
use crate::machine::attributed_variables::*;
use crate::machine::copier::*;
use crate::machine::heap::AllocError;
use crate::machine::heap::*;
use crate::machine::machine_errors::*;
use crate::machine::machine_indices::*;
use crate::machine::stack::*;
use crate::machine::streams::*;
use crate::machine::Machine;
use crate::parser::ast::*;
use crate::read::TermWriteResult;
use crate::types::*;
use crate::parser::dashu::Integer;
use fxhash::FxBuildHasher;
use indexmap::IndexMap;
use indexmap::IndexSet;
use std::convert::TryFrom;
use std::fmt;
@@ -56,11 +57,68 @@ pub enum OnEOF {
Return,
Continue,
}
pub(crate) trait OccursCheckImpl {
fn flag_value(&self) -> Atom;
fn unify(&self, state: &mut MachineState);
fn bind(&self, state: &mut MachineState, r: Ref, h: HeapCellValue);
}
/// Not subject to occurs-check
pub(crate) struct Nsto;
impl OccursCheckImpl for Nsto {
fn flag_value(&self) -> Atom {
atom!("false")
}
fn unify(&self, state: &mut MachineState) {
state.unify();
}
fn bind(&self, state: &mut MachineState, r: Ref, h: HeapCellValue) {
state.bind(r, h);
}
}
/// Subject to occurs-check
pub(crate) struct Sto;
impl OccursCheckImpl for Sto {
fn flag_value(&self) -> Atom {
atom!("true")
}
fn unify(&self, state: &mut MachineState) {
state.unify_with_occurs_check();
}
fn bind(&self, state: &mut MachineState, r: Ref, h: HeapCellValue) {
state.bind_with_occurs_check_wrapper(r, h);
}
}
/// Subject to occurs-check -> error
pub(crate) struct StoError;
impl OccursCheckImpl for StoError {
fn flag_value(&self) -> Atom {
atom!("error")
}
fn unify(&self, state: &mut MachineState) {
state.unify_with_occurs_check_with_error();
}
fn bind(&self, state: &mut MachineState, r: Ref, h: HeapCellValue) {
state.bind_with_occurs_check_with_error_wrapper(r, h);
}
}
pub struct MachineState {
pub atom_tbl: Arc<AtomTable>,
pub arena: Arena,
pub(super) pdl: Vec<HeapCellValue>,
pub(super) pdl: Vec<(HeapCellValue, HeapCellValue)>,
pub(super) unify_tabu_list: IndexSet<(HeapCellValue, HeapCellValue), FxBuildHasher>,
pub(super) s: HeapPtr,
pub(super) s_offset: usize,
pub(super) p: usize,
@@ -73,6 +131,7 @@ pub struct MachineState {
pub(super) cp: usize,
pub(super) attr_var_init: AttrVarInitializer,
pub(super) fail: bool,
pub throwing_resource_error: bool,
pub heap: Heap,
pub(super) mode: MachineMode,
pub(crate) stack: Stack,
@@ -85,7 +144,6 @@ pub struct MachineState {
pub(super) ball: Ball,
pub(super) ball_stack: Vec<Ball>, // save current ball before jumping via, e.g., verify_attr interrupt.
pub(super) lifted_heap: Heap,
pub(super) interms: Vec<Number>, // intermediate numbers.
// locations of cleaners, cut points, the previous scc_block. for setup_call_cleanup/3.
pub(super) cont_pts: Vec<(HeapCellValue, usize, usize)>,
pub(super) cwil: CWIL,
@@ -93,8 +151,7 @@ pub struct MachineState {
pub(crate) cc: usize,
pub(crate) global_clock: usize,
pub(crate) dynamic_mode: FirstOrNext,
pub(crate) unify_fn: fn(&mut MachineState),
pub(crate) bind_fn: fn(&mut MachineState, Ref, HeapCellValue),
pub(crate) occurs_check: &'static dyn OccursCheckImpl,
pub(crate) run_cleaners_fn: fn(&mut Machine) -> bool,
}
@@ -124,33 +181,12 @@ impl fmt::Debug for MachineState {
.field("ball", &self.ball)
.field("ball_stack", &self.ball_stack)
.field("lifted_heap", &self.lifted_heap)
.field("interms", &self.interms)
.field("flags", &self.flags)
.field("cc", &self.cc)
.field("global_clock", &self.global_clock)
.field("dynamic_mode", &self.dynamic_mode)
.field(
"unify_fn",
if self.unify_fn as usize == MachineState::unify as usize {
&"MachineState::unify"
} else if self.unify_fn as usize == MachineState::unify_with_occurs_check as usize {
&"MachineState::unify_with_occurs_check"
} else {
&"MachineState::unify_with_occurs_check_with_error"
},
)
.field(
"bind_fn",
if self.bind_fn as usize == MachineState::bind as usize {
&"MachineState::bind"
} else if self.bind_fn as usize
== MachineState::bind_with_occurs_check_wrapper as usize
{
&"MachineState::bind_with_occurs_check"
} else {
&"MachineState::bind_with_occurs_check_with_error_wrapper"
},
)
.field("unify_fn", &&*self.occurs_check.flag_value().as_str())
.field("bind_fn", &&*self.occurs_check.flag_value().as_str())
.finish()
}
}
@@ -192,7 +228,7 @@ fn push_var_eq_functors<'a>(
size: usize,
iter: impl Iterator<Item = (&'a VarKey, &'a HeapCellValue)>,
atom_tbl: &AtomTable,
) -> Result<HeapCellValue, usize> {
) -> Result<HeapCellValue, AllocError> {
let src_h = heap.cell_len();
let true_size = if size > 0 {
@@ -257,7 +293,7 @@ impl Ball {
self.stub.clear();
}
pub(super) fn copy_and_align_to(&self, dest: &mut Heap) -> Result<usize, usize> {
pub(super) fn copy_and_align_to(&self, dest: &mut Heap) -> Result<usize, AllocError> {
let h = dest.cell_len();
let diff = self.boundary as i64 - h as i64;
@@ -346,17 +382,17 @@ impl<'a> CopierTarget for CopyTerm<'a> {
}
#[inline(always)]
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, usize> {
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, AllocError> {
self.state.heap.copy_pstr_within(pstr_loc)
}
#[inline(always)]
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize> {
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, AllocError> {
self.state.heap.reserve(num_cells)
}
#[inline(always)]
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), usize> {
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), AllocError> {
self.state.heap.copy_slice_to_end(bounds)
}
}
@@ -455,7 +491,7 @@ impl<'a> CopierTarget for CopyBallTerm<'a> {
self.stack
}
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, usize> {
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, AllocError> {
debug_assert!(pstr_loc < self.heap.byte_len());
let HeapStringScan { string, tail_idx } = self.heap.scan_slice_to_str(pstr_loc);
@@ -477,11 +513,11 @@ impl<'a> CopierTarget for CopyBallTerm<'a> {
}
#[inline]
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize> {
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, AllocError> {
self.stub.reserve(num_cells)
}
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), usize> {
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), AllocError> {
let len = bounds.end - bounds.start;
let mut stub_writer = self.stub.reserve(len)?;
@@ -515,7 +551,7 @@ impl MachineState {
return true;
}
self.cwil.global_count += 1;
self.cwil.global_count = self.cwil.global_count.strict_add(1);
if let Some(&(ref limit, block)) = self.cwil.limits.last() {
if self.cwil.local_count == *limit {
@@ -617,7 +653,7 @@ impl MachineState {
mut var_list: Vec<(VarKey, HeapCellValue, usize)>,
singleton_heap_list: HeapCellValue,
) -> CallResult {
var_list.sort_by(|(_, _, idx_1), (_, _, idx_2)| idx_1.cmp(idx_2));
var_list.sort_by_key(|(_, _, idx_1)| *idx_1);
let singleton_addr = self.registers[3];
unify_fn!(*self, singleton_heap_list, singleton_addr);
@@ -1077,47 +1113,47 @@ impl MachineState {
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
pub(crate) struct CWIL {
local_count: Integer,
pub(crate) global_count: Integer,
limits: Vec<(Integer, usize)>,
local_count: u128,
pub(crate) global_count: u128,
limits: Vec<(u128, usize)>,
pub(crate) inference_limit_exceeded: bool,
}
impl CWIL {
pub(crate) fn new() -> Self {
CWIL {
local_count: Integer::from(0),
global_count: Integer::from(0),
local_count: 0,
global_count: 0,
limits: vec![],
inference_limit_exceeded: false,
}
}
pub(crate) fn add_limit(&mut self, mut limit: Integer, block: usize) -> &Integer {
limit += &self.local_count;
pub(crate) fn add_limit(&mut self, mut limit: u128, block: usize) -> u128 {
limit = limit.strict_add(self.local_count);
match self.limits.last() {
Some((ref inner_limit, _)) if *inner_limit <= limit => {}
Some((inner_limit, _)) if *inner_limit <= limit => {}
_ => self.limits.push((limit, block)),
}
&self.local_count
self.local_count
}
#[inline(always)]
pub(crate) fn remove_limit(&mut self, block: usize) -> &Integer {
pub(crate) fn remove_limit(&mut self, block: usize) -> u128 {
if let Some((_, bl)) = self.limits.last() {
if bl == &block {
self.limits.pop();
}
}
&self.local_count
self.local_count
}
#[inline(always)]
pub(crate) fn reset(&mut self) {
self.local_count = Integer::from(0);
self.local_count = 0;
self.limits.clear();
self.inference_limit_exceeded = false;
}

View File

@@ -1,12 +1,15 @@
use fxhash::FxBuildHasher;
use indexmap::IndexSet;
use crate::arena::*;
use crate::atom_table::*;
use crate::forms::*;
use crate::heap_iter::*;
use crate::machine::attributed_variables::*;
use crate::machine::copier::*;
use crate::machine::heap::AllocError;
use crate::machine::heap::*;
use crate::machine::machine_errors::*;
use crate::machine::machine_indices::*;
use crate::machine::machine_state::*;
use crate::machine::partial_string::*;
use crate::machine::stack::*;
@@ -16,8 +19,6 @@ use crate::parser::ast::*;
use crate::parser::dashu::{Integer, Rational};
use crate::types::*;
use indexmap::IndexSet;
use std::cmp::Ordering;
use std::convert::TryFrom;
@@ -30,9 +31,10 @@ impl MachineState {
heap.store_resource_error();
MachineState {
arena: Arena::new(),
atom_tbl: AtomTable::new(),
arena: Arena::new().unwrap(),
atom_tbl: AtomTable::new().unwrap(),
pdl: Vec::with_capacity(1024),
unify_tabu_list: IndexSet::with_hasher(FxBuildHasher::default()),
s: HeapPtr::default(),
s_offset: 0,
p: 0,
@@ -47,7 +49,7 @@ impl MachineState {
fail: false,
heap,
mode: MachineMode::Write,
stack: Stack::new(),
stack: Stack::new().unwrap(),
registers: [heap_loc_as_cell!(0); MAX_ARITY + 1], // self.registers[0] is never used.
trail: vec![],
tr: 0,
@@ -57,16 +59,15 @@ impl MachineState {
ball: Ball::new(),
ball_stack: vec![],
lifted_heap: Heap::new(),
interms: vec![Number::default(); 256],
cont_pts: Vec::with_capacity(256),
cwil: CWIL::new(),
flags: MachineFlags::default(),
cc: 0,
global_clock: 0,
dynamic_mode: FirstOrNext::First,
unify_fn: MachineState::unify,
bind_fn: MachineState::bind,
occurs_check: &Nsto,
run_cleaners_fn: |_| false,
throwing_resource_error: false,
}
}
@@ -174,8 +175,8 @@ impl MachineState {
}
}
pub fn allocate(&mut self, num_cells: usize) {
let e = self.stack.allocate_and_frame(num_cells);
pub fn allocate(&mut self, num_cells: usize) -> Result<(), AllocError> {
let e = self.stack.allocate_and_frame(num_cells)?;
let and_frame = self.stack.index_and_frame_mut(e);
and_frame.prelude.e = self.e;
@@ -183,6 +184,8 @@ impl MachineState {
self.e = e;
self.p += 1;
Ok(())
}
pub fn bind(&mut self, r1: Ref, a2: HeapCellValue) {
@@ -377,347 +380,6 @@ impl MachineState {
}
}
pub fn compare_term_test(&mut self, var_comparison: VarComparison) -> Option<Ordering> {
let mut tabu_list = IndexSet::new();
while let Some(s1) = self.pdl.pop() {
let s1 = self.deref(s1);
let s2 = self.pdl.pop().unwrap();
let s2 = self.deref(s2);
if s1 == s2 {
continue;
}
let v1 = self.store(s1);
let v2 = self.store(s2);
let order_cat_v1 = v1.order_category(&self.heap);
let order_cat_v2 = v2.order_category(&self.heap);
if order_cat_v1 != order_cat_v2 {
self.pdl.clear();
return Some(order_cat_v1.cmp(&order_cat_v2));
}
match order_cat_v1 {
Some(TermOrderCategory::Variable) => {
if let VarComparison::Distinct = var_comparison {
let v1 = v1.as_var().unwrap();
let v2 = v2.as_var().unwrap();
if v1 != v2 {
self.pdl.clear();
return Some(v1.cmp(&v2));
}
}
}
Some(TermOrderCategory::FloatingPoint) => {
let v1 = cell_as_f64_offset!(v1);
let v2 = cell_as_f64_offset!(v2);
let v1 = self.arena.f64_tbl.get_entry(v1);
let v2 = self.arena.f64_tbl.get_entry(v2);
if v1 != v2 {
self.pdl.clear();
return Some(v1.cmp(&v2));
}
}
Some(TermOrderCategory::Integer) => {
let v1 = Number::try_from((v1, &self.arena.f64_tbl)).unwrap();
let v2 = Number::try_from((v2, &self.arena.f64_tbl)).unwrap();
if v1 != v2 {
self.pdl.clear();
return Some(v1.cmp(&v2));
}
}
Some(TermOrderCategory::Atom) => {
read_heap_cell!(v1,
(HeapCellValueTag::Atom, (n1, _a1)) => {
read_heap_cell!(v2,
(HeapCellValueTag::Atom, (n2, _a2)) => {
if n1 != n2 {
self.pdl.clear();
return Some(n1.cmp(&n2));
}
}
(HeapCellValueTag::Str, s) => {
let n2 = cell_as_atom_cell!(self.heap[s])
.get_name();
if n1 != n2 {
self.pdl.clear();
return Some(n1.cmp(&n2));
}
}
_ => {
unreachable!();
}
)
}
(HeapCellValueTag::Str, s) => {
let n1 = cell_as_atom_cell!(self.heap[s])
.get_name();
read_heap_cell!(v2,
(HeapCellValueTag::Atom, (n2, _a2)) => {
if n1 != n2 {
self.pdl.clear();
return Some(n1.cmp(&n2));
}
}
(HeapCellValueTag::Str, s) => {
let n2 = cell_as_atom_cell!(self.heap[s])
.get_name();
if n1 != n2 {
self.pdl.clear();
return Some(n1.cmp(&n2));
}
}
_ => {
unreachable!();
}
)
}
_ => {
unreachable!()
}
)
}
Some(TermOrderCategory::Compound) => {
read_heap_cell!(v1,
(HeapCellValueTag::Lis, l1) => {
read_heap_cell!(v2,
(HeapCellValueTag::PStrLoc, l2) => {
if tabu_list.contains(&(l1, l2)) {
continue;
}
tabu_list.insert((l1, l2));
// like the action of
// partial_string_to_pdl here but
// the ordering of PDL pushes is
// (crucially for comparison
// correctness) different.
let (c, succ_cell) = self.heap.last_str_char_and_tail(l2);
self.pdl.push(succ_cell);
self.pdl.push(heap_loc_as_cell!(l1 + 1));
self.pdl.push(char_as_cell!(c));
self.pdl.push(heap_loc_as_cell!(l1));
}
(HeapCellValueTag::Lis, l2) => {
if tabu_list.contains(&(l1, l2)) {
continue;
}
tabu_list.insert((l1, l2));
self.pdl.push(self.heap[l2 + 1]);
self.pdl.push(self.heap[l1 + 1]);
self.pdl.push(self.heap[l2]);
self.pdl.push(self.heap[l1]);
}
(HeapCellValueTag::Str, s2) => {
if tabu_list.contains(&(l1, s2)) {
continue;
}
let (name, arity) = cell_as_atom_cell!(self.heap[s2])
.get_name_and_arity();
match (2, atom!(".")).cmp(&(arity, name)) {
Ordering::Equal => {
tabu_list.insert((l1, s2));
self.pdl.push(self.heap[s2 + 2]);
self.pdl.push(self.heap[l1 + 1]);
self.pdl.push(self.heap[s2 + 1]);
self.pdl.push(self.heap[l1]);
}
ordering => {
self.pdl.clear();
return Some(ordering);
}
}
}
_ => {
unreachable!();
}
)
}
(HeapCellValueTag::PStrLoc, l1) => {
read_heap_cell!(v2,
(HeapCellValueTag::PStrLoc, l2) => {
if tabu_list.contains(&(l1, l2)) {
continue;
}
tabu_list.insert((l1, l2));
match self.heap.compare_pstr_segments(l1, l2) {
PStrSegmentCmpResult::Continue(v1, v2) => {
self.pdl.push(v1.offset_by(l1));
self.pdl.push(v2.offset_by(l2));
}
PStrSegmentCmpResult::Less => {
self.pdl.clear();
return Some(Ordering::Less);
}
PStrSegmentCmpResult::Greater => {
self.pdl.clear();
return Some(Ordering::Greater);
}
}
}
(HeapCellValueTag::Lis, l2) => {
if tabu_list.contains(&(l1, l2)) {
continue;
}
tabu_list.insert((l1, l2));
let (c, succ_cell) = self.heap.last_str_char_and_tail(l1);
self.pdl.push(succ_cell);
self.pdl.push(heap_loc_as_cell!(l2 + 1));
self.pdl.push(char_as_cell!(c));
self.pdl.push(heap_loc_as_cell!(l2));
}
(HeapCellValueTag::Str, s2) => {
if tabu_list.contains(&(l1, s2)) {
continue;
}
tabu_list.insert((l1, s2));
let (n2, a2) = cell_as_atom_cell!(self.heap[s2])
.get_name_and_arity();
match (2, atom!(".")).cmp(&(a2,n2)) {
Ordering::Equal => {
let (c, succ_cell) = self.heap.last_str_char_and_tail(l1);
self.pdl.push(heap_loc_as_cell!(s2+2));
self.pdl.push(succ_cell);
self.pdl.push(heap_loc_as_cell!(s2+1));
self.pdl.push(char_as_cell!(c));
}
ordering => {
self.pdl.clear();
return Some(ordering);
}
}
}
_ => {
unreachable!()
}
);
}
(HeapCellValueTag::Str, s1) => {
read_heap_cell!(v2,
(HeapCellValueTag::Str, s2) => {
if tabu_list.contains(&(s1, s2)) {
continue;
}
let (n1, a1) = cell_as_atom_cell!(self.heap[s1])
.get_name_and_arity();
let (n2, a2) = cell_as_atom_cell!(self.heap[s2])
.get_name_and_arity();
match (a1,n1).cmp(&(a2, n2)) {
Ordering::Equal => {
tabu_list.insert((s1, s2));
for idx in (1 .. a1+1).rev() {
self.pdl.push(self.heap[s2+idx]);
self.pdl.push(self.heap[s1+idx]);
}
}
ordering => {
self.pdl.clear();
return Some(ordering);
}
}
}
(HeapCellValueTag::Lis, l2) => {
if tabu_list.contains(&(s1, l2)) {
continue;
}
tabu_list.insert((s1, l2));
let (n1, a1) = cell_as_atom_cell!(self.heap[s1])
.get_name_and_arity();
match (a1,n1).cmp(&(2, atom!("."))) {
Ordering::Equal => {
self.pdl.push(self.heap[l2]);
self.pdl.push(self.heap[s1+1]);
self.pdl.push(self.heap[l2+1]);
self.pdl.push(self.heap[s1+2]);
}
ordering => {
self.pdl.clear();
return Some(ordering);
}
}
}
(HeapCellValueTag::PStrLoc, l2) => {
let (n1, a1) = cell_as_atom_cell!(self.heap[s1])
.get_name_and_arity();
match (a1,n1).cmp(&(2, atom!("."))) {
Ordering::Equal => {
let (c, succ_cell) = self.heap.last_str_char_and_tail(l2);
self.pdl.push(succ_cell);
self.pdl.push(heap_loc_as_cell!(s1+2));
self.pdl.push(char_as_cell!(c));
self.pdl.push(heap_loc_as_cell!(s1+1));
}
ordering => {
self.pdl.clear();
return Some(ordering);
}
}
}
_ => {
unreachable!()
}
)
}
_ => {
unreachable!()
}
);
}
None => {
if v1 != v2 {
self.pdl.clear();
return None;
}
}
}
}
Some(Ordering::Equal)
}
pub(crate) fn setup_call_n_init_goal_info(
&mut self,
goal: HeapCellValue,
@@ -888,16 +550,37 @@ impl MachineState {
}
// returns true on failure, false on success.
pub fn eq_test(&mut self, h1: HeapCellValue, h2: HeapCellValue) -> bool {
pub fn eq_test(&self, h1: HeapCellValue, h2: HeapCellValue) -> bool {
if h1 == h2 {
return false;
}
compare_term_test!(self, h1, h2)
.map(|o| o != Ordering::Equal)
self.compare_term_test(h1, h2)
.map(|o| !o.is_eq())
.unwrap_or(true)
}
pub fn compare_term_test(&self, h1: HeapCellValue, h2: HeapCellValue) -> Option<Ordering> {
// all derefs downstack from this function are heap bound so
// dereference stack vars pointing into the heap while possible
let h1 = self.store(h1);
let h2 = self.store(h2);
for term_pair in ParallelHeapIter::from(self, h1, h2) {
match term_pair {
TermPair::Vars(v1_offset, v2_offset) if v1_offset != v2_offset => {
return Some(v1_offset.cmp(&v2_offset));
}
TermPair::Less(..) => return Some(Ordering::Less),
TermPair::Greater(..) => return Some(Ordering::Greater),
TermPair::Unordered(cell_1, cell_2) if cell_1 != cell_2 => return None,
_ => {}
}
}
Some(Ordering::Equal)
}
#[inline(always)]
fn try_functor_compound_case(&mut self, name: Atom, arity: usize) {
self.try_functor_unify_components(atom_as_cell!(name), arity);
@@ -922,7 +605,7 @@ impl MachineState {
name: Atom,
arity: usize,
r: Ref,
) -> Result<(), usize> {
) -> Result<(), AllocError> {
let h = self.heap.cell_len();
let mut writer = self.heap.reserve(arity + 1)?;
@@ -949,7 +632,7 @@ impl MachineState {
}
};
(self.bind_fn)(self, r, f_a);
self.occurs_check.bind(self, r, f_a);
Ok(())
}
@@ -1259,7 +942,10 @@ impl MachineState {
}
// see 8.4.4.3 of Draft Technical Corrigendum 2 for an error guide.
pub fn project_onto_key(&mut self, value: HeapCellValue) -> Result<HeapCellValue, MachineStub> {
pub fn key_val_pair(
&mut self,
value: HeapCellValue,
) -> Result<(HeapCellValue, HeapCellValue), MachineStub> {
let stub_gen = || functor_stub(atom!("keysort"), 2);
let store_v = self.store(self.deref(value));
@@ -1273,7 +959,7 @@ impl MachineState {
let (name, arity) = cell_as_atom_cell!(self.heap[s]).get_name_and_arity();
if name == atom!("-") && arity == 2 {
Ok(heap_loc_as_cell!(s + 1))
Ok((heap_loc_as_cell!(s+1), heap_loc_as_cell!(s+2)))
} else {
let err = self.type_error(ValidType::Pair, self.heap[s]);
Err(self.error_form(err, stub_gen()))

View File

@@ -168,7 +168,7 @@ impl<'a> CopierTarget for TermCopyingMockWAM<'a> {
}
#[inline(always)]
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, usize> {
fn copy_pstr_to_threshold(&mut self, pstr_loc: usize) -> Result<usize, AllocError> {
self.wam.machine_st.heap.copy_pstr_within(pstr_loc)
}
@@ -178,12 +178,12 @@ impl<'a> CopierTarget for TermCopyingMockWAM<'a> {
}
#[inline(always)]
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, usize> {
fn reserve(&mut self, num_cells: usize) -> Result<HeapWriter<'_>, AllocError> {
self.wam.machine_st.heap.reserve(num_cells)
}
#[inline(always)]
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), usize> {
fn copy_slice_to_end(&mut self, bounds: Range<usize>) -> Result<(), AllocError> {
self.wam.machine_st.heap.copy_slice_to_end(bounds)
}
}
@@ -581,56 +581,44 @@ mod tests {
});
assert_eq!(
compare_term_test!(wam, wam.heap[0], wam.heap[1]),
wam.compare_term_test(wam.heap[0], wam.heap[1]),
Some(Ordering::Less)
);
assert_eq!(
compare_term_test!(wam, wam.heap[1], wam.heap[0]),
wam.compare_term_test(wam.heap[1], wam.heap[0]),
Some(Ordering::Greater)
);
assert_eq!(
compare_term_test!(wam, wam.heap[0], wam.heap[0]),
wam.compare_term_test(wam.heap[0], wam.heap[0]),
Some(Ordering::Equal)
);
assert_eq!(
compare_term_test!(wam, wam.heap[1], wam.heap[1]),
wam.compare_term_test(wam.heap[1], wam.heap[1]),
Some(Ordering::Equal)
);
let cstr_cell = wam.heap.allocate_cstr("string").unwrap();
assert_eq!(
compare_term_test!(wam, atom_as_cell!(atom!("atom")), cstr_cell),
wam.compare_term_test(atom_as_cell!(atom!("atom")), cstr_cell),
Some(Ordering::Less)
);
assert_eq!(
compare_term_test!(
wam,
atom_as_cell!(atom!("atom")),
atom_as_cell!(atom!("atom"))
),
wam.compare_term_test(atom_as_cell!(atom!("atom")), atom_as_cell!(atom!("atom"))),
Some(Ordering::Equal)
);
assert_eq!(
compare_term_test!(
wam,
atom_as_cell!(atom!("atom")),
atom_as_cell!(atom!("aaa"))
),
wam.compare_term_test(atom_as_cell!(atom!("atom")), atom_as_cell!(atom!("aaa"))),
Some(Ordering::Greater)
);
assert_eq!(
compare_term_test!(
wam,
fixnum_as_cell!(Fixnum::build_with(6)),
heap_loc_as_cell!(1)
),
wam.compare_term_test(fixnum_as_cell!(Fixnum::build_with(6)), heap_loc_as_cell!(1)),
Some(Ordering::Greater)
);
@@ -644,12 +632,12 @@ mod tests {
});
assert_eq!(
compare_term_test!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(0)),
wam.compare_term_test(str_loc_as_cell!(0), str_loc_as_cell!(0)),
Some(Ordering::Equal)
);
assert_eq!(
compare_term_test!(wam, heap_loc_as_cell!(0), atom_as_cell!(atom!("a"))),
wam.compare_term_test(str_loc_as_cell!(0), atom_as_cell!(atom!("a"))),
Some(Ordering::Greater)
);
@@ -676,23 +664,22 @@ mod tests {
});
assert_eq!(
compare_term_test!(wam, heap_loc_as_cell!(7), heap_loc_as_cell!(7)),
wam.compare_term_test(heap_loc_as_cell!(7), heap_loc_as_cell!(7)),
Some(Ordering::Equal)
);
assert_eq!(
compare_term_test!(wam, heap_loc_as_cell!(0), heap_loc_as_cell!(7)),
wam.compare_term_test(heap_loc_as_cell!(0), heap_loc_as_cell!(7)),
Some(Ordering::Greater)
);
assert_eq!(
compare_term_test!(wam, empty_list_as_cell!(), heap_loc_as_cell!(7)),
wam.compare_term_test(empty_list_as_cell!(), heap_loc_as_cell!(7)),
Some(Ordering::Less)
);
assert_eq!(
compare_term_test!(
wam,
wam.compare_term_test(
empty_list_as_cell!(),
fixnum_as_cell!(Fixnum::build_with(1))
),
@@ -702,29 +689,29 @@ mod tests {
let cstr_cell = wam.heap.allocate_cstr("string").unwrap();
assert_eq!(
compare_term_test!(wam, empty_list_as_cell!(), cstr_cell),
wam.compare_term_test(empty_list_as_cell!(), cstr_cell),
Some(Ordering::Less)
);
assert_eq!(
compare_term_test!(wam, empty_list_as_cell!(), atom_as_cell!(atom!("atom"))),
wam.compare_term_test(empty_list_as_cell!(), atom_as_cell!(atom!("atom"))),
Some(Ordering::Less)
);
assert_eq!(
compare_term_test!(wam, atom_as_cell!(atom!("atom")), empty_list_as_cell!()),
wam.compare_term_test(atom_as_cell!(atom!("atom")), empty_list_as_cell!()),
Some(Ordering::Greater)
);
let one_p_one = HeapCellValue::from(float_alloc!(1.1, &mut wam.arena));
assert_eq!(
compare_term_test!(wam, one_p_one, fixnum_as_cell!(Fixnum::build_with(1))),
wam.compare_term_test(one_p_one, fixnum_as_cell!(Fixnum::build_with(1))),
Some(Ordering::Less)
);
assert_eq!(
compare_term_test!(wam, fixnum_as_cell!(Fixnum::build_with(1)), one_p_one),
wam.compare_term_test(fixnum_as_cell!(Fixnum::build_with(1)), one_p_one),
Some(Ordering::Greater)
);
}

View File

@@ -38,6 +38,7 @@ use crate::instructions::*;
use crate::machine::args::*;
use crate::machine::compile::*;
use crate::machine::copier::*;
use crate::machine::heap::AllocError;
use crate::machine::heap::*;
use crate::machine::loader::*;
use crate::machine::machine_errors::*;
@@ -51,7 +52,6 @@ use crate::parser::dashu::{Integer, Rational};
use crate::types::*;
use indexmap::IndexMap;
use lazy_static::lazy_static;
use ordered_float::OrderedFloat;
use rand::rngs::StdRng;
@@ -60,12 +60,11 @@ use std::cmp::Ordering;
use std::env;
use std::io::Read;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::process::ExitCode;
use std::sync::OnceLock;
use std::sync::atomic::AtomicBool;
lazy_static! {
pub static ref INTERRUPT: AtomicBool = AtomicBool::new(false);
}
pub static INTERRUPT: AtomicBool = AtomicBool::new(false);
/// An instance of Scryer Prolog.
///
@@ -141,9 +140,8 @@ mod libraries {
}
pub static BREAK_FROM_DISPATCH_LOOP_LOC: usize = 0;
pub static INSTALL_VERIFY_ATTR_INTERRUPT: usize = 1;
pub static VERIFY_ATTR_INTERRUPT_LOC: usize = 2;
pub static LIB_QUERY_SUCCESS: usize = 3;
pub static VERIFY_ATTR_INTERRUPT_LOC: usize = 1;
pub static LIB_QUERY_SUCCESS: usize = 2;
pub struct MachinePreludeView<'a> {
pub indices: &'a mut IndexStore,
@@ -233,12 +231,7 @@ impl Machine {
/// Gets the current inference count.
pub fn get_inference_count(&mut self) -> u64 {
self.machine_st
.cwil
.global_count
.clone()
.try_into()
.unwrap()
self.machine_st.cwil.global_count.try_into().unwrap()
}
/// Runs the predicate `key` in `module_name` until completion.
@@ -261,7 +254,9 @@ impl Machine {
let p = index_ptr.local().unwrap();
// Leave a halting choice point to backtrack to in case the predicate fails or throws.
self.allocate_stub_choice_point();
if self.allocate_stub_choice_point().is_err() {
return ExitCode::FAILURE;
}
self.machine_st.cp = BREAK_FROM_DISPATCH_LOOP_LOC;
self.machine_st.p = p;
@@ -414,12 +409,11 @@ impl Machine {
}
pub(crate) fn add_impls_to_indices(&mut self) {
let impls_offset = self.code.len() + 4;
let impls_offset = self.code.len() + 3;
self.code.extend(vec![
Instruction::BreakFromDispatchLoop,
Instruction::InstallVerifyAttr,
Instruction::VerifyAttrInterrupt(0),
Instruction::RunVerifyAttr,
Instruction::BreakFromDispatchLoop, // the location of LIB_QUERY_SUCCESS
Instruction::ExecuteTermGreaterThan,
Instruction::ExecuteTermLessThan,
@@ -729,10 +723,10 @@ impl Machine {
}
#[inline(always)]
pub(super) fn try_me_else(&mut self, offset: usize) {
pub(super) fn try_me_else(&mut self, offset: usize) -> Result<(), AllocError> {
if let Some(offset) = self.next_applicable_clause(offset) {
let n = self.machine_st.num_of_args;
let b = self.machine_st.stack.allocate_or_frame(n);
let b = self.machine_st.stack.allocate_or_frame(n)?;
let or_frame = self.machine_st.stack.index_or_frame_mut(b);
or_frame.prelude.num_cells = n;
@@ -758,13 +752,15 @@ impl Machine {
}
self.machine_st.p += 1;
Ok(())
}
#[inline(always)]
pub(super) fn indexed_try(&mut self, offset: usize) {
pub(super) fn indexed_try(&mut self, offset: usize) -> Result<(), AllocError> {
if let Some(iip_offset) = self.next_inner_applicable_clause() {
let n = self.machine_st.num_of_args;
let b = self.machine_st.stack.allocate_or_frame(n);
let b = self.machine_st.stack.allocate_or_frame(n)?;
let or_frame = self.machine_st.stack.index_or_frame_mut(b);
or_frame.prelude.num_cells = n;
@@ -793,6 +789,7 @@ impl Machine {
}
self.machine_st.p += offset;
Ok(())
}
#[inline(always)]
@@ -1227,7 +1224,7 @@ impl Machine {
let key = Atom::from(h as u64);
match self.indices.global_variables.get_mut(&key) {
Some((_, ref mut loc)) => *loc = None,
Some((_, loc)) => *loc = None,
None => unreachable!(),
}
}
@@ -1236,7 +1233,7 @@ impl Machine {
let value_cell = HeapCellValue::from(u64::from(self.machine_st.trail[i + 1]));
match self.indices.global_variables.get_mut(&key) {
Some((_, ref mut loc)) => *loc = Some(value_cell),
Some((_, loc)) => *loc = Some(value_cell),
None => unreachable!(),
}
}

View File

@@ -5,13 +5,42 @@ use crate::machine::machine_errors::CycleSearchResult;
use crate::machine::system_calls::BrentAlgState;
use crate::types::*;
trait StepperImpl {
fn step<'a>(&self, iter: &mut HeapPStrIter<'a>) -> Option<PStrIteratee>;
fn is_cyclic(&self) -> bool;
}
struct PreCycleDiscoverStepper;
impl StepperImpl for PreCycleDiscoverStepper {
fn step<'a>(&self, iter: &mut HeapPStrIter<'a>) -> Option<PStrIteratee> {
iter.pre_cycle_discovery_stepper()
}
fn is_cyclic(&self) -> bool {
false
}
}
struct PostCycleDiscoverStepper;
impl StepperImpl for PostCycleDiscoverStepper {
fn step<'a>(&self, iter: &mut HeapPStrIter<'a>) -> Option<PStrIteratee> {
iter.post_cycle_discovery_stepper()
}
fn is_cyclic(&self) -> bool {
true
}
}
#[derive(Clone, Copy)]
pub struct HeapPStrIter<'a> {
pub heap: &'a Heap,
// pub focus: HeapCellValue,
orig_focus: usize,
brent_st: BrentAlgState,
stepper: fn(&mut HeapPStrIter<'a>) -> Option<PStrIteratee>,
stepper: &'static dyn StepperImpl,
}
struct PStrIterStep {
@@ -33,7 +62,7 @@ impl<'a> HeapPStrIter<'a> {
heap,
orig_focus,
brent_st: BrentAlgState::new(orig_focus),
stepper: HeapPStrIter::pre_cycle_discovery_stepper,
stepper: &PreCycleDiscoverStepper,
}
}
@@ -161,7 +190,7 @@ impl<'a> HeapPStrIter<'a> {
debug_assert!(matches!(cycle_result, CycleSearchResult::Cyclic { .. }));
self.walk_hare_to_cycle_end();
self.stepper = HeapPStrIter::post_cycle_discovery_stepper;
self.stepper = &PostCycleDiscoverStepper;
}
None => {
// self.focus = self.heap[next_hare];
@@ -194,7 +223,7 @@ impl<'a> HeapPStrIter<'a> {
}
pub(crate) fn is_cyclic(&self) -> bool {
self.stepper as usize == Self::post_cycle_discovery_stepper as usize
self.stepper.is_cyclic()
}
}
@@ -203,7 +232,7 @@ impl<'a> Iterator for HeapPStrIter<'a> {
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
(self.stepper)(self)
self.stepper.step(self)
}
}
@@ -217,6 +246,8 @@ mod test {
fn pstr_iter_tests() {
let mut wam = MockWAM::new();
let init_len = wam.machine_st.heap.cell_len();
let pstr_cell = wam.machine_st.heap.allocate_pstr("abc ").unwrap();
wam.machine_st
.heap
@@ -233,7 +264,7 @@ mod test {
assert_eq!(
iter.next(),
Some(PStrIteratee::PStrSlice {
slice_loc: heap_index!(1),
slice_loc: heap_index!(init_len),
slice_len: "abc ".len()
}),
);
@@ -241,9 +272,9 @@ mod test {
assert!(!iter.is_cyclic());
}
assert_eq!(wam.machine_st.heap[2], empty_list_as_cell!());
assert_eq!(wam.machine_st.heap[init_len + 1], empty_list_as_cell!());
wam.machine_st.heap[2] = pstr_loc_as_cell!(heap_index!(3));
wam.machine_st.heap[init_len + 1] = pstr_loc_as_cell!(heap_index!(init_len + 2));
wam.machine_st.heap.allocate_pstr("def").unwrap();
let h = wam.machine_st.heap.cell_len();
@@ -256,14 +287,14 @@ mod test {
assert_eq!(
iter.next(),
Some(PStrIteratee::PStrSlice {
slice_loc: heap_index!(1),
slice_loc: heap_index!(init_len),
slice_len: "abc ".len()
})
);
assert_eq!(
iter.next(),
Some(PStrIteratee::PStrSlice {
slice_loc: heap_index!(3),
slice_loc: heap_index!(init_len + 2),
slice_len: "def".len(),
})
);
@@ -282,14 +313,14 @@ mod test {
assert_eq!(
iter.next(),
Some(PStrIteratee::PStrSlice {
slice_loc: heap_index!(1),
slice_loc: heap_index!(init_len),
slice_len: "abc ".len()
})
);
assert_eq!(
iter.next(),
Some(PStrIteratee::PStrSlice {
slice_loc: heap_index!(3),
slice_loc: heap_index!(init_len + 2),
slice_len: "def".len(),
})
);
@@ -298,7 +329,7 @@ mod test {
assert!(!iter.is_cyclic());
}
wam.machine_st.heap[h] = pstr_loc_as_cell!(heap_index!(3));
wam.machine_st.heap[h] = pstr_loc_as_cell!(heap_index!(init_len + 2));
{
let mut iter = HeapPStrIter::new(&wam.machine_st.heap, 0);

View File

@@ -37,7 +37,7 @@ fn setup_op_decl(mut terms: Vec<Term>) -> Result<OpDecl, CompilationError> {
other => {
return Err(CompilationError::InvalidDirective(
DirectiveError::InvalidOpDeclSpecDomain(other),
))
));
}
};
@@ -82,7 +82,7 @@ fn setup_op_decl(mut terms: Vec<Term>) -> Result<OpDecl, CompilationError> {
fn setup_predicate_indicator(term: &mut Term) -> Result<PredicateKey, CompilationError> {
match term {
Term::Clause(_, slash, ref mut terms)
Term::Clause(_, slash, terms)
if (*slash == atom!("/") || *slash == atom!("//")) && terms.len() == 2 =>
{
let arity = terms.pop().unwrap();

View File

@@ -1,111 +0,0 @@
use core::marker::PhantomData;
use std::alloc;
use std::mem;
use std::ptr;
pub(crate) trait RawBlockTraits {
fn init_size() -> usize;
fn align() -> usize;
#[inline]
fn base_offset(base: *const u8) -> *const u8 {
base
}
}
#[derive(Debug)]
pub(crate) struct RawBlock<T: RawBlockTraits> {
pub(crate) size: usize,
pub(crate) base: *const u8,
pub(crate) top: *const u8,
_marker: PhantomData<T>,
}
impl<T: RawBlockTraits> RawBlock<T> {
pub(crate) fn new() -> Self {
let mut block = RawBlock {
size: 0,
base: ptr::null(),
top: ptr::null(),
_marker: PhantomData,
};
unsafe {
block.grow();
}
block
}
unsafe fn init_at_size(&mut self, cap: usize) {
let layout = alloc::Layout::from_size_align_unchecked(cap, T::align());
self.base = alloc::alloc(layout) as *const _;
self.size = cap;
self.top = T::base_offset(self.base);
}
pub(super) unsafe fn grow(&mut self) {
if self.size == 0 {
self.init_at_size(T::init_size());
} else {
let layout = alloc::Layout::from_size_align_unchecked(T::init_size(), T::align());
let top_dist = self.top as usize - self.base as usize;
self.base = alloc::realloc(self.base as *mut _, layout, self.size * 2) as *const _;
self.top = (self.base as usize + top_dist) as *const _;
self.size *= 2;
}
}
fn empty_block() -> Self {
RawBlock {
size: 0,
base: ptr::null(),
top: ptr::null(),
_marker: PhantomData,
}
}
#[inline]
pub(crate) fn take(&mut self) -> Self {
mem::replace(self, Self::empty_block())
}
#[inline]
fn free_space(&self) -> usize {
debug_assert!(
self.top >= self.base,
"self.top = {:?} < {:?} = self.base",
self.top,
self.base
);
self.size - (self.top as usize - self.base as usize)
}
#[inline]
pub(crate) unsafe fn new_block(&mut self, size: usize) -> *const u8 {
loop {
if self.free_space() >= size {
return (self.top as usize + size) as *const _;
} else {
self.grow();
}
}
}
pub(crate) fn deallocate(&mut self) {
unsafe {
let layout = alloc::Layout::from_size_align_unchecked(self.size, T::align());
alloc::dealloc(self.base as *mut u8, layout);
self.top = ptr::null();
self.base = ptr::null();
self.size = 0;
}
}
}

View File

@@ -1,5 +1,7 @@
use core::marker::PhantomData;
use std::ptr::NonNull;
use crate::machine::heap::AllocError;
use crate::raw_block::*;
use crate::types::*;
@@ -87,14 +89,20 @@ impl Index<usize> for Stack {
#[inline]
fn index(&self, index: usize) -> &Self::Output {
unsafe { &*self.buf.base.add(index).cast() }
unsafe {
let ptr = self.buf.get_unchecked(index);
&*ptr.cast::<HeapCellValue>()
}
}
}
impl IndexMut<usize> for Stack {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
unsafe { &mut *self.buf.base.add(index).cast_mut().cast() }
unsafe {
let ptr = self.buf.get_unchecked(index);
&mut *ptr.cast_mut().cast::<HeapCellValue>()
}
}
}
@@ -159,41 +167,39 @@ impl OrFrame {
}
impl Stack {
pub(crate) fn new() -> Self {
Stack {
buf: RawBlock::new(),
pub(crate) fn new() -> Result<Self, AllocError> {
Ok(Stack {
buf: RawBlock::new()?,
_marker: PhantomData,
}
})
}
#[inline(always)]
unsafe fn alloc(&mut self, frame_size: usize) -> *mut u8 {
unsafe fn alloc(&mut self, frame_size: usize) -> Result<NonNull<u8>, AllocError> {
loop {
let ptr = self.buf.alloc(frame_size);
if ptr.is_null() {
if !self.buf.grow() {
panic!("growing the stack failed")
unsafe {
let ptr = self.buf.alloc(frame_size);
if let Some(ptr) = NonNull::new(ptr) {
return Ok(ptr);
}
} else {
return ptr;
self.buf.grow()?;
}
}
}
pub(crate) fn allocate_and_frame(&mut self, num_cells: usize) -> usize {
pub(crate) fn allocate_and_frame(&mut self, num_cells: usize) -> Result<usize, AllocError> {
let frame_size = AndFrame::size_of(num_cells);
unsafe {
let e = (*self.buf.ptr.get_mut()).addr() - self.buf.base.addr();
let new_ptr = self.alloc(frame_size);
let e = self.buf.used_bytes();
let new_ptr = self.alloc(frame_size)?;
let mut offset = prelude_size::<AndFramePrelude>();
for idx in 0..num_cells {
let cell_ptr = new_ptr.add(offset) as *mut HeapCellValue;
ptr::write(cell_ptr, stack_loc_as_cell!(AndFrame, e, idx + 1));
let cell_ptr = new_ptr.add(offset).cast::<HeapCellValue>();
ptr::write(cell_ptr.as_ptr(), stack_loc_as_cell!(AndFrame, e, idx + 1));
// Because in the Index and IndexMut inplementations we need to get this from
// Because in the Index and IndexMut implementations we need to get this from
// exposed provenance, we need to expose the provenance here, even though we don't
// actually use the value for anything. This is a reminder that `expose_provenance`
// isn't just a cast from a pointer to an integer but has actual side effects.
@@ -205,27 +211,27 @@ impl Stack {
let and_frame = self.index_and_frame_mut(e);
and_frame.prelude.num_cells = num_cells;
e
Ok(e)
}
}
pub(crate) fn top(&self) -> usize {
unsafe { (*self.buf.ptr.get()).addr() - self.buf.base.addr() }
self.buf.used_bytes()
}
pub(crate) fn allocate_or_frame(&mut self, num_cells: usize) -> usize {
pub(crate) fn allocate_or_frame(&mut self, num_cells: usize) -> Result<usize, AllocError> {
let frame_size = OrFrame::size_of(num_cells);
unsafe {
let b = (*self.buf.ptr.get_mut()).addr() - self.buf.base.addr();
let new_ptr = self.alloc(frame_size);
let b = self.buf.used_bytes();
let new_ptr = self.alloc(frame_size)?;
let mut offset = prelude_size::<OrFramePrelude>();
for idx in 0..num_cells {
let cell_ptr = new_ptr.byte_add(offset) as *mut HeapCellValue;
ptr::write(cell_ptr, stack_loc_as_cell!(OrFrame, b, idx));
let cell_ptr = new_ptr.byte_add(offset).cast::<HeapCellValue>();
ptr::write(cell_ptr.as_ptr(), stack_loc_as_cell!(OrFrame, b, idx));
// Because in the Index and IndexMut inplementations we need to get this from
// Because in the Index and IndexMut implementations we need to get this from
// exposed provenance, we need to expose the provenance here, even though we don't
// actually use the value for anything. This is a reminder that `expose_provenance`
// isn't just a cast from a pointer to an integer but has actual side effects.
@@ -237,41 +243,61 @@ impl Stack {
let or_frame = self.index_or_frame_mut(b);
or_frame.prelude.num_cells = num_cells;
b
Ok(b)
}
}
fn get_raw(&self, index: usize) -> *const u8 {
debug_assert!(index < self.buf.used_bytes());
unsafe { self.buf.get_unchecked(index) }
}
#[inline(always)]
pub(crate) fn index_and_frame(&self, e: usize) -> &AndFrame {
unsafe { &*self.buf.base.add(e).cast() }
let ptr = self.get_raw(e);
unsafe { &*ptr.cast::<AndFrame>() }
}
#[inline(always)]
pub(crate) fn index_and_frame_mut(&mut self, e: usize) -> &mut AndFrame {
unsafe {
// This is doing alignment wrong
let ptr = self.buf.base.add(e);
&mut *(ptr as *mut AndFrame)
}
let ptr = self.get_raw(e);
unsafe { &mut *ptr.cast_mut().cast::<AndFrame>() }
}
#[inline(always)]
pub(crate) fn index_or_frame(&self, b: usize) -> &OrFrame {
unsafe { &*self.buf.base.add(b).cast() }
let ptr = self.get_raw(b);
unsafe { &*ptr.cast::<OrFrame>() }
}
#[inline(always)]
pub(crate) fn index_or_frame_mut(&mut self, b: usize) -> &mut OrFrame {
unsafe { &mut *self.buf.base.add(b).cast_mut().cast() }
let ptr = self.get_raw(b);
unsafe { &mut *ptr.cast_mut().cast::<OrFrame>() }
}
/// # Safety
///
/// The stack must contain a valid OrFrame at [`self.top()`](Self::top),
/// which can only be achieved by allocating it in the first place and later truncating the stack.
///
/// No allocation must have been done since the last call to [`truncate()`](Self::truncate).
#[inline(always)]
pub(crate) unsafe fn index_dangling_or_frame(&self) -> &OrFrame {
unsafe {
let ptr = self.buf.get_unchecked(self.top());
&*ptr.cast::<OrFrame>()
}
}
#[inline(always)]
pub(crate) fn truncate(&mut self, b: usize) {
let base = unsafe { self.buf.base.add(b) };
if base < (*self.buf.ptr.get_mut()) {
*self.buf.ptr.get_mut() = base.cast_mut();
}
self.buf.shift_back(b);
}
}
@@ -285,7 +311,7 @@ mod tests {
fn stack_tests() {
let mut wam = MockWAM::new();
let e = wam.machine_st.stack.allocate_and_frame(10); // create an AND frame!
let e = wam.machine_st.stack.allocate_and_frame(10).unwrap(); // create an AND frame!
let and_frame = wam.machine_st.stack.index_and_frame_mut(e);
assert_eq!(
@@ -303,7 +329,7 @@ mod tests {
assert_eq!(and_frame[5], empty_list_as_cell!());
let b = wam.machine_st.stack.allocate_or_frame(5);
let b = wam.machine_st.stack.allocate_or_frame(5).unwrap();
let or_frame = wam.machine_st.stack.index_or_frame_mut(b);
@@ -311,7 +337,7 @@ mod tests {
assert_eq!(or_frame[idx], stack_loc_as_cell!(OrFrame, b, idx));
}
let next_e = wam.machine_st.stack.allocate_and_frame(9); // create an AND frame!
let next_e = wam.machine_st.stack.allocate_and_frame(9).unwrap(); // create an AND frame!
let and_frame = wam.machine_st.stack.index_and_frame_mut(next_e);
for idx in 0..9 {

View File

@@ -12,10 +12,10 @@ use crate::machine::machine_indices::*;
use crate::machine::machine_state::*;
use crate::types::*;
pub use scryer_modular_bitfield::prelude::*;
pub use modular_bitfield::prelude::*;
#[cfg(feature = "http")]
use bytes::{buf::Reader as BufReader, Buf, Bytes};
use bytes::{Buf, Bytes, buf::Reader as BufReader};
use std::cmp::Ordering;
use std::error::Error;
use std::fmt;
@@ -23,6 +23,8 @@ use std::fmt::Debug;
use std::fs::{File, OpenOptions};
use std::hash::Hash;
use std::io;
use std::io::PipeReader;
use std::io::PipeWriter;
use std::io::{Cursor, ErrorKind, Read, Seek, SeekFrom, Write};
use std::mem::ManuallyDrop;
use std::net::{Shutdown, TcpStream};
@@ -38,10 +40,7 @@ use native_tls::TlsStream;
#[cfg(feature = "http")]
use warp::hyper;
mod compat;
pub use compat::*;
#[derive(Debug, BitfieldSpecifier, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Specifier, Clone, Copy, PartialEq, Eq, Hash)]
#[bits = 1]
pub enum StreamType {
Binary,
@@ -74,7 +73,7 @@ impl StreamType {
}
}
#[derive(Debug, BitfieldSpecifier, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Specifier, Clone, Copy, PartialEq, Eq, Hash)]
#[bits = 2]
pub enum EOFAction {
EOFCode,
@@ -82,7 +81,7 @@ pub enum EOFAction {
Reset,
}
#[derive(Debug, BitfieldSpecifier, Copy, Clone, PartialEq)]
#[derive(Debug, Specifier, Copy, Clone, PartialEq)]
#[bits = 2]
pub(crate) enum AtEndOfStream {
Not,
@@ -328,10 +327,7 @@ impl Write for HttpWriteStream {
}
#[cfg(feature = "http")]
impl HttpWriteStream {
// TODO why is this suddenly dead code and should it be used somewhere?
// Should this be impl Drop for HttpWriteStream?
#[allow(dead_code)]
impl Drop for HttpWriteStream {
fn drop(&mut self) {
let headers = unsafe { std::mem::ManuallyDrop::take(&mut self.headers) };
let buffer = unsafe { std::mem::ManuallyDrop::take(&mut self.buffer) };
@@ -660,6 +656,18 @@ impl Stream {
#[inline]
pub fn stdin(arena: &mut Arena, add_history: bool) -> Stream {
#[cfg(unix)]
use std::io::IsTerminal;
#[cfg(unix)]
if !std::io::stdin().is_terminal() {
use std::os::unix::io::{FromRawFd, RawFd};
// dup fd 0 so the File can be owned (and closed later) without
// closing the real stdin.
let fd = unsafe { libc::dup(0 as RawFd) };
let file = unsafe { File::from_raw_fd(fd) };
return Stream::from_file_as_input(atom!("user_input"), file, arena);
}
Stream::Readline(arena_alloc!(
StreamLayout::new(ReadlineStream::new("", add_history)),
arena
@@ -742,49 +750,49 @@ impl Stream {
pub fn options(&self) -> &StreamOptions {
match self {
Stream::Byte(ref ptr) => &ptr.options,
Stream::InputFile(ref ptr) => &ptr.options,
Stream::OutputFile(ref ptr) => &ptr.options,
Stream::StaticString(ref ptr) => &ptr.options,
Stream::NamedTcp(ref ptr) => &ptr.options,
Stream::Byte(ptr) => &ptr.options,
Stream::InputFile(ptr) => &ptr.options,
Stream::OutputFile(ptr) => &ptr.options,
Stream::StaticString(ptr) => &ptr.options,
Stream::NamedTcp(ptr) => &ptr.options,
#[cfg(feature = "tls")]
Stream::NamedTls(ref ptr) => &ptr.options,
Stream::NamedTls(ptr) => &ptr.options,
#[cfg(feature = "http")]
Stream::HttpRead(ref ptr) => &ptr.options,
Stream::HttpRead(ptr) => &ptr.options,
#[cfg(feature = "http")]
Stream::HttpWrite(ref ptr) => &ptr.options,
Stream::Null(ref options) => options,
Stream::Readline(ref ptr) => &ptr.options,
Stream::StandardOutput(ref ptr) => &ptr.options,
Stream::StandardError(ref ptr) => &ptr.options,
Stream::Callback(ref ptr) => &ptr.options,
Stream::InputChannel(ref ptr) => &ptr.options,
Stream::PipeReader(ref ptr) => &ptr.options,
Stream::PipeWriter(ref ptr) => &ptr.options,
Stream::HttpWrite(ptr) => &ptr.options,
Stream::Null(options) => options,
Stream::Readline(ptr) => &ptr.options,
Stream::StandardOutput(ptr) => &ptr.options,
Stream::StandardError(ptr) => &ptr.options,
Stream::Callback(ptr) => &ptr.options,
Stream::InputChannel(ptr) => &ptr.options,
Stream::PipeReader(ptr) => &ptr.options,
Stream::PipeWriter(ptr) => &ptr.options,
}
}
pub(super) fn options_mut(&mut self) -> &mut StreamOptions {
match self {
Stream::Byte(ref mut ptr) => &mut ptr.options,
Stream::InputFile(ref mut ptr) => &mut ptr.options,
Stream::OutputFile(ref mut ptr) => &mut ptr.options,
Stream::StaticString(ref mut ptr) => &mut ptr.options,
Stream::NamedTcp(ref mut ptr) => &mut ptr.options,
Stream::Byte(ptr) => &mut ptr.options,
Stream::InputFile(ptr) => &mut ptr.options,
Stream::OutputFile(ptr) => &mut ptr.options,
Stream::StaticString(ptr) => &mut ptr.options,
Stream::NamedTcp(ptr) => &mut ptr.options,
#[cfg(feature = "tls")]
Stream::NamedTls(ref mut ptr) => &mut ptr.options,
Stream::NamedTls(ptr) => &mut ptr.options,
#[cfg(feature = "http")]
Stream::HttpRead(ref mut ptr) => &mut ptr.options,
Stream::HttpRead(ptr) => &mut ptr.options,
#[cfg(feature = "http")]
Stream::HttpWrite(ref mut ptr) => &mut ptr.options,
Stream::Null(ref mut options) => options,
Stream::Readline(ref mut ptr) => &mut ptr.options,
Stream::StandardOutput(ref mut ptr) => &mut ptr.options,
Stream::StandardError(ref mut ptr) => &mut ptr.options,
Stream::Callback(ref mut ptr) => &mut ptr.options,
Stream::InputChannel(ref mut ptr) => &mut ptr.options,
Stream::PipeReader(ref mut ptr) => &mut ptr.options,
Stream::PipeWriter(ref mut ptr) => &mut ptr.options,
Stream::HttpWrite(ptr) => &mut ptr.options,
Stream::Null(options) => options,
Stream::Readline(ptr) => &mut ptr.options,
Stream::StandardOutput(ptr) => &mut ptr.options,
Stream::StandardError(ptr) => &mut ptr.options,
Stream::Callback(ptr) => &mut ptr.options,
Stream::InputChannel(ptr) => &mut ptr.options,
Stream::PipeReader(ptr) => &mut ptr.options,
Stream::PipeWriter(ptr) => &mut ptr.options,
}
}
@@ -952,17 +960,17 @@ impl CharRead for Stream {
fn consume(&mut self, nread: usize) {
match self {
Stream::InputFile(ref mut file) => file.consume(nread),
Stream::NamedTcp(ref mut tcp_stream) => tcp_stream.consume(nread),
Stream::InputFile(file) => file.consume(nread),
Stream::NamedTcp(tcp_stream) => tcp_stream.consume(nread),
#[cfg(feature = "tls")]
Stream::NamedTls(ref mut tls_stream) => tls_stream.consume(nread),
Stream::NamedTls(tls_stream) => tls_stream.consume(nread),
#[cfg(feature = "http")]
Stream::HttpRead(ref mut http_stream) => http_stream.consume(nread),
Stream::Readline(ref mut rl_stream) => rl_stream.consume(nread),
Stream::StaticString(ref mut src) => src.consume(nread),
Stream::Byte(ref mut cursor) => cursor.consume(nread),
Stream::InputChannel(ref mut cursor) => cursor.consume(nread),
Stream::PipeReader(ref mut cursor) => cursor.consume(nread),
Stream::HttpRead(http_stream) => http_stream.consume(nread),
Stream::Readline(rl_stream) => rl_stream.consume(nread),
Stream::StaticString(src) => src.consume(nread),
Stream::Byte(cursor) => cursor.consume(nread),
Stream::InputChannel(cursor) => cursor.consume(nread),
Stream::PipeReader(cursor) => cursor.consume(nread),
#[cfg(feature = "http")]
Stream::HttpWrite(_) => {}
Stream::OutputFile(_)
@@ -1011,17 +1019,17 @@ impl Read for Stream {
impl Write for Stream {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self {
Stream::OutputFile(ref mut file) => file.write(buf),
Stream::NamedTcp(ref mut tcp_stream) => tcp_stream.get_mut().write(buf),
Stream::OutputFile(file) => file.write(buf),
Stream::NamedTcp(tcp_stream) => tcp_stream.get_mut().write(buf),
#[cfg(feature = "tls")]
Stream::NamedTls(ref mut tls_stream) => tls_stream.get_mut().write(buf),
Stream::Byte(ref mut cursor) => cursor.get_mut().write(buf),
Stream::Callback(ref mut callback_stream) => callback_stream.get_mut().write(buf),
Stream::NamedTls(tls_stream) => tls_stream.get_mut().write(buf),
Stream::Byte(cursor) => cursor.get_mut().write(buf),
Stream::Callback(callback_stream) => callback_stream.get_mut().write(buf),
Stream::StandardOutput(stream) => stream.write(buf),
Stream::StandardError(stream) => stream.write(buf),
#[cfg(feature = "http")]
Stream::HttpWrite(ref mut stream) => stream.get_mut().write(buf),
Stream::PipeWriter(ref mut stream) => stream.get_mut().write(buf),
Stream::HttpWrite(stream) => stream.get_mut().write(buf),
Stream::PipeWriter(stream) => stream.get_mut().write(buf),
#[cfg(feature = "http")]
Stream::HttpRead(_) => Err(std::io::Error::new(
ErrorKind::PermissionDenied,
@@ -1041,17 +1049,17 @@ impl Write for Stream {
fn flush(&mut self) -> std::io::Result<()> {
match self {
Stream::OutputFile(ref mut file) => file.stream.flush(),
Stream::NamedTcp(ref mut tcp_stream) => tcp_stream.stream.get_mut().flush(),
Stream::OutputFile(file) => file.stream.flush(),
Stream::NamedTcp(tcp_stream) => tcp_stream.stream.get_mut().flush(),
#[cfg(feature = "tls")]
Stream::NamedTls(ref mut tls_stream) => tls_stream.stream.get_mut().flush(),
Stream::Byte(ref mut cursor) => cursor.stream.get_mut().flush(),
Stream::Callback(ref mut callback_stream) => callback_stream.stream.get_mut().flush(),
Stream::NamedTls(tls_stream) => tls_stream.stream.get_mut().flush(),
Stream::Byte(cursor) => cursor.stream.get_mut().flush(),
Stream::Callback(callback_stream) => callback_stream.stream.get_mut().flush(),
Stream::StandardError(stream) => stream.stream.flush(),
Stream::StandardOutput(stream) => stream.stream.flush(),
Stream::PipeWriter(ref mut stream) => stream.stream.get_mut().flush(),
Stream::PipeWriter(stream) => stream.stream.get_mut().flush(),
#[cfg(feature = "http")]
Stream::HttpWrite(ref mut stream) => stream.stream.get_mut().flush(),
Stream::HttpWrite(stream) => stream.stream.get_mut().flush(),
#[cfg(feature = "http")]
Stream::HttpRead(_) => Err(std::io::Error::new(
ErrorKind::PermissionDenied,
@@ -1522,58 +1530,58 @@ impl Stream {
#[inline]
pub(crate) fn close(&mut self) -> Result<(), std::io::Error> {
match self {
Stream::NamedTcp(ref mut tcp_stream) => {
Stream::NamedTcp(tcp_stream) => {
tcp_stream.inner_mut().tcp_stream.shutdown(Shutdown::Both)
}
#[cfg(feature = "tls")]
Stream::NamedTls(ref mut tls_stream) => tls_stream.inner_mut().tls_stream.shutdown(),
Stream::NamedTls(tls_stream) => tls_stream.inner_mut().tls_stream.shutdown(),
#[cfg(feature = "http")]
Stream::HttpRead(ref mut http_stream) => {
Stream::HttpRead(http_stream) => {
http_stream.drop_payload();
Ok(())
}
#[cfg(feature = "http")]
Stream::HttpWrite(mut http_stream) => {
&mut Stream::HttpWrite(mut http_stream) => {
http_stream.drop_payload();
Ok(())
}
Stream::InputFile(mut file_stream) => {
&mut Stream::InputFile(mut file_stream) => {
// close the stream by dropping the inner File.
file_stream.drop_payload();
Ok(())
}
Stream::OutputFile(mut file_stream) => {
&mut Stream::OutputFile(mut file_stream) => {
// close the stream by dropping the inner File.
file_stream.drop_payload();
Ok(())
}
Stream::Byte(mut stream) => {
&mut Stream::Byte(mut stream) => {
stream.drop_payload();
Ok(())
}
Stream::Callback(mut stream) => {
&mut Stream::Callback(mut stream) => {
stream.drop_payload();
Ok(())
}
Stream::InputChannel(mut stream) => {
&mut Stream::InputChannel(mut stream) => {
stream.drop_payload();
Ok(())
}
Stream::StaticString(mut stream) => {
&mut Stream::StaticString(mut stream) => {
stream.drop_payload();
Ok(())
}
Stream::PipeReader(mut stream) => {
&mut Stream::PipeReader(mut stream) => {
stream.drop_payload();
Ok(())
}
Stream::PipeWriter(mut stream) => {
&mut Stream::PipeWriter(mut stream) => {
stream.drop_payload();
Ok(())
}
@@ -1636,11 +1644,11 @@ impl Stream {
self.set_past_end_of_stream(false);
match self {
Stream::Byte(ref mut cursor) => {
Stream::Byte(cursor) => {
cursor.stream.get_mut().0.set_position(0);
true
}
Stream::InputFile(ref mut file_stream) => {
Stream::InputFile(file_stream) => {
file_stream
.stream
.get_mut()
@@ -1649,11 +1657,11 @@ impl Stream {
.unwrap();
true
}
Stream::Readline(ref mut readline_stream) => {
Stream::Readline(readline_stream) => {
readline_stream.reset();
true
}
Stream::InputChannel(ref mut input_channel_stream) => {
Stream::InputChannel(input_channel_stream) => {
input_channel_stream.stream.get_mut().inner.set_position(0);
true
}
@@ -1664,7 +1672,7 @@ impl Stream {
#[inline]
pub(crate) fn peek_byte(&mut self) -> std::io::Result<u8> {
match self {
Stream::Byte(ref mut cursor) => {
Stream::Byte(cursor) => {
let mut b = [0u8; 1];
let pos = cursor.stream.get_mut().0.position();
@@ -1676,15 +1684,15 @@ impl Stream {
_ => Err(std::io::Error::new(ErrorKind::UnexpectedEof, "end of file")),
}
}
Stream::InputFile(ref mut file) => match file.peek_byte() {
Stream::InputFile(file) => match file.peek_byte() {
Some(result) => Ok(result?),
_ => Err(std::io::Error::new(
ErrorKind::UnexpectedEof,
StreamError::PeekByteFailed,
)),
},
Stream::Readline(ref mut stream) => stream.stream.peek_byte(),
Stream::NamedTcp(ref mut stream) => {
Stream::Readline(stream) => stream.stream.peek_byte(),
Stream::NamedTcp(stream) => {
let mut b = [0u8; 1];
stream.stream.get_mut().tcp_stream.peek(&mut b)?;
Ok(b[0])
@@ -1950,7 +1958,7 @@ impl MachineState {
) -> Result<Stream, ParserError> {
match stream.peek_char() {
None => Ok(stream), // empty stream is handled gracefully by Lexer::eof
Some(Err(e)) => Err(ParserError::IO(e)),
Some(Err(e)) => Err(ParserError::from(e)),
Some(Ok(c)) => {
if c == '\u{feff}' {
// skip UTF-8 BOM
@@ -2187,8 +2195,8 @@ mod test {
use crate::*;
use std::{cell::RefCell, io::Read, io::Write, rc::Rc};
use crate::machine::config::*;
use crate::LeafAnswer;
use crate::machine::config::*;
use super::{Stream, StreamOptions};

View File

@@ -1,70 +0,0 @@
#[cfg(rust_version = "1.87.0")]
pub use ge_1_87_0::{PipeReader, PipeWriter};
#[cfg(not(rust_version = "1.87.0"))]
pub use lt_1_87_0::{PipeReader, PipeWriter};
#[cfg(not(rust_version = "1.87.0"))]
pub(crate) use lt_1_87_0::PipeReaderInner;
#[cfg(not(rust_version = "1.87.0"))]
mod lt_1_87_0 {
use std::process::{ChildStderr, ChildStdout};
pub type PipeWriter = std::process::ChildStdin;
#[derive(Debug)]
pub struct PipeReader(pub(crate) PipeReaderInner);
#[derive(Debug)]
pub(crate) enum PipeReaderInner {
Stdout(ChildStdout),
Stderr(ChildStderr),
}
impl std::io::Read for PipeReader {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read(buf),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read(buf),
}
}
fn read_vectored(
&mut self,
bufs: &mut [std::io::IoSliceMut<'_>],
) -> std::io::Result<usize> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read_vectored(bufs),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read_vectored(bufs),
}
}
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read_to_end(buf),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read_to_end(buf),
}
}
fn read_to_string(&mut self, buf: &mut String) -> std::io::Result<usize> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read_to_string(buf),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read_to_string(buf),
}
}
fn read_exact(&mut self, buf: &mut [u8]) -> std::io::Result<()> {
match &mut self.0 {
PipeReaderInner::Stdout(child_stdout) => child_stdout.read_exact(buf),
PipeReaderInner::Stderr(child_stderr) => child_stderr.read_exact(buf),
}
}
}
}
#[cfg(rust_version = "1.87.0")]
mod ge_1_87_0 {
pub type PipeReader = std::io::PipeReader;
pub type PipeWriter = std::io::PipeWriter;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
use crate::machine::Number;
use crate::Machine;
use crate::machine::Number;
use ordered_float::OrderedFloat;
use puruspe::beta::*;
use puruspe::error::*;

View File

@@ -1,6 +1,6 @@
use crate::arena::*;
use crate::forms::*;
use crate::heap_iter::{stackful_preorder_iter, NonListElider};
use crate::heap_iter::{NonListElider, stackful_preorder_iter};
use crate::machine::machine_state::*;
use crate::machine::*;
use crate::offset_table::*;
@@ -9,19 +9,14 @@ use crate::types::*;
use std::ops::{Deref, DerefMut};
use derive_more::*;
use fxhash::FxBuildHasher;
use indexmap::IndexSet;
use num_order::NumOrd;
impl MachineState {
pub(crate) fn partial_string_to_pdl(&mut self, pstr_loc: usize, l: usize) {
let (c, succ_cell) = self.heap.last_str_char_and_tail(pstr_loc);
self.pdl.push(heap_loc_as_cell!(l + 1));
self.pdl.push(succ_cell);
self.pdl.push(heap_loc_as_cell!(l));
self.pdl.push(char_as_cell!(c));
self.pdl.push((succ_cell, heap_loc_as_cell!(l + 1)));
self.pdl.push((char_as_cell!(c), heap_loc_as_cell!(l)));
}
}
@@ -37,8 +32,7 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
if n1 == n2 && a1 == a2 {
for idx in (0..a1).rev() {
self.pdl.push(heap_loc_as_cell!(s2+1+idx));
self.pdl.push(heap_loc_as_cell!(s1+1+idx));
self.pdl.push((heap_loc_as_cell!(s1+1+idx), heap_loc_as_cell!(s2+1+idx)));
}
} else {
self.fail = true;
@@ -47,8 +41,7 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
(HeapCellValueTag::Lis, l2) => {
if a1 == 2 && n1 == atom!(".") {
for idx in (0..2).rev() {
self.pdl.push(heap_loc_as_cell!(l2+1+idx));
self.pdl.push(heap_loc_as_cell!(s1+1+idx));
self.pdl.push((heap_loc_as_cell!(s1+1+idx), heap_loc_as_cell!(l2+1+idx)));
}
} else {
self.fail = true;
@@ -76,8 +69,7 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
read_heap_cell!(value,
(HeapCellValueTag::Lis, l2) => {
for idx in (0..2).rev() {
self.pdl.push(heap_loc_as_cell!(l2 + idx));
self.pdl.push(heap_loc_as_cell!(l1 + idx));
self.pdl.push((heap_loc_as_cell!(l1 + idx), heap_loc_as_cell!(l2 + idx)));
}
}
(HeapCellValueTag::Str, s2) => {
@@ -86,8 +78,7 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
if a2 == 2 && n2 == atom!(".") {
for idx in (0..2).rev() {
self.pdl.push(heap_loc_as_cell!(s2+1+idx));
self.pdl.push(heap_loc_as_cell!(l1+idx));
self.pdl.push((heap_loc_as_cell!(l1+idx), heap_loc_as_cell!(s2+1+idx)));
}
} else {
self.fail = true;
@@ -136,8 +127,7 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
(HeapCellValueTag::PStrLoc, other_pstr_loc) => {
match machine_st.heap.compare_pstr_segments(pstr_loc, other_pstr_loc) {
PStrSegmentCmpResult::Continue(v1, v2) => {
machine_st.pdl.push(v1.offset_by(pstr_loc));
machine_st.pdl.push(v2.offset_by(other_pstr_loc));
machine_st.pdl.push((v2.offset_by(other_pstr_loc), v1.offset_by(pstr_loc)));
}
_ => {
machine_st.fail = true;
@@ -355,13 +345,15 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
}
fn unify_internal(&mut self) {
let mut tabu_list = IndexSet::with_hasher(FxBuildHasher::default());
debug_assert!(self.unify_tabu_list.is_empty());
while let Some((s1, s2)) = self.pdl.pop() {
if self.fail {
// FIXME(msrv) FIXME(edition2024) use let chain to move this to check this in the while condition
break;
}
while !(self.pdl.is_empty() || self.fail) {
let s1 = self.pdl.pop().unwrap();
let s1 = (self.deref() as &MachineState).deref(s1);
let s2 = self.pdl.pop().unwrap();
let s2 = (self.deref() as &MachineState).deref(s2);
if s1 != s2 {
@@ -383,7 +375,7 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
Self::unify_atom(self, name, d2);
}
(HeapCellValueTag::Str, s1) => {
if tabu_list.contains(&(d1, d2)) {
if self.unify_tabu_list.contains(&(d1, d2)) {
continue;
}
@@ -391,11 +383,11 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
if !self.fail {
let d2 = self.store(d2);
tabu_list.insert((d1, d2));
self.unify_tabu_list.insert((d1, d2));
}
}
(HeapCellValueTag::Lis, l1) => {
if d2.is_ref() && tabu_list.contains(&(d1, d2)) {
if d2.is_ref() && self.unify_tabu_list.contains(&(d1, d2)) {
continue;
}
@@ -403,7 +395,7 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
if !self.fail {
let d2 = self.store(d2);
tabu_list.insert((d1, d2));
self.unify_tabu_list.insert((d1, d2));
}
}
(HeapCellValueTag::PStrLoc, l) => {
@@ -411,7 +403,7 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
(HeapCellValueTag::PStrLoc |
HeapCellValueTag::Lis |
HeapCellValueTag::Str) => {
if tabu_list.contains(&(d1, d2)) {
if self.unify_tabu_list.contains(&(d1, d2)) {
continue;
}
}
@@ -429,7 +421,7 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
if !self.fail && !d2.is_constant() {
let d2 = self.store(d2);
tabu_list.insert((d1, d2));
self.unify_tabu_list.insert((d1, d2));
}
}
(HeapCellValueTag::F64Offset, f1) => {
@@ -450,6 +442,8 @@ pub(crate) trait Unifier: DerefMut<Target = MachineState> {
);
}
}
self.unify_tabu_list.clear();
}
fn bind(&mut self, r: Ref, value: HeapCellValue);

View File

@@ -138,14 +138,7 @@ macro_rules! typed_arena_ptr_as_cell {
}
macro_rules! raw_ptr_as_cell {
($ptr:expr) => {{
// Cell is 64-bit, but raw ptr is 32-bit in 32-bit systems
let ptr: *const _ = $ptr;
// This needs to expose provenance because it needs to be turned back into a pointer
// in contexts where there is no available provenance locally. For example, in
// `ConsPtr::as_ptr`.
HeapCellValue::from_ptr_addr(ptr.expose_provenance())
}};
($ptr:expr) => {{ HeapCellValue::from_arena_header_ptr($ptr) }};
}
macro_rules! untyped_arena_ptr_as_cell {
@@ -369,12 +362,6 @@ macro_rules! compare_number_instr {
}};
}
macro_rules! interm {
($n: expr) => {
ArithmeticTerm::Interm($n)
};
}
macro_rules! ar_reg {
($r: expr) => {
ArithmeticTerm::Reg($r)
@@ -412,47 +399,32 @@ macro_rules! index_store {
}
macro_rules! unify {
($machine_st:expr, $($value:expr),*) => {{
$($machine_st.pdl.push($value);)*
($machine_st:expr, $($v1:expr, $v2:expr),*) => {{
$($machine_st.pdl.push(($v2, $v1));)*
$machine_st.unify()
}};
}
macro_rules! unify_fn {
($machine_st:expr, $($value:expr),*) => {{
$($machine_st.pdl.push($value);)*
($machine_st.unify_fn)(&mut $machine_st)
($machine_st:expr, $($v1:expr, $v2: expr),*) => {{
$($machine_st.pdl.push(($v2, $v1));)*
$machine_st.occurs_check.unify(&mut $machine_st)
}};
}
macro_rules! unify_with_occurs_check {
($machine_st:expr, $($value:expr),*) => {{
$($machine_st.pdl.push($value);)*
($machine_st:expr, $($v1:expr, $v2:expr),*) => {{
$($machine_st.pdl.push(($v2, $v1));)*
$machine_st.unify_with_occurs_check()
}};
}
macro_rules! compare_term_test {
($machine_st:expr, $e1:expr, $e2:expr) => {{
$machine_st.pdl.push($e2);
$machine_st.pdl.push($e1);
$machine_st.compare_term_test(VarComparison::Distinct)
}};
($machine_st:expr, $e1:expr, $e2:expr, $var_comparison:expr) => {{
$machine_st.pdl.push($e2);
$machine_st.pdl.push($e1);
$machine_st.compare_term_test($var_comparison)
}};
}
macro_rules! step_or_resource_error {
($machine_st:expr, $val:expr) => {{
match $val {
Ok(r) => r,
Err(err_loc) => {
$machine_st.throw_resource_error(err_loc);
Err(err) => {
$machine_st.throw_resource_error(err);
return;
}
}
@@ -460,8 +432,8 @@ macro_rules! step_or_resource_error {
($machine_st:expr, $val:expr, $fail:block) => {{
match $val {
Ok(r) => r,
Err(err_loc) => {
$machine_st.throw_resource_error(err_loc);
Err(err) => {
$machine_st.throw_resource_error(err);
$fail
}
}
@@ -476,14 +448,29 @@ macro_rules! resource_error_call_result {
};
}
macro_rules! heap_index {
macro_rules! heap_index_checked {
($idx:expr) => {
($idx) * std::mem::size_of::<HeapCellValue>()
std::mem::size_of::<HeapCellValue>().checked_mul($idx)
};
}
pub(crate) use heap_index_checked;
macro_rules! heap_index {
($idx:expr) => {{
let idx = $idx;
$crate::macros::heap_index_checked!(idx).unwrap_or_else(|| {
panic!(
"overflow while calculating heap index {idx} * {} > {}",
std::mem::size_of::<HeapCellValue>(),
usize::MAX,
)
})
}};
}
macro_rules! cell_index {
($idx:expr) => {
(($idx) / std::mem::size_of::<HeapCellValue>())
($idx) / std::mem::size_of::<HeapCellValue>()
};
}

View File

@@ -2,17 +2,18 @@ use std::cell::UnsafeCell;
use std::sync::Arc;
use std::{fmt, mem, ptr};
use arcu::Rcu;
use arcu::atomic::Arcu;
use arcu::epoch_counters::GlobalEpochCounterPool;
use arcu::rcu_ref::RcuRef;
use arcu::Rcu;
use fxhash::FxBuildHasher;
use indexmap::IndexMap;
use parking_lot::{Mutex, RwLock};
use crate::machine::heap::AllocError;
use crate::machine::machine_indices::IndexPtr;
use crate::raw_block::RawBlock;
use crate::raw_block::RawBlockTraits;
use crate::raw_block::{RawBlock, RawBlockConcurrent};
use ordered_float::OrderedFloat;
@@ -58,8 +59,10 @@ impl<T: RawBlockTraits> From<Arc<ConcurrentOffsetTable<T>>> for OffsetTableImpl<
impl<T: fmt::Debug + RawBlockTraits> OffsetTableImpl<T> {
#[inline(always)]
pub fn new() -> Self {
Self(InnerOffsetTableImpl::Serial(SerialOffsetTable::new()))
pub fn new() -> Result<Self, AllocError> {
Ok(Self(
InnerOffsetTableImpl::Serial(SerialOffsetTable::new()?),
))
}
#[must_use = "the returned concurrent table must be absorbed into the owned OffsetTable"]
@@ -90,8 +93,9 @@ impl<T: fmt::Debug + RawBlockTraits> OffsetTableImpl<T> {
// this shouldn't be able to fail
let raw_block =
Arc::try_unwrap(table.block.replace(RawBlock::empty_block())).unwrap();
self.0 =
InnerOffsetTableImpl::Serial(SerialOffsetTable { block: raw_block });
self.0 = InnerOffsetTableImpl::Serial(SerialOffsetTable {
block: raw_block.into(),
});
Ok(())
}
Err(table_arc) => {
@@ -116,7 +120,7 @@ impl<T: fmt::Debug + RawBlockTraits> OffsetTableImpl<T> {
impl<T: fmt::Debug + RawBlockTraits> Default for OffsetTableImpl<T> {
fn default() -> Self {
Self::new()
Self::new().unwrap()
}
}
@@ -127,7 +131,7 @@ struct SerialOffsetTable<T: RawBlockTraits> {
#[derive(Debug)]
pub struct ConcurrentOffsetTable<T: RawBlockTraits> {
block: Arcu<RawBlock<T>, GlobalEpochCounterPool>,
block: Arcu<RawBlock<T, RawBlockConcurrent>, GlobalEpochCounterPool>,
growth_lock: RwLock<()>,
offset_locks: RwLock<Vec<RwLock<()>>>,
}
@@ -201,40 +205,44 @@ impl OffsetTable<IndexPtr> for OffsetTableImpl<IndexPtr> {
impl<T: RawBlockTraits> SerialOffsetTable<T> {
#[inline]
fn new() -> Self {
Self {
block: RawBlock::new(),
}
fn new() -> Result<Self, AllocError> {
Ok(Self {
block: RawBlock::new()?,
})
}
unsafe fn build_with(&mut self, value: T) -> usize {
let mut ptr;
unsafe {
let mut ptr;
loop {
ptr = self.block.alloc(size_of::<T>());
loop {
ptr = self.block.alloc(size_of::<T>());
if ptr.is_null() {
let new_block = self.block.grow_new().unwrap();
self.block = new_block;
} else {
break;
if ptr.is_null() {
let new_block = self.block.grow_new().unwrap();
self.block = new_block;
} else {
break;
}
}
}
ptr::write(ptr as *mut T, value);
ptr.addr() - self.block.base.addr()
ptr::write(ptr as *mut T, value);
// SAFETY: `ptr` was obtained from `self.block.alloc()`
self.block.get_offset(ptr)
}
}
#[inline]
unsafe fn lookup(&self, offset: usize) -> &T {
&*self.block.base.add(offset).cast::<T>()
unsafe { &*self.block.get_unchecked(offset).cast::<T>() }
}
#[inline]
unsafe fn lookup_mut(&mut self, offset: usize) -> &mut T {
&mut *self.block.base.add(offset).cast::<T>().cast_mut()
unsafe { &mut *self.block.get_unchecked(offset).cast::<T>().cast_mut() }
}
#[allow(clippy::wrong_self_convention)]
fn to_concurrent(&mut self) -> ConcurrentOffsetTable<T>
where
T: fmt::Debug,
@@ -244,8 +252,9 @@ impl<T: RawBlockTraits> SerialOffsetTable<T> {
};
let serial_tbl = mem::replace(self, empty_serial_tbl);
let num_tbl_entries = serial_tbl.block.size() / size_of::<T>();
let block = Arcu::new(serial_tbl.block, GlobalEpochCounterPool);
let num_tbl_entries = serial_tbl.block.used_bytes() / size_of::<T>();
let raw_block: RawBlock<T, RawBlockConcurrent> = serial_tbl.block.into();
let block = Arcu::new(raw_block, GlobalEpochCounterPool);
let offset_locks: Vec<RwLock<()>> = (0..num_tbl_entries).map(|_| RwLock::new(())).collect();
@@ -279,7 +288,7 @@ impl<T: RawBlockTraits> ConcurrentOffsetTable<T> {
}
}
let new_tbl_sz = block_epoch.size() / size_of::<T>();
let new_tbl_sz = block_epoch.used_bytes() / size_of::<T>();
let mut offset_locks = self.offset_locks.write();
offset_locks.resize_with(new_tbl_sz, || RwLock::new(()));
@@ -288,7 +297,8 @@ impl<T: RawBlockTraits> ConcurrentOffsetTable<T> {
ptr::write(ptr as *mut T, value);
}
let value = ptr.addr() - block_epoch.base.addr();
// SAFETY: `ptr` was obtained from `block_epoch.alloc()`
let value = unsafe { block_epoch.get_offset(ptr) };
// AtomTable would have to update the index table at this point
// explicit drop to ensure we don't accidentally drop it early
@@ -303,7 +313,7 @@ impl<T: RawBlockTraits> ConcurrentOffsetTable<T> {
let inner_offset_lock = outer_offset_lock[offset / size_of::<T>()].read();
let rcu_ref = RcuRef::try_map(self.block.read(), |raw_block| unsafe {
raw_block.base.add(offset).cast::<T>().as_ref()
raw_block.get_unchecked(offset).cast::<T>().as_ref()
})
.expect("offset valid");
@@ -322,8 +332,7 @@ impl<T: RawBlockTraits> ConcurrentOffsetTable<T> {
let rcu_ref = RcuRef::try_map(self.block.read(), |raw_block| unsafe {
raw_block
.base
.add(offset)
.get_unchecked(offset)
.cast_mut()
.cast::<UnsafeCell<T>>()
.as_ref()
@@ -374,11 +383,11 @@ pub enum F64Table {
}
impl F64Table {
pub fn new() -> Self {
Self::Serial(SerialF64Table {
pub fn new() -> Result<Self, AllocError> {
Ok(Self::Serial(SerialF64Table {
indirection_tbl: IndexMap::with_hasher(FxBuildHasher::new()),
offset_tbl: SerialOffsetTable::new(),
})
offset_tbl: SerialOffsetTable::new()?,
}))
}
pub fn build_with(&mut self, value: OrderedFloat<f64>) -> F64Offset {
@@ -398,6 +407,8 @@ impl F64Table {
// which breaks the invariant indirection_tbl is meant to enforce.
// Since this branch is never invoked, it does no harm, but that
// that will eventually change.
//
// Note: may be indirectly fixed by the use of the new RawBlockConcurrency trait.
{
let indirection_tbl = concurrent_tbl.indirection_tbl.lock();
@@ -468,7 +479,9 @@ impl F64Table {
.unwrap();
*self = Self::Serial(SerialF64Table {
indirection_tbl: indirection_tbl.into_inner(),
offset_tbl: SerialOffsetTable { block: raw_block },
offset_tbl: SerialOffsetTable {
block: raw_block.into(),
},
});
Ok(())

View File

@@ -1,9 +1,6 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
use crate::arena::*;
use crate::atom_table::*;
use crate::offset_table::*;
use crate::parser::char_reader::*;
use crate::types::HeapCellValueTag;
use std::cell::{Cell, Ref, RefCell, RefMut};
@@ -22,9 +19,9 @@ use dashu::Integer;
use dashu::Rational;
use fxhash::FxBuildHasher;
use indexmap::IndexMap;
use modular_bitfield::error::OutOfBounds;
use modular_bitfield::prelude::*;
use ordered_float::OrderedFloat;
use scryer_modular_bitfield::error::OutOfBounds;
use scryer_modular_bitfield::prelude::*;
pub type Specifier = u32;
@@ -249,6 +246,7 @@ pub enum GInteger {
impl GInteger {
#[inline]
#[allow(clippy::wrong_self_convention)]
pub fn to_literal(self) -> Literal {
match self {
GInteger::Integer(integer) => Literal::Integer(integer),
@@ -359,16 +357,12 @@ impl OpDesc {
#[inline]
pub fn get_spec(self) -> OpDeclSpec {
OpDeclSpec::try_from(self.spec()).expect("OpDecl always contains a valud OpDeclSpec")
OpDeclSpec::try_from(self.spec()).expect("OpDecl always contains a valid OpDeclSpec")
}
#[inline]
pub fn arity(self) -> usize {
if !self.get_spec().is_infix() {
1
} else {
2
}
if !self.get_spec().is_infix() { 1 } else { 2 }
}
}
@@ -424,72 +418,92 @@ pub enum ArithmeticError {
UninstantiatedVar,
}
#[derive(Debug, Clone)]
pub struct Location {
pub(super) line: usize,
pub(super) column: usize,
}
impl Location {
// beginning of file
pub(crate) const BOF: Self = Self { line: 0, column: 0 };
pub fn line(&self) -> usize {
self.line
}
pub fn column(&self) -> usize {
self.column
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub enum ParserError {
BackQuotedString(usize, usize),
pub struct ParserError {
pub(crate) location: Option<Location>,
pub(crate) kind: ParserErrorKind,
}
#[allow(dead_code)]
#[derive(Debug)]
#[non_exhaustive]
pub(crate) enum ParserErrorKind {
BackQuotedString,
IO(IOError),
IncompleteReduction(usize, usize),
InfiniteFloat(usize, usize),
IncompleteReduction,
InfiniteFloat,
InvalidSingleQuotedCharacter(char),
LexicalError(lexical::Error),
MissingQuote(usize, usize),
NonPrologChar(usize, usize),
ParseBigInt(usize, usize),
UnexpectedChar(char, usize, usize),
ParseFloat,
MissingQuote,
NonPrologChar,
ParseBigInt,
UnexpectedChar(char),
// UnexpectedEOF,
Utf8Error(usize, usize),
Utf8Error,
}
impl ParserError {
pub fn line_and_col_num(&self) -> Option<(usize, usize)> {
match self {
&ParserError::BackQuotedString(line_num, col_num)
| &ParserError::IncompleteReduction(line_num, col_num)
| &ParserError::InfiniteFloat(line_num, col_num)
| &ParserError::MissingQuote(line_num, col_num)
| &ParserError::NonPrologChar(line_num, col_num)
| &ParserError::ParseBigInt(line_num, col_num)
| &ParserError::UnexpectedChar(_, line_num, col_num)
| &ParserError::Utf8Error(line_num, col_num) => Some((line_num, col_num)),
_ => None,
}
pub(crate) fn location(&self) -> Option<Location> {
self.location.as_ref().cloned()
}
pub fn as_atom(&self) -> Atom {
match self {
ParserError::BackQuotedString(..) => atom!("back_quoted_string"),
ParserError::IncompleteReduction(..) => atom!("incomplete_reduction"),
ParserError::InvalidSingleQuotedCharacter(..) => {
pub(crate) fn as_atom(&self) -> Atom {
match &self.kind {
ParserErrorKind::BackQuotedString => atom!("back_quoted_string"),
ParserErrorKind::IncompleteReduction => atom!("incomplete_reduction"),
ParserErrorKind::InvalidSingleQuotedCharacter(..) => {
atom!("invalid_single_quoted_character")
}
ParserError::InfiniteFloat(..) => {
ParserErrorKind::InfiniteFloat => {
atom!("infinite_float")
}
ParserError::IO(e) if e.kind() == ErrorKind::UnexpectedEof => {
ParserErrorKind::IO(e) if e.kind() == ErrorKind::UnexpectedEof => {
atom!("unexpected_end_of_file")
}
ParserError::IO(e) if e.kind() == ErrorKind::InvalidData => {
ParserErrorKind::IO(e) if e.kind() == ErrorKind::InvalidData => {
atom!("invalid_data")
}
ParserError::IO(_) => atom!("input_output_error"),
ParserError::LexicalError(_) => atom!("lexical_error"),
ParserError::MissingQuote(..) => atom!("missing_quote"),
ParserError::NonPrologChar(..) => atom!("non_prolog_character"),
ParserError::ParseBigInt(..) => atom!("cannot_parse_big_int"),
ParserError::UnexpectedChar(..) => atom!("unexpected_char"),
ParserError::Utf8Error(..) => atom!("utf8_conversion_error"),
ParserErrorKind::IO(_) => atom!("input_output_error"),
ParserErrorKind::ParseFloat => atom!("parse_float"),
ParserErrorKind::MissingQuote => atom!("missing_quote"),
ParserErrorKind::NonPrologChar => atom!("non_prolog_character"),
ParserErrorKind::ParseBigInt => atom!("cannot_parse_big_int"),
ParserErrorKind::UnexpectedChar(..) => atom!("unexpected_char"),
ParserErrorKind::Utf8Error => atom!("utf8_conversion_error"),
}
}
#[inline]
pub fn unexpected_eof() -> Self {
ParserError::IO(std::io::Error::from(ErrorKind::UnexpectedEof))
pub(crate) fn unexpected_eof() -> Self {
ParserError {
location: None,
kind: ParserErrorKind::IO(std::io::Error::from(ErrorKind::UnexpectedEof)),
}
}
#[inline]
pub fn is_unexpected_eof(&self) -> bool {
if let ParserError::IO(e) = self {
pub(crate) fn is_unexpected_eof(&self) -> bool {
if let ParserErrorKind::IO(e) = &self.kind {
e.kind() == ErrorKind::UnexpectedEof
} else {
false
@@ -497,24 +511,11 @@ impl ParserError {
}
}
impl From<lexical::Error> for ParserError {
fn from(e: lexical::Error) -> ParserError {
ParserError::LexicalError(e)
}
}
impl From<IOError> for ParserError {
fn from(e: IOError) -> ParserError {
ParserError::IO(e)
}
}
impl From<&IOError> for ParserError {
fn from(error: &IOError) -> ParserError {
if error.get_ref().filter(|e| e.is::<BadUtf8Error>()).is_some() {
ParserError::Utf8Error(0, 0)
} else {
ParserError::IO(error.kind().into())
ParserError {
location: None,
kind: ParserErrorKind::IO(e),
}
}
}
@@ -603,6 +604,7 @@ mod private {
impl<T: FitsInFixnumSeal> MightNotFitInFixnumSeal for T {}
impl MightNotFitInFixnumSeal for i64 {}
impl MightNotFitInFixnumSeal for u64 {}
impl MightNotFitInFixnumSeal for u128 {}
impl MightNotFitInFixnumSeal for &Integer {}
impl MightNotFitInFixnumSeal for Integer {}
impl MightNotFitInFixnumSeal for usize {}
@@ -669,7 +671,7 @@ impl Fixnum {
#[inline]
pub fn get_tag(&self) -> HeapCellValueTag {
use scryer_modular_bitfield::Specifier;
use modular_bitfield::Specifier;
HeapCellValueTag::from_bytes(self.tag()).unwrap()
}
@@ -871,7 +873,7 @@ pub enum Term {
impl Term {
pub fn first_arg(&self) -> Option<&Term> {
match self {
Term::Clause(_, _, ref terms) => terms.first(),
Term::Clause(_, _, terms) => terms.first(),
_ => None,
}
}
@@ -886,14 +888,14 @@ impl Term {
pub fn arity(&self) -> usize {
match self {
Term::Clause(_, _, ref child_terms, ..) => child_terms.len(),
Term::Clause(_, _, child_terms, ..) => child_terms.len(),
_ => 0,
}
}
}
pub(crate) fn unfold_by_str_once(term: &mut Term, s: Atom) -> Option<(Term, Term)> {
if let Term::Clause(_, ref name, ref mut subterms) = term {
if let &mut Term::Clause(_, ref name, ref mut subterms) = term {
if let Some(Term::Literal(_, Literal::CodeIndexOffset(_))) = subterms.last() {
subterms.pop();
}

View File

@@ -102,19 +102,27 @@ impl<R> CharReader<R> {
}
impl<R: Read> CharReader<R> {
pub fn read_chunk(&mut self) -> io::Result<usize> {
let mut chunk = [0u8; 8 * 1024];
let nread = self.inner.read(&mut chunk)?;
self.buf.extend_from_slice(&chunk[..nread]);
Ok(nread)
}
pub fn refresh_buffer(&mut self) -> io::Result<&[u8]> {
// If we've reached the end of our internal buffer then we need to fetch
// some more data from the underlying reader.
// Branch using `>=` instead of the more correct `==`
// to tell the compiler that the pos..cap slice is always valid.
if self.pos >= self.buf.len() {
self.buf.clear();
// make some space in buf
if self.buf.len() > 4 {
// keep 4 bytes so that put_back_char can put back at least one char
self.buf.drain(4..);
}
self.pos = self.buf.len();
let mut word = [0u8; std::mem::size_of::<char>()];
let nread = self.inner.read(&mut word)?;
self.buf.extend_from_slice(&word[..nread]);
self.pos = 0;
self.read_chunk()?;
}
Ok(&self.buf[self.pos..])
@@ -135,7 +143,7 @@ impl<R: Read> CharRead for CharReader<R> {
Err(e) => return Some(Err(e)),
}
let bad_bytes_error = |buf: &[u8]| {
fn bad_bytes_error(buf: &[u8]) -> std::io::Error {
// If we have 4 bytes that still don't make up
// a valid code point, then we have garbage.
@@ -143,110 +151,95 @@ impl<R: Read> CharRead for CharReader<R> {
// leading bytes until either the buffer is
// empty, or we have a valid code point.
let mut split_point = 1;
let mut badbytes = vec![];
// note we might have a sequence of invalid bytes followed by valid bytes followed by invalid bytes
loop {
let (bad, rest) = buf.split_at(split_point);
let err = str::from_utf8(buf).expect_err("the start of buf should be invalid utf-8");
assert_eq!(err.valid_up_to(), 0, "the error should be a prefix");
if rest.is_empty() || str::from_utf8(rest).is_ok() {
badbytes.extend_from_slice(bad);
break;
}
let invalid_prefix = err.error_len().expect("we should have at least 4 bytes");
split_point += 1;
}
let bad_bytes = buf[..invalid_prefix].to_vec();
// Raise the error. If we still have data in
// the buffer, it will be returned on the next
// loop.
io::Error::new(
io::ErrorKind::InvalidData,
BadUtf8Error { bytes: bad_bytes },
)
}
io::Error::new(io::ErrorKind::InvalidData, BadUtf8Error { bytes: badbytes })
};
loop {
// while we haven't consumed all bytes from the buffer
while self.pos < self.buf.len() {
// buf must be non-empty
let buf = &self.buf[self.pos..];
if !buf.is_empty() {
let e = match str::from_utf8(buf) {
Ok(s) => {
let mut chars = s.chars();
let c = chars.next().unwrap();
// we need at most 4 bytes for a char so don't decode the whole buffer
// as it can be quite large and we are going to discard the remaining chars anyway
// if there is a valid prefix
let prefix = if buf.len() > 4 { &buf[..4] } else { buf };
return Some(Ok(c));
}
Err(e) => e,
};
let e = match str::from_utf8(prefix) {
Ok(s) => {
let mut chars = s.chars();
let c = chars.next().expect(
"a non-empty buffer that is valid utf-8 contains at least one character",
);
if buf.len() - e.valid_up_to() >= 4 {
return Some(Err(bad_bytes_error(buf)));
} else if self.pos >= self.buf.len() {
return None;
} else if self.buf.len() - self.pos >= 4 && self.pos < e.valid_up_to() {
return match str::from_utf8(&self.buf[self.pos..self.pos + e.valid_up_to()]) {
Ok(s) => {
let mut chars = s.chars();
let c = chars.next().unwrap();
Some(Ok(c))
}
Err(e) => {
let badbytes = self.buf[self.pos..self.pos + e.valid_up_to()].to_vec();
Some(Err(io::Error::new(
io::ErrorKind::InvalidData,
BadUtf8Error { bytes: badbytes },
)))
}
};
} else {
let buf_len = self.buf.len();
for (c, idx) in (self.pos..buf_len).enumerate() {
self.buf[c] = self.buf[idx];
}
self.buf.truncate(buf_len - self.pos);
let buf_len = self.buf.len();
self.pos = 0;
if buf_len >= 4 {
continue;
}
let mut word = [0u8; 4];
let word_slice = &mut word[buf_len..4];
match self.inner.read(word_slice) {
Err(e) => return Some(Err(e)),
Ok(0) => return Some(Err(bad_bytes_error(&self.buf))),
Ok(nread) => {
self.buf.extend_from_slice(&word_slice[0..nread]);
}
}
return Some(Ok(c));
}
Err(e) => e,
};
if e.valid_up_to() != 0 {
// the valid prefix is non-empty so it is guaranteed that we can decode at least one char
let c = str::from_utf8(&prefix[..e.valid_up_to()])
.expect("prefix is verified valid up to this point")
.chars()
.next()
.expect("the valid prefix was non-empty");
return Some(Ok(c));
}
if e.error_len().is_some() {
return Some(Err(bad_bytes_error(buf)));
}
// buf is too short to determine if the remaining bytes in buf are a valid char
// i.e. the content of buf is a prefix of a valid utf-8 encoded char
//
// we need to read more data from the underlying stream
// so that we can determine its validity
if self.buf.len() > 4 {
// keep a prefix of 4 bytes so that we can put back at least one char
self.buf.drain(4..self.pos);
self.pos = 4;
}
match self.read_chunk() {
Err(e) => return Some(Err(e)),
Ok(0) => return Some(Err(bad_bytes_error(&self.buf))),
Ok(_) => {
// successfully filled the buffer with another chunk of data
}
} else {
return None;
}
}
None
}
#[inline(always)]
fn put_back_char(&mut self, c: char) {
let src_len = self.buf.len() - self.pos;
debug_assert!(src_len <= self.buf.capacity());
let c_len = c.len_utf8();
let mut shifted_slice = [0u8; 32];
if c_len <= self.pos {
self.pos -= c_len;
} else {
self.buf.insert_from_slice(
0,
&[0u8; 4/* char::MAX_LEN_UTF8 once msrv reached 1.93 */][..c_len - self.pos],
);
self.pos = 0;
}
shifted_slice[0..src_len].copy_from_slice(&self.buf[self.pos..self.buf.len()]);
self.buf.resize(c_len, 0);
self.buf.extend_from_slice(&shifted_slice[0..src_len]);
self.pos = 0;
c.encode_utf8(&mut self.buf[0..c_len]);
c.encode_utf8(&mut self.buf[self.pos..]);
}
#[inline(always)]
@@ -381,6 +374,51 @@ mod tests {
assert!(read_string.read_char().is_none());
}
#[test]
fn interspersed_bad_utf8() {
let mut read_string = CharReader::new(Cursor::new(b"a string\xffmore_text\xff"));
for c in "a string".chars() {
assert_eq!(read_string.peek_char().unwrap().ok(), Some(c));
assert_eq!(read_string.read_char().unwrap().ok(), Some(c));
}
assert_eq!(
read_string.peek_char().unwrap().unwrap_err().kind(),
std::io::ErrorKind::InvalidData
);
let err = read_string
.read_char()
.unwrap()
.unwrap_err()
.downcast::<BadUtf8Error>()
.unwrap();
read_string.consume(err.bytes.len());
for c in "more_text".chars() {
assert_eq!(read_string.peek_char().unwrap().ok(), Some(c));
assert_eq!(read_string.read_char().unwrap().ok(), Some(c));
}
assert_eq!(
read_string.peek_char().unwrap().unwrap_err().kind(),
std::io::ErrorKind::InvalidData
);
let err = read_string
.read_char()
.unwrap()
.unwrap_err()
.downcast::<BadUtf8Error>()
.unwrap();
read_string.consume(err.bytes.len());
assert!(read_string.read_char().is_none());
}
#[test]
fn greek_string() {
let mut read_string = CharReader::new(Cursor::new("λέξη"));
@@ -406,7 +444,6 @@ mod tests {
}
#[test]
#[cfg_attr(miri, ignore = "slow and not very relevant")]
fn greek_lorem_ipsum() {
let lorem_ipsum = "Λορεμ ιπσθμ δολορ σιτ αμετ, οφφενδιτ
εφφιcιενδι σιτ ει, ηαρθμ λεγερε αερενδθμ ιθσ νε. Ηασ νο εροσ
@@ -478,7 +515,6 @@ mod tests {
}
#[test]
#[cfg_attr(miri, ignore = "slow and not very relevant")]
fn armenian_lorem_ipsum() {
let lorem_ipsum = "լոռեմ իպսում դոլոռ սիթ ամեթ, նովում գռաեծո
սեա եա, աբհոռռեանթ դիսպութանդո եի քուի. իդ քուոդ ինդոծթում
@@ -552,7 +588,6 @@ mod tests {
}
#[test]
#[cfg_attr(miri, ignore = "slow and not very relevant")]
fn russian_lorem_ipsum() {
let lorem_ipsum = "Лорем ипсум долор сит амет, атяуи дицам еи
сит, ид сеа фацилис елаборарет. Меа еу яуас алияуид, те яуи

View File

@@ -16,7 +16,10 @@ macro_rules! consume_chars_with {
match $e {
Ok(Some(c)) => $token.push(c),
Ok(None) => continue,
Err($crate::parser::ast::ParserError::UnexpectedChar(..)) => break,
Err($crate::parser::ast::ParserError {
kind: $crate::parser::ast::ParserErrorKind::UnexpectedChar(..),
..
}) => break,
Err(e) => return Err(e),
}
}
@@ -82,7 +85,7 @@ macro_rules! try_nt {
Ok(NumberToken::Partial($token))
} else {
Err(e)
}
};
}
}
}};
@@ -91,27 +94,45 @@ macro_rules! try_nt {
pub(crate) struct Lexer<'a, R> {
pub(crate) reader: R,
pub(crate) machine_st: &'a mut MachineState,
pub(crate) line_num: usize,
pub(crate) col_num: usize,
pub(crate) location: Location,
}
impl<'a, R: fmt::Debug> fmt::Debug for Lexer<'a, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LexerParser")
.field("reader", &"&'a mut R") // Hacky solution.
.field("line_num", &self.line_num)
.field("col_num", &self.col_num)
.field("location", &self.location)
.finish()
}
}
impl<R> Lexer<'_, R> {
pub(crate) fn located_error(&self, kind: ParserErrorKind) -> ParserError {
ParserError {
location: Some(self.location.clone()),
kind,
}
}
pub(crate) fn parse_big_int_error(&self) -> ParserError {
self.located_error(ParserErrorKind::ParseBigInt)
}
pub(crate) fn incomplete_reduction(&self) -> ParserError {
self.located_error(ParserErrorKind::IncompleteReduction)
}
pub(crate) fn unexpected_char(&self, c: char) -> ParserError {
self.located_error(ParserErrorKind::UnexpectedChar(c))
}
}
impl<'a, R: CharRead> Lexer<'a, R> {
pub fn new(src: R, machine_st: &'a mut MachineState) -> Self {
Self {
reader: src,
machine_st,
line_num: 0,
col_num: 0,
location: Location::BOF,
}
}
@@ -138,10 +159,10 @@ impl<'a, R: CharRead> Lexer<'a, R> {
self.reader.consume(c.len_utf8());
if new_line_char!(c) {
self.line_num += 1;
self.col_num = 0;
self.location.line += 1;
self.location.column = 0;
} else {
self.col_num += 1;
self.location.column += 1;
}
}
@@ -200,10 +221,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
match comment_loop() {
Err(e) if e.is_unexpected_eof() => {
return Err(ParserError::IncompleteReduction(
self.line_num,
self.col_num,
));
return Err(self.incomplete_reduction());
}
Err(e) => {
return Err(e);
@@ -215,7 +233,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
self.skip_char(c);
Ok(true)
} else {
Err(ParserError::NonPrologChar(self.line_num, self.col_num))
Err(self.located_error(ParserErrorKind::NonPrologChar))
}
} else {
self.return_char('/');
@@ -232,7 +250,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
if !back_quote_char!(c2) {
self.return_char(c);
Err(ParserError::UnexpectedChar(c, self.line_num, self.col_num))
Err(self.unexpected_char(c))
} else {
self.skip_char(c2);
Ok(c2)
@@ -257,7 +275,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
Ok(None)
} else {
self.return_char(c);
Err(ParserError::UnexpectedChar(c, self.line_num, self.col_num))
Err(self.unexpected_char(c))
}
} else {
self.get_back_quoted_char().map(Some)
@@ -279,10 +297,13 @@ impl<'a, R: CharRead> Lexer<'a, R> {
self.skip_char(c);
Ok(token)
} else {
Err(ParserError::MissingQuote(self.line_num, self.col_num))
Err({
let this = &self;
this.located_error(ParserErrorKind::MissingQuote)
})
}
} else {
Err(ParserError::UnexpectedChar(c, self.line_num, self.col_num))
Err(self.unexpected_char(c))
}
}
@@ -313,7 +334,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
if !single_quote_char!(c2) {
self.return_char(c);
Err(ParserError::UnexpectedChar(c, self.line_num, self.col_num))
Err(self.unexpected_char(c))
} else {
self.skip_char(c2);
Ok(c2)
@@ -354,7 +375,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
if !double_quote_char!(c2) {
self.return_char(c);
Err(ParserError::UnexpectedChar(c, self.line_num, self.col_num))
Err(self.unexpected_char(c))
} else {
self.skip_char(c2);
Ok(c2)
@@ -378,7 +399,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
't' => '\t',
'n' => '\n',
'r' => '\r',
c => return Err(ParserError::UnexpectedChar(c, self.line_num, self.col_num)),
c => return Err(self.unexpected_char(c)),
};
self.skip_char(c);
@@ -396,10 +417,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
if hexadecimal_digit_char!(c) {
self.escape_sequence_to_char(|c| hexadecimal_digit_char!(c), 16)
} else {
Err(ParserError::IncompleteReduction(
self.line_num,
self.col_num,
))
Err(self.incomplete_reduction())
}
}
@@ -425,17 +443,11 @@ impl<'a, R: CharRead> Lexer<'a, R> {
if backslash_char!(c) {
self.skip_char(c);
u32::from_str_radix(&token, radix).map_or_else(
|_| Err(ParserError::ParseBigInt(self.line_num, self.col_num)),
|n| {
char::try_from(n)
.map_err(|_| ParserError::Utf8Error(self.line_num, self.col_num))
},
|_| Err(self.parse_big_int_error()),
|n| char::try_from(n).map_err(|_| self.located_error(ParserErrorKind::Utf8Error)),
)
} else {
Err(ParserError::IncompleteReduction(
self.line_num,
self.col_num,
))
Err(self.incomplete_reduction())
}
}
@@ -447,7 +459,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
Ok(c)
} else {
if !backslash_char!(c) {
return Err(ParserError::UnexpectedChar(c, self.line_num, self.col_num));
return Err(self.unexpected_char(c));
}
self.skip_char(c);
@@ -478,7 +490,10 @@ impl<'a, R: CharRead> Lexer<'a, R> {
self.skip_char(c);
Ok(token)
} else {
Err(ParserError::MissingQuote(self.line_num, self.col_num))
Err({
let this = &self;
this.located_error(ParserErrorKind::MissingQuote)
})
}
}
@@ -509,7 +524,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
.map(NumberToken::Integer)
} else {
self.return_char(start);
Err(ParserError::ParseBigInt(self.line_num, self.col_num))
Err(self.parse_big_int_error())
}
}
@@ -540,7 +555,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
.map(NumberToken::Integer)
} else {
self.return_char(start);
Err(ParserError::ParseBigInt(self.line_num, self.col_num))
Err(self.parse_big_int_error())
}
}
@@ -571,7 +586,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
.map(NumberToken::Integer)
} else {
self.return_char(start);
Err(ParserError::ParseBigInt(self.line_num, self.col_num))
Err(self.parse_big_int_error())
}
}
@@ -644,11 +659,11 @@ impl<'a, R: CharRead> Lexer<'a, R> {
}
}
} else {
return Err(ParserError::InvalidSingleQuotedCharacter(c));
return Err(self.located_error(ParserErrorKind::InvalidSingleQuotedCharacter(c)));
}
} else {
match self.get_back_quoted_string() {
Ok(_) => return Err(ParserError::BackQuotedString(self.line_num, self.col_num)),
Ok(_) => return Err(self.located_error(ParserErrorKind::BackQuotedString)),
Err(e) => return Err(e),
}
}
@@ -669,7 +684,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
) -> Result<(F64Offset, OrderedFloat<f64>), ParserError> {
self.return_char(token.pop().unwrap());
let n = parse_float_lossy(&token)?;
let n = self.parse_float_lossy(&token)?;
let offset = float_alloc!(n, self.machine_st.arena);
Ok((offset, OrderedFloat(n)))
@@ -686,7 +701,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
if decimal_digit_char!(c) {
Ok(c)
} else {
Err(ParserError::ParseBigInt(self.line_num, self.col_num))
Err(self.parse_big_int_error())
}
} else {
Ok(c)
@@ -708,7 +723,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
.or_else(|_| {
Integer::from_str_radix(token, radix)
.map(|n| GInteger::Integer(arena_alloc!(n, &mut self.machine_st.arena)))
.map_err(|_| ParserError::ParseBigInt(self.line_num, self.col_num))
.map_err(|_| self.parse_big_int_error())
})
}
@@ -756,7 +771,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
Err(_) => {
return self
.vacate_with_float(token)
.map(|(offset, fl)| NumberToken::Float(offset, fl))
.map(|(offset, fl)| NumberToken::Float(offset, fl));
}
Ok(c) => c,
};
@@ -806,7 +821,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
}
}
let n = parse_float_lossy(&token)?;
let n = self.parse_float_lossy(&token)?;
let offset = float_alloc!(n, self.machine_st.arena);
Ok(NumberToken::Float(offset, OrderedFloat(n)))
@@ -815,7 +830,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
.map(|(offset, fl)| NumberToken::Float(offset, fl))
}
} else {
let n = parse_float_lossy(&token)?;
let n = self.parse_float_lossy(&token)?;
let offset = float_alloc!(n, self.machine_st.arena);
Ok(NumberToken::Float(offset, OrderedFloat(n)))
}
@@ -826,7 +841,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
} else if token.starts_with('0') && token.len() == 1 {
if c == 'x' {
self.hexadecimal_constant(c).or_else(|e| {
if let ParserError::ParseBigInt(..) = e {
if let ParserErrorKind::ParseBigInt = e.kind {
self.parse_integer(&token).map(NumberToken::Integer)
} else {
Err(e)
@@ -834,7 +849,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
})
} else if c == 'o' {
self.octal_constant(c).or_else(|e| {
if let ParserError::ParseBigInt(..) = e {
if let ParserErrorKind::ParseBigInt = e.kind {
self.parse_integer(&token).map(NumberToken::Integer)
} else {
Err(e)
@@ -842,7 +857,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
})
} else if c == 'b' {
self.binary_constant(c).or_else(|e| {
if let ParserError::ParseBigInt(..) = e {
if let ParserErrorKind::ParseBigInt = e.kind {
self.parse_integer(&token).map(NumberToken::Integer)
} else {
Err(e)
@@ -871,9 +886,9 @@ impl<'a, R: CharRead> Lexer<'a, R> {
self.get_single_quoted_char()
.map(|c| NumberToken::Integer(GInteger::Fixnum(Fixnum::build_with(c))))
.or_else(|err| {
match err {
ParserError::UnexpectedChar('\'', ..) => {}
err => return Err(err),
match &err.kind {
ParserErrorKind::UnexpectedChar('\'', ..) => {}
_ => return Err(err),
}
self.return_char(c);
@@ -954,7 +969,7 @@ impl<'a, R: CharRead> Lexer<'a, R> {
Ok(NumberToken::Partial(token_string)) => match self.parse_integer(&token_string) {
Ok(n) => Ok(Token::Literal(n.to_literal())),
Err(_) => {
let n = parse_float_lossy(&token_string)?;
let n = self.parse_float_lossy(&token_string)?;
let offset = float_alloc!(n, self.machine_st.arena);
Ok(Token::Literal(Literal::F64(offset, OrderedFloat(n))))
}
@@ -1068,14 +1083,17 @@ impl<'a, R: CharRead> Lexer<'a, R> {
Err(e) => Err(e),
}
}
}
fn parse_float_lossy(token: &str) -> Result<f64, ParserError> {
const FORMAT: u128 = lexical::format::STANDARD;
let options = lexical::ParseFloatOptions::builder()
.lossy(true)
.build()
.unwrap();
let n = lexical::parse_with_options::<f64, _, FORMAT>(token.as_bytes(), &options)?;
Ok(n)
fn parse_float_lossy(&self, token: &str) -> Result<f64, ParserError> {
const FORMAT: u128 = lexical::format::STANDARD;
let Ok(options) = lexical::ParseFloatOptions::builder().lossy(true).build() else {
return Err(self.located_error(ParserErrorKind::ParseFloat));
};
let Ok(n) = lexical::parse_with_options::<f64, _, FORMAT>(token.as_bytes(), &options)
else {
return Err(self.located_error(ParserErrorKind::ParseFloat));
};
Ok(n)
}
}

View File

@@ -254,10 +254,7 @@ pub fn read_tokens<R: CharRead>(lexer: &mut Lexer<'_, R>) -> Result<Vec<Token>,
}
}
Err(e) if e.is_unexpected_eof() && !tokens.is_empty() => {
return Err(ParserError::IncompleteReduction(
lexer.line_num,
lexer.col_num,
));
return Err(lexer.incomplete_reduction());
}
Err(e) => {
return Err(e);
@@ -700,12 +697,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
} else {
let term = match self.terms.pop() {
Some(term) => term,
_ => {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
))
}
_ => return Err(self.lexer.incomplete_reduction()),
};
if self.stack[idx].priority > 1000 {
@@ -718,10 +710,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
};
if arity > self.terms.len() {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
return Err(self.lexer.incomplete_reduction());
}
let idx = self.terms.len() - arity;
@@ -789,12 +778,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
let term = match self.terms.pop() {
Some(term) => term,
_ => {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
))
}
_ => return Err(self.lexer.incomplete_reduction()),
};
self.terms
@@ -972,10 +956,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
self.negate_number(n, negate_rat_rc, Literal::Rational)
}
Token::Literal(Literal::F64(_offset, n)) if n.is_infinite() => {
return Err(ParserError::InfiniteFloat(
self.lexer.line_num,
self.lexer.col_num,
));
return Err(self.lexer.located_error(ParserErrorKind::InfiniteFloat));
}
Token::Literal(Literal::F64(offset, n)) => {
self.negate_number((offset, n), negate_f64, |(offset, n)| {
@@ -997,28 +978,19 @@ impl<'a, R: CharRead> Parser<'a, R> {
Token::OpenCT => self.shift(Token::OpenCT, 1300, DELIMITER),
Token::Close => {
if !self.reduce_term() && !self.reduce_brackets() {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
return Err(self.lexer.incomplete_reduction());
}
}
Token::OpenList => self.shift(Token::OpenList, 1300, DELIMITER),
Token::CloseList => {
if !self.reduce_list()? {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
return Err(self.lexer.incomplete_reduction());
}
}
Token::OpenCurly => self.shift(Token::OpenCurly, 1300, DELIMITER),
Token::CloseCurly => {
if !self.reduce_curly()? {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
return Err(self.lexer.incomplete_reduction());
}
}
Token::HeadTailSeparator => {
@@ -1051,12 +1023,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
| Some(TokenType::OpenList)
| Some(TokenType::OpenCurly)
| Some(TokenType::HeadTailSeparator)
| Some(TokenType::Comma) => {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
))
}
| Some(TokenType::Comma) => return Err(self.lexer.incomplete_reduction()),
_ => {}
},
}
@@ -1066,12 +1033,12 @@ impl<'a, R: CharRead> Parser<'a, R> {
#[inline]
pub fn add_lines_read(&mut self, lines_read: usize) {
self.lexer.line_num += lines_read;
self.lexer.location.line += lines_read;
}
#[inline]
pub fn lines_read(&self) -> usize {
self.lexer.line_num
self.lexer.location.line
}
// on success, returns the parsed term and the number of lines read.
@@ -1092,10 +1059,7 @@ impl<'a, R: CharRead> Parser<'a, R> {
self.reduce_op(1400);
if self.terms.len() > 1 || self.stack.len() > 1 {
return Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
));
return Err(self.lexer.incomplete_reduction());
}
match self.terms.pop() {
@@ -1103,16 +1067,10 @@ impl<'a, R: CharRead> Parser<'a, R> {
if self.terms.is_empty() {
Ok(term)
} else {
Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
))
Err(self.lexer.incomplete_reduction())
}
}
_ => Err(ParserError::IncompleteReduction(
self.lexer.line_num,
self.lexer.col_num,
)),
_ => Err(self.lexer.incomplete_reduction()),
}
}
}

View File

@@ -1,136 +1,417 @@
#![deny(unsafe_op_in_unsafe_fn)]
use core::marker::PhantomData;
use std::alloc;
use std::cell::UnsafeCell;
use std::cell::Cell;
use std::ptr;
use std::sync::atomic::{AtomicPtr, Ordering};
trait PtrCellTrait: std::fmt::Debug {
fn new(val: *mut u8) -> Self;
fn get(&self) -> *mut u8;
/// Modifies the wrapped value.
fn set(&mut self, val: *mut u8);
/// Performs an atomic compare-and-swap on the wrapped value.
///
/// If the compare succeeded and `cb` returns `Some(new_ptr)`, stored `new_ptr` and returns `Ok(old_ptr).
///
/// If `cb` returns `None`, returns `Err(old_ptr)`.
///
/// May retry multiple times if the comparison fails.
fn try_update(&self, cb: impl Fn(*mut u8) -> Option<*mut u8>) -> Result<*mut u8, *mut u8>;
}
impl PtrCellTrait for Cell<*mut u8> {
fn new(val: *mut u8) -> Self {
Cell::new(val)
}
#[inline(always)]
fn get(&self) -> *mut u8 {
Cell::get(self)
}
#[inline(always)]
fn set(&mut self, val: *mut u8) {
Cell::set(self, val)
}
#[inline(always)]
fn try_update(&self, cb: impl Fn(*mut u8) -> Option<*mut u8>) -> Result<*mut u8, *mut u8> {
let val = Cell::get(self);
if let Some(new_val) = cb(val) {
Cell::set(self, new_val);
Ok(val)
} else {
Err(val)
}
}
}
impl PtrCellTrait for AtomicPtr<u8> {
fn new(val: *mut u8) -> Self {
AtomicPtr::new(val)
}
#[inline(always)]
fn get(&self) -> *mut u8 {
self.load(Ordering::Acquire)
}
#[inline(always)]
fn set(&mut self, val: *mut u8) {
*self.get_mut() = val;
}
#[inline]
fn try_update(&self, cb: impl Fn(*mut u8) -> Option<*mut u8>) -> Result<*mut u8, *mut u8> {
let mut prev = PtrCellTrait::get(self);
while let Some(next) = cb(prev) {
match self.compare_exchange_weak(prev, next, Ordering::Relaxed, Ordering::Acquire) {
x @ Ok(_) => return x,
Err(next_prev) => prev = next_prev,
}
}
Err(prev)
// TODO: replace with the following once 1.95 is the msrv:
// AtomicPtr::try_update(self, Ordering::Relaxed, Ordering::Acquire, cb)
}
}
/// Allows the choice of implementation for the mutable pointer in [`RawBlock`].
/// Can be one of:
/// - [`RawBlockSerial`] (using a [`Cell<*mut u8>`])
/// - [`RawBlockConcurrent`] (using a [`AtomicPtr<u8>`])
pub(crate) trait RawBlockConcurrency {
#[allow(private_bounds)]
type PtrCell: PtrCellTrait;
}
#[derive(Debug, Clone, Copy)]
pub struct RawBlockSerial();
#[derive(Debug, Clone, Copy)]
pub struct RawBlockConcurrent();
impl RawBlockConcurrency for RawBlockSerial {
type PtrCell = Cell<*mut u8>;
}
impl RawBlockConcurrency for RawBlockConcurrent {
type PtrCell = AtomicPtr<u8>;
}
use crate::machine::heap::AllocError;
pub trait RawBlockTraits {
/// ## Safety
///
/// Must be non-zero.
fn init_size() -> usize;
/// ## Safety
///
/// Must be constant.
///
/// Must respect the invariants of [`std::alloc::Layout::from_size_align()`], namely:
/// - must not be zero
/// - must be a power of two
/// - must not overflow `usize`
fn align() -> usize;
}
/// A block of memory with fast, lock-free appends.
///
/// ## Invariants
///
/// - `base.is_null()` iff `capacity == 0`
/// - if `!base.is_null()`, then `ptr.get()` is in the same allocation as `base`.
#[derive(Debug)]
pub struct RawBlock<T: RawBlockTraits> {
pub base: *const u8,
pub top: *const u8,
pub ptr: UnsafeCell<*mut u8>,
pub struct RawBlock<T: RawBlockTraits, C: RawBlockConcurrency = RawBlockSerial> {
base: *const u8,
capacity: usize,
ptr: C::PtrCell,
_marker: PhantomData<T>,
_c_marker: PhantomData<C>,
}
impl<T: RawBlockTraits> RawBlock<T> {
impl<T: RawBlockTraits, C: RawBlockConcurrency> RawBlock<T, C> {
#[inline]
pub fn empty_block() -> Self {
RawBlock {
base: ptr::null(),
top: ptr::null(),
ptr: UnsafeCell::new(ptr::null_mut()),
capacity: 0,
ptr: C::PtrCell::new(ptr::null_mut()),
_marker: PhantomData,
_c_marker: PhantomData,
}
}
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
pub fn new() -> Result<Self, AllocError> {
let mut block = Self::empty_block();
unsafe {
block.grow();
block.grow()?;
}
block
Ok(block)
}
unsafe fn init_at_size(&mut self, cap: usize) {
let layout = alloc::Layout::from_size_align_unchecked(cap, T::align());
let new_base = alloc::alloc(layout).cast_const();
/// ## Safety
///
/// Assumes that the object has not been initialized before (ie. `self.base.is_null()`)
/// and assumes that `cap > 0`.
unsafe fn init_at_size(&mut self, cap: usize) -> Result<(), AllocError> {
debug_assert!(cap > 0);
debug_assert!(self.base.is_null());
// SAFETY:
// - Guaranteed by caller: `cap > 0`
// - Guaranteed by caller: `T::align()` respects the invariants of `Layout::from_size_align`
let new_base = unsafe {
let layout = alloc::Layout::from_size_align_unchecked(cap, T::align());
alloc::alloc(layout).cast_const()
};
if new_base.is_null() {
panic!(
"failed to allocate in init_at_size for {}",
std::any::type_name::<Self>()
);
return Err(AllocError);
}
self.base = new_base;
self.top = self.base.add(cap);
*self.ptr.get_mut() = self.base.cast_mut();
self.capacity = cap;
self.ptr.set(self.base.cast_mut());
Ok(())
}
pub unsafe fn grow(&mut self) -> bool {
if self.base.is_null() {
self.init_at_size(T::init_size());
true
} else {
let size = self.size();
let layout = alloc::Layout::from_size_align_unchecked(size, T::align());
/// ## Safety
///
/// Invalidates all pointers previously obtained by [`RawBlock::get()`] or [`RawBlock::alloc()`].
pub unsafe fn grow(&mut self) -> Result<(), AllocError> {
self.debug_check_invariants();
if self.base.is_null() {
// SAFETY:
// - Guaranteed by caller: `T::init_size() > 0`
// - Asserted: `self.base.is_null()`
unsafe { self.init_at_size(T::init_size()) }
} else {
let size = self.capacity();
let used_bytes = self.used_bytes();
// SAFETY:
// - Guaranteed by caller: `T::align()` respects the invariants of `Layout::from_size_align`
// - Asserted: `!self.base.is_null()`
// - Invariant: `self.base.is_null()` iff `self.capacity() == 0`
// - Thus `self.capacity() > 0`
let new_base = unsafe {
let layout = alloc::Layout::from_size_align_unchecked(size, T::align());
alloc::realloc(self.base.cast_mut(), layout, size * 2).cast_const()
};
let new_base = alloc::realloc(self.base.cast_mut(), layout, size * 2).cast_const();
if new_base.is_null() {
false
Err(AllocError)
} else {
self.base = new_base;
self.top = self.base.add(size * 2);
*self.ptr.get_mut() = self.base.add(size).cast_mut();
true
self.capacity = size * 2;
// SAFETY:
// - Invariant: `used_bytes < size`
// - Definition: `new_base` has allocation size `2 * size`
let new_ptr = unsafe { self.base.add(used_bytes).cast_mut() };
self.ptr.set(new_ptr);
self.debug_check_invariants();
Ok(())
}
}
}
pub unsafe fn grow_new(&self) -> Option<Self> {
pub unsafe fn grow_new(&self) -> Result<Self, AllocError> {
self.debug_check_invariants();
if self.base.is_null() {
Some(Self::new())
Self::new()
} else {
let mut new_block = Self::empty_block();
new_block.init_at_size(self.size() * 2);
if new_block.base.is_null() {
// allocation failed
None
} else {
let allocated = (*self.ptr.get()).addr() - self.base.addr();
self.base.copy_to(new_block.base.cast_mut(), allocated);
*new_block.ptr.get_mut() = new_block.base.add(allocated).cast_mut();
Some(new_block)
// SAFETY:
// - Asserted: !self.base.is_null()
// - Invariant: self.base.is_null() iff self.capacity == 0
// - Thus self.capacity > 0
// - Definition: `new_block` was not yet initialized
unsafe {
new_block.init_at_size(self.capacity() * 2)?;
}
let used_bytes = self.used_bytes();
// SAFETY:
// - Definition: `self.base` contains `self.allocated()` bytes
// - Invariant: `self.used_bytes() < self.allocated()`
unsafe {
self.base.copy_to(new_block.base.cast_mut(), used_bytes);
new_block.ptr.set(new_block.base.add(used_bytes).cast_mut());
}
new_block.debug_check_invariants();
Ok(new_block)
}
}
#[inline(always)]
fn debug_check_invariants(&self) {
if cfg!(debug_assertions) {
assert!(
self.ptr.get().cast_const() >= self.base,
"self.ptr = {:?} < {:?} = self.base",
self.ptr.get(),
self.base
);
assert!(self.used_bytes() <= self.capacity());
}
}
#[inline]
pub fn size(&self) -> usize {
self.top.addr() - self.base.addr()
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline(always)]
unsafe fn free_space(&self) -> usize {
debug_assert!(
*self.ptr.get() as *const _ >= self.base,
"self.ptr = {:?} < {:?} = self.base",
*self.ptr.get(),
self.base
);
self.top.addr() - (*self.ptr.get()).addr()
#[inline]
pub fn used_bytes(&self) -> usize {
// SAFETY:
// - Invariant: `ptr` is in the same allocation as `base`
unsafe { self.ptr.get().offset_from(self.base) as usize }
}
pub unsafe fn alloc(&self, size: usize) -> *mut u8 {
let aligned_size = size.next_multiple_of(size);
if self.free_space() >= aligned_size {
let ptr = *self.ptr.get();
*self.ptr.get() = ptr.add(aligned_size) as *mut _;
ptr
} else {
ptr::null_mut()
self.debug_check_invariants();
let aligned_size = size.next_multiple_of(T::align());
match self.ptr.try_update(|ptr| {
// SAFETY:
// - Invariant: `ptr` is in the same allocation as `base`
let free_bytes = unsafe { self.capacity() - ptr.offset_from(self.base) as usize };
if free_bytes >= aligned_size {
Some(unsafe { ptr.add(aligned_size) })
} else {
// Not enough space: don't allocate and return a null pointer
None
}
}) {
Ok(ptr) => ptr,
Err(_) => ptr::null_mut(),
}
}
/// Moves `ptr` back to `new_size`.
///
/// Note that this method does *not* deallocate what was placed in the [`RawBlock`].
/// Pointers to data past `new_size` remain valid until the next call to [`RawBlock::alloc()`].
pub fn shift_back(&mut self, new_size: usize) {
self.debug_check_invariants();
assert!(
new_size <= self.used_bytes(),
"Shrink cannot grow: new_size = {:?} > allocated = {:?}",
new_size,
self.used_bytes()
);
// SAFETY:
// - Asserted: new_size <= self.capacity
// - Definition: self.base := alloc(self.capacity)
let new_ptr = unsafe { self.base.add(new_size) };
debug_assert!(new_ptr as usize <= self.ptr.get() as usize,);
self.ptr.set(new_ptr.cast_mut());
self.debug_check_invariants();
}
/// Returns a pointer at a given `offset` within the block of memory.
///
/// Panics if that range of bytes wasn't allocated yet with [`RawBlock::alloc()`].
pub fn get(&self, offset: usize) -> *const u8 {
assert!(offset < self.used_bytes());
// SAFETY: Asserted.
unsafe { self.get_unchecked(offset) }
}
/// Returns a pointer at a given `offset` within the block of memory.
///
/// ## Safety
///
/// Assumes that `offset < self.capacity()`.
#[inline]
pub unsafe fn get_unchecked(&self, offset: usize) -> *const u8 {
debug_assert!(
offset < self.capacity(),
"offset out of bounds: offset is {:?} but {:?} bytes are available",
offset,
self.used_bytes()
);
// SAFETY: Guaranteed by caller.
unsafe { self.base.add(offset) }
}
/// ## Safety
///
/// `ptr` is a valid pointer be obtained from [`RawBlock::get()`] or [`RawBlock::alloc()`].
#[inline]
pub unsafe fn get_offset(&self, ptr: *const u8) -> usize {
// SAFETY:
// - Guaranteed by caller: `ptr` is still valid
// - Guranteed by caller: `ptr` was obtained from `get()` or `alloc()`
// - get() and alloc() return pointers in the same allocation as `self.base`
// - All functions modifying `self.base` invalidate pointers in their contract
// - Thus `ptr` and `self.base` originate from the same allocation
unsafe { ptr.offset_from(self.base) as usize }
}
}
impl<T: RawBlockTraits> Drop for RawBlock<T> {
impl<T: RawBlockTraits, C: RawBlockConcurrency> Drop for RawBlock<T, C> {
fn drop(&mut self) {
if !self.base.is_null() {
unsafe {
let layout = alloc::Layout::from_size_align_unchecked(self.size(), T::align());
let layout = alloc::Layout::from_size_align_unchecked(self.capacity(), T::align());
alloc::dealloc(self.base as *mut _, layout);
}
self.top = ptr::null();
self.base = ptr::null();
*self.ptr.get_mut() = ptr::null_mut();
}
}
}
impl<T: RawBlockTraits> From<RawBlock<T, RawBlockConcurrent>> for RawBlock<T, RawBlockSerial> {
fn from(other: RawBlock<T, RawBlockConcurrent>) -> Self {
Self {
base: other.base,
capacity: other.capacity,
ptr: PtrCellTrait::new(other.ptr.get()),
_marker: PhantomData,
_c_marker: PhantomData,
}
}
}
impl<T: RawBlockTraits> From<RawBlock<T, RawBlockSerial>> for RawBlock<T, RawBlockConcurrent> {
fn from(other: RawBlock<T, RawBlockSerial>) -> Self {
Self {
base: other.base,
capacity: other.capacity,
ptr: PtrCellTrait::new(other.ptr.get()),
_marker: PhantomData,
_c_marker: PhantomData,
}
}
}

View File

@@ -1,3 +1,8 @@
#[cfg(feature = "repl")]
pub mod fallback_mode;
#[cfg(feature = "repl")]
pub mod user_interaction;
use crate::parser::ast::*;
use crate::parser::lexer::Lexer;
use crate::parser::parser::*;
@@ -48,12 +53,11 @@ pub(crate) fn error_after_read_term<R>(
parser: &Parser<R>,
) -> CompilationError {
if err.is_unexpected_eof() {
let line_num = parser.lexer.line_num;
let col_num = parser.lexer.col_num;
let location = &parser.lexer.location;
// rough overlap with errors 8.14.1.3 k) & l) of the ISO standard here
if !(line_num == prior_num_lines_read && col_num == 0) {
return CompilationError::from(ParserError::IncompleteReduction(line_num, col_num));
if !(location.line() == prior_num_lines_read && location.column() == 0) {
return CompilationError::from(parser.lexer.incomplete_reduction());
}
}
@@ -98,13 +102,7 @@ pub(crate) fn set_prompt(value: bool) {
#[cfg(feature = "repl")]
#[inline]
fn get_prompt() -> &'static str {
unsafe {
if PROMPT {
"?- "
} else {
""
}
}
if unsafe { PROMPT } { "?- " } else { "" }
}
thread_local! {
@@ -379,9 +377,7 @@ impl<'a> TermWriter<'a> {
#[inline]
fn push_cell(&mut self, cell: HeapCellValue) -> Result<(), CompilationError> {
self.heap
.push_cell(cell)
.map_err(CompilationError::FiniteMemoryInHeap)
Ok(self.heap.push_cell(cell)?)
}
fn term_as_addr(&mut self, term: &TermRef, h: usize) -> HeapCellValue {
@@ -475,10 +471,7 @@ impl<'a> TermWriter<'a> {
self.push_stub_addr()?;
}
let cell = self
.heap
.allocate_cstr(src)
.map_err(CompilationError::FiniteMemoryInHeap)?;
let cell = self.heap.allocate_cstr(src)?;
let new_h = self.heap.cell_len();
self.push_cell(cell)?;
@@ -496,10 +489,7 @@ impl<'a> TermWriter<'a> {
self.push_stub_addr()?;
}
let cell = self
.heap
.allocate_pstr(src)
.map_err(CompilationError::FiniteMemoryInHeap)?;
let cell = self.heap.allocate_pstr(src)?;
let tail_h = self.heap.cell_len();
self.push_stub_addr()?;

46
src/read/fallback_mode.rs Normal file
View File

@@ -0,0 +1,46 @@
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers};
use std::io::{Error, ErrorKind, Read};
// Provide graceful degradation limited support mode if reading from stdin that does not
// support terminal features.
//
// Retrieve a single byte from stdin, does not support full Unicode decoding; only convert
// byte to KeyEvent char if it falls within the ASCII subset of UTF-8.
// Does not support passing through Ctrl-C as a KeyEvent.
//
// We are not supporting multibyte UTF-8 encodings because we are only
// interested in single ASCII characters which are 1-byte UTF-8 characters.
// Multibyte non-ASCII characters would be ignored anyway, so it's not worth the effort.
//
// It is expected that `limited_support_read` will only be used when prompting for user
// input during solution enumeration and the available commands are single ASCII characters.
pub(crate) fn limited_support_read() -> std::io::Result<KeyEvent> {
#[allow(
clippy::unbuffered_bytes,
reason = "We are aware of `bytes()` performance pitfall but don't expect it to be relevant here, we are doing single byte user input I/O."
)]
let byte_or_none = std::io::stdin().bytes().next();
match byte_or_none {
Some(byte) => match byte {
Ok(b) => {
if b.is_ascii() {
Ok(map_ascii_to_keyevent(b as char))
} else {
Err(Error::new(ErrorKind::Unsupported, "not supported input"))
}
}
Err(e) => Err(e),
},
None => Err(Error::new(ErrorKind::UnexpectedEof, "EOF")),
}
}
fn map_ascii_to_keyevent(c: char) -> KeyEvent {
KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::empty(),
}
}

View File

@@ -0,0 +1,43 @@
use crossterm::event::{Event, KeyEventKind, read};
pub(crate) use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use crossterm::tty::IsTty;
use crate::read::fallback_mode;
pub(crate) fn get_key() -> KeyEvent {
let key;
if supported_terminal() {
enable_raw_mode().expect("failed to enable raw mode");
loop {
let key_ = read();
if let Ok(Event::Key(key_)) = key_ {
if key_.kind != KeyEventKind::Release {
match key_.code {
KeyCode::Char(_) | KeyCode::Enter | KeyCode::Tab => {
key = key_;
break;
}
_ => (),
}
}
}
}
disable_raw_mode().expect("failed to disable raw mode");
} else {
// stdin is not supported terminal type, fallback to limited support mode.
loop {
let key_ = fallback_mode::limited_support_read();
if let Ok(key_) = key_ {
key = key_;
break;
}
}
}
key
}
fn supported_terminal() -> bool {
// If OS is Windows and stdin is not a tty, it's either a pipe or an unsupported terminal configuration.
cfg!(not(windows)) || std::io::stdin().is_tty()
}

View File

@@ -1,5 +1,5 @@
use rustyline::completion::Completer;
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
use rustyline::highlight::{CmdKind, Highlighter, MatchingBracketHighlighter};
use rustyline::hint::Hinter;
use rustyline::validate::Validator;
use rustyline::{Context, Helper as RlHelper, Result};
@@ -10,14 +10,14 @@ use crate::atom_table::{AtomString, AtomTable, STATIC_ATOMS_MAP};
// TODO: Maybe add validation to the helper
pub struct Helper {
highligher: MatchingBracketHighlighter,
highlighter: MatchingBracketHighlighter,
pub atoms: Weak<AtomTable>,
}
impl Helper {
pub fn new() -> Self {
Self {
highligher: MatchingBracketHighlighter::new(),
highlighter: MatchingBracketHighlighter::new(),
atoms: Weak::new(),
}
}
@@ -90,11 +90,11 @@ impl Completer for Helper {
impl Highlighter for Helper {
fn highlight<'l>(&self, line: &'l str, pos: usize) -> std::borrow::Cow<'l, str> {
self.highligher.highlight(line, pos)
self.highlighter.highlight(line, pos)
}
fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
self.highligher.highlight_char(line, pos, forced)
fn highlight_char(&self, line: &str, pos: usize, forced: CmdKind) -> bool {
self.highlighter.highlight_char(line, pos, forced)
}
}

View File

@@ -46,7 +46,7 @@ test_queries_on_builtins :-
\+ float([1,2,_]),
\+ (X is 3 rdiv 4, float(X)),
\+ \+ (X is 3 rdiv 4, rational(X)),
\+ rational(3),
rational(3),
\+ rational(f(_)),
\+ rational("sdfa"),
\+ rational(atom),

View File

@@ -16,6 +16,8 @@
:- dynamic(disabled_init_file/0).
:- dynamic(started/0).
:- dynamic(custom_toplevel/1).
:- dynamic(g_caused_exception/2).
load_scryerrc :-
( '$home_directory'(HomeDir) ->
@@ -53,7 +55,18 @@ start_repl :-
; true
),
(\+ disabled_init_file -> load_scryerrc ; true),
repl.
start_toplevel.
start_toplevel :-
( custom_toplevel(Goal) ->
catch(user:call(Goal),
Exception,
( print_exception(Exception),
halt(1)
)
)
; repl
).
args_consults_goals([], [], []).
args_consults_goals([Arg|Args], Consults, Goals) :-
@@ -64,19 +77,19 @@ arg_consults_goals(c(Mod), Args, [c(Mod)|Consults], Goals) :-
arg_consults_goals(g(Goal), Args, Consults, [g(Goal)|Goals]) :-
args_consults_goals(Args, Consults, Goals).
delegate_task([], []).
delegate_task([], Goals0) :-
(\+ disabled_init_file -> load_scryerrc ; true),
reverse(Goals0, Goals1),
args_consults_goals(Goals1, Consults, Goals),
run_goals(Consults),
run_goals(Goals),
repl.
start_toplevel.
delegate_task([Arg0|Args], Goals0) :-
( ( member(Arg0, ["-h", "--help"]) -> print_help
; member(Arg0, ["-v", "--version"]) -> print_version
; member(Arg0, ["-g", "--goal"]) -> gather_goal(g, Args, Goals0)
; member(Arg0, ["-t"]) -> gather_toplevel(Args, Goals0)
; member(Arg0, ["-f"]) -> disable_init_file
; member(Arg0, ["--no-add-history"]) -> ignore_machine_arg
),
@@ -96,6 +109,8 @@ print_help :-
write('Print version information and exit'), nl,
write(' -g, --goal GOAL '),
write('Run the query GOAL'), nl,
write(' -t GOAL '),
write('Use GOAL as custom toplevel'), nl,
write(' -f '),
write('Fast startup. Do not load initialization file (~/.scryerrc)'), nl,
write(' --no-add-history '),
@@ -117,6 +132,17 @@ gather_goal(Type, Args0, Goals) :-
Gs =.. [Type, Gs1],
delegate_task(Args, [Gs|Goals]).
gather_toplevel(Args0, Goals0) :-
length(Args0, N),
( N < 1 -> print_help, halt
; true
),
[TopLevel|Args] = Args0,
atom_chars(Goal, TopLevel),
retractall(custom_toplevel(_)),
asserta(custom_toplevel(Goal)),
delegate_task(Args, Goals0).
disable_init_file :-
asserta('disabled_init_file').
@@ -154,7 +180,8 @@ run_goals([g(Gs0)|Goals]) :- !,
Exception,
( write_term(Goal, [variable_names(VNs),double_quotes(DQ)]),
write(' causes: '),
write_term(Exception, [double_quotes(DQ)]), nl % halt?
write_term(Exception, [double_quotes(DQ)]), nl,
asserta(g_caused_exception(Goal, Exception))
)
) -> true
; write('% Warning: initialization failed for: '),
@@ -193,6 +220,77 @@ expand_op_list([Op | OtherOps], Pri, Spec, [(:- op(Pri, Spec, Op)) | OtherResult
expand_op_list(OtherOps, Pri, Spec, OtherResults).
% Implement the include/1 directive via term expansion.
user:term_expansion((:- Include), Clauses) :-
nonvar(Include),
Include = include(File0),
( si:atom_si(File0) ->
atom_chars(File0, File),
format("% Warning: include/1: atom arguments are deprecated. Use chars for file paths:~n", []),
format("% :- include(\"~s\").~n", [File])
; error:must_be(chars, File0),
File = File0
),
'$toplevel':gather_clauses_from_file(File, Clauses).
gather_clauses_from_file(File, Clauses) :-
( file_exists(File) ->
setup_call_cleanup(open(File, read, Stream),
gather_clauses_(Stream, File, Clauses),
close(Stream))
; format("include/1: ~s does not exist.", [File]),
Clauses = []
).
gather_clauses_(Stream, _, []) :- at_end_of_stream(Stream), !.
gather_clauses_(Stream, File, Clauses) :-
catch((read(Stream, Clause),
Continue = true),
Error,
( Error = error(syntax_error(incomplete_reduction),_),
at_end_of_stream(Stream) ->
true
; format("~s: ~q~n", [File,Error])
)),
( Continue == true ->
( var(Clause) ->
format("~s: variable clause is ignored.~n", [File]),
gather_clauses_(Stream, File, Clauses)
; Clause = (?- _Query) ->
devour_answer_descriptions(Stream, File, Clauses)
; Clauses = [Clause|Rest],
gather_clauses_(Stream, File, Rest)
)
; Clauses = []
).
devour_answer_descriptions(Stream, File, Clauses) :-
catch((read(Stream, Clause),
Continue = true),
Error,
( Error = error(syntax_error(incomplete_reduction),_),
at_end_of_stream(Stream) ->
true
; format("~s: ~q~n", [File,Error])
)),
( Continue == true ->
( var(Clause) ->
format("~s: variable clause is ignored.~n", [File])
; Clause = (?- _Query) ->
devour_answer_descriptions(Stream, File, Clauses)
; loader:answer_description(Clause) ->
devour_answer_descriptions(Stream, File, Clauses)
; Clauses = [Clause|Rest],
gather_clauses_(Stream, File, Rest)
)
; Clauses = []
).
read_and_match :-
'$read_query_term'(_, Term, _, _, VarList),
instruction_match(Term, VarList).

View File

@@ -1,5 +1,3 @@
#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work
use crate::arena::*;
use crate::atom_table::*;
use crate::forms::*;
@@ -18,7 +16,8 @@ use std::ops::{Add, Sub, SubAssign};
use dashu::{Integer, Rational};
#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
// Variant tag MUST be odd for all but Cons
#[derive(Specifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
#[bits = 6]
pub enum HeapCellValueTag {
@@ -45,34 +44,7 @@ pub enum HeapCellValueTag {
TrailedBlackboardOffset = 0b110001,
}
#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
#[bits = 6]
pub enum HeapCellValueView {
Str = 0b000001,
Lis = 0b000101,
Var = 0b001011,
StackVar = 0b001101,
AttrVar = 0b010001,
PStrLoc = 0b010011,
// constants.
Cons = 0b0,
F64Offset = 0b010101,
Fixnum = 0b011001,
CodeIndexOffset = 0b011011,
Atom = 0b011111,
CutPoint = 0b011101,
// trail elements.
TrailedHeapVar = 0b100001,
TrailedStackVar = 0b100011,
TrailedAttrVar = 0b100101,
TrailedAttrVarListLink = 0b101001,
TrailedAttachedValue = 0b101011,
TrailedBlackboardEntry = 0b101101,
TrailedBlackboardOffset = 0b110001,
}
#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Specifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[bits = 1]
pub enum ConsPtrMaskTag {
Cons = 0b0,
@@ -90,19 +62,46 @@ pub struct ConsPtr {
}
impl ConsPtr {
// ConstPtr.ptr has 61 bits if usize on the current arch is larger than that
// use the niche provided by the alignment of ArenaHeader.
// The niece is that the log2(alignment) least significant bits are always 0, so we can shift the
// address down by that many places without losing information.
// That way with an alignment of 8 a 64-bit address fits into 61-bits after shifting it down by 3 places
// and as a result be can store it without loss in ConsPtr.ptr
pub(crate) const NICHE_SHIFT: u32 = if usize::BITS > 61 {
std::mem::align_of::<ArenaHeader>().ilog2()
} else {
0
};
#[inline(always)]
pub fn build_with(ptr: *const ArenaHeader, tag: ConsPtrMaskTag) -> Self {
pub fn from_ptr(ptr: *const ArenaHeader) -> Self {
Self::build_with(ptr, ConsPtrMaskTag::Cons)
}
#[inline(always)]
fn build_with(ptr: *const ArenaHeader, tag: ConsPtrMaskTag) -> Self {
let mut addr = u64::try_from(ptr.expose_provenance())
.expect("pointer address {ptr:p} should fit into u64");
debug_assert_eq!(addr % std::mem::align_of::<ArenaHeader>() as u64, 0);
addr >>= Self::NICHE_SHIFT;
ConsPtr::new()
.with_ptr(ptr.expose_provenance() as u64)
.with_ptr(addr)
.with_f(false)
.with_m(false)
.with_tag(tag)
}
#[inline(always)]
pub fn as_ptr(self) -> *mut u8 {
let addr: u64 = self.ptr();
std::ptr::with_exposed_provenance_mut(addr as usize)
pub fn as_ptr(self) -> *const ArenaHeader {
let mut addr: u64 = self.ptr();
addr <<= Self::NICHE_SHIFT;
std::ptr::with_exposed_provenance(addr as usize)
}
#[inline(always)]
@@ -111,7 +110,7 @@ impl ConsPtr {
}
}
#[derive(BitfieldSpecifier, Copy, Clone, Debug)]
#[derive(Specifier, Copy, Clone, Debug)]
#[bits = 6]
pub(crate) enum RefTag {
HeapCell = 0b001011,
@@ -198,7 +197,7 @@ pub enum TrailRef {
}
#[allow(clippy::enum_variant_names)] // allow the common "Trailed" prefix
#[derive(BitfieldSpecifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Specifier, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[bits = 6]
pub(crate) enum TrailEntryTag {
TrailedHeapVar = 0b101111,
@@ -365,7 +364,7 @@ where
{
#[inline]
fn from(arena_ptr: TypedArenaPtr<T>) -> HeapCellValue {
HeapCellValue::from(arena_ptr.header_ptr().expose_provenance() as u64)
HeapCellValue::from_arena_header_ptr(arena_ptr.header_ptr())
}
}
@@ -386,18 +385,6 @@ impl From<CodeIndexOffset> for HeapCellValue {
}
}
impl From<ConsPtr> for HeapCellValue {
#[inline(always)]
fn from(cons_ptr: ConsPtr) -> HeapCellValue {
HeapCellValue::from_bytes(
ConsPtr::from(cons_ptr.as_ptr().expose_provenance() as u64)
.with_tag(ConsPtrMaskTag::Cons)
.with_m(false)
.into_bytes(),
)
}
}
impl From<(Number, &mut Arena)> for HeapCellValue {
#[inline(always)]
fn from((n, arena): (Number, &mut Arena)) -> HeapCellValue {
@@ -587,12 +574,12 @@ impl HeapCellValue {
}
#[inline]
pub fn from_ptr_addr(ptr_bytes: usize) -> Self {
HeapCellValue::from_bytes((ptr_bytes as u64).to_ne_bytes())
pub fn from_arena_header_ptr(ptr: *const ArenaHeader) -> Self {
HeapCellValue::from_bytes(ConsPtr::from_ptr(ptr).into_bytes())
}
pub fn to_ptr_addr(self) -> usize {
u64::from_ne_bytes(self.into_bytes()) as usize
pub fn to_arena_header_ptr(self) -> *const ArenaHeader {
ConsPtr::from_bytes(self.into_bytes()).as_ptr()
}
#[inline]
@@ -736,21 +723,14 @@ const_assert!(mem::size_of::<UntypedArenaPtr>() == 8);
impl From<*const ArenaHeader> for UntypedArenaPtr {
#[inline]
fn from(ptr: *const ArenaHeader) -> UntypedArenaPtr {
UntypedArenaPtr::build_with(ptr.expose_provenance())
}
}
impl From<*const IndexPtr> for UntypedArenaPtr {
#[inline]
fn from(ptr: *const IndexPtr) -> UntypedArenaPtr {
UntypedArenaPtr::build_with(ptr.expose_provenance())
UntypedArenaPtr::from_bytes(ConsPtr::from_ptr(ptr).into_bytes())
}
}
impl From<UntypedArenaPtr> for *const ArenaHeader {
#[inline]
fn from(ptr: UntypedArenaPtr) -> *const ArenaHeader {
ptr.get_ptr().cast::<ArenaHeader>()
ptr.get_ptr()
}
}
@@ -761,9 +741,8 @@ impl UntypedArenaPtr {
}
#[inline]
pub fn get_ptr(self) -> *const u8 {
let addr: u64 = self.ptr();
std::ptr::with_exposed_provenance(addr as usize)
pub fn get_ptr(self) -> *const ArenaHeader {
ConsPtr::from_bytes(self.into_bytes()).as_ptr()
}
#[inline]
@@ -775,20 +754,15 @@ impl UntypedArenaPtr {
}
}
#[inline]
pub fn payload_offset(self) -> *const u8 {
unsafe { self.get_ptr().add(size_of::<ArenaHeader>()) }
}
/// # Safety
/// - this UntypedArenaPtr actuall pointee type is T
/// - this UntypedArenaPtr actual pointee type is T
/// - the pointer must be non-null
#[inline]
pub unsafe fn as_typed_ptr<T: ?Sized + ArenaAllocated>(self) -> TypedArenaPtr<T>
where
T::Payload: Sized,
{
T::typed_ptr(self)
unsafe { T::typed_ptr(self) }
}
#[inline]

View File

@@ -1,10 +1,13 @@
use crate::forms::GenContext;
use crate::forms::{BranchNumber, GenContext};
use crate::parser::ast::*;
use bit_set::*;
use fxhash::FxBuildHasher;
use indexmap::{IndexMap, IndexSet};
use num_order::NumOrd;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct TempVarData {
@@ -13,20 +16,19 @@ pub struct TempVarData {
pub(crate) conflict_set: BitSet<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BranchDesignator {
pub branch_stack_num: usize,
pub branch_num: usize,
pub branch_num: Arc<BranchNumber>,
}
impl BranchDesignator {
#[inline]
pub fn is_sub_branch(&self) -> bool {
self.branch_stack_num > 0
self.branch_num.branch_num.num_gt(&0)
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub enum VarSafetyStatus {
Needed,
// which branch planted the last unsafe guarded instruction? It may still be needed.
@@ -35,27 +37,27 @@ pub enum VarSafetyStatus {
}
impl VarSafetyStatus {
pub(crate) fn unneeded(current_branch: BranchDesignator) -> Self {
pub(crate) fn unneeded(current_branch: &BranchDesignator) -> Self {
if current_branch.is_sub_branch() {
VarSafetyStatus::LocallyUnneeded(current_branch)
VarSafetyStatus::LocallyUnneeded(current_branch.clone())
} else {
VarSafetyStatus::GloballyUnneeded
}
}
#[inline]
pub(crate) fn needed_if(needed: bool, branch_designator: BranchDesignator) -> Self {
pub(crate) fn needed_if(needed: bool, branch_designator: &BranchDesignator) -> Self {
if needed {
VarSafetyStatus::Needed
} else if branch_designator.branch_stack_num == 0 {
} else if branch_designator.branch_num.branch_num.num_eq(&0) {
VarSafetyStatus::GloballyUnneeded
} else {
VarSafetyStatus::LocallyUnneeded(branch_designator)
VarSafetyStatus::LocallyUnneeded(branch_designator.clone())
}
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub enum PermVarAllocation {
Done {
shallow_safety: VarSafetyStatus,
@@ -103,10 +105,8 @@ impl VarAlloc {
#[inline]
pub(crate) fn set_register(&mut self, reg_num: usize) {
match self {
VarAlloc::Perm(ref mut p, _) => *p = reg_num,
VarAlloc::Temp {
ref mut temp_reg, ..
} => *temp_reg = reg_num,
VarAlloc::Perm(p, _) => *p = reg_num,
VarAlloc::Temp { temp_reg, .. } => *temp_reg = reg_num,
};
}
}

View File

@@ -129,23 +129,21 @@ impl WasmQueryState {
let mut error = None;
let mut to_drop = false;
match &mut self.inner {
Some(ref mut inner) => {
inner.with_query_state_mut(|query_state| match query_state.next() {
Some(Ok(leaf_answer)) => {
js_sys::Reflect::set(&ret, &"value".into(), &leaf_answer.into()).unwrap();
js_sys::Reflect::set(&ret, &"done".into(), &false.into()).unwrap();
}
Some(Err(error_term)) => {
let js_error = js_sys::Error::new("Prolog error");
js_error.set_cause(&error_term.into());
error = Some(js_error);
}
None => {
js_sys::Reflect::set(&ret, &"done".into(), &true.into()).unwrap();
to_drop = true;
}
})
}
Some(inner) => inner.with_query_state_mut(|query_state| match query_state.next() {
Some(Ok(leaf_answer)) => {
js_sys::Reflect::set(&ret, &"value".into(), &leaf_answer.into()).unwrap();
js_sys::Reflect::set(&ret, &"done".into(), &false.into()).unwrap();
}
Some(Err(error_term)) => {
let js_error = js_sys::Error::new("Prolog error");
js_error.set_cause(&error_term.into());
error = Some(js_error);
}
None => {
js_sys::Reflect::set(&ret, &"done".into(), &true.into()).unwrap();
to_drop = true;
}
}),
None => return Err(js_sys::Error::new("This query was already dropped").into()),
}

View File

@@ -0,0 +1,11 @@
:- use_module(library(dcgs)).
:- use_module(library(tabling)).
:- table expr//0.
expr --> "1".
expr --> expr, "+", expr.
run :- phrase(expr, "1+1+1+1+1").
:- initialization(run).

View File

@@ -0,0 +1,16 @@
:- use_module(library(os)).
:- use_module(library(ffi)).
init :-
read(Body),
term_variables(Body, [LIB]),
Body,
use_foreign_module(LIB, [
'ffi_invalid_utf8_cstr'([], cstr)
]).
test :-
ffi:'ffi_invalid_utf8_cstr'(Str),
write(Str), nl.
:- initialization((init,test)).

View File

@@ -1,5 +1,26 @@
:- module(http_open_hanging, [submit_request/0]).
% Server
:- use_module(library(process)).
:- use_module(library(iso_ext)).
:- use_module(library(os)).
prolog_path(Prolog) :-
read(Body),
term_variables(Body, [Prolog]),
Body.
server_start([Process,Out]) :-
prolog_path(Prolog),
process_create(Prolog,
["tests-pl/issue-http_open-hanging_server", "-t", "server"],
[process(Process), stdout(pipe(Out))]).
server_wait_start([_Process, Out]) :-
get_char(Out, _C).
server_stop([Process,_Out]) :-
process_kill(Process).
% Client
:- use_module(library(charsio)).
:- use_module(library(http/http_open)).
@@ -10,14 +31,21 @@ send_request :-
request_headers([]),
headers(_)
],
http_open("https://scryer.pl", _Stream, Options),
http_open("http://localhost:8472", _Stream, Options),
write_term('received response with status code':StatusCode, []), nl.
main :-
send_request,
send_request,
send_request,
send_request,
send_request.
setup_call_cleanup(
server_start(Server),
(
server_wait_start(Server),
send_request,
send_request,
send_request,
send_request,
send_request
),
server_stop(Server)
).
:- initialization(main).

View File

@@ -0,0 +1,8 @@
:- use_module(library(process)).
:- use_module(library(http/http_server)).
server :-
http_listen(8472, [get(/, hello)]).
hello(_Req, Res) :-
http_body(Res, text("Ok")).

View File

@@ -0,0 +1,38 @@
% Server
:- use_module(library(process)).
:- use_module(library(charsio)).
:- use_module(library(lists)).
:- use_module(library(time)).
prolog_path(Prolog) :-
read(Body),
term_variables(Body, [Prolog]),
Body.
main :-
prolog_path(Prolog),
CMD = "\
use_module(library(os)), \
pid(PID), \
write(PID), nl, \
asserta((f :- f)), \
catch(f, Err, (write(Err),nl)), \
write(done), nl, \
halt.",
process_create("tests-pl/pty_exec.py", [Prolog, "-g", CMD], [stdout(pipe(O))]),
get_line_to_chars(O, PID0, ""),
append(PID, "\r\n", PID0),
process_create("kill", ["-s", "INT", PID], []),
sleep(5),
% second kill should fail because process should exit after one kill
process_create("kill", ["-s", "INT", PID], [stderr(null), process(PK)]),
process_wait(PK, Status),
status_report(Status, PID), nl.
status_report(exit(1), _PID) :- write(ok).
status_report(exit(0), PID) :-
write(not_dead),
% kill by force to exit cleanly
process_create("kill", ["-9", PID], []).
:- initialization(main).

2
tests-pl/issue2914.pl Normal file
View File

@@ -0,0 +1,2 @@
:- initialization(main).
main :- ( current_prolog_flag(X, X) -> write(true) ; write(false) ).

5
tests-pl/issue3256.pl Normal file
View File

@@ -0,0 +1,5 @@
:- use_module(library(sgml)).
test :- load_xml("<foo><bar>hello</bar></foo>", Es, []), write(Es).
:- initialization(test).

Some files were not shown because too many files have changed in this diff Show More