├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.md └── workflows │ ├── bench.yml │ ├── checks.yml │ └── delete-cancelled.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── examples ├── demo_io │ ├── main.bend │ └── main.hvm ├── sort_bitonic │ ├── main.bend │ └── main.hvm ├── sort_radix │ ├── main.bend │ └── main.hvm ├── stress │ ├── README.md │ ├── main.bend │ ├── main.hvm │ ├── main.js │ └── main.py ├── sum_rec │ ├── main.bend │ ├── main.hvm │ ├── main.js │ └── sum.js ├── sum_tree │ ├── main.bend │ └── main.hvm └── tuples │ ├── tuples.bend │ └── tuples.hvm ├── paper ├── HVM2 - Extended Abstract.pdf ├── HVM2.pdf ├── HVM2.typst ├── README.md └── inet.typ ├── src ├── ast.rs ├── cmp.rs ├── hvm.c ├── hvm.cu ├── hvm.cuh ├── hvm.h ├── hvm.rs ├── lib.rs ├── main.rs ├── run.c └── run.cu └── tests ├── programs ├── empty.hvm ├── hello-world.hvm ├── io │ ├── basic.bend │ ├── basic.hvm │ ├── invalid-name.bend │ ├── invalid-name.hvm │ ├── open1.bend │ ├── open1.hvm │ ├── open2.bend │ ├── open2.hvm │ ├── open3.bend │ └── open3.hvm ├── list.hvm ├── numeric-casts.hvm ├── numerics │ ├── f24.hvm │ ├── i24.hvm │ └── u24.hvm └── safety-check.hvm ├── run.rs └── snapshots ├── run__file@empty.hvm.snap ├── run__file@hello-world.hvm.snap ├── run__file@list.hvm.snap ├── run__file@numeric-casts.hvm.snap ├── run__file@numerics__f24.hvm.snap ├── run__file@numerics__i24.hvm.snap ├── run__file@numerics__u24.hvm.snap ├── run__file@safety-check.hvm.snap ├── run__file@sort_bitonic__main.hvm.snap ├── run__file@sort_radix__main.hvm.snap ├── run__file@stress__main.hvm.snap ├── run__file@sum_rec__main.hvm.snap ├── run__file@sum_tree__main.hvm.snap ├── run__file@tuples__tuples.hvm.snap ├── run__io_file@demo_io__main.hvm.snap ├── run__io_file@io__basic.hvm.snap ├── run__io_file@io__invalid-name.hvm.snap ├── run__io_file@io__open1.hvm.snap ├── run__io_file@io__open2.hvm.snap ├── run__io_file@io__open3.hvm.snap └── run__io_file@io__read_and_print.hvm.snap /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve. 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | ### Bug Report 8 | Note, your issue might have been already reported, please check [issues](https://github.com/HigherOrderCO/HVM/issues). If you find a similar issue, respond with a reaction or any additional information that you feel may be helpful. 9 | 10 | ### For Windows Users 11 | There is currently no native way to install HVM, as a temporary workaround, please use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install). 12 | 13 | - type: textarea 14 | attributes: 15 | label: Reproducing the behavior 16 | description: A clear and concise description of what the bug is. 17 | value: | 18 | Example: 19 | Running command... 20 | With code.... 21 | Error... 22 | Expected behavior.... 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | attributes: 28 | label: System Settings 29 | description: Your System's settings 30 | value: | 31 | Example: 32 | - OS: [e.g. Linux (Ubuntu 22.04)] 33 | - CPU: [e.g. Intel i9-14900KF] 34 | - GPU: [e.g. RTX 4090] 35 | - Cuda Version [e.g. release 12.4, V12.4.131] 36 | validations: 37 | required: true 38 | - type: textarea 39 | attributes: 40 | label: Additional context 41 | description: Add any other context about the problem here (Optional). 42 | 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Bend Related Issues 4 | url: https://github.com/HigherOrderCO/Bend/issues/new/choose 5 | about: For Bend related Issues, please Report them on the Bend repository. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest a feature that you think should be added. 4 | title: '' 5 | labels: '' 6 | 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | name: Bench 2 | 3 | on: 4 | pull_request: 5 | 6 | concurrency: 7 | group: bench-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | bench: 12 | runs-on: [self-hosted, cuda] 13 | timeout-minutes: 10 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: compare perf 17 | run: | 18 | git fetch origin main 19 | git clone https://github.com/HigherOrderCO/hvm-bench 20 | cd hvm-bench 21 | NO_COLOR=1 cargo run bench --repo-dir ../ -r main --timeout 20 > ../table 22 | shell: bash -l {0} 23 | - name: write comment 24 | run: | 25 | echo 'Perf run for [`'`git rev-parse --short ${{ github.sha }}`'`](https://github.com/higherorderco/HVM/commit/${{ github.sha }}):' >> comment 26 | echo '```' >> comment 27 | cat table >> comment 28 | echo '```' >> comment 29 | - name: post comment 30 | run: gh pr comment ${{ github.event.number }} -F comment 31 | env: 32 | GH_TOKEN: ${{ secrets.PAT }} 33 | - name: hide old comment 34 | env: 35 | GH_TOKEN: ${{ secrets.PAT }} 36 | run: | 37 | COMMENT_ID=$( 38 | gh api graphql -F pr=${{ github.event.number }} -f query=' 39 | query($pr: Int!) { 40 | organization(login: "higherorderco") { 41 | repository(name: "HVM") { 42 | pullRequest(number: $pr) { 43 | comments(last: 100) { 44 | nodes { id author { login } } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | ' \ 51 | | jq -r ' 52 | [ 53 | .data.organization.repository.pullRequest.comments.nodes | .[] 54 | | select(.author.login == "HigherOrderBot") 55 | | .id 56 | ] | .[-2] 57 | ' 58 | ) 59 | 60 | if [ $COMMENT_ID != null ] 61 | then 62 | gh api graphql -F id=$COMMENT_ID -f query=' 63 | mutation($id: ID!) { 64 | minimizeComment(input: { 65 | subjectId: $id, 66 | classifier: OUTDATED, 67 | }) { minimizedComment { ...on Comment { id } } } 68 | } 69 | ' 70 | fi 71 | - name: delete on cancel 72 | if: ${{ cancelled() }} 73 | run: gh workflow run delete-cancelled.yml -f run_id=${{ github.run_id }} 74 | env: 75 | GH_TOKEN: ${{ secrets.PAT }} 76 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: Checks 2 | 3 | on: 4 | pull_request: 5 | merge_group: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | check: 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 10 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/cache@v2 17 | with: 18 | path: | 19 | ~/.cargo/registry 20 | ~/.cargo/git 21 | target 22 | key: ${{ runner.os }}-check-${{ hashFiles('**/Cargo.lock') }} 23 | - run: RUSTFLAGS="-D warnings" cargo check --all-targets 24 | test: 25 | runs-on: ubuntu-latest 26 | timeout-minutes: 10 27 | steps: 28 | - uses: actions/checkout@v3 29 | - uses: actions/cache@v2 30 | with: 31 | path: | 32 | ~/.cargo/registry 33 | ~/.cargo/git 34 | target 35 | key: ${{ runner.os }}-test-${{ hashFiles('**/Cargo.lock') }} 36 | - run: cargo test --release 37 | test-cuda: 38 | needs: test # don't bother the cuda machine if other tests are failing 39 | runs-on: [self-hosted, cuda] 40 | timeout-minutes: 10 41 | steps: 42 | - uses: actions/checkout@v3 43 | - run: cargo test --release 44 | shell: bash -l {0} 45 | 46 | -------------------------------------------------------------------------------- /.github/workflows/delete-cancelled.yml: -------------------------------------------------------------------------------- 1 | name: Delete Cancelled Benchmarks 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | run_id: 7 | type: string 8 | description: "" 9 | 10 | jobs: 11 | delete: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - run: gh api "repos/higherorderco/hvm-core/actions/runs/${{ inputs.run_id }}" -X DELETE 15 | env: 16 | GH_TOKEN: ${{ secrets.PAT }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .fill.tmp 2 | hvm-cuda-experiments/ 3 | src/hvm 4 | src/old_cmp.rs 5 | src/tmp/ 6 | target/ 7 | tmp/ 8 | .hvm/ 9 | examples/**/main 10 | examples/**/*.c 11 | examples/**/*.cu 12 | .out.hvm 13 | 14 | # nix-direnv 15 | /.direnv/ 16 | /.envrc 17 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "TSPL" 7 | version = "0.0.13" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fe639519d49b56c98fd4fde7a5a7be01b5563862341a783b9bc2eb58f5120d8b" 10 | dependencies = [ 11 | "highlight_error", 12 | ] 13 | 14 | [[package]] 15 | name = "aho-corasick" 16 | version = "1.1.3" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 19 | dependencies = [ 20 | "memchr", 21 | ] 22 | 23 | [[package]] 24 | name = "anstream" 25 | version = "0.6.14" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 28 | dependencies = [ 29 | "anstyle", 30 | "anstyle-parse", 31 | "anstyle-query", 32 | "anstyle-wincon", 33 | "colorchoice", 34 | "is_terminal_polyfill", 35 | "utf8parse", 36 | ] 37 | 38 | [[package]] 39 | name = "anstyle" 40 | version = "1.0.7" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 43 | 44 | [[package]] 45 | name = "anstyle-parse" 46 | version = "0.2.4" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 49 | dependencies = [ 50 | "utf8parse", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-query" 55 | version = "1.0.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" 58 | dependencies = [ 59 | "windows-sys", 60 | ] 61 | 62 | [[package]] 63 | name = "anstyle-wincon" 64 | version = "3.0.3" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 67 | dependencies = [ 68 | "anstyle", 69 | "windows-sys", 70 | ] 71 | 72 | [[package]] 73 | name = "bstr" 74 | version = "1.9.1" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" 77 | dependencies = [ 78 | "memchr", 79 | "serde", 80 | ] 81 | 82 | [[package]] 83 | name = "cc" 84 | version = "1.0.97" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" 87 | 88 | [[package]] 89 | name = "clap" 90 | version = "4.5.4" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 93 | dependencies = [ 94 | "clap_builder", 95 | ] 96 | 97 | [[package]] 98 | name = "clap_builder" 99 | version = "4.5.2" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 102 | dependencies = [ 103 | "anstream", 104 | "anstyle", 105 | "clap_lex", 106 | "strsim", 107 | ] 108 | 109 | [[package]] 110 | name = "clap_lex" 111 | version = "0.7.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 114 | 115 | [[package]] 116 | name = "colorchoice" 117 | version = "1.0.1" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 120 | 121 | [[package]] 122 | name = "console" 123 | version = "0.15.8" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" 126 | dependencies = [ 127 | "encode_unicode", 128 | "lazy_static", 129 | "libc", 130 | "windows-sys", 131 | ] 132 | 133 | [[package]] 134 | name = "encode_unicode" 135 | version = "0.3.6" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 138 | 139 | [[package]] 140 | name = "globset" 141 | version = "0.4.14" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" 144 | dependencies = [ 145 | "aho-corasick", 146 | "bstr", 147 | "log", 148 | "regex-automata", 149 | "regex-syntax", 150 | ] 151 | 152 | [[package]] 153 | name = "hermit-abi" 154 | version = "0.3.9" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 157 | 158 | [[package]] 159 | name = "highlight_error" 160 | version = "0.1.1" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "809e18805660d7b6b2e2b9f316a5099521b5998d5cba4dda11b5157a21aaef03" 163 | 164 | [[package]] 165 | name = "hvm" 166 | version = "2.0.22" 167 | dependencies = [ 168 | "TSPL", 169 | "cc", 170 | "clap", 171 | "highlight_error", 172 | "insta", 173 | "num_cpus", 174 | ] 175 | 176 | [[package]] 177 | name = "insta" 178 | version = "1.39.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" 181 | dependencies = [ 182 | "console", 183 | "globset", 184 | "lazy_static", 185 | "linked-hash-map", 186 | "similar", 187 | "walkdir", 188 | ] 189 | 190 | [[package]] 191 | name = "is_terminal_polyfill" 192 | version = "1.70.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 195 | 196 | [[package]] 197 | name = "lazy_static" 198 | version = "1.4.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 201 | 202 | [[package]] 203 | name = "libc" 204 | version = "0.2.155" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 207 | 208 | [[package]] 209 | name = "linked-hash-map" 210 | version = "0.5.6" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 213 | 214 | [[package]] 215 | name = "log" 216 | version = "0.4.21" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 219 | 220 | [[package]] 221 | name = "memchr" 222 | version = "2.7.2" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 225 | 226 | [[package]] 227 | name = "num_cpus" 228 | version = "1.16.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 231 | dependencies = [ 232 | "hermit-abi", 233 | "libc", 234 | ] 235 | 236 | [[package]] 237 | name = "proc-macro2" 238 | version = "1.0.82" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 241 | dependencies = [ 242 | "unicode-ident", 243 | ] 244 | 245 | [[package]] 246 | name = "quote" 247 | version = "1.0.36" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 250 | dependencies = [ 251 | "proc-macro2", 252 | ] 253 | 254 | [[package]] 255 | name = "regex-automata" 256 | version = "0.4.6" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 259 | dependencies = [ 260 | "aho-corasick", 261 | "memchr", 262 | "regex-syntax", 263 | ] 264 | 265 | [[package]] 266 | name = "regex-syntax" 267 | version = "0.8.3" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 270 | 271 | [[package]] 272 | name = "same-file" 273 | version = "1.0.6" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 276 | dependencies = [ 277 | "winapi-util", 278 | ] 279 | 280 | [[package]] 281 | name = "serde" 282 | version = "1.0.200" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" 285 | dependencies = [ 286 | "serde_derive", 287 | ] 288 | 289 | [[package]] 290 | name = "serde_derive" 291 | version = "1.0.200" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" 294 | dependencies = [ 295 | "proc-macro2", 296 | "quote", 297 | "syn", 298 | ] 299 | 300 | [[package]] 301 | name = "similar" 302 | version = "2.5.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" 305 | 306 | [[package]] 307 | name = "strsim" 308 | version = "0.11.1" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 311 | 312 | [[package]] 313 | name = "syn" 314 | version = "2.0.64" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f" 317 | dependencies = [ 318 | "proc-macro2", 319 | "quote", 320 | "unicode-ident", 321 | ] 322 | 323 | [[package]] 324 | name = "unicode-ident" 325 | version = "1.0.12" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 328 | 329 | [[package]] 330 | name = "utf8parse" 331 | version = "0.2.1" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 334 | 335 | [[package]] 336 | name = "walkdir" 337 | version = "2.5.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 340 | dependencies = [ 341 | "same-file", 342 | "winapi-util", 343 | ] 344 | 345 | [[package]] 346 | name = "winapi-util" 347 | version = "0.1.8" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" 350 | dependencies = [ 351 | "windows-sys", 352 | ] 353 | 354 | [[package]] 355 | name = "windows-sys" 356 | version = "0.52.0" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 359 | dependencies = [ 360 | "windows-targets", 361 | ] 362 | 363 | [[package]] 364 | name = "windows-targets" 365 | version = "0.52.5" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 368 | dependencies = [ 369 | "windows_aarch64_gnullvm", 370 | "windows_aarch64_msvc", 371 | "windows_i686_gnu", 372 | "windows_i686_gnullvm", 373 | "windows_i686_msvc", 374 | "windows_x86_64_gnu", 375 | "windows_x86_64_gnullvm", 376 | "windows_x86_64_msvc", 377 | ] 378 | 379 | [[package]] 380 | name = "windows_aarch64_gnullvm" 381 | version = "0.52.5" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 384 | 385 | [[package]] 386 | name = "windows_aarch64_msvc" 387 | version = "0.52.5" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 390 | 391 | [[package]] 392 | name = "windows_i686_gnu" 393 | version = "0.52.5" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 396 | 397 | [[package]] 398 | name = "windows_i686_gnullvm" 399 | version = "0.52.5" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 402 | 403 | [[package]] 404 | name = "windows_i686_msvc" 405 | version = "0.52.5" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 408 | 409 | [[package]] 410 | name = "windows_x86_64_gnu" 411 | version = "0.52.5" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 414 | 415 | [[package]] 416 | name = "windows_x86_64_gnullvm" 417 | version = "0.52.5" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 420 | 421 | [[package]] 422 | name = "windows_x86_64_msvc" 423 | version = "0.52.5" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 426 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hvm" 3 | description = "A massively parallel, optimal functional runtime in Rust." 4 | license = "Apache-2.0" 5 | version = "2.0.22" 6 | edition = "2021" 7 | rust-version = "1.74" 8 | build = "build.rs" 9 | repository = "https://github.com/HigherOrderCO/HVM" 10 | 11 | [lib] 12 | name = "hvm" 13 | path = "src/lib.rs" 14 | 15 | [dependencies] 16 | TSPL = "0.0.13" 17 | clap = "4.5.2" 18 | highlight_error = "0.1.1" 19 | num_cpus = "1.0" 20 | 21 | [build-dependencies] 22 | cc = "1.0" 23 | num_cpus = "1.0" 24 | 25 | [features] 26 | default = [] 27 | # C and CUDA features are determined during build 28 | c = [] 29 | cuda = [] 30 | 31 | [dev-dependencies] 32 | insta = { version = "1.39.0", features = ["glob"] } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Higher-order Virtual Machine 2 (HVM2) 2 | ===================================== 3 | 4 | **Higher-order Virtual Machine 2 (HVM2)** is a massively parallel [Interaction 5 | Combinator](https://www.semanticscholar.org/paper/Interaction-Combinators-Lafont/6cfe09aa6e5da6ce98077b7a048cb1badd78cc76) 6 | evaluator. 7 | 8 | By compiling programs from high-level languages (such as Python and Haskell) to 9 | HVM, one can run these languages directly on massively parallel hardware, like 10 | GPUs, with near-ideal speedup. 11 | 12 | HVM2 is the successor to [HVM1](https://github.com/HigherOrderCO/HVM1), a 2022 13 | prototype of this concept. Compared to its predecessor, HVM2 is simpler, faster 14 | and, most importantly, more correct. [HOC](https://HigherOrderCO.com/) provides 15 | long-term support for all features listed on its [PAPER](./paper/HVM2.pdf). 16 | 17 | This repository provides a low-level IR language for specifying the HVM2 nets 18 | and a compiler from that language to C and CUDA. It is not meant for direct 19 | human usage. If you're looking for a high-level language to interface with HVM2, 20 | check [Bend](https://github.com/HigherOrderCO/Bend) instead. 21 | 22 | Usage 23 | ----- 24 | 25 | > DISCLAIMER: Windows is currently not supported, please use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) for now as a workaround. 26 | 27 | First install the dependencies: 28 | * If you want to use the C runtime, install a C-11 compatible compiler like GCC or Clang. 29 | * If you want to use the CUDA runtime, install CUDA and nvcc (the CUDA compiler). 30 | - _HVM requires CUDA 12.x and currently only works on Nvidia GPUs._ 31 | 32 | Install HVM2: 33 | 34 | ```sh 35 | cargo install hvm 36 | ``` 37 | 38 | There are multiple ways to run an HVM program: 39 | 40 | ```sh 41 | hvm run # interpret via Rust 42 | hvm run-c # interpret via C 43 | hvm run-cu # interpret via CUDA 44 | hvm gen-c # compile to standalone C 45 | hvm gen-cu # compile to standalone CUDA 46 | ``` 47 | 48 | All modes produce the same output. The compiled modes require you to compile the 49 | generated file (with `gcc file.c -o file`, for example), but are faster to run. 50 | The CUDA versions have much higher peak performance but are less stable. As a 51 | rule of thumb, `gen-c` should be used in production. 52 | 53 | Language 54 | -------- 55 | 56 | HVM is a low-level compile target for high-level languages. It provides a raw 57 | syntax for wiring interaction nets. For example: 58 | 59 | ```javascript 60 | @main = a 61 | & @sum ~ (28 (0 a)) 62 | 63 | @sum = (?(((a a) @sum__C0) b) b) 64 | 65 | @sum__C0 = ({c a} ({$([*2] $([+1] d)) $([*2] $([+0] b))} f)) 66 | &! @sum ~ (a (b $([+] $(e f)))) 67 | &! @sum ~ (c (d e)) 68 | ``` 69 | 70 | The file above implements a recursive sum. If that looks unreadable to you - 71 | don't worry, it isn't meant to. [Bend](https://github.com/HigherOrderCO/Bend) is 72 | the human-readable language and should be used both by end users and by languages 73 | aiming to target the HVM. If you're looking to learn more about the core 74 | syntax and tech, though, please check the [PAPER](./paper/HVM2.pdf). 75 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let cores = num_cpus::get(); 3 | let tpcl2 = (cores as f64).log2().floor() as u32; 4 | 5 | println!("cargo:rerun-if-changed=src/run.c"); 6 | println!("cargo:rerun-if-changed=src/hvm.c"); 7 | println!("cargo:rerun-if-changed=src/run.cu"); 8 | println!("cargo:rerun-if-changed=src/hvm.cu"); 9 | println!("cargo:rustc-link-arg=-rdynamic"); 10 | 11 | match cc::Build::new() 12 | .file("src/run.c") 13 | .opt_level(3) 14 | .warnings(false) 15 | .define("TPC_L2", &*tpcl2.to_string()) 16 | .define("IO", None) 17 | .try_compile("hvm-c") { 18 | Ok(_) => println!("cargo:rustc-cfg=feature=\"c\""), 19 | Err(e) => { 20 | println!("cargo:warning=\x1b[1m\x1b[31mWARNING: Failed to compile/run.c:\x1b[0m {}", e); 21 | println!("cargo:warning=Ignoring/run.c and proceeding with build. \x1b[1mThe C runtime will not be available.\x1b[0m"); 22 | } 23 | } 24 | 25 | // Builds hvm.cu 26 | if std::process::Command::new("nvcc").arg("--version").stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null()).status().is_ok() { 27 | if let Ok(cuda_path) = std::env::var("CUDA_HOME") { 28 | println!("cargo:rustc-link-search=native={}/lib64", cuda_path); 29 | } else { 30 | println!("cargo:rustc-link-search=native=/usr/local/cuda/lib64"); 31 | } 32 | 33 | cc::Build::new() 34 | .cuda(true) 35 | .file("src/run.cu") 36 | .define("IO", None) 37 | .flag("-diag-suppress=177") // variable was declared but never referenced 38 | .flag("-diag-suppress=550") // variable was set but never used 39 | .flag("-diag-suppress=20039") // a __host__ function redeclared with __device__, hence treated as a __host__ __device__ function 40 | .compile("hvm-cu"); 41 | 42 | println!("cargo:rustc-cfg=feature=\"cuda\""); 43 | } 44 | else { 45 | println!("cargo:warning=\x1b[1m\x1b[31mWARNING: CUDA compiler not found.\x1b[0m \x1b[1mHVM will not be able to run on GPU.\x1b[0m"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/demo_io/main.bend: -------------------------------------------------------------------------------- 1 | test-io = 1 2 | 3 | def unwrap(res): 4 | match res: 5 | case Result/Ok: 6 | return res.val 7 | case Result/Err: 8 | return res.val 9 | 10 | def open(): 11 | return call("OPEN", ("./LICENSE", "r")) 12 | 13 | def read(f): 14 | return call("READ", (f, 47)) 15 | 16 | def print(bytes): 17 | with IO: 18 | * <- call("WRITE", (1, bytes)) 19 | * <- call("WRITE", (1, "\n")) 20 | 21 | return wrap(*) 22 | 23 | def close(f): 24 | return call("CLOSE", f) 25 | 26 | def main(): 27 | with IO: 28 | f <- open() 29 | f = unwrap(f) 30 | bytes <- read(f) 31 | bytes = unwrap(bytes) 32 | * <- print(bytes) 33 | res <- close(f) 34 | 35 | return wrap(res) 36 | -------------------------------------------------------------------------------- /examples/demo_io/main.hvm: -------------------------------------------------------------------------------- 1 | @IO/Call = (a (b (c (d ((@IO/Call/tag (a (b (c (d e))))) e))))) 2 | 3 | @IO/Call/tag = 1 4 | 5 | @IO/Done = (a (b ((@IO/Done/tag (a (b c))) c))) 6 | 7 | @IO/Done/tag = 0 8 | 9 | @IO/MAGIC = (13683217 16719857) 10 | 11 | @IO/bind = ((@IO/bind__C2 a) a) 12 | 13 | @IO/bind__C0 = (* (b (a c))) 14 | & @undefer ~ (a (b c)) 15 | 16 | @IO/bind__C1 = (* (* (a (b ((c d) (e g)))))) 17 | & @IO/Call ~ (@IO/MAGIC (a (b ((c f) g)))) 18 | & @IO/bind ~ (d (e f)) 19 | 20 | @IO/bind__C2 = (?((@IO/bind__C0 @IO/bind__C1) a) a) 21 | 22 | @IO/wrap = a 23 | & @IO/Done ~ (@IO/MAGIC a) 24 | 25 | @String/Cons = (a (b ((@String/Cons/tag (a (b c))) c))) 26 | 27 | @String/Cons/tag = 1 28 | 29 | @String/Nil = ((@String/Nil/tag a) a) 30 | 31 | @String/Nil/tag = 0 32 | 33 | @call = (a (b c)) 34 | & @IO/Call ~ (@IO/MAGIC (a (b (@call__C0 c)))) 35 | 36 | @call__C0 = a 37 | & @IO/Done ~ (@IO/MAGIC a) 38 | 39 | @close = f 40 | & @call ~ (e f) 41 | & @String/Cons ~ (67 (d e)) 42 | & @String/Cons ~ (76 (c d)) 43 | & @String/Cons ~ (79 (b c)) 44 | & @String/Cons ~ (83 (a b)) 45 | & @String/Cons ~ (69 (@String/Nil a)) 46 | 47 | @main = w 48 | & @IO/bind ~ (@open ((((s (a u)) (@IO/wrap v)) v) w)) 49 | & @IO/bind ~ (c ((((n (o (d q))) (r (s t))) t) u)) 50 | & @unwrap ~ (a {b r}) 51 | & @read ~ (b c) 52 | & @IO/bind ~ (f ((((g (k (* m))) (n (o p))) p) q)) 53 | & @print ~ (e f) 54 | & @unwrap ~ (d e) 55 | & @IO/bind ~ (h ((((i i) (k l)) l) m)) 56 | & @close ~ (g h) 57 | 58 | @open = o 59 | & @call ~ (d ((m n) o)) 60 | & @String/Cons ~ (79 (c d)) 61 | & @String/Cons ~ (80 (b c)) 62 | & @String/Cons ~ (69 (a b)) 63 | & @String/Cons ~ (78 (@String/Nil a)) 64 | & @String/Cons ~ (46 (l m)) 65 | & @String/Cons ~ (47 (k l)) 66 | & @String/Cons ~ (76 (j k)) 67 | & @String/Cons ~ (73 (i j)) 68 | & @String/Cons ~ (67 (h i)) 69 | & @String/Cons ~ (69 (g h)) 70 | & @String/Cons ~ (78 (f g)) 71 | & @String/Cons ~ (83 (e f)) 72 | & @String/Cons ~ (69 (@String/Nil e)) 73 | & @String/Cons ~ (114 (@String/Nil n)) 74 | 75 | @print = (f h) 76 | & @IO/bind ~ (g (@print__C3 h)) 77 | & @call ~ (e ((1 f) g)) 78 | & @String/Cons ~ (87 (d e)) 79 | & @String/Cons ~ (82 (c d)) 80 | & @String/Cons ~ (73 (b c)) 81 | & @String/Cons ~ (84 (a b)) 82 | & @String/Cons ~ (69 (@String/Nil a)) 83 | 84 | @print__C0 = ((* a) (* a)) 85 | 86 | @print__C1 = g 87 | & @call ~ (e ((1 f) g)) 88 | & @String/Cons ~ (87 (d e)) 89 | & @String/Cons ~ (82 (c d)) 90 | & @String/Cons ~ (73 (b c)) 91 | & @String/Cons ~ (84 (a b)) 92 | & @String/Cons ~ (69 (@String/Nil a)) 93 | & @String/Cons ~ (10 (@String/Nil f)) 94 | 95 | @print__C2 = (a (* c)) 96 | & @IO/bind ~ (@print__C1 (((@print__C0 (a b)) b) c)) 97 | 98 | @print__C3 = ((@print__C2 (@IO/wrap a)) a) 99 | 100 | @read = (e f) 101 | & @call ~ (d ((e 47) f)) 102 | & @String/Cons ~ (82 (c d)) 103 | & @String/Cons ~ (69 (b c)) 104 | & @String/Cons ~ (65 (a b)) 105 | & @String/Cons ~ (68 (@String/Nil a)) 106 | 107 | @test-io = 1 108 | 109 | @undefer = (((a a) b) b) 110 | 111 | @unwrap = ((@unwrap__C0 a) a) 112 | 113 | @unwrap__C0 = (?(((a a) (* (b b))) c) c) 114 | 115 | 116 | -------------------------------------------------------------------------------- /examples/sort_bitonic/main.bend: -------------------------------------------------------------------------------- 1 | def gen(d, x): 2 | switch d: 3 | case 0: 4 | return x 5 | case _: 6 | return (gen(d-1, x * 2 + 1), gen(d-1, x * 2)) 7 | 8 | def sum(d, t): 9 | switch d: 10 | case 0: 11 | return t 12 | case _: 13 | (t.a, t.b) = t 14 | return sum(d-1, t.a) + sum(d-1, t.b) 15 | 16 | def swap(s, a, b): 17 | switch s: 18 | case 0: 19 | return (a,b) 20 | case _: 21 | return (b,a) 22 | 23 | def warp(d, s, a, b): 24 | switch d: 25 | case 0: 26 | return swap(s ^ (a > b), a, b) 27 | case _: 28 | (a.a,a.b) = a 29 | (b.a,b.b) = b 30 | (A.a,A.b) = warp(d-1, s, a.a, b.a) 31 | (B.a,B.b) = warp(d-1, s, a.b, b.b) 32 | return ((A.a,B.a),(A.b,B.b)) 33 | 34 | def flow(d, s, t): 35 | switch d: 36 | case 0: 37 | return t 38 | case _: 39 | (t.a, t.b) = t 40 | return down(d, s, warp(d-1, s, t.a, t.b)) 41 | 42 | def down(d,s,t): 43 | switch d: 44 | case 0: 45 | return t 46 | case _: 47 | (t.a, t.b) = t 48 | return (flow(d-1, s, t.a), flow(d-1, s, t.b)) 49 | 50 | def sort(d, s, t): 51 | switch d: 52 | case 0: 53 | return t 54 | case _: 55 | (t.a, t.b) = t 56 | return flow(d, s, (sort(d-1, 0, t.a), sort(d-1, 1, t.b))) 57 | 58 | def main: 59 | return sum(12, sort(12, 0, gen(12, 0))) 60 | -------------------------------------------------------------------------------- /examples/sort_bitonic/main.hvm: -------------------------------------------------------------------------------- 1 | @down = (?(((a (* a)) @down__C0) (b (c d))) (c (b d))) 2 | 3 | @down__C0 = ({a e} ((c g) ({b f} (d h)))) 4 | &! @flow ~ (a (b (c d))) 5 | &! @flow ~ (e (f (g h))) 6 | 7 | @flow = (?(((a (* a)) @flow__C0) (b (c d))) (c (b d))) 8 | 9 | @flow__C0 = ({$([+1] a) c} ((e f) ({b d} h))) 10 | & @down ~ (a (b (g h))) 11 | & @warp ~ (c (d (e (f g)))) 12 | 13 | @gen = (?(((a a) @gen__C0) b) b) 14 | 15 | @gen__C0 = ({a d} ({$([*2] $([+1] b)) $([*2] e)} (c f))) 16 | &! @gen ~ (a (b c)) 17 | &! @gen ~ (d (e f)) 18 | 19 | @main = a 20 | & @sum ~ (12 (@main__C1 a)) 21 | 22 | @main__C0 = a 23 | & @gen ~ (12 (0 a)) 24 | 25 | @main__C1 = a 26 | & @sort ~ (12 (0 (@main__C0 a))) 27 | 28 | @sort = (?(((a (* a)) @sort__C0) (b (c d))) (c (b d))) 29 | 30 | @sort__C0 = ({$([+1] a) {c f}} ((d g) (b i))) 31 | & @flow ~ (a (b ((e h) i))) 32 | &! @sort ~ (c (0 (d e))) 33 | &! @sort ~ (f (1 (g h))) 34 | 35 | @sum = (?(((a a) @sum__C0) b) b) 36 | 37 | @sum__C0 = ({a c} ((b d) f)) 38 | &! @sum ~ (a (b $([+] $(e f)))) 39 | &! @sum ~ (c (d e)) 40 | 41 | @swap = (?((@swap__C0 @swap__C1) (a (b c))) (b (a c))) 42 | 43 | @swap__C0 = (b (a (a b))) 44 | 45 | @swap__C1 = (* (a (b (a b)))) 46 | 47 | @warp = (?((@warp__C0 @warp__C1) (a (b (c d)))) (c (b (a d)))) 48 | 49 | @warp__C0 = ({a e} ({$([>] $(a b)) d} ($([^] $(b c)) f))) 50 | & @swap ~ (c (d (e f))) 51 | 52 | @warp__C1 = ({a f} ((d i) ((c h) ({b g} ((e j) (k l)))))) 53 | &! @warp ~ (f (g (h (i (j l))))) 54 | &! @warp ~ (a (b (c (d (e k))))) 55 | -------------------------------------------------------------------------------- /examples/sort_radix/main.bend: -------------------------------------------------------------------------------- 1 | 2 | # data Arr = Empty | (Single x) | (Concat x0 x1) 3 | Empty = λempty λsingle λconcat empty 4 | Single = λx λempty λsingle λconcat (single x) 5 | Concat = λx0 λx1 λempty λsingle λconcat (concat x0 x1) 6 | 7 | # data Map = Free | Busy | (Node x0 x1) 8 | Free = λfree λbusy λnode free 9 | Busy = λfree λbusy λnode busy 10 | Node = λx0 λx1 λfree λbusy λnode (node x0 x1) 11 | 12 | # gen : u32 -> Arr 13 | gen = λn switch n { 14 | 0: λx (Single x) 15 | _: λx 16 | let x0 = (* x 2) 17 | let x1 = (+ x0 1) 18 | (Concat (gen n-1 x1) (gen n-1 x0)) 19 | } 20 | 21 | # sum : Arr -> u32 22 | sum = λa 23 | let a_empty = 0 24 | let a_single = λx x 25 | let a_concat = λx0 λx1 (+ (sum x0) (sum x1)) 26 | (a a_empty a_single a_concat) 27 | 28 | # sort : Arr -> Arr 29 | sort = λt (to_arr (to_map t) 0) 30 | 31 | # to_arr : Map -> u32 -> Arr 32 | to_arr = λa 33 | let a_free = λk Empty 34 | let a_busy = λk (Single k) 35 | let a_node = λx0 λx1 λk 36 | let x0 = (to_arr x0 (+ (* k 2) 0)) 37 | let x1 = (to_arr x1 (+ (* k 2) 1)) 38 | (Concat x0 x1) 39 | (a a_free a_busy a_node) 40 | 41 | # to_map : Arr -> Map 42 | to_map = λa 43 | let a_empty = Free 44 | let a_single = λx (radix 24 x 1 Busy) 45 | let a_concat = λx0 λx1 (merge (to_map x0) (to_map x1)) 46 | (a a_empty a_single a_concat) 47 | 48 | # merge : Map -> Map -> Map 49 | merge = λa 50 | let a_free = λb 51 | let b_free = Free 52 | let b_busy = Busy 53 | let b_node = λb0 λb1 (Node b0 b1) 54 | (b b_free b_busy b_node) 55 | let a_busy = λb 56 | let b_free = Busy 57 | let b_busy = Busy 58 | let b_node = λb0 λb1 0 59 | (b b_free b_busy b_node) 60 | let a_node = λa0 λa1 λb 61 | let b_free = λa0 λa1 (Node a0 a1) 62 | let b_busy = λa0 λa1 0 63 | let b_node = λb0 λb1 λa0 λa1 (Node (merge a0 b0) (merge a1 b1)) 64 | (b b_free b_busy b_node a0 a1) 65 | (a a_free a_busy a_node) 66 | 67 | # radix : u32 -> Map 68 | radix = λi λn λk λr switch i { 69 | 0: r 70 | _: (radix i-1 n (* k 2) (swap (& n k) r Free)) 71 | } 72 | 73 | # swap : u32 -> Map -> Map -> Map 74 | swap = λn switch n { 75 | 0: λx0 λx1 (Node x0 x1) 76 | _: λx0 λx1 (Node x1 x0) 77 | } 78 | 79 | # main : u32 80 | main = (sum (sort (gen 16 0))) 81 | -------------------------------------------------------------------------------- /examples/sort_radix/main.hvm: -------------------------------------------------------------------------------- 1 | @Busy = (* (a (* a))) 2 | 3 | @Concat = (a (b (* (* ((a (b c)) c))))) 4 | 5 | @Empty = (a (* (* a))) 6 | 7 | @Free = (a (* (* a))) 8 | 9 | @Node = (a (b (* (* ((a (b c)) c))))) 10 | 11 | @Single = (a (* ((a b) (* b)))) 12 | 13 | @gen = (?((@gen__C0 @gen__C1) a) a) 14 | 15 | @gen__C0 = a 16 | & @Single ~ a 17 | 18 | @gen__C1 = ({a d} ($([*2] {e $([+1] b)}) g)) 19 | & @Concat ~ (c (f g)) 20 | &! @gen ~ (a (b c)) 21 | &! @gen ~ (d (e f)) 22 | 23 | @main = a 24 | & @sum ~ (@main__C1 a) 25 | 26 | @main__C0 = a 27 | & @gen ~ (16 (0 a)) 28 | 29 | @main__C1 = a 30 | & @sort ~ (@main__C0 a) 31 | 32 | @merge = ((@merge__C5 (@merge__C4 (@merge__C3 a))) a) 33 | 34 | @merge__C0 = (b (e (a (d g)))) 35 | & @Node ~ (c (f g)) 36 | &! @merge ~ (a (b c)) 37 | &! @merge ~ (d (e f)) 38 | 39 | @merge__C1 = a 40 | & @Node ~ a 41 | 42 | @merge__C2 = a 43 | & @Node ~ a 44 | 45 | @merge__C3 = (a (b ((@merge__C1 ((* (* 0)) (@merge__C0 (a (b c))))) c))) 46 | 47 | @merge__C4 = ((@Busy (@Busy ((* (* 0)) a))) a) 48 | 49 | @merge__C5 = ((@Free (@Busy (@merge__C2 a))) a) 50 | 51 | @radix = (?(((* (* (a a))) @radix__C0) b) b) 52 | 53 | @radix__C0 = (a ({b $([&] $(d e))} ({$([*2] c) d} (f h)))) 54 | & @radix ~ (a (b (c (g h)))) 55 | & @swap ~ (e (f (@Free g))) 56 | 57 | @sort = (a c) 58 | & @to_arr ~ (b (0 c)) 59 | & @to_map ~ (a b) 60 | 61 | @sum = ((0 ((a a) (@sum__C0 b))) b) 62 | 63 | @sum__C0 = (a (b d)) 64 | &! @sum ~ (a $([+] $(c d))) 65 | &! @sum ~ (b c) 66 | 67 | @swap = (?((@swap__C0 @swap__C1) a) a) 68 | 69 | @swap__C0 = a 70 | & @Node ~ a 71 | 72 | @swap__C1 = (* (b (a c))) 73 | & @Node ~ (a (b c)) 74 | 75 | @to_arr = (((* @Empty) (@to_arr__C1 (@to_arr__C0 a))) a) 76 | 77 | @to_arr__C0 = (a (d ({$([*2] $([+1] e)) $([*2] $([+0] b))} g))) 78 | & @Concat ~ (c (f g)) 79 | &! @to_arr ~ (a (b c)) 80 | &! @to_arr ~ (d (e f)) 81 | 82 | @to_arr__C1 = a 83 | & @Single ~ a 84 | 85 | @to_map = ((@Free (@to_map__C1 (@to_map__C0 a))) a) 86 | 87 | @to_map__C0 = (a (c e)) 88 | & @merge ~ (b (d e)) 89 | &! @to_map ~ (a b) 90 | &! @to_map ~ (c d) 91 | 92 | @to_map__C1 = (a b) 93 | & @radix ~ (24 (a (1 (@Busy b)))) 94 | -------------------------------------------------------------------------------- /examples/stress/README.md: -------------------------------------------------------------------------------- 1 | # stress 2 | 3 | This is the basic stress-test used to test an implementation's maximum IPS. It 4 | recursively creates a tree with a given depth, and then performs a recursive 5 | computation with a given length: 6 | 7 | ``` 8 | def sum(n): 9 | if n == 0: 10 | return 0 11 | else: 12 | return n + sum(n - 1) 13 | 14 | def fun(n): 15 | if n == 0: 16 | return sum(LENGTH) 17 | else: 18 | return fun(n - 1) + fun(n - 1) 19 | 20 | fun(DEPTH) 21 | ``` 22 | 23 | This lets us test both the parallel and sequential performance of a runtime. For 24 | example, by testing a tree of depth 14 and breadth 2^20, for example, we have 25 | enough parallelism to use all the 32k threads of a RTX 4090, and enough 26 | sequential work (1m calls) to keep each thread busy for a long time. 27 | -------------------------------------------------------------------------------- /examples/stress/main.bend: -------------------------------------------------------------------------------- 1 | def loop(n): 2 | switch n: 3 | case 0: 4 | return 0 5 | case _: 6 | return loop(n-1) 7 | 8 | def fun(n): 9 | switch n: 10 | case 0: 11 | return loop(0x10000) 12 | case _: 13 | return fun(n-1) + fun(n-1) 14 | 15 | def main: 16 | return fun(8) 17 | -------------------------------------------------------------------------------- /examples/stress/main.hvm: -------------------------------------------------------------------------------- 1 | @fun = (?((@fun__C0 @fun__C1) a) a) 2 | 3 | @fun__C0 = a 4 | & @loop ~ (65536 a) 5 | 6 | @fun__C1 = ({a b} d) 7 | &! @fun ~ (a $([+] $(c d))) 8 | &! @fun ~ (b c) 9 | 10 | @loop = (?((0 @loop__C0) a) a) 11 | 12 | @loop__C0 = a 13 | & @loop ~ a 14 | 15 | @main = a 16 | & @fun ~ (8 a) 17 | -------------------------------------------------------------------------------- /examples/stress/main.js: -------------------------------------------------------------------------------- 1 | function sum(n) { 2 | if (n === 0) { 3 | return 0; 4 | } else { 5 | return n + sum(n - 1); 6 | } 7 | } 8 | 9 | function fun(n) { 10 | if (n === 0) { 11 | return sum(4096); 12 | } else { 13 | return fun(n - 1) + fun(n - 1); 14 | } 15 | } 16 | 17 | console.log(fun(18)); 18 | -------------------------------------------------------------------------------- /examples/stress/main.py: -------------------------------------------------------------------------------- 1 | def sum(n): 2 | if n == 0: 3 | return 0 4 | else: 5 | return n + sum(n - 1) 6 | 7 | def fun(n): 8 | if n == 0: 9 | return sum(16) 10 | else: 11 | return fun(n - 1) + fun(n - 1) 12 | 13 | print(fun(8)) 14 | 15 | # Demo Micro-Benchmark / Stress-Test 16 | # 17 | # Complexity: 120,264,589,303 Interactions 18 | # 19 | # CPython: 640s on Apple M3 Max (1 thread) * 20 | # HVM-CPU: 268s on Apple M3 Max (1 thread) 21 | # Node.js: 128s on Apple M3 Max (1 thread) * 22 | # HVM-CPU: 14s on Apple M3 Max (12 threads) 23 | # HVM-GPU: 2s on NVIDIA RTX 4090 (32k threads) 24 | # 25 | # * estimated due to stack overflow 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/sum_rec/main.bend: -------------------------------------------------------------------------------- 1 | #flavor core 2 | 3 | sum = λn λx switch n { 4 | 0: x 5 | _: let fst = (sum n-1 (+ (* x 2) 0)) 6 | let snd = (sum n-1 (+ (* x 2) 1)) 7 | (+ fst snd) 8 | } 9 | 10 | main = (sum 20 0) 11 | -------------------------------------------------------------------------------- /examples/sum_rec/main.hvm: -------------------------------------------------------------------------------- 1 | @main = a 2 | & @sum ~ (20 (0 a)) 3 | 4 | @sum = (?(((a a) @sum__C0) b) b) 5 | 6 | @sum__C0 = ({c a} ({$([*2] $([+1] d)) $([*2] $([+0] b))} f)) 7 | &! @sum ~ (a (b $([+] $(e f)))) 8 | &! @sum ~ (c (d e)) 9 | -------------------------------------------------------------------------------- /examples/sum_rec/main.js: -------------------------------------------------------------------------------- 1 | function sum(a, b) { 2 | if (a === b) { 3 | return a; 4 | } else { 5 | let mid = Math.floor((a + b) / 2); 6 | let fst = sum(a, mid + 0); 7 | let snd = sum(mid + 1, b); 8 | return fst + snd; 9 | } 10 | } 11 | 12 | console.log(sum(0, 10000000)); 13 | -------------------------------------------------------------------------------- /examples/sum_rec/sum.js: -------------------------------------------------------------------------------- 1 | var sum = 0; 2 | 3 | for (var i = 0; i < 2**30; ++i) { 4 | sum += i; 5 | } 6 | 7 | console.log(sum); 8 | -------------------------------------------------------------------------------- /examples/sum_tree/main.bend: -------------------------------------------------------------------------------- 1 | gen = λd switch d { 2 | 0: λx x 3 | _: λx ((gen d-1 (+ (* x 2) 1)), (gen d-1 (* x 2))) 4 | } 5 | 6 | sum = λd λt switch d { 7 | 0: 1 8 | _: let (t.a,t.b) = t 9 | (+ (sum d-1 t.a) (sum d-1 t.b)) 10 | } 11 | 12 | main = (sum 20 (gen 20 0)) 13 | -------------------------------------------------------------------------------- /examples/sum_tree/main.hvm: -------------------------------------------------------------------------------- 1 | @gen = (?(((a a) @gen__C0) b) b) 2 | 3 | @gen__C0 = ({a d} ({$([*2] $([+1] b)) $([*2] e)} (c f))) 4 | &! @gen ~ (a (b c)) 5 | &! @gen ~ (d (e f)) 6 | 7 | @main = a 8 | & @sum ~ (20 (@main__C0 a)) 9 | 10 | @main__C0 = a 11 | & @gen ~ (20 (0 a)) 12 | 13 | @sum = (?(((* 1) @sum__C0) a) a) 14 | 15 | @sum__C0 = ({a c} ((b d) f)) 16 | &! @sum ~ (a (b $([+] $(e f)))) 17 | &! @sum ~ (c (d e)) 18 | -------------------------------------------------------------------------------- /examples/tuples/tuples.bend: -------------------------------------------------------------------------------- 1 | type Tup8: 2 | New { a, b, c, d, e, f, g, h } 3 | 4 | rot = λx match x { 5 | Tup8/New: (Tup8/New x.b x.c x.d x.e x.f x.g x.h x.a) 6 | } 7 | 8 | app = λn switch n { 9 | 0: λf λx x 10 | _: λf λx (app n-1 f (f x)) 11 | } 12 | 13 | main = (app 1234 rot (Tup8/New 1 2 3 4 5 6 7 8)) 14 | -------------------------------------------------------------------------------- /examples/tuples/tuples.hvm: -------------------------------------------------------------------------------- 1 | @Tup8/New = (a (b (c (d (e (f (g (h ((0 (a (b (c (d (e (f (g (h i))))))))) i))))))))) 2 | 3 | @app = (?(((* (a a)) @app__C0) b) b) 4 | 5 | @app__C0 = (a ({b (c d)} (c e))) 6 | & @app ~ (a (b (d e))) 7 | 8 | @main = b 9 | & @app ~ (1234 (@rot (a b))) 10 | & @Tup8/New ~ (1 (2 (3 (4 (5 (6 (7 (8 a)))))))) 11 | 12 | @rot = ((@rot__C1 a) a) 13 | 14 | @rot__C0 = (h (a (b (c (d (e (f (g i)))))))) 15 | & @Tup8/New ~ (a (b (c (d (e (f (g (h i)))))))) 16 | 17 | @rot__C1 = (?((@rot__C0 *) a) a) 18 | -------------------------------------------------------------------------------- /paper/HVM2 - Extended Abstract.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HigherOrderCO/HVM/654276018084b8f44a22b562dd68ab18583bfb5b/paper/HVM2 - Extended Abstract.pdf -------------------------------------------------------------------------------- /paper/HVM2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HigherOrderCO/HVM/654276018084b8f44a22b562dd68ab18583bfb5b/paper/HVM2.pdf -------------------------------------------------------------------------------- /paper/README.md: -------------------------------------------------------------------------------- 1 | # HVM - Paper(s) 2 | 3 | - HVM2 (Work in progress) 4 | - HVM2's theoretical foundations, implementation, early benchmarks, current limitations, and future work. 5 | - Extended Abstract 6 | - Accepted to [FProPer 2024][1]. 7 | - A much shorter version of the main paper. 8 | 9 | [1]: https://icfp24.sigplan.org/home/fproper-2024 10 | -------------------------------------------------------------------------------- /paper/inet.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/cetz:0.2.2": draw, canvas 2 | 3 | #let port = (name, pos, dir) => { 4 | import draw: * 5 | group(name: name, { 6 | translate(pos) 7 | rotate(dir) 8 | // scale(1.5) 9 | anchor("p", (0, 0)) 10 | anchor("c", (0, 0.5)) 11 | }) 12 | } 13 | 14 | #let agent = (..agent) => (..args) => { 15 | import draw: * 16 | let style = agent.named().at("style", default: ()) 17 | let name = args.named().at("name") 18 | let pos = args.named().at("pos") 19 | let rot = args.named().at("rot", default: 0deg) 20 | group(name: name, { 21 | translate(pos) 22 | rotate(rot) 23 | translate((0, -calc.sqrt(3)/4)) 24 | stroke(2pt) 25 | line((-.5, 0), (.5, 0), (0, calc.sqrt(3)/2), close: true, ..style, stroke: 0.5pt) 26 | port("0", (0, calc.sqrt(3)/2), 0deg) 27 | port("1", (-1/2+1/3, 0), 180deg) 28 | port("2", (+1/2-1/3, 0), 180deg) 29 | }) 30 | } 31 | 32 | #let link = (a, b) => { 33 | import draw: * 34 | stroke(2pt) 35 | bezier(a + ".p", b + ".p", a + ".c", b + ".c", stroke: 0.5pt) 36 | } 37 | 38 | #let con = agent() 39 | #let dup = agent(style: (fill: black)) 40 | -------------------------------------------------------------------------------- /src/ast.rs: -------------------------------------------------------------------------------- 1 | use TSPL::{new_parser, Parser, ParseError}; 2 | use highlight_error::highlight_error; 3 | use crate::hvm; 4 | use std::fmt::{Debug, Display}; 5 | use std::collections::{BTreeMap, BTreeSet}; 6 | 7 | // Types 8 | // ----- 9 | 10 | #[derive(Clone, Hash, PartialEq, Eq, Debug)] 11 | pub struct Numb(pub u32); 12 | 13 | #[derive(Clone, Hash, PartialEq, Eq, Debug)] 14 | pub enum Tree { 15 | Var { nam: String }, 16 | Ref { nam: String }, 17 | Era, 18 | Num { val: Numb }, 19 | Con { fst: Box, snd: Box }, 20 | Dup { fst: Box, snd: Box }, 21 | Opr { fst: Box, snd: Box }, 22 | Swi { fst: Box, snd: Box }, 23 | } 24 | 25 | pub type Redex = (bool, Tree, Tree); 26 | 27 | #[derive(Clone, Hash, PartialEq, Eq, Debug)] 28 | pub struct Net { 29 | pub root: Tree, 30 | pub rbag: Vec, 31 | } 32 | 33 | pub struct Book { 34 | pub defs: BTreeMap, 35 | } 36 | 37 | // Parser 38 | // ------ 39 | 40 | pub type ParseResult = std::result::Result; 41 | 42 | new_parser!(CoreParser); 43 | 44 | impl<'i> CoreParser<'i> { 45 | 46 | pub fn parse_numb_sym(&mut self) -> ParseResult { 47 | self.consume("[")?; 48 | 49 | // numeric casts 50 | if let Some(cast) = match () { 51 | _ if self.try_consume("u24") => Some(hvm::TY_U24), 52 | _ if self.try_consume("i24") => Some(hvm::TY_I24), 53 | _ if self.try_consume("f24") => Some(hvm::TY_F24), 54 | _ => None 55 | } { 56 | // Casts can't be partially applied, so nothing should follow. 57 | self.consume("]")?; 58 | 59 | return Ok(Numb(hvm::Numb::new_sym(cast).0)); 60 | } 61 | 62 | // Parses the symbol 63 | let op = hvm::Numb::new_sym(match () { 64 | // numeric operations 65 | _ if self.try_consume("+") => hvm::OP_ADD, 66 | _ if self.try_consume("-") => hvm::OP_SUB, 67 | _ if self.try_consume(":-") => hvm::FP_SUB, 68 | _ if self.try_consume("*") => hvm::OP_MUL, 69 | _ if self.try_consume("/") => hvm::OP_DIV, 70 | _ if self.try_consume(":/") => hvm::FP_DIV, 71 | _ if self.try_consume("%") => hvm::OP_REM, 72 | _ if self.try_consume(":%") => hvm::FP_REM, 73 | _ if self.try_consume("=") => hvm::OP_EQ, 74 | _ if self.try_consume("!") => hvm::OP_NEQ, 75 | _ if self.try_consume("<<") => hvm::OP_SHL, 76 | _ if self.try_consume(":<<") => hvm::FP_SHL, 77 | _ if self.try_consume(">>") => hvm::OP_SHR, 78 | _ if self.try_consume(":>>") => hvm::FP_SHR, 79 | _ if self.try_consume("<") => hvm::OP_LT, 80 | _ if self.try_consume(">") => hvm::OP_GT, 81 | _ if self.try_consume("&") => hvm::OP_AND, 82 | _ if self.try_consume("|") => hvm::OP_OR, 83 | _ if self.try_consume("^") => hvm::OP_XOR, 84 | _ => self.expected("operator symbol")?, 85 | }); 86 | 87 | self.skip_trivia(); 88 | // Syntax for partial operations, like `[*2]` 89 | let num = if self.peek_one() != Some(']') { 90 | hvm::Numb::partial(op, hvm::Numb(self.parse_numb_lit()?.0)) 91 | } else { 92 | op 93 | }; 94 | 95 | // Closes symbol bracket 96 | self.consume("]")?; 97 | 98 | // Returns the symbol 99 | return Ok(Numb(num.0)); 100 | } 101 | 102 | pub fn parse_numb_lit(&mut self) -> ParseResult { 103 | let ini = self.index; 104 | let num = self.take_while(|x| x.is_alphanumeric() || x == '+' || x == '-' || x == '.'); 105 | let mut num_parser = CoreParser::new(num); 106 | let end = self.index; 107 | Ok(Numb(if num.contains('.') || num.contains("inf") || num.contains("NaN") { 108 | let val: f32 = num.parse() 109 | .map_err(|err| { 110 | let msg = format!("invalid number literal: {}\n{}", err, highlight_error(ini, end, self.input)); 111 | self.expected_and::("number literal", &msg).unwrap_err() 112 | })?; 113 | hvm::Numb::new_f24(val) 114 | } else if num.starts_with('+') || num.starts_with('-') { 115 | *num_parser.index() += 1; 116 | let val = num_parser.parse_u64()? as i32; 117 | hvm::Numb::new_i24(if num.starts_with('-') { -val } else { val }) 118 | } else { 119 | let val = num_parser.parse_u64()? as u32; 120 | hvm::Numb::new_u24(val) 121 | }.0)) 122 | } 123 | 124 | pub fn parse_numb(&mut self) -> ParseResult { 125 | self.skip_trivia(); 126 | 127 | // Parses symbols (SYM) 128 | if let Some('[') = self.peek_one() { 129 | return self.parse_numb_sym(); 130 | // Parses numbers (U24,I24,F24) 131 | } else { 132 | return self.parse_numb_lit(); 133 | } 134 | } 135 | 136 | pub fn parse_tree(&mut self) -> ParseResult { 137 | self.skip_trivia(); 138 | //println!("aaa ||{}", &self.input[self.index..]); 139 | match self.peek_one() { 140 | Some('(') => { 141 | self.advance_one(); 142 | let fst = Box::new(self.parse_tree()?); 143 | self.skip_trivia(); 144 | let snd = Box::new(self.parse_tree()?); 145 | self.consume(")")?; 146 | Ok(Tree::Con { fst, snd }) 147 | } 148 | Some('{') => { 149 | self.advance_one(); 150 | let fst = Box::new(self.parse_tree()?); 151 | self.skip_trivia(); 152 | let snd = Box::new(self.parse_tree()?); 153 | self.consume("}")?; 154 | Ok(Tree::Dup { fst, snd }) 155 | } 156 | Some('$') => { 157 | self.advance_one(); 158 | self.consume("(")?; 159 | let fst = Box::new(self.parse_tree()?); 160 | self.skip_trivia(); 161 | let snd = Box::new(self.parse_tree()?); 162 | self.consume(")")?; 163 | Ok(Tree::Opr { fst, snd }) 164 | } 165 | Some('?') => { 166 | self.advance_one(); 167 | self.consume("(")?; 168 | let fst = Box::new(self.parse_tree()?); 169 | self.skip_trivia(); 170 | let snd = Box::new(self.parse_tree()?); 171 | self.consume(")")?; 172 | Ok(Tree::Swi { fst, snd }) 173 | } 174 | Some('@') => { 175 | self.advance_one(); 176 | let nam = self.parse_name()?; 177 | Ok(Tree::Ref { nam }) 178 | } 179 | Some('*') => { 180 | self.advance_one(); 181 | Ok(Tree::Era) 182 | } 183 | _ => { 184 | if let Some(c) = self.peek_one() { 185 | if "0123456789+-[".contains(c) { 186 | return Ok(Tree::Num { val: self.parse_numb()? }); 187 | } 188 | } 189 | let nam = self.parse_name()?; 190 | Ok(Tree::Var { nam }) 191 | } 192 | } 193 | } 194 | 195 | pub fn parse_net(&mut self) -> ParseResult { 196 | let root = self.parse_tree()?; 197 | let mut rbag = Vec::new(); 198 | self.skip_trivia(); 199 | while self.peek_one() == Some('&') { 200 | self.consume("&")?; 201 | let par = if let Some('!') = self.peek_one() { self.consume("!")?; true } else { false }; 202 | let fst = self.parse_tree()?; 203 | self.consume("~")?; 204 | let snd = self.parse_tree()?; 205 | rbag.push((par,fst,snd)); 206 | self.skip_trivia(); 207 | } 208 | Ok(Net { root, rbag }) 209 | } 210 | 211 | pub fn parse_book(&mut self) -> ParseResult { 212 | let mut defs = BTreeMap::new(); 213 | while !self.is_eof() { 214 | self.consume("@")?; 215 | let name = self.parse_name()?; 216 | self.consume("=")?; 217 | let net = self.parse_net()?; 218 | defs.insert(name, net); 219 | } 220 | Ok(Book { defs }) 221 | } 222 | 223 | fn try_consume(&mut self, str: &str) -> bool { 224 | let matches = self.peek_many(str.len()) == Some(str); 225 | if matches { 226 | self.advance_many(str.len()); 227 | } 228 | matches 229 | } 230 | } 231 | 232 | // Stringifier 233 | // ----------- 234 | 235 | impl Numb { 236 | pub fn show(&self) -> String { 237 | let numb = hvm::Numb(self.0); 238 | match numb.get_typ() { 239 | hvm::TY_SYM => match numb.get_sym() as hvm::Tag { 240 | // casts 241 | hvm::TY_U24 => "[u24]".to_string(), 242 | hvm::TY_I24 => "[i24]".to_string(), 243 | hvm::TY_F24 => "[f24]".to_string(), 244 | // operations 245 | hvm::OP_ADD => "[+]".to_string(), 246 | hvm::OP_SUB => "[-]".to_string(), 247 | hvm::FP_SUB => "[:-]".to_string(), 248 | hvm::OP_MUL => "[*]".to_string(), 249 | hvm::OP_DIV => "[/]".to_string(), 250 | hvm::FP_DIV => "[:/]".to_string(), 251 | hvm::OP_REM => "[%]".to_string(), 252 | hvm::FP_REM => "[:%]".to_string(), 253 | hvm::OP_EQ => "[=]".to_string(), 254 | hvm::OP_NEQ => "[!]".to_string(), 255 | hvm::OP_LT => "[<]".to_string(), 256 | hvm::OP_GT => "[>]".to_string(), 257 | hvm::OP_AND => "[&]".to_string(), 258 | hvm::OP_OR => "[|]".to_string(), 259 | hvm::OP_XOR => "[^]".to_string(), 260 | hvm::OP_SHL => "[<<]".to_string(), 261 | hvm::FP_SHL => "[:<<]".to_string(), 262 | hvm::OP_SHR => "[>>]".to_string(), 263 | hvm::FP_SHR => "[:>>]".to_string(), 264 | _ => "[?]".to_string(), 265 | } 266 | hvm::TY_U24 => { 267 | let val = numb.get_u24(); 268 | format!("{}", val) 269 | } 270 | hvm::TY_I24 => { 271 | let val = numb.get_i24(); 272 | format!("{:+}", val) 273 | } 274 | hvm::TY_F24 => { 275 | let val = numb.get_f24(); 276 | if val.is_infinite() { 277 | if val.is_sign_positive() { 278 | format!("+inf") 279 | } else { 280 | format!("-inf") 281 | } 282 | } else if val.is_nan() { 283 | format!("+NaN") 284 | } else { 285 | format!("{:?}", val) 286 | } 287 | } 288 | _ => { 289 | let typ = numb.get_typ(); 290 | let val = numb.get_u24(); 291 | format!("[{}0x{:07X}]", match typ { 292 | hvm::OP_ADD => "+", 293 | hvm::OP_SUB => "-", 294 | hvm::FP_SUB => ":-", 295 | hvm::OP_MUL => "*", 296 | hvm::OP_DIV => "/", 297 | hvm::FP_DIV => ":/", 298 | hvm::OP_REM => "%", 299 | hvm::FP_REM => ":%", 300 | hvm::OP_EQ => "=", 301 | hvm::OP_NEQ => "!", 302 | hvm::OP_LT => "<", 303 | hvm::OP_GT => ">", 304 | hvm::OP_AND => "&", 305 | hvm::OP_OR => "|", 306 | hvm::OP_XOR => "^", 307 | hvm::OP_SHL => "<<", 308 | hvm::FP_SHL => ":<<", 309 | hvm::OP_SHR => ">>", 310 | hvm::FP_SHR => ":>>", 311 | _ => "?", 312 | }, val) 313 | } 314 | } 315 | } 316 | } 317 | 318 | impl Tree { 319 | pub fn show(&self) -> String { 320 | match self { 321 | Tree::Var { nam } => nam.to_string(), 322 | Tree::Ref { nam } => format!("@{}", nam), 323 | Tree::Era => "*".to_string(), 324 | Tree::Num { val } => format!("{}", val.show()), 325 | Tree::Con { fst, snd } => format!("({} {})", fst.show(), snd.show()), 326 | Tree::Dup { fst, snd } => format!("{{{} {}}}", fst.show(), snd.show()), 327 | Tree::Opr { fst, snd } => format!("$({} {})", fst.show(), snd.show()), 328 | Tree::Swi { fst, snd } => format!("?({} {})", fst.show(), snd.show()), 329 | } 330 | } 331 | } 332 | 333 | impl Net { 334 | pub fn show(&self) -> String { 335 | let mut s = self.root.show(); 336 | for (par, fst, snd) in &self.rbag { 337 | s.push_str(" &"); 338 | s.push_str(if *par { "!" } else { " " }); 339 | s.push_str(&fst.show()); 340 | s.push_str(" ~ "); 341 | s.push_str(&snd.show()); 342 | } 343 | s 344 | } 345 | } 346 | 347 | impl Book { 348 | pub fn show(&self) -> String { 349 | let mut s = String::new(); 350 | for (name, net) in &self.defs { 351 | s.push_str("@"); 352 | s.push_str(name); 353 | s.push_str(" = "); 354 | s.push_str(&net.show()); 355 | s.push('\n'); 356 | } 357 | s 358 | } 359 | } 360 | 361 | // Readback 362 | // -------- 363 | 364 | impl Tree { 365 | pub fn readback(net: &hvm::GNet, port: hvm::Port, fids: &BTreeMap) -> Option { 366 | //println!("reading {}", port.show()); 367 | match port.get_tag() { 368 | hvm::VAR => { 369 | let got = net.enter(port); 370 | if got != port { 371 | return Tree::readback(net, got, fids); 372 | } else { 373 | return Some(Tree::Var { nam: format!("v{:x}", port.get_val()) }); 374 | } 375 | } 376 | hvm::REF => { 377 | return Some(Tree::Ref { nam: fids.get(&port.get_val())?.clone() }); 378 | } 379 | hvm::ERA => { 380 | return Some(Tree::Era); 381 | } 382 | hvm::NUM => { 383 | return Some(Tree::Num { val: Numb(port.get_val()) }); 384 | } 385 | hvm::CON => { 386 | let pair = net.node_load(port.get_val() as usize); 387 | let fst = Tree::readback(net, pair.get_fst(), fids)?; 388 | let snd = Tree::readback(net, pair.get_snd(), fids)?; 389 | return Some(Tree::Con { fst: Box::new(fst), snd: Box::new(snd) }); 390 | } 391 | hvm::DUP => { 392 | let pair = net.node_load(port.get_val() as usize); 393 | let fst = Tree::readback(net, pair.get_fst(), fids)?; 394 | let snd = Tree::readback(net, pair.get_snd(), fids)?; 395 | return Some(Tree::Dup { fst: Box::new(fst), snd: Box::new(snd) }); 396 | } 397 | hvm::OPR => { 398 | let pair = net.node_load(port.get_val() as usize); 399 | let fst = Tree::readback(net, pair.get_fst(), fids)?; 400 | let snd = Tree::readback(net, pair.get_snd(), fids)?; 401 | return Some(Tree::Opr { fst: Box::new(fst), snd: Box::new(snd) }); 402 | } 403 | hvm::SWI => { 404 | let pair = net.node_load(port.get_val() as usize); 405 | let fst = Tree::readback(net, pair.get_fst(), fids)?; 406 | let snd = Tree::readback(net, pair.get_snd(), fids)?; 407 | return Some(Tree::Swi { fst: Box::new(fst), snd: Box::new(snd) }); 408 | } 409 | _ => { 410 | unreachable!() 411 | } 412 | } 413 | } 414 | } 415 | 416 | impl Net { 417 | pub fn readback(net: &hvm::GNet, book: &hvm::Book) -> Option { 418 | let mut fids = BTreeMap::new(); 419 | for (fid, def) in book.defs.iter().enumerate() { 420 | fids.insert(fid as hvm::Val, def.name.clone()); 421 | } 422 | let root = net.enter(hvm::ROOT); 423 | let root = Tree::readback(net, root, &fids)?; 424 | let rbag = Vec::new(); 425 | return Some(Net { root, rbag }); 426 | } 427 | } 428 | 429 | // Def Builder 430 | // ----------- 431 | 432 | impl Tree { 433 | pub fn build(&self, def: &mut hvm::Def, fids: &BTreeMap, vars: &mut BTreeMap) -> hvm::Port { 434 | match self { 435 | Tree::Var { nam } => { 436 | if !vars.contains_key(nam) { 437 | vars.insert(nam.clone(), vars.len() as hvm::Val); 438 | def.vars += 1; 439 | } 440 | return hvm::Port::new(hvm::VAR, *vars.get(nam).unwrap()); 441 | } 442 | Tree::Ref { nam } => { 443 | if let Some(fid) = fids.get(nam) { 444 | return hvm::Port::new(hvm::REF, *fid); 445 | } else { 446 | panic!("Unbound definition: {}", nam); 447 | } 448 | } 449 | Tree::Era => { 450 | return hvm::Port::new(hvm::ERA, 0); 451 | } 452 | Tree::Num { val } => { 453 | return hvm::Port::new(hvm::NUM, val.0); 454 | } 455 | Tree::Con { fst, snd } => { 456 | let index = def.node.len(); 457 | def.node.push(hvm::Pair(0)); 458 | let p1 = fst.build(def, fids, vars); 459 | let p2 = snd.build(def, fids, vars); 460 | def.node[index] = hvm::Pair::new(p1, p2); 461 | return hvm::Port::new(hvm::CON, index as hvm::Val); 462 | } 463 | Tree::Dup { fst, snd } => { 464 | def.safe = false; 465 | let index = def.node.len(); 466 | def.node.push(hvm::Pair(0)); 467 | let p1 = fst.build(def, fids, vars); 468 | let p2 = snd.build(def, fids, vars); 469 | def.node[index] = hvm::Pair::new(p1, p2); 470 | return hvm::Port::new(hvm::DUP, index as hvm::Val); 471 | }, 472 | Tree::Opr { fst, snd } => { 473 | let index = def.node.len(); 474 | def.node.push(hvm::Pair(0)); 475 | let p1 = fst.build(def, fids, vars); 476 | let p2 = snd.build(def, fids, vars); 477 | def.node[index] = hvm::Pair::new(p1, p2); 478 | return hvm::Port::new(hvm::OPR, index as hvm::Val); 479 | }, 480 | Tree::Swi { fst, snd } => { 481 | let index = def.node.len(); 482 | def.node.push(hvm::Pair(0)); 483 | let p1 = fst.build(def, fids, vars); 484 | let p2 = snd.build(def, fids, vars); 485 | def.node[index] = hvm::Pair::new(p1, p2); 486 | return hvm::Port::new(hvm::SWI, index as hvm::Val); 487 | }, 488 | } 489 | } 490 | 491 | pub fn direct_dependencies<'name>(&'name self) -> BTreeSet<&'name str> { 492 | let mut stack: Vec<&Tree> = vec![self]; 493 | let mut acc: BTreeSet<&'name str> = BTreeSet::new(); 494 | 495 | while let Some(curr) = stack.pop() { 496 | match curr { 497 | Tree::Ref { nam } => { acc.insert(nam); }, 498 | Tree::Con { fst, snd } => { stack.push(fst); stack.push(snd); }, 499 | Tree::Dup { fst, snd } => { stack.push(fst); stack.push(snd); }, 500 | Tree::Opr { fst, snd } => { stack.push(fst); stack.push(snd); }, 501 | Tree::Swi { fst, snd } => { stack.push(fst); stack.push(snd); }, 502 | Tree::Num { val } => {}, 503 | Tree::Var { nam } => {}, 504 | Tree::Era => {}, 505 | }; 506 | } 507 | acc 508 | } 509 | } 510 | 511 | impl Net { 512 | pub fn build(&self, def: &mut hvm::Def, fids: &BTreeMap, vars: &mut BTreeMap) { 513 | let index = def.node.len(); 514 | def.root = self.root.build(def, fids, vars); 515 | for (par, fst, snd) in &self.rbag { 516 | let index = def.rbag.len(); 517 | def.rbag.push(hvm::Pair(0)); 518 | let p1 = fst.build(def, fids, vars); 519 | let p2 = snd.build(def, fids, vars); 520 | let rx = hvm::Pair::new(p1, p2); 521 | let rx = if *par { rx.set_par_flag() } else { rx }; 522 | def.rbag[index] = rx; 523 | } 524 | } 525 | } 526 | 527 | impl Book { 528 | pub fn parse(code: &str) -> ParseResult { 529 | CoreParser::new(code).parse_book() 530 | } 531 | 532 | pub fn build(&self) -> hvm::Book { 533 | let mut name_to_fid = BTreeMap::new(); 534 | let mut fid_to_name = BTreeMap::new(); 535 | fid_to_name.insert(0, "main".to_string()); 536 | name_to_fid.insert("main".to_string(), 0); 537 | for (_i, (name, _)) in self.defs.iter().enumerate() { 538 | if name != "main" { 539 | fid_to_name.insert(name_to_fid.len() as hvm::Val, name.clone()); 540 | name_to_fid.insert(name.clone(), name_to_fid.len() as hvm::Val); 541 | } 542 | } 543 | let mut book = hvm::Book { defs: Vec::new() }; 544 | for (fid, name) in &fid_to_name { 545 | let ast_def = self.defs.get(name).expect("missing `@main` definition"); 546 | let mut def = hvm::Def { 547 | name: name.clone(), 548 | safe: true, 549 | root: hvm::Port(0), 550 | rbag: vec![], 551 | node: vec![], 552 | vars: 0, 553 | }; 554 | ast_def.build(&mut def, &name_to_fid, &mut BTreeMap::new()); 555 | book.defs.push(def); 556 | } 557 | self.propagate_safety(&mut book, &name_to_fid); 558 | return book; 559 | } 560 | 561 | /// Propagate unsafe definitions to those that reference them. 562 | /// 563 | /// When calling this function, it is expected that definitions that are directly 564 | /// unsafe are already marked as such in the `compiled_book`. 565 | /// 566 | /// This does not completely solve the cloning safety in HVM. It only stops invalid 567 | /// **global** definitions from being cloned, but local unsafe code can still be 568 | /// cloned and can generate seemingly unexpected results, such as placing eraser 569 | /// nodes in weird places. See HVM issue [#362](https://github.com/HigherOrderCO/HVM/issues/362) 570 | /// for an example. 571 | fn propagate_safety(&self, compiled_book: &mut hvm::Book, lookup: &BTreeMap) { 572 | let dependents = self.direct_dependents(); 573 | let mut stack: Vec<&str> = Vec::new(); 574 | 575 | for (name, _) in self.defs.iter() { 576 | let def = &mut compiled_book.defs[lookup[name] as usize]; 577 | if !def.safe { 578 | for next in dependents[name.as_str()].iter() { 579 | stack.push(next); 580 | } 581 | } 582 | } 583 | 584 | while let Some(curr) = stack.pop() { 585 | let def = &mut compiled_book.defs[lookup[curr] as usize]; 586 | if !def.safe { 587 | // Already visited, skip this 588 | continue; 589 | } 590 | 591 | def.safe = false; 592 | 593 | for &next in dependents[curr].iter() { 594 | stack.push(next); 595 | } 596 | } 597 | } 598 | 599 | /// Calculates the dependents of each definition, that is, if definition `A` 600 | /// requires `B`, `B: A` is in the return map. This is used to propagate unsafe 601 | /// definitions to others that depend on them. 602 | /// 603 | /// This solution has linear complexity on the number of definitions in the 604 | /// book and the number of direct references in each definition, but it also 605 | /// traverses each definition's trees entirely once. 606 | /// 607 | /// Complexity: O(d*t + r) 608 | /// - `d` is the number of definitions in the book 609 | /// - `r` is the number of direct references in each definition 610 | /// - `t` is the number of nodes in each tree 611 | fn direct_dependents<'name>(&'name self) -> BTreeMap<&'name str, BTreeSet<&'name str>> { 612 | let mut result = BTreeMap::new(); 613 | for (name, _) in self.defs.iter() { 614 | result.insert(name.as_str(), BTreeSet::new()); 615 | } 616 | 617 | let mut process = |tree: &'name Tree, name: &'name str| { 618 | for dependency in tree.direct_dependencies() { 619 | result 620 | .get_mut(dependency) 621 | .expect("global definition depends on undeclared reference") 622 | .insert(name); 623 | } 624 | }; 625 | 626 | for (name, net) in self.defs.iter() { 627 | process(&net.root, name); 628 | for (_, r1, r2) in net.rbag.iter() { 629 | process(r1, name); 630 | process(r2, name); 631 | } 632 | } 633 | result 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /src/cmp.rs: -------------------------------------------------------------------------------- 1 | use crate::ast; 2 | use crate::hvm; 3 | 4 | use std::collections::HashMap; 5 | 6 | #[derive(Clone, Copy, PartialEq, Eq)] 7 | pub enum Target { CUDA, C } 8 | 9 | // Compiles a whole Book. 10 | pub fn compile_book(trg: Target, book: &hvm::Book) -> String { 11 | let mut code = String::new(); 12 | 13 | // Compiles functions 14 | for fid in 0..book.defs.len() { 15 | compile_def(trg, &mut code, book, 0, fid as hvm::Val); 16 | code.push_str(&format!("\n")); 17 | } 18 | 19 | // Compiles interact_call 20 | if trg == Target::CUDA { 21 | code.push_str(&format!("__device__ ")); 22 | } 23 | code.push_str(&format!("bool interact_call(Net *net, TM *tm, Port a, Port b) {{\n")); 24 | code.push_str(&format!(" u32 fid = get_val(a) & 0xFFFFFFF;\n")); 25 | code.push_str(&format!(" switch (fid) {{\n")); 26 | for (fid, def) in book.defs.iter().enumerate() { 27 | code.push_str(&format!(" case {}: return interact_call_{}(net, tm, a, b);\n", fid, &def.name.replace("/","_").replace(".","_").replace("-","_"))); 28 | } 29 | code.push_str(&format!(" default: return false;\n")); 30 | code.push_str(&format!(" }}\n")); 31 | code.push_str(&format!("}}")); 32 | 33 | return code; 34 | } 35 | 36 | // Compiles a single Def. 37 | pub fn compile_def(trg: Target, code: &mut String, book: &hvm::Book, tab: usize, fid: hvm::Val) { 38 | let def = &book.defs[fid as usize]; 39 | let fun = &def.name.replace("/","_").replace(".","_").replace("-","_"); 40 | 41 | // Initializes context 42 | let neo = &mut 0; 43 | 44 | // Generates function 45 | if trg == Target::CUDA { 46 | code.push_str(&format!("__device__ ")); 47 | } 48 | code.push_str(&format!("{}bool interact_call_{}(Net *net, TM *tm, Port a, Port b) {{\n", indent(tab), fun)); 49 | // Fast DUP-REF 50 | if def.safe { 51 | code.push_str(&format!("{}if (get_tag(b) == DUP) {{\n", indent(tab+1))); 52 | code.push_str(&format!("{}return interact_eras(net, tm, a, b);\n", indent(tab+2))); 53 | code.push_str(&format!("{}}}\n", indent(tab+1))); 54 | } 55 | code.push_str(&format!("{}u32 vl = 0;\n", indent(tab+1))); 56 | code.push_str(&format!("{}u32 nl = 0;\n", indent(tab+1))); 57 | 58 | // Allocs resources (using fast allocator) 59 | for i in 0 .. def.vars { 60 | code.push_str(&format!("{}Val v{:x} = vars_alloc_1(net, tm, &vl);\n", indent(tab+1), i)); 61 | } 62 | for i in 0 .. def.node.len() { 63 | code.push_str(&format!("{}Val n{:x} = node_alloc_1(net, tm, &nl);\n", indent(tab+1), i)); 64 | } 65 | code.push_str(&format!("{}if (0", indent(tab+1))); 66 | for i in 0 .. def.vars { 67 | code.push_str(&format!(" || !v{:x}", i)); 68 | } 69 | for i in 0 .. def.node.len() { 70 | code.push_str(&format!(" || !n{:x}", i)); 71 | } 72 | code.push_str(&format!(") {{\n")); 73 | code.push_str(&format!("{}return false;\n", indent(tab+2))); 74 | code.push_str(&format!("{}}}\n", indent(tab+1))); 75 | for i in 0 .. def.vars { 76 | code.push_str(&format!("{}vars_create(net, v{:x}, NONE);\n", indent(tab+1), i)); 77 | } 78 | 79 | // Allocs resources (using slow allocator) 80 | //code.push_str(&format!("{}// Allocates needed resources.\n", indent(tab+1))); 81 | //code.push_str(&format!("{}if (!get_resources(net, tm, {}, {}, {})) {{\n", indent(tab+1), def.rbag.len()+1, def.node.len(), def.vars)); 82 | //code.push_str(&format!("{}return false;\n", indent(tab+2))); 83 | //code.push_str(&format!("{}}}\n", indent(tab+1))); 84 | //for i in 0 .. def.node.len() { 85 | //code.push_str(&format!("{}Val n{:x} = tm->nloc[0x{:x}];\n", indent(tab+1), i, i)); 86 | //} 87 | //for i in 0 .. def.vars { 88 | //code.push_str(&format!("{}Val v{:x} = tm->vloc[0x{:x}];\n", indent(tab+1), i, i)); 89 | //} 90 | //for i in 0 .. def.vars { 91 | //code.push_str(&format!("{}vars_create(net, v{:x}, NONE);\n", indent(tab+1), i)); 92 | //} 93 | 94 | // Compiles root 95 | compile_link_fast(trg, code, book, neo, tab+1, def, def.root, "b"); 96 | 97 | // Compiles rbag 98 | for redex in &def.rbag { 99 | let fun = compile_node(trg, code, book, neo, tab+1, def, redex.get_fst()); 100 | let arg = compile_node(trg, code, book, neo, tab+1, def, redex.get_snd()); 101 | code.push_str(&format!("{}link(net, tm, {}, {});\n", indent(tab+1), &fun, &arg)); 102 | } 103 | 104 | // Return 105 | code.push_str(&format!("{}return true;\n", indent(tab+1))); 106 | code.push_str(&format!("{}}}\n", indent(tab))); 107 | } 108 | 109 | // Compiles a link, performing some pre-defined static reductions. 110 | pub fn compile_link_fast(trg: Target, code: &mut String, book: &hvm::Book, neo: &mut usize, tab: usize, def: &hvm::Def, a: hvm::Port, b: &str) { 111 | 112 | // ( a2) <~ (#X R) 113 | // --------------------------------- fast SWITCH 114 | // if X == 0: 115 | // a111 <~ R 116 | // a112 <~ ERAS 117 | // else: 118 | // a111 <~ ERAS 119 | // a112 <~ (#(X-1) R) 120 | if trg != Target::CUDA && a.get_tag() == hvm::CON { 121 | let a_ = &def.node[a.get_val() as usize]; 122 | let a1 = a_.get_fst(); 123 | let a2 = a_.get_snd(); 124 | if a1.get_tag() == hvm::SWI { 125 | let a1_ = &def.node[a1.get_val() as usize]; 126 | let a11 = a1_.get_fst(); 127 | let a12 = a1_.get_snd(); 128 | if a11.get_tag() == hvm::CON && a2.get_tag() == hvm::VAR && a12.0 == a2.0 { 129 | let a11_ = &def.node[a11.get_val() as usize]; 130 | let a111 = a11_.get_fst(); 131 | let a112 = a11_.get_snd(); 132 | let op = fresh(neo); 133 | let bv = fresh(neo); 134 | let x1 = fresh(neo); 135 | let x2 = fresh(neo); 136 | let nu = fresh(neo); 137 | code.push_str(&format!("{}bool {} = 0;\n", indent(tab), &op)); 138 | code.push_str(&format!("{}Pair {} = 0;\n", indent(tab), &bv)); 139 | code.push_str(&format!("{}Port {} = NONE;\n", indent(tab), &nu)); 140 | code.push_str(&format!("{}Port {} = NONE;\n", indent(tab), &x1)); 141 | code.push_str(&format!("{}Port {} = NONE;\n", indent(tab), &x2)); 142 | code.push_str(&format!("{}//fast switch\n", indent(tab))); 143 | code.push_str(&format!("{}if (get_tag({}) == CON) {{\n", indent(tab), b)); 144 | code.push_str(&format!("{}{} = node_load(net, get_val({}));\n", indent(tab+1), &bv, b)); // recycled 145 | code.push_str(&format!("{}{} = enter(net,get_fst({}));\n", indent(tab+1), &nu, &bv)); 146 | code.push_str(&format!("{}if (get_tag({}) == NUM) {{\n", indent(tab+1), &nu)); 147 | code.push_str(&format!("{}tm->itrs += 3;\n", indent(tab+2))); 148 | code.push_str(&format!("{}vars_take(net, v{});\n", indent(tab+2), a2.get_val())); 149 | code.push_str(&format!("{}{} = 1;\n", indent(tab+2), &op)); 150 | code.push_str(&format!("{}if (get_u24(get_val({})) == 0) {{\n", indent(tab+2), &nu)); 151 | code.push_str(&format!("{}node_take(net, get_val({}));\n", indent(tab+3), b)); 152 | code.push_str(&format!("{}{} = get_snd({});\n", indent(tab+3), &x1, &bv)); 153 | code.push_str(&format!("{}{} = new_port(ERA,0);\n", indent(tab+3), &x2)); 154 | code.push_str(&format!("{}}} else {{\n", indent(tab+2))); 155 | code.push_str(&format!("{}node_store(net, get_val({}), new_pair(new_port(NUM,new_u24(get_u24(get_val({}))-1)), get_snd({})));\n", indent(tab+3), b, &nu, &bv)); 156 | code.push_str(&format!("{}{} = new_port(ERA,0);\n", indent(tab+3), &x1)); 157 | code.push_str(&format!("{}{} = {};\n", indent(tab+3), &x2, b)); 158 | code.push_str(&format!("{}}}\n", indent(tab+2))); 159 | code.push_str(&format!("{}}} else {{\n", indent(tab+1))); 160 | code.push_str(&format!("{}node_store(net, get_val({}), new_pair({},get_snd({})));\n", indent(tab+2), b, &nu, &bv)); // update "entered" var 161 | code.push_str(&format!("{}}}\n", indent(tab+1))); 162 | code.push_str(&format!("{}}}\n", indent(tab+0))); 163 | compile_link_fast(trg, code, book, neo, tab, def, a111, &x1); 164 | compile_link_fast(trg, code, book, neo, tab, def, a112, &x2); 165 | code.push_str(&format!("{}if (!{}) {{\n", indent(tab), &op)); 166 | code.push_str(&format!("{}node_create(net, n{:x}, new_pair(new_port(SWI,n{}),new_port(VAR,v{})));\n", indent(tab+1), a.get_val(), a1.get_val(), a2.get_val())); 167 | code.push_str(&format!("{}node_create(net, n{:x}, new_pair(new_port(CON,n{}),new_port(VAR,v{})));\n", indent(tab+1), a1.get_val(), a11.get_val(), a12.get_val())); 168 | code.push_str(&format!("{}node_create(net, n{:x}, new_pair({},{}));\n", indent(tab+1), a11.get_val(), &x1, &x2)); 169 | link_or_store(trg, code, book, neo, tab+1, def, &format!("new_port(CON, n{:x})", a.get_val()), b); 170 | code.push_str(&format!("{}}}\n", indent(tab))); 171 | return; 172 | } 173 | } 174 | } 175 | 176 | // FIXME: REVIEW 177 | // <+ #B r> <~ #A 178 | // --------------- fast OPER 179 | // r <~ #(op(A,B)) 180 | if trg != Target::CUDA && a.get_tag() == hvm::OPR { 181 | let a_ = &def.node[a.get_val() as usize]; 182 | let a1 = a_.get_fst(); 183 | let a2 = a_.get_snd(); 184 | let op = fresh(neo); 185 | let x1 = compile_node(trg, code, book, neo, tab, def, a1); 186 | let x2 = fresh(neo); 187 | code.push_str(&format!("{}bool {} = 0;\n", indent(tab), &op)); 188 | code.push_str(&format!("{}Port {} = NONE;\n", indent(tab), &x2)); 189 | code.push_str(&format!("{}// fast oper\n", indent(tab))); 190 | code.push_str(&format!("{}if (get_tag({}) == NUM && get_tag({}) == NUM) {{\n", indent(tab), b, &x1)); 191 | code.push_str(&format!("{}tm->itrs += 1;\n", indent(tab+1))); 192 | code.push_str(&format!("{}{} = 1;\n", indent(tab+1), &op)); 193 | code.push_str(&format!("{}{} = new_port(NUM, operate(get_val({}), get_val({})));\n", indent(tab+1), &x2, b, &x1)); 194 | code.push_str(&format!("{}}}\n", indent(tab))); 195 | compile_link_fast(trg, code, book, neo, tab, def, a2, &x2); 196 | code.push_str(&format!("{}if (!{}) {{\n", indent(tab), &op)); 197 | code.push_str(&format!("{}node_create(net, n{:x}, new_pair({},{}));\n", indent(tab+1), a.get_val(), &x1, &x2)); 198 | link_or_store(trg, code, book, neo, tab+1, def, &format!("new_port(OPR, n{:x})", a.get_val()), b); 199 | code.push_str(&format!("{}}}\n", indent(tab))); 200 | return; 201 | } 202 | 203 | // FIXME: REVIEW 204 | // {a1 a2} <~ #v 205 | // ------------- Fast COPY 206 | // a1 <~ #v 207 | // a2 <~ #v 208 | if trg != Target::CUDA && a.get_tag() == hvm::DUP { 209 | let a_ = &def.node[a.get_val() as usize]; 210 | let p1 = a_.get_fst(); 211 | let p2 = a_.get_snd(); 212 | let op = fresh(neo); 213 | let x1 = fresh(neo); 214 | let x2 = fresh(neo); 215 | code.push_str(&format!("{}bool {} = 0;\n", indent(tab), &op)); 216 | code.push_str(&format!("{}Port {} = NONE;\n", indent(tab), &x1)); 217 | code.push_str(&format!("{}Port {} = NONE;\n", indent(tab), &x2)); 218 | code.push_str(&format!("{}// fast copy\n", indent(tab))); 219 | code.push_str(&format!("{}if (get_tag({}) == NUM) {{\n", indent(tab), b)); 220 | code.push_str(&format!("{}tm->itrs += 1;\n", indent(tab+1))); 221 | code.push_str(&format!("{}{} = 1;\n", indent(tab+1), &op)); 222 | code.push_str(&format!("{}{} = {};\n", indent(tab+1), &x1, b)); 223 | code.push_str(&format!("{}{} = {};\n", indent(tab+1), &x2, b)); 224 | code.push_str(&format!("{}}}\n", indent(tab))); 225 | compile_link_fast(trg, code, book, neo, tab, def, p2, &x2); 226 | compile_link_fast(trg, code, book, neo, tab, def, p1, &x1); 227 | code.push_str(&format!("{}if (!{}) {{\n", indent(tab), &op)); 228 | code.push_str(&format!("{}node_create(net, n{:x}, new_pair({},{}));\n", indent(tab+1), a.get_val(), x1, x2)); 229 | link_or_store(trg, code, book, neo, tab+1, def, &format!("new_port(DUP,n{:x})", a.get_val()), b); 230 | code.push_str(&format!("{}}}\n", indent(tab))); 231 | return; 232 | } 233 | 234 | // (a1 a2) <~ (x1 x2) 235 | // ------------------ Fast ANNI 236 | // a1 <~ x1 237 | // a2 <~ x2 238 | if trg != Target::CUDA && a.get_tag() == hvm::CON { 239 | let a_ = &def.node[a.get_val() as usize]; 240 | let a1 = a_.get_fst(); 241 | let a2 = a_.get_snd(); 242 | let op = fresh(neo); 243 | let bv = fresh(neo); 244 | let x1 = fresh(neo); 245 | let x2 = fresh(neo); 246 | code.push_str(&format!("{}bool {} = 0;\n", indent(tab), &op)); 247 | code.push_str(&format!("{}Pair {} = 0;\n", indent(tab), &bv)); 248 | code.push_str(&format!("{}Port {} = NONE;\n", indent(tab), &x1)); 249 | code.push_str(&format!("{}Port {} = NONE;\n", indent(tab), &x2)); 250 | code.push_str(&format!("{}// fast anni\n", indent(tab))); 251 | code.push_str(&format!("{}if (get_tag({}) == CON && node_load(net, get_val({})) != 0) {{\n", indent(tab), b, b)); 252 | //code.push_str(&format!("{}atomic_fetch_add(&FAST, 1);\n", indent(tab+1))); 253 | code.push_str(&format!("{}tm->itrs += 1;\n", indent(tab+1))); 254 | code.push_str(&format!("{}{} = 1;\n", indent(tab+1), &op)); 255 | code.push_str(&format!("{}{} = node_take(net, get_val({}));\n", indent(tab+1), &bv, b)); 256 | code.push_str(&format!("{}{} = get_fst({});\n", indent(tab+1), x1, &bv)); 257 | code.push_str(&format!("{}{} = get_snd({});\n", indent(tab+1), x2, &bv)); 258 | code.push_str(&format!("{}}}\n", indent(tab))); 259 | //code.push_str(&format!("{}else {{ atomic_fetch_add(&SLOW, 1); }}\n", indent(tab))); 260 | compile_link_fast(trg, code, book, neo, tab, def, a2, &x2); 261 | compile_link_fast(trg, code, book, neo, tab, def, a1, &x1); 262 | code.push_str(&format!("{}if (!{}) {{\n", indent(tab), &op)); 263 | code.push_str(&format!("{}node_create(net, n{:x}, new_pair({},{}));\n", indent(tab+1), a.get_val(), x1, x2)); 264 | link_or_store(trg, code, book, neo, tab+1, def, &format!("new_port(CON,n{:x})", a.get_val()), b); 265 | code.push_str(&format!("{}}}\n", indent(tab))); 266 | return; 267 | } 268 | 269 | // FIXME: since get_tag(NONE) == REF, comparing if something's tag is REF always has the issue of 270 | // returning true when that thing is NONE. this caused a bug in the optimization below. in 271 | // general, this is a potential source of bugs across the entire implementation, so we always 272 | // need to check that case. an alternative, of course, would be to make get_tag handle this, but 273 | // I'm concerned about the performance issues. so, instead, we should make sure that, across the 274 | // entire codebase, we never use get_tag expecting a REF on something that might be NONE 275 | 276 | // ATOM <~ * 277 | // --------- Fast VOID 278 | // nothing 279 | if trg != Target::CUDA && (a.get_tag() == hvm::NUM || a.get_tag() == hvm::ERA) { 280 | code.push_str(&format!("{}// fast void\n", indent(tab))); 281 | code.push_str(&format!("{}if (get_tag({}) == ERA || get_tag({}) == NUM) {{\n", indent(tab), b, b)); 282 | code.push_str(&format!("{}tm->itrs += 1;\n", indent(tab+1))); 283 | code.push_str(&format!("{}}} else {{\n", indent(tab))); 284 | compile_link_slow(trg, code, book, neo, tab+1, def, a, b); 285 | code.push_str(&format!("{}}}\n", indent(tab))); 286 | return; 287 | } 288 | 289 | compile_link_slow(trg, code, book, neo, tab, def, a, b); 290 | } 291 | 292 | // Compiles a link, without pre-defined reductions. 293 | pub fn compile_link_slow(trg: Target, code: &mut String, book: &hvm::Book, neo: &mut usize, tab: usize, def: &hvm::Def, a: hvm::Port, b: &str) { 294 | let a_node = compile_node(trg, code, book, neo, tab, def, a); 295 | link_or_store(trg, code, book, neo, tab, def, &a_node, b); 296 | } 297 | 298 | // TODO: comment 299 | pub fn link_or_store(trg: Target, code: &mut String, book: &hvm::Book, neo: &mut usize, tab: usize, def: &hvm::Def, a: &str, b: &str) { 300 | code.push_str(&format!("{}if ({} != NONE) {{\n", indent(tab), b)); 301 | code.push_str(&format!("{}link(net, tm, {}, {});\n", indent(tab+1), a, b)); 302 | code.push_str(&format!("{}}} else {{\n", indent(tab))); 303 | code.push_str(&format!("{}{} = {};\n", indent(tab+1), b, a)); 304 | code.push_str(&format!("{}}}\n", indent(tab))); 305 | } 306 | 307 | // Compiles just a node. 308 | pub fn compile_node(trg: Target, code: &mut String, book: &hvm::Book, neo: &mut usize, tab: usize, def: &hvm::Def, a: hvm::Port) -> String { 309 | if a.is_nod() { 310 | let nd = &def.node[a.get_val() as usize]; 311 | let p1 = compile_node(trg, code, book, neo, tab, def, nd.get_fst()); 312 | let p2 = compile_node(trg, code, book, neo, tab, def, nd.get_snd()); 313 | code.push_str(&format!("{}node_create(net, n{:x}, new_pair({},{}));\n", indent(tab), a.get_val(), p1, p2)); 314 | return format!("new_port({},n{:x})", compile_tag(trg, a.get_tag()), a.get_val()); 315 | } else if a.is_var() { 316 | return format!("new_port(VAR,v{:x})", a.get_val()); 317 | } else { 318 | return format!("new_port({},0x{:08x})", compile_tag(trg, a.get_tag()), a.get_val()); 319 | } 320 | } 321 | 322 | // Compiles an atomic port. 323 | //fn compile_atom(trg: Target, port: hvm::Port) -> String { 324 | //return format!("new_port({},0x{:08x})/*atom*/", compile_tag(trg, port.get_tag()), port.get_val()); 325 | //} 326 | 327 | // Compiles a tag. 328 | pub fn compile_tag(trg: Target, tag: hvm::Tag) -> &'static str { 329 | match tag { 330 | hvm::VAR => "VAR", 331 | hvm::REF => "REF", 332 | hvm::ERA => "ERA", 333 | hvm::NUM => "NUM", 334 | hvm::OPR => "OPR", 335 | hvm::SWI => "SWI", 336 | hvm::CON => "CON", 337 | hvm::DUP => "DUP", 338 | _ => unreachable!(), 339 | } 340 | } 341 | 342 | // Creates indentation. 343 | pub fn indent(tab: usize) -> String { 344 | return " ".repeat(tab); 345 | } 346 | 347 | // Generates a fresh name. 348 | fn fresh(count: &mut usize) -> String { 349 | *count += 1; 350 | format!("k{}", count) 351 | } 352 | -------------------------------------------------------------------------------- /src/hvm.cuh: -------------------------------------------------------------------------------- 1 | #ifndef hvm_cuh_INCLUDED 2 | #define hvm_cuh_INCLUDED 3 | 4 | #include 5 | #include 6 | 7 | // Types 8 | // ----- 9 | 10 | typedef uint8_t u8; 11 | typedef uint16_t u16; 12 | typedef uint32_t u32; 13 | typedef unsigned long long int u64; 14 | typedef int32_t i32; 15 | typedef float f32; 16 | typedef double f64; 17 | 18 | // Local Types 19 | typedef u8 Tag; // Tag ::= 3-bit (rounded up to u8) 20 | typedef u32 Val; // Val ::= 29-bit (rounded up to u32) 21 | typedef u32 Port; // Port ::= Tag + Val (fits a u32) 22 | typedef u64 Pair; // Pair ::= Port + Port (fits a u64) 23 | 24 | // Numbs 25 | typedef u32 Numb; // Numb ::= 29-bit (rounded up to u32) 26 | 27 | // Tags 28 | const Tag VAR = 0x0; // variable 29 | const Tag REF = 0x1; // reference 30 | const Tag ERA = 0x2; // eraser 31 | const Tag NUM = 0x3; // number 32 | const Tag CON = 0x4; // constructor 33 | const Tag DUP = 0x5; // duplicator 34 | const Tag OPR = 0x6; // operator 35 | const Tag SWI = 0x7; // switch 36 | 37 | // Numbers 38 | static const f32 U24_MAX = (f32) (1 << 24) - 1; 39 | static const f32 U24_MIN = 0.0; 40 | static const f32 I24_MAX = (f32) (1 << 23) - 1; 41 | static const f32 I24_MIN = (f32) (i32) ((-1u) << 23); 42 | const Tag TY_SYM = 0x00; 43 | const Tag TY_U24 = 0x01; 44 | const Tag TY_I24 = 0x02; 45 | const Tag TY_F24 = 0x03; 46 | const Tag OP_ADD = 0x04; 47 | const Tag OP_SUB = 0x05; 48 | const Tag FP_SUB = 0x06; 49 | const Tag OP_MUL = 0x07; 50 | const Tag OP_DIV = 0x08; 51 | const Tag FP_DIV = 0x09; 52 | const Tag OP_REM = 0x0A; 53 | const Tag FP_REM = 0x0B; 54 | const Tag OP_EQ = 0x0C; 55 | const Tag OP_NEQ = 0x0D; 56 | const Tag OP_LT = 0x0E; 57 | const Tag OP_GT = 0x0F; 58 | const Tag OP_AND = 0x10; 59 | const Tag OP_OR = 0x11; 60 | const Tag OP_XOR = 0x12; 61 | const Tag OP_SHL = 0x13; 62 | const Tag FP_SHL = 0x14; 63 | const Tag OP_SHR = 0x15; 64 | const Tag FP_SHR = 0x16; 65 | 66 | typedef struct GNet GNet; 67 | 68 | // Debugger 69 | // -------- 70 | 71 | // Port: Constructor and Getters 72 | // ----------------------------- 73 | 74 | static inline Port new_port(Tag tag, Val val) { 75 | return (val << 3) | tag; 76 | } 77 | 78 | static inline Tag get_tag(Port port) { 79 | return port & 7; 80 | } 81 | 82 | static inline Val get_val(Port port) { 83 | return port >> 3; 84 | } 85 | 86 | // Pair: Constructor and Getters 87 | // ----------------------------- 88 | 89 | static inline const Pair new_pair(Port fst, Port snd) { 90 | return ((u64)snd << 32) | fst; 91 | } 92 | 93 | static inline Port get_fst(Pair pair) { 94 | return pair & 0xFFFFFFFF; 95 | } 96 | 97 | static inline Port get_snd(Pair pair) { 98 | return pair >> 32; 99 | } 100 | 101 | // Utils 102 | // ----- 103 | 104 | // Swaps two ports. 105 | static inline void swap(Port *a, Port *b) { 106 | Port x = *a; *a = *b; *b = x; 107 | } 108 | 109 | static inline u32 min(u32 a, u32 b) { 110 | return (a < b) ? a : b; 111 | } 112 | 113 | static inline f32 clamp(f32 x, f32 min, f32 max) { 114 | const f32 t = x < min ? min : x; 115 | return (t > max) ? max : t; 116 | } 117 | 118 | // Numbs 119 | // ----- 120 | 121 | // Constructor and getters for SYM (operation selector) 122 | static inline Numb new_sym(u32 val) { 123 | return (val << 5) | TY_SYM; 124 | } 125 | 126 | static inline u32 get_sym(Numb word) { 127 | return (word >> 5); 128 | } 129 | 130 | // Constructor and getters for U24 (unsigned 24-bit integer) 131 | static inline Numb new_u24(u32 val) { 132 | return (val << 5) | TY_U24; 133 | } 134 | 135 | static inline u32 get_u24(Numb word) { 136 | return word >> 5; 137 | } 138 | 139 | // Constructor and getters for I24 (signed 24-bit integer) 140 | static inline Numb new_i24(i32 val) { 141 | return ((u32)val << 5) | TY_I24; 142 | } 143 | 144 | static inline i32 get_i24(Numb word) { 145 | return ((i32)word) << 3 >> 8; 146 | } 147 | 148 | // Constructor and getters for F24 (24-bit float) 149 | static inline Numb new_f24(float val) { 150 | u32 bits = *(u32*)&val; 151 | u32 shifted_bits = bits >> 8; 152 | u32 lost_bits = bits & 0xFF; 153 | // round ties to even 154 | shifted_bits += (!isnan(val)) & ((lost_bits - ((lost_bits >> 7) & !shifted_bits)) >> 7); 155 | // ensure NaNs don't become infinities 156 | shifted_bits |= isnan(val); 157 | return (shifted_bits << 5) | TY_F24; 158 | } 159 | 160 | static inline float get_f24(Numb word) { 161 | u32 bits = (word << 3) & 0xFFFFFF00; 162 | return *(float*)&bits; 163 | } 164 | 165 | static inline Tag get_typ(Numb word) { 166 | return word & 0x1F; 167 | } 168 | 169 | static inline bool is_num(Numb word) { 170 | return get_typ(word) >= TY_U24 && get_typ(word) <= TY_F24; 171 | } 172 | 173 | static inline bool is_cast(Numb word) { 174 | return get_typ(word) == TY_SYM && get_sym(word) >= TY_U24 && get_sym(word) <= TY_F24; 175 | } 176 | 177 | // Partial application 178 | static inline Numb partial(Numb a, Numb b) { 179 | return (b & ~0x1F) | get_sym(a); 180 | } 181 | 182 | // Readback 183 | // --------- 184 | 185 | // Readback: Tuples 186 | typedef struct Tup { 187 | u32 elem_len; 188 | Port elem_buf[8]; 189 | } Tup; 190 | 191 | // Reads a tuple of `size` elements from `port`. 192 | // Tuples are con nodes nested to the right auxilliary port, 193 | // For example, `(CON a (CON b (CON c)))` is a 3-tuple (a, b, c). 194 | extern Tup gnet_readback_tup(GNet* gnet, Port port, u32 size); 195 | 196 | typedef struct Str { 197 | u32 len; 198 | char *buf; 199 | } Str; 200 | 201 | // Reads a constructor-encoded string (of length at most 255 characters), 202 | // into a null-terminated `Str`. 203 | extern Str gnet_readback_str(GNet* gnet, Port port); 204 | 205 | typedef struct Bytes { 206 | u32 len; 207 | char *buf; 208 | } Bytes; 209 | 210 | // Reads a constructor-encoded string (of length at most 256 characters), 211 | // into a `Bytes`. The returned `Bytes` is not null terminated. 212 | extern Bytes gnet_readback_bytes(GNet* net, Port port); 213 | 214 | // Creates a construtor-encoded string of arbitrary length from the 215 | // provided `bytes`. This string can be consumed on the HVM-side. This 216 | // will return an `ERA` if nodes cannot be allocated. 217 | extern Port gnet_inject_bytes(GNet* net, Bytes *bytes); 218 | 219 | #endif // hvm_cuh_INCLUDED 220 | -------------------------------------------------------------------------------- /src/hvm.h: -------------------------------------------------------------------------------- 1 | #ifndef hvm_h_INCLUDED 2 | #define hvm_h_INCLUDED 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // Types 9 | // ----- 10 | 11 | typedef uint8_t u8; 12 | typedef uint16_t u16; 13 | typedef uint32_t u32; 14 | typedef int32_t i32; 15 | typedef uint64_t u64; 16 | typedef float f32; 17 | typedef double f64; 18 | 19 | // Local Types 20 | typedef u8 Tag; // Tag ::= 3-bit (rounded up to u8) 21 | typedef u32 Val; // Val ::= 29-bit (rounded up to u32) 22 | typedef u32 Port; // Port ::= Tag + Val (fits a u32) 23 | typedef u64 Pair; // Pair ::= Port + Port (fits a u64) 24 | 25 | // Numbs 26 | typedef u32 Numb; // Numb ::= 29-bit (rounded up to u32) 27 | 28 | // Tags 29 | #define VAR 0x0 // variable 30 | #define REF 0x1 // reference 31 | #define ERA 0x2 // eraser 32 | #define NUM 0x3 // number 33 | #define CON 0x4 // constructor 34 | #define DUP 0x5 // duplicator 35 | #define OPR 0x6 // operator 36 | #define SWI 0x7 // switch 37 | 38 | // Numbers 39 | static const f32 U24_MAX = (f32) (1 << 24) - 1; 40 | static const f32 U24_MIN = 0.0; 41 | static const f32 I24_MAX = (f32) (1 << 23) - 1; 42 | static const f32 I24_MIN = (f32) (i32) ((-1u) << 23); 43 | #define TY_SYM 0x00 44 | #define TY_U24 0x01 45 | #define TY_I24 0x02 46 | #define TY_F24 0x03 47 | #define OP_ADD 0x04 48 | #define OP_SUB 0x05 49 | #define FP_SUB 0x06 50 | #define OP_MUL 0x07 51 | #define OP_DIV 0x08 52 | #define FP_DIV 0x09 53 | #define OP_REM 0x0A 54 | #define FP_REM 0x0B 55 | #define OP_EQ 0x0C 56 | #define OP_NEQ 0x0D 57 | #define OP_LT 0x0E 58 | #define OP_GT 0x0F 59 | #define OP_AND 0x10 60 | #define OP_OR 0x11 61 | #define OP_XOR 0x12 62 | #define OP_SHL 0x13 63 | #define FP_SHL 0x14 64 | #define OP_SHR 0x15 65 | #define FP_SHR 0x16 66 | 67 | typedef struct Net Net; 68 | typedef struct Book Book; 69 | 70 | // Debugger 71 | // -------- 72 | 73 | typedef struct { 74 | char x[13]; 75 | } Show; 76 | 77 | void put_u16(char* B, u16 val); 78 | Show show_port(Port port); 79 | void print_net(Net* net); 80 | void pretty_print_numb(Numb word); 81 | void pretty_print_port(Net* net, Book* book, Port port); 82 | 83 | // Port: Constructor and Getters 84 | // ----------------------------- 85 | 86 | static inline Port new_port(Tag tag, Val val) { 87 | return (val << 3) | tag; 88 | } 89 | 90 | static inline Tag get_tag(Port port) { 91 | return port & 7; 92 | } 93 | 94 | static inline Val get_val(Port port) { 95 | return port >> 3; 96 | } 97 | 98 | // Pair: Constructor and Getters 99 | // ----------------------------- 100 | 101 | static inline const Pair new_pair(Port fst, Port snd) { 102 | return ((u64)snd << 32) | fst; 103 | } 104 | 105 | static inline Port get_fst(Pair pair) { 106 | return pair & 0xFFFFFFFF; 107 | } 108 | 109 | static inline Port get_snd(Pair pair) { 110 | return pair >> 32; 111 | } 112 | 113 | // Utils 114 | // ----- 115 | 116 | // Swaps two ports. 117 | static inline void swap(Port *a, Port *b) { 118 | Port x = *a; *a = *b; *b = x; 119 | } 120 | 121 | static inline u32 min(u32 a, u32 b) { 122 | return (a < b) ? a : b; 123 | } 124 | 125 | static inline f32 clamp(f32 x, f32 min, f32 max) { 126 | const f32 t = x < min ? min : x; 127 | return (t > max) ? max : t; 128 | } 129 | 130 | // Numbs 131 | // ----- 132 | 133 | // Constructor and getters for SYM (operation selector) 134 | static inline Numb new_sym(u32 val) { 135 | return (val << 5) | TY_SYM; 136 | } 137 | 138 | static inline u32 get_sym(Numb word) { 139 | return (word >> 5); 140 | } 141 | 142 | // Constructor and getters for U24 (unsigned 24-bit integer) 143 | static inline Numb new_u24(u32 val) { 144 | return (val << 5) | TY_U24; 145 | } 146 | 147 | static inline u32 get_u24(Numb word) { 148 | return word >> 5; 149 | } 150 | 151 | // Constructor and getters for I24 (signed 24-bit integer) 152 | static inline Numb new_i24(i32 val) { 153 | return ((u32)val << 5) | TY_I24; 154 | } 155 | 156 | static inline i32 get_i24(Numb word) { 157 | return ((i32)word) << 3 >> 8; 158 | } 159 | 160 | // Constructor and getters for F24 (24-bit float) 161 | static inline Numb new_f24(float val) { 162 | u32 bits = *(u32*)&val; 163 | u32 shifted_bits = bits >> 8; 164 | u32 lost_bits = bits & 0xFF; 165 | // round ties to even 166 | shifted_bits += (!isnan(val)) & ((lost_bits - ((lost_bits >> 7) & !shifted_bits)) >> 7); 167 | // ensure NaNs don't become infinities 168 | shifted_bits |= isnan(val); 169 | return (shifted_bits << 5) | TY_F24; 170 | } 171 | 172 | static inline float get_f24(Numb word) { 173 | u32 bits = (word << 3) & 0xFFFFFF00; 174 | return *(float*)&bits; 175 | } 176 | 177 | static inline Tag get_typ(Numb word) { 178 | return word & 0x1F; 179 | } 180 | 181 | static inline bool is_num(Numb word) { 182 | return get_typ(word) >= TY_U24 && get_typ(word) <= TY_F24; 183 | } 184 | 185 | static inline bool is_cast(Numb word) { 186 | return get_typ(word) == TY_SYM && get_sym(word) >= TY_U24 && get_sym(word) <= TY_F24; 187 | } 188 | 189 | // Partial application 190 | static inline Numb partial(Numb a, Numb b) { 191 | return (b & ~0x1F) | get_sym(a); 192 | } 193 | 194 | // Readback 195 | // --------- 196 | 197 | // Readback: Tuples 198 | typedef struct Tup { 199 | u32 elem_len; 200 | Port elem_buf[8]; 201 | } Tup; 202 | 203 | // Reads a tuple of `size` elements from `port`. 204 | // Tuples are con nodes nested to the right auxilliary port, 205 | // For example, `(CON a (CON b (CON c)))` is a 3-tuple (a, b, c). 206 | extern Tup readback_tup(Net* net, Book* book, Port port, u32 size); 207 | 208 | typedef struct Str { 209 | u32 len; 210 | char *buf; 211 | } Str; 212 | 213 | // Reads a constructor-encoded string (of length at most 255 characters), 214 | // into a null-terminated `Str`. 215 | extern Str readback_str(Net* net, Book* book, Port port); 216 | 217 | typedef struct Bytes { 218 | u32 len; 219 | char *buf; 220 | } Bytes; 221 | 222 | // Reads a constructor-encoded string (of length at most 256 characters), 223 | // into a `Bytes`. The returned `Bytes` is not null terminated. 224 | extern Bytes readback_bytes(Net* net, Book* book, Port port); 225 | 226 | // Creates a construtor-encoded string of arbitrary length from the 227 | // provided `bytes`. This string can be consumed on the HVM-side. This 228 | // will return an `ERA` if nodes cannot be allocated. 229 | extern Port inject_bytes(Net* net, Bytes *bytes); 230 | 231 | #endif // hvm_h_INCLUDED 232 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | #![allow(unused_variables)] 4 | 5 | pub mod ast; 6 | pub mod cmp; 7 | pub mod hvm; 8 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_imports)] 3 | #![allow(unused_variables)] 4 | 5 | use clap::{Arg, ArgAction, Command}; 6 | use ::hvm::{ast, cmp, hvm}; 7 | use std::fs; 8 | use std::io::Write; 9 | use std::path::PathBuf; 10 | use std::process::Command as SysCommand; 11 | 12 | #[cfg(feature = "c")] 13 | extern "C" { 14 | fn hvm_c(book_buffer: *const u32); 15 | } 16 | 17 | #[cfg(feature = "cuda")] 18 | extern "C" { 19 | fn hvm_cu(book_buffer: *const u32); 20 | } 21 | 22 | fn main() { 23 | let matches = Command::new("hvm") 24 | .about("HVM2: Higher-order Virtual Machine 2 (32-bit Version)") 25 | .version(env!("CARGO_PKG_VERSION")) 26 | .subcommand_required(true) 27 | .arg_required_else_help(true) 28 | .subcommand( 29 | Command::new("run") 30 | .about("Interprets a file (using Rust)") 31 | .arg(Arg::new("file").required(true))) 32 | .subcommand( 33 | Command::new("run-c") 34 | .about("Interprets a file (using C)") 35 | .arg(Arg::new("file").required(true)) 36 | .arg(Arg::new("io") 37 | .long("io") 38 | .action(ArgAction::SetTrue) 39 | .help("Run with IO enabled")) 40 | ) 41 | .subcommand( 42 | Command::new("run-cu") 43 | .about("Interprets a file (using CUDA)") 44 | .arg(Arg::new("file").required(true)) 45 | .arg(Arg::new("io") 46 | .long("io") 47 | .action(ArgAction::SetTrue) 48 | .help("Run with IO enabled"))) 49 | .subcommand( 50 | Command::new("gen-c") 51 | .about("Compiles a file with IO (to standalone C)") 52 | .arg(Arg::new("file").required(true)) 53 | .arg(Arg::new("io") 54 | .long("io") 55 | .action(ArgAction::SetTrue) 56 | .help("Generate with IO enabled"))) 57 | .subcommand( 58 | Command::new("gen-cu") 59 | .about("Compiles a file (to standalone CUDA)") 60 | .arg(Arg::new("file").required(true)) 61 | .arg(Arg::new("io") 62 | .long("io") 63 | .action(ArgAction::SetTrue) 64 | .help("Generate with IO enabled"))) 65 | .get_matches(); 66 | 67 | match matches.subcommand() { 68 | Some(("run", sub_matches)) => { 69 | let file = sub_matches.get_one::("file").expect("required"); 70 | let code = fs::read_to_string(file).expect("Unable to read file"); 71 | let book = ast::Book::parse(&code).unwrap_or_else(|er| panic!("{}",er)).build(); 72 | run(&book); 73 | } 74 | Some(("run-c", sub_matches)) => { 75 | let file = sub_matches.get_one::("file").expect("required"); 76 | let code = fs::read_to_string(file).expect("Unable to read file"); 77 | let book = ast::Book::parse(&code).unwrap_or_else(|er| panic!("{}",er)).build(); 78 | let mut data : Vec = Vec::new(); 79 | book.to_buffer(&mut data); 80 | #[cfg(feature = "c")] 81 | unsafe { 82 | hvm_c(data.as_mut_ptr() as *mut u32); 83 | } 84 | #[cfg(not(feature = "c"))] 85 | println!("C runtime not available!\n"); 86 | } 87 | Some(("run-cu", sub_matches)) => { 88 | let file = sub_matches.get_one::("file").expect("required"); 89 | let code = fs::read_to_string(file).expect("Unable to read file"); 90 | let book = ast::Book::parse(&code).unwrap_or_else(|er| panic!("{}",er)).build(); 91 | let mut data : Vec = Vec::new(); 92 | book.to_buffer(&mut data); 93 | #[cfg(feature = "cuda")] 94 | unsafe { 95 | hvm_cu(data.as_mut_ptr() as *mut u32); 96 | } 97 | #[cfg(not(feature = "cuda"))] 98 | println!("CUDA runtime not available!\n If you've installed CUDA and nvcc after HVM, please reinstall HVM."); 99 | } 100 | Some(("gen-c", sub_matches)) => { 101 | // Reads book from file 102 | let file = sub_matches.get_one::("file").expect("required"); 103 | let code = fs::read_to_string(file).expect("Unable to read file"); 104 | let book = ast::Book::parse(&code).unwrap_or_else(|er| panic!("{}",er)).build(); 105 | 106 | // Gets optimal core count 107 | let cores = num_cpus::get(); 108 | let tpcl2 = (cores as f64).log2().floor() as u32; 109 | 110 | // Generates the interpreted book 111 | let mut book_buf : Vec = Vec::new(); 112 | book.to_buffer(&mut book_buf); 113 | let bookb = format!("{:?}", book_buf).replace("[","{").replace("]","}"); 114 | let bookb = format!("static const u8 BOOK_BUF[] = {};", bookb); 115 | 116 | // Generates the C file 117 | let hvm_c = include_str!("hvm.c"); 118 | let hvm_c = format!("#define IO\n\n{hvm_c}"); 119 | let hvm_c = hvm_c.replace("///COMPILED_INTERACT_CALL///", &cmp::compile_book(cmp::Target::C, &book)); 120 | let hvm_c = hvm_c.replace("#define INTERPRETED", "#define COMPILED"); 121 | let hvm_c = hvm_c.replace("//COMPILED_BOOK_BUF//", &bookb); 122 | let hvm_c = hvm_c.replace("#define WITHOUT_MAIN", "#define WITH_MAIN"); 123 | let hvm_c = hvm_c.replace("#define TPC_L2 0", &format!("#define TPC_L2 {} // {} cores", tpcl2, cores)); 124 | let hvm_c = format!("{hvm_c}\n\n{}", include_str!("run.c")); 125 | let hvm_c = hvm_c.replace(r#"#include "hvm.c""#, ""); 126 | println!("{}", hvm_c); 127 | } 128 | Some(("gen-cu", sub_matches)) => { 129 | // Reads book from file 130 | let file = sub_matches.get_one::("file").expect("required"); 131 | let code = fs::read_to_string(file).expect("Unable to read file"); 132 | let book = ast::Book::parse(&code).unwrap_or_else(|er| panic!("{}",er)).build(); 133 | 134 | // Generates the interpreted book 135 | let mut book_buf : Vec = Vec::new(); 136 | book.to_buffer(&mut book_buf); 137 | let bookb = format!("{:?}", book_buf).replace("[","{").replace("]","}"); 138 | let bookb = format!("static const u8 BOOK_BUF[] = {};", bookb); 139 | 140 | //FIXME: currently, CUDA is faster on interpreted mode, so the compiler uses it. 141 | 142 | // Compile with compiled functions: 143 | //let hvm_c = include_str!("hvm.cu"); 144 | //let hvm_c = hvm_c.replace("///COMPILED_INTERACT_CALL///", &cmp::compile_book(cmp::Target::CUDA, &book)); 145 | //let hvm_c = hvm_c.replace("#define INTERPRETED", "#define COMPILED"); 146 | 147 | // Generates the Cuda file 148 | let hvm_cu = include_str!("hvm.cu"); 149 | let hvm_cu = format!("#define IO\n\n{hvm_cu}"); 150 | let hvm_cu = hvm_cu.replace("//COMPILED_BOOK_BUF//", &bookb); 151 | let hvm_cu = hvm_cu.replace("#define WITHOUT_MAIN", "#define WITH_MAIN"); 152 | let hvm_cu = format!("{hvm_cu}\n\n{}", include_str!("run.cu")); 153 | let hvm_cu = hvm_cu.replace(r#"#include "hvm.cu""#, ""); 154 | println!("{}", hvm_cu); 155 | } 156 | _ => unreachable!(), 157 | } 158 | } 159 | 160 | pub fn run(book: &hvm::Book) { 161 | // Initializes the global net 162 | let net = hvm::GNet::new(1 << 29, 1 << 29); 163 | 164 | // Initializes threads 165 | let mut tm = hvm::TMem::new(0, 1); 166 | 167 | // Creates an initial redex that calls main 168 | let main_id = book.defs.iter().position(|def| def.name == "main").unwrap(); 169 | tm.rbag.push_redex(hvm::Pair::new(hvm::Port::new(hvm::REF, main_id as u32), hvm::ROOT)); 170 | net.vars_create(hvm::ROOT.get_val() as usize, hvm::NONE); 171 | 172 | // Starts the timer 173 | let start = std::time::Instant::now(); 174 | 175 | // Evaluates 176 | tm.evaluator(&net, &book); 177 | 178 | // Stops the timer 179 | let duration = start.elapsed(); 180 | 181 | //println!("{}", net.show()); 182 | 183 | // Prints the result 184 | if let Some(tree) = ast::Net::readback(&net, book) { 185 | println!("Result: {}", tree.show()); 186 | } else { 187 | println!("Readback failed. Printing GNet memdump...\n"); 188 | println!("{}", net.show()); 189 | } 190 | 191 | // Prints interactions and time 192 | let itrs = net.itrs.load(std::sync::atomic::Ordering::Relaxed); 193 | println!("- ITRS: {}", itrs); 194 | println!("- TIME: {:.2}s", duration.as_secs_f64()); 195 | println!("- MIPS: {:.2}", itrs as f64 / duration.as_secs_f64() / 1_000_000.0); 196 | } 197 | -------------------------------------------------------------------------------- /src/run.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "hvm.c" 5 | 6 | // Readback: λ-Encoded Ctr 7 | typedef struct Ctr { 8 | u32 tag; 9 | u32 args_len; 10 | Port args_buf[16]; 11 | } Ctr; 12 | 13 | // Readback: Tuples 14 | typedef struct Tup { 15 | u32 elem_len; 16 | Port elem_buf[8]; 17 | } Tup; 18 | 19 | // Readback: λ-Encoded Str (UTF-32), null-terminated 20 | // FIXME: this is actually ASCII :| 21 | typedef struct Str { 22 | u32 len; 23 | char *buf; 24 | } Str; 25 | 26 | // Readback: λ-Encoded list of bytes 27 | typedef struct Bytes { 28 | u32 len; 29 | char *buf; 30 | } Bytes; 31 | 32 | // IO Magic Number 33 | #define IO_MAGIC_0 0xD0CA11 34 | #define IO_MAGIC_1 0xFF1FF1 35 | 36 | // IO Tags 37 | #define IO_DONE 0 38 | #define IO_CALL 1 39 | 40 | // Result Tags = Result 41 | #define RESULT_OK 0 42 | #define RESULT_ERR 1 43 | 44 | // IOError = { 45 | // Type, -- a type error 46 | // Name, -- invalid io func name 47 | // Inner {val: T}, -- an error while calling an io func 48 | // } 49 | #define IO_ERR_TYPE 0 50 | #define IO_ERR_NAME 1 51 | #define IO_ERR_INNER 2 52 | 53 | typedef struct IOError { 54 | u32 tag; 55 | Port val; 56 | } IOError; 57 | 58 | // List Tags 59 | #define LIST_NIL 0 60 | #define LIST_CONS 1 61 | 62 | // Readback 63 | // -------- 64 | 65 | // Reads back a λ-Encoded constructor from device to host. 66 | // Encoding: λt ((((t TAG) arg0) arg1) ...) 67 | Ctr readback_ctr(Net* net, Book* book, Port port) { 68 | Ctr ctr; 69 | ctr.tag = -1; 70 | ctr.args_len = 0; 71 | 72 | // Loads root lambda 73 | Port lam_port = expand(net, book, port); 74 | if (get_tag(lam_port) != CON) return ctr; 75 | Pair lam_node = node_load(net, get_val(lam_port)); 76 | 77 | // Loads first application 78 | Port app_port = expand(net, book, get_fst(lam_node)); 79 | if (get_tag(app_port) != CON) return ctr; 80 | Pair app_node = node_load(net, get_val(app_port)); 81 | 82 | // Loads first argument (as the tag) 83 | Port arg_port = expand(net, book, get_fst(app_node)); 84 | if (get_tag(arg_port) != NUM) return ctr; 85 | ctr.tag = get_u24(get_val(arg_port)); 86 | 87 | // Loads remaining arguments 88 | while (true) { 89 | app_port = expand(net, book, get_snd(app_node)); 90 | if (get_tag(app_port) != CON) break; 91 | app_node = node_load(net, get_val(app_port)); 92 | arg_port = expand(net, book, get_fst(app_node)); 93 | ctr.args_buf[ctr.args_len++] = arg_port; 94 | } 95 | 96 | return ctr; 97 | } 98 | 99 | // Reads back a tuple of at most `size` elements. Tuples are 100 | // (right-nested con nodes) (CON 1 (CON 2 (CON 3 (...)))) 101 | // The provided `port` should be `expanded` before calling. 102 | extern Tup readback_tup(Net* net, Book* book, Port port, u32 size) { 103 | Tup tup; 104 | tup.elem_len = 0; 105 | 106 | // Loads remaining arguments 107 | while (get_tag(port) == CON && (tup.elem_len + 1 < size)) { 108 | Pair node = node_load(net, get_val(port)); 109 | tup.elem_buf[tup.elem_len++] = expand(net, book, get_fst(node)); 110 | 111 | port = expand(net, book, get_snd(node)); 112 | } 113 | 114 | tup.elem_buf[tup.elem_len++] = port; 115 | 116 | return tup; 117 | } 118 | 119 | // Converts a Port into a list of bytes. 120 | // Encoding: 121 | // - λt (t NIL) 122 | // - λt (((t CONS) head) tail) 123 | Bytes readback_bytes(Net* net, Book* book, Port port) { 124 | Bytes bytes; 125 | u32 capacity = 256; 126 | bytes.buf = (char*) malloc(sizeof(char) * capacity); 127 | bytes.len = 0; 128 | 129 | // Readback loop 130 | while (true) { 131 | // Normalizes the net 132 | normalize(net, book); 133 | 134 | // Reads the λ-Encoded Ctr 135 | Ctr ctr = readback_ctr(net, book, peek(net, port)); 136 | 137 | // Reads string layer 138 | switch (ctr.tag) { 139 | case LIST_NIL: { 140 | break; 141 | } 142 | case LIST_CONS: { 143 | if (ctr.args_len != 2) break; 144 | if (get_tag(ctr.args_buf[0]) != NUM) break; 145 | 146 | if (bytes.len == capacity - 1) { 147 | capacity *= 2; 148 | bytes.buf = realloc(bytes.buf, capacity); 149 | } 150 | 151 | bytes.buf[bytes.len++] = get_u24(get_val(ctr.args_buf[0])); 152 | boot_redex(net, new_pair(ctr.args_buf[1], ROOT)); 153 | port = ROOT; 154 | continue; 155 | } 156 | } 157 | break; 158 | } 159 | 160 | return bytes; 161 | } 162 | 163 | // Converts a Port into a UTF-32 (truncated to 24 bits) null-terminated string. 164 | // Since unicode scalars can fit in 21 bits, HVM's u24 165 | // integers can contain any unicode scalar value. 166 | // Encoding: 167 | // - λt (t NIL) 168 | // - λt (((t CONS) head) tail) 169 | Str readback_str(Net* net, Book* book, Port port) { 170 | // readback_bytes is guaranteed to return a buffer with a capacity of at least one more 171 | // than the number of bytes read, so we can null-terminate it. 172 | Bytes bytes = readback_bytes(net, book, port); 173 | 174 | Str str; 175 | str.len = bytes.len; 176 | str.buf = bytes.buf; 177 | str.buf[str.len] = 0; 178 | 179 | return str; 180 | } 181 | 182 | /// Returns a λ-Encoded Ctr for a NIL: λt (t NIL) 183 | /// A previous call to `get_resources(tm, 0, 2, 1)` is required. 184 | Port inject_nil(Net* net) { 185 | u32 v1 = tm[0]->vloc[0]; 186 | 187 | u32 n1 = tm[0]->nloc[0]; 188 | u32 n2 = tm[0]->nloc[1]; 189 | 190 | vars_create(net, v1, NONE); 191 | Port var = new_port(VAR, v1); 192 | 193 | node_create(net, n1, new_pair(new_port(NUM, new_u24(LIST_NIL)), var)); 194 | node_create(net, n2, new_pair(new_port(CON, n1), var)); 195 | 196 | return new_port(CON, n2); 197 | } 198 | 199 | /// Returns a λ-Encoded Ctr for a CONS: λt (((t CONS) head) tail) 200 | /// A previous call to `get_resources(tm, 0, 4, 1)` is required. 201 | Port inject_cons(Net* net, Port head, Port tail) { 202 | u32 v1 = tm[0]->vloc[0]; 203 | 204 | u32 n1 = tm[0]->nloc[0]; 205 | u32 n2 = tm[0]->nloc[1]; 206 | u32 n3 = tm[0]->nloc[2]; 207 | u32 n4 = tm[0]->nloc[3]; 208 | 209 | vars_create(net, v1, NONE); 210 | Port var = new_port(VAR, v1); 211 | 212 | node_create(net, n1, new_pair(tail, var)); 213 | node_create(net, n2, new_pair(head, new_port(CON, n1))); 214 | node_create(net, n3, new_pair(new_port(NUM, new_u24(LIST_CONS)), new_port(CON, n2))); 215 | node_create(net, n4, new_pair(new_port(CON, n3), var)); 216 | 217 | return new_port(CON, n4); 218 | } 219 | 220 | // Converts a list of bytes to a Port. 221 | // Encoding: 222 | // - λt (t NIL) 223 | // - λt (((t CONS) head) tail) 224 | Port inject_bytes(Net* net, Bytes *bytes) { 225 | // Allocate all resources up front: 226 | // - NIL needs 2 nodes & 1 var 227 | // - CONS needs 4 nodes & 1 var 228 | u32 len = bytes->len; 229 | if (!get_resources(net, tm[0], 0, 2, 1)) { 230 | fprintf(stderr, "inject_bytes: failed to get resources\n"); 231 | return new_port(ERA, 0); 232 | } 233 | Port port = inject_nil(net); 234 | 235 | // TODO: batch-allocate these (within the limits of TM) 236 | for (u32 i = 0; i < len; i++) { 237 | if (!get_resources(net, tm[0], 0, 4, 1)) { 238 | fprintf(stderr, "inject_bytes: failed to get resources\n"); 239 | return new_port(ERA, 0); 240 | } 241 | Port byte = new_port(NUM, new_u24(bytes->buf[len - i - 1])); 242 | port = inject_cons(net, byte, port); 243 | } 244 | 245 | return port; 246 | } 247 | 248 | /// Returns a λ-Encoded Ctr for a RESULT_OK: λt ((t RESULT_OK) val) 249 | Port inject_ok(Net* net, Port val) { 250 | if (!get_resources(net, tm[0], 0, 3, 1)) { 251 | fprintf(stderr, "inject_ok: failed to get resources\n"); 252 | return new_port(ERA, 0); 253 | } 254 | 255 | u32 v1 = tm[0]->vloc[0]; 256 | 257 | u32 n1 = tm[0]->nloc[0]; 258 | u32 n2 = tm[0]->nloc[1]; 259 | u32 n3 = tm[0]->nloc[2]; 260 | 261 | vars_create(net, v1, NONE); 262 | Port var = new_port(VAR, v1); 263 | 264 | node_create(net, n1, new_pair(val, var)); 265 | node_create(net, n2, new_pair(new_port(NUM, new_u24(RESULT_OK)), new_port(CON, n1))); 266 | node_create(net, n3, new_pair(new_port(CON, n2), var)); 267 | 268 | return new_port(CON, n3); 269 | } 270 | 271 | /// Returns a λ-Encoded Ctr for a RESULT_ERR: λt ((t RESULT_ERR) err) 272 | Port inject_err(Net* net, Port err) { 273 | if (!get_resources(net, tm[0], 0, 3, 1)) { 274 | fprintf(stderr, "inject_err: failed to get resources\n"); 275 | return new_port(ERA, 0); 276 | } 277 | 278 | u32 v1 = tm[0]->vloc[0]; 279 | 280 | u32 n1 = tm[0]->nloc[0]; 281 | u32 n2 = tm[0]->nloc[1]; 282 | u32 n3 = tm[0]->nloc[2]; 283 | 284 | vars_create(net, v1, NONE); 285 | Port var = new_port(VAR, v1); 286 | 287 | node_create(net, n1, new_pair(err, var)); 288 | node_create(net, n2, new_pair(new_port(NUM, new_u24(RESULT_ERR)), new_port(CON, n1))); 289 | node_create(net, n3, new_pair(new_port(CON, n2), var)); 290 | 291 | return new_port(CON, n3); 292 | } 293 | 294 | /// Returns a λ-Encoded Ctr for a Result/Err(IOError(..)) 295 | Port inject_io_err(Net* net, IOError err) { 296 | if (err.tag <= IO_ERR_NAME) { 297 | if (!get_resources(net, tm[0], 0, 2, 1)) { 298 | fprintf(stderr, "inject_io_err: failed to get resources\n"); 299 | return new_port(ERA, 0); 300 | } 301 | 302 | u32 v1 = tm[0]->vloc[0]; 303 | 304 | u32 n1 = tm[0]->nloc[0]; 305 | u32 n2 = tm[0]->nloc[1]; 306 | 307 | vars_create(net, v1, NONE); 308 | Port var = new_port(VAR, v1); 309 | 310 | node_create(net, n1, new_pair(new_port(NUM, new_u24(err.tag)), var)); 311 | node_create(net, n2, new_pair(new_port(CON, n1), var)); 312 | 313 | return inject_err(net, new_port(CON, n2)); 314 | } 315 | 316 | if (!get_resources(net, tm[0], 0, 3, 1)) { 317 | fprintf(stderr, "inject_io_err: failed to get resources\n"); 318 | return new_port(ERA, 0); 319 | } 320 | 321 | u32 v1 = tm[0]->vloc[0]; 322 | 323 | u32 n1 = tm[0]->nloc[0]; 324 | u32 n2 = tm[0]->nloc[1]; 325 | u32 n3 = tm[0]->nloc[2]; 326 | 327 | vars_create(net, v1, NONE); 328 | Port var = new_port(VAR, v1); 329 | 330 | node_create(net, n1, new_pair(err.val, var)); 331 | node_create(net, n2, new_pair(new_port(NUM, new_u24(IO_ERR_INNER)), new_port(CON, n1))); 332 | node_create(net, n3, new_pair(new_port(CON, n2), var)); 333 | 334 | return inject_err(net, new_port(CON, n3)); 335 | } 336 | 337 | /// Returns a λ-Encoded Ctr for a Result/Err(IOError/Type) 338 | Port inject_io_err_type(Net* net) { 339 | IOError io_error = { 340 | .tag = IO_ERR_TYPE, 341 | }; 342 | 343 | return inject_io_err(net, io_error); 344 | } 345 | 346 | /// Returns a λ-Encoded Ctr for a Result/Err(IOError/Name) 347 | Port inject_io_err_name(Net* net) { 348 | IOError io_error = { 349 | .tag = IO_ERR_NAME, 350 | }; 351 | 352 | return inject_io_err(net, io_error); 353 | } 354 | 355 | /// Returns a λ-Encoded Ctr for a Result/Err(IOError/Inner(val)) 356 | Port inject_io_err_inner(Net* net, Port val) { 357 | IOError io_error = { 358 | .tag = IO_ERR_INNER, 359 | .val = val, 360 | }; 361 | 362 | return inject_io_err(net, io_error); 363 | } 364 | 365 | /// Returns a λ-Encoded Ctr for an Result> 366 | /// `err` must be `NUL`-terminated. 367 | Port inject_io_err_str(Net* net, char* err) { 368 | Bytes err_bytes; 369 | err_bytes.buf = err; 370 | err_bytes.len = strlen(err_bytes.buf); 371 | Port err_port = inject_bytes(net, &err_bytes); 372 | 373 | return inject_io_err_inner(net, err_port); 374 | } 375 | 376 | // Primitive IO Fns 377 | // ----------------- 378 | 379 | // Open file pointers. Indices into this array 380 | // are used as "file descriptors". 381 | // Indices 0 1 and 2 are reserved. 382 | // - 0 -> stdin 383 | // - 1 -> stdout 384 | // - 2 -> stderr 385 | static FILE* FILE_POINTERS[256]; 386 | 387 | // Open dylibs handles. Indices into this array 388 | // are used as opaque loadedd object "handles". 389 | static void* DYLIBS[256]; 390 | 391 | // Converts a NUM port (file descriptor) to file pointer. 392 | FILE* readback_file(Port port) { 393 | if (get_tag(port) != NUM) { 394 | fprintf(stderr, "non-num where file descriptor was expected: %i\n", get_tag(port)); 395 | return NULL; 396 | } 397 | 398 | u32 idx = get_u24(get_val(port)); 399 | 400 | if (idx == 0) return stdin; 401 | if (idx == 1) return stdout; 402 | if (idx == 2) return stderr; 403 | 404 | FILE* fp = FILE_POINTERS[idx]; 405 | if (fp == NULL) { 406 | return NULL; 407 | } 408 | 409 | return fp; 410 | } 411 | 412 | // Converts a NUM port (dylib handle) to an opaque dylib object. 413 | void* readback_dylib(Port port) { 414 | if (get_tag(port) != NUM) { 415 | fprintf(stderr, "non-num where dylib handle was expected: %i\n", get_tag(port)); 416 | return NULL; 417 | } 418 | 419 | u32 idx = get_u24(get_val(port)); 420 | 421 | void* dl = DYLIBS[idx]; 422 | if (dl == NULL) { 423 | fprintf(stderr, "invalid dylib handle\n"); 424 | return NULL; 425 | } 426 | 427 | return dl; 428 | } 429 | 430 | // Reads from a file a specified number of bytes. 431 | // `argm` is a tuple of (file_descriptor, num_bytes). 432 | // Returns: Result> 433 | Port io_read(Net* net, Book* book, Port argm) { 434 | Tup tup = readback_tup(net, book, argm, 2); 435 | if (tup.elem_len != 2) { 436 | return inject_io_err_type(net); 437 | } 438 | 439 | FILE* fp = readback_file(tup.elem_buf[0]); 440 | u32 num_bytes = get_u24(get_val(tup.elem_buf[1])); 441 | 442 | if (fp == NULL) { 443 | return inject_io_err_inner(net, new_port(NUM, new_i24(EBADF))); 444 | } 445 | 446 | /// Read a string. 447 | Bytes bytes; 448 | bytes.buf = (char*) malloc(sizeof(char) * num_bytes); 449 | bytes.len = fread(bytes.buf, sizeof(char), num_bytes, fp); 450 | 451 | if ((bytes.len != num_bytes) && ferror(fp)) { 452 | free(bytes.buf); 453 | return inject_io_err_inner(net, new_port(NUM, new_i24(ferror(fp)))); 454 | } 455 | 456 | // Convert it to a port. 457 | Port ret = inject_bytes(net, &bytes); 458 | free(bytes.buf); 459 | 460 | return inject_ok(net, ret); 461 | } 462 | 463 | // Opens a file with the provided mode. 464 | // `argm` is a tuple (CON node) of the 465 | // file name and mode as strings. 466 | // Returns: Result> 467 | Port io_open(Net* net, Book* book, Port argm) { 468 | Tup tup = readback_tup(net, book, argm, 2); 469 | if (tup.elem_len != 2) { 470 | return inject_io_err_type(net); 471 | } 472 | 473 | Str name = readback_str(net, book, tup.elem_buf[0]); 474 | Str mode = readback_str(net, book, tup.elem_buf[1]); 475 | 476 | for (u32 fd = 3; fd < sizeof(FILE_POINTERS); fd++) { 477 | if (FILE_POINTERS[fd] == NULL) { 478 | FILE_POINTERS[fd] = fopen(name.buf, mode.buf); 479 | 480 | free(name.buf); 481 | free(mode.buf); 482 | 483 | if (FILE_POINTERS[fd] == NULL) { 484 | return inject_io_err_inner(net, new_port(NUM, new_i24(errno))); 485 | } 486 | 487 | return inject_ok(net, new_port(NUM, new_u24(fd))); 488 | } 489 | } 490 | 491 | free(name.buf); 492 | free(mode.buf); 493 | 494 | // too many open files 495 | return inject_io_err_inner(net, new_port(NUM, new_i24(EMFILE))); 496 | } 497 | 498 | // Closes a file, reclaiming the file descriptor. 499 | // Returns: Result<*, IOError> 500 | Port io_close(Net* net, Book* book, Port argm) { 501 | FILE* fp = readback_file(argm); 502 | if (fp == NULL) { 503 | return inject_io_err_inner(net, new_port(NUM, new_i24(EBADF))); 504 | } 505 | 506 | if (fclose(fp) != 0) { 507 | return inject_io_err_inner(net, new_port(NUM, new_i24(ferror(fp)))); 508 | } 509 | 510 | FILE_POINTERS[get_u24(get_val(argm))] = NULL; 511 | 512 | return inject_ok(net, new_port(ERA, 0)); 513 | } 514 | 515 | // Writes a list of bytes to a file. 516 | // `argm` is a tuple (CON node) of the 517 | // file descriptor and list of bytes to write. 518 | // Returns: Result<*, IOError> 519 | Port io_write(Net* net, Book* book, Port argm) { 520 | Tup tup = readback_tup(net, book, argm, 2); 521 | if (tup.elem_len != 2) { 522 | return inject_io_err_type(net); 523 | } 524 | 525 | FILE* fp = readback_file(tup.elem_buf[0]); 526 | Bytes bytes = readback_bytes(net, book, tup.elem_buf[1]); 527 | 528 | if (fp == NULL) { 529 | free(bytes.buf); 530 | 531 | return inject_io_err_inner(net, new_port(NUM, new_i24(EBADF))); 532 | } 533 | 534 | if (fwrite(bytes.buf, sizeof(char), bytes.len, fp) != bytes.len) { 535 | free(bytes.buf); 536 | 537 | return inject_io_err_inner(net, new_port(NUM, new_i24(ferror(fp)))); 538 | } 539 | 540 | free(bytes.buf); 541 | 542 | return inject_ok(net, new_port(ERA, 0)); 543 | } 544 | 545 | // Flushes an output stream. 546 | // Returns: Result<*, IOError> 547 | Port io_flush(Net* net, Book* book, Port argm) { 548 | FILE* fp = readback_file(argm); 549 | if (fp == NULL) { 550 | return inject_io_err_inner(net, new_port(NUM, new_i24(EBADF))); 551 | } 552 | 553 | if (fflush(fp) != 0) { 554 | return inject_io_err_inner(net, new_port(NUM, new_i24(ferror(fp)))); 555 | } 556 | 557 | return inject_ok(net, new_port(ERA, 0)); 558 | } 559 | 560 | // Seeks to a position in a file. 561 | // `argm` is a 3-tuple (CON fd (CON offset whence)), where 562 | // - fd is a file descriptor 563 | // - offset is a signed byte offset 564 | // - whence is what that offset is relative to: 565 | // - 0 (SEEK_SET): beginning of file 566 | // - 1 (SEEK_CUR): current position of the file pointer 567 | // - 2 (SEEK_END): end of the file 568 | // Returns: Result<*, IOError> 569 | Port io_seek(Net* net, Book* book, Port argm) { 570 | Tup tup = readback_tup(net, book, argm, 3); 571 | if (tup.elem_len != 3) { 572 | return inject_io_err_type(net); 573 | } 574 | 575 | FILE* fp = readback_file(tup.elem_buf[0]); 576 | i32 offset = get_i24(get_val(tup.elem_buf[1])); 577 | u32 whence = get_i24(get_val(tup.elem_buf[2])); 578 | 579 | if (fp == NULL) { 580 | return inject_io_err_inner(net, new_port(NUM, new_i24(EBADF))); 581 | } 582 | 583 | int cwhence; 584 | switch (whence) { 585 | case 0: cwhence = SEEK_SET; break; 586 | case 1: cwhence = SEEK_CUR; break; 587 | case 2: cwhence = SEEK_END; break; 588 | default: 589 | return inject_io_err_type(net); 590 | } 591 | 592 | if (fseek(fp, offset, cwhence) != 0) { 593 | return inject_io_err_inner(net, new_port(NUM, new_i24(ferror(fp)))); 594 | } 595 | 596 | return inject_ok(net, new_port(ERA, 0)); 597 | } 598 | 599 | // Returns the current time as a tuple of the high 600 | // and low 24 bits of a 48-bit nanosecond timestamp. 601 | // Returns: Result<(u24, u24), IOError<*>> 602 | Port io_get_time(Net* net, Book* book, Port argm) { 603 | // Get the current time in nanoseconds 604 | u64 time_ns = time64(); 605 | // Encode the time as a 64-bit unsigned integer 606 | u32 time_hi = (u32)(time_ns >> 24) & 0xFFFFFFF; 607 | u32 time_lo = (u32)(time_ns & 0xFFFFFFF); 608 | // Allocate a node to store the time 609 | u32 lps = 0; 610 | u32 loc = node_alloc_1(net, tm[0], &lps); 611 | node_create(net, loc, new_pair(new_port(NUM, new_u24(time_hi)), new_port(NUM, new_u24(time_lo)))); 612 | 613 | return inject_ok(net, new_port(CON, loc)); 614 | } 615 | 616 | // Sleeps. 617 | // `argm` is a tuple (CON node) of the high and low 618 | // 24 bits for a 48-bit duration in nanoseconds. 619 | // Returns: Result<*, IOError<*>> 620 | Port io_sleep(Net* net, Book* book, Port argm) { 621 | Tup tup = readback_tup(net, book, argm, 2); 622 | if (tup.elem_len != 2) { 623 | return inject_io_err_type(net); 624 | } 625 | 626 | // Get the sleep duration node 627 | Pair dur_node = node_load(net, get_val(argm)); 628 | // Get the high and low 24-bit parts of the duration 629 | u32 dur_hi = get_u24(get_val(tup.elem_buf[0])); 630 | u32 dur_lo = get_u24(get_val(tup.elem_buf[1])); 631 | // Combine into a 48-bit duration in nanoseconds 632 | u64 dur_ns = (((u64)dur_hi) << 24) | dur_lo; 633 | // Sleep for the specified duration 634 | struct timespec ts; 635 | ts.tv_sec = dur_ns / 1000000000; 636 | ts.tv_nsec = dur_ns % 1000000000; 637 | nanosleep(&ts, NULL); 638 | 639 | return inject_ok(net, new_port(ERA, 0)); 640 | } 641 | 642 | // Opens a dylib at the provided path. 643 | // `argm` is a tuple of `filename` and `lazy`. 644 | // `filename` is a λ-encoded string. 645 | // `lazy` is a `bool` indicating if functions should be lazily loaded. 646 | // Returns: Result> 647 | Port io_dl_open(Net* net, Book* book, Port argm) { 648 | Tup tup = readback_tup(net, book, argm, 2); 649 | Str str = readback_str(net, book, tup.elem_buf[0]); 650 | u32 lazy = get_u24(get_val(tup.elem_buf[1])); 651 | 652 | int flags = lazy ? RTLD_LAZY : RTLD_NOW; 653 | 654 | for (u32 dl = 0; dl < sizeof(DYLIBS); dl++) { 655 | if (DYLIBS[dl] == NULL) { 656 | DYLIBS[dl] = dlopen(str.buf, flags); 657 | 658 | free(str.buf); 659 | 660 | if (DYLIBS[dl] == NULL) { 661 | return inject_io_err_str(net, dlerror()); 662 | } 663 | 664 | return inject_ok(net, new_port(NUM, new_u24(dl))); 665 | } 666 | } 667 | 668 | return inject_io_err_str(net, "too many open dylibs"); 669 | } 670 | 671 | // Calls a function from a loaded dylib. 672 | // `argm` is a 3-tuple of `dylib_handle`, `symbol`, `args`. 673 | // `dylib_handle` is the numeric node returned from a `DL_OPEN` call. 674 | // `symbol` is a λ-encoded string of the symbol name. 675 | // `args` is the argument to be provided to the dylib symbol. 676 | // 677 | // This function returns a Result with an Ok variant containing an 678 | // arbitrary type. 679 | // 680 | // Returns Result> 681 | Port io_dl_call(Net* net, Book* book, Port argm) { 682 | Tup tup = readback_tup(net, book, argm, 3); 683 | if (tup.elem_len != 3) { 684 | return inject_io_err_type(net); 685 | } 686 | 687 | void* dl = readback_dylib(tup.elem_buf[0]); 688 | Str symbol = readback_str(net, book, tup.elem_buf[1]); 689 | 690 | dlerror(); 691 | Port (*func)(Net*, Book*, Port) = dlsym(dl, symbol.buf); 692 | char* error = dlerror(); 693 | if (error != NULL) { 694 | return inject_io_err_str(net, error); 695 | } 696 | 697 | return inject_ok(net, func(net, book, tup.elem_buf[2])); 698 | } 699 | 700 | // Closes a loaded dylib, reclaiming the handle. 701 | // 702 | // Returns: Result<*, IOError> 703 | Port io_dl_close(Net* net, Book* book, Port argm) { 704 | void* dl = readback_dylib(argm); 705 | if (dl == NULL) { 706 | return inject_io_err_type(net); 707 | } 708 | 709 | int err = dlclose(dl) != 0; 710 | if (err != 0) { 711 | return inject_io_err_str(net, dlerror()); 712 | } 713 | 714 | DYLIBS[get_u24(get_val(argm))] = NULL; 715 | 716 | return inject_ok(net, new_port(ERA, 0)); 717 | } 718 | 719 | // Book Loader 720 | // ----------- 721 | 722 | void book_init(Book* book) { 723 | book->ffns_buf[book->ffns_len++] = (FFn){"READ", io_read}; 724 | book->ffns_buf[book->ffns_len++] = (FFn){"OPEN", io_open}; 725 | book->ffns_buf[book->ffns_len++] = (FFn){"CLOSE", io_close}; 726 | book->ffns_buf[book->ffns_len++] = (FFn){"FLUSH", io_flush}; 727 | book->ffns_buf[book->ffns_len++] = (FFn){"WRITE", io_write}; 728 | book->ffns_buf[book->ffns_len++] = (FFn){"SEEK", io_seek}; 729 | book->ffns_buf[book->ffns_len++] = (FFn){"GET_TIME", io_get_time}; 730 | book->ffns_buf[book->ffns_len++] = (FFn){"SLEEP", io_sleep}; 731 | book->ffns_buf[book->ffns_len++] = (FFn){"DL_OPEN", io_dl_open}; 732 | book->ffns_buf[book->ffns_len++] = (FFn){"DL_CALL", io_dl_call}; 733 | book->ffns_buf[book->ffns_len++] = (FFn){"DL_CLOSE", io_dl_open}; 734 | } 735 | 736 | // Monadic IO Evaluator 737 | // --------------------- 738 | 739 | // Runs an IO computation. 740 | void do_run_io(Net* net, Book* book, Port port) { 741 | book_init(book); 742 | 743 | setlinebuf(stdout); 744 | setlinebuf(stderr); 745 | 746 | // IO loop 747 | while (true) { 748 | // Normalizes the net 749 | normalize(net, book); 750 | 751 | // Reads the λ-Encoded Ctr 752 | Ctr ctr = readback_ctr(net, book, peek(net, port)); 753 | 754 | // Checks if IO Magic Number is a CON 755 | if (ctr.args_len < 1 || get_tag(ctr.args_buf[0]) != CON) { 756 | break; 757 | } 758 | 759 | // Checks the IO Magic Number 760 | Pair io_magic = node_load(net, get_val(ctr.args_buf[0])); 761 | //printf("%08x %08x\n", get_u24(get_val(get_fst(io_magic))), get_u24(get_val(get_snd(io_magic)))); 762 | if (get_val(get_fst(io_magic)) != new_u24(IO_MAGIC_0) || get_val(get_snd(io_magic)) != new_u24(IO_MAGIC_1)) { 763 | break; 764 | } 765 | 766 | switch (ctr.tag) { 767 | case IO_CALL: { 768 | if (ctr.args_len != 4) { 769 | fprintf(stderr, "invalid IO_CALL: args_len = %u\n", ctr.args_len); 770 | break; 771 | } 772 | 773 | Str func = readback_str(net, book, ctr.args_buf[1]); 774 | FFn* ffn = NULL; 775 | // FIXME: optimize this linear search 776 | for (u32 fid = 0; fid < book->ffns_len; ++fid) { 777 | if (strcmp(func.buf, book->ffns_buf[fid].name) == 0) { 778 | ffn = &book->ffns_buf[fid]; 779 | break; 780 | } 781 | } 782 | 783 | free(func.buf); 784 | 785 | Port argm = ctr.args_buf[2]; 786 | Port cont = ctr.args_buf[3]; 787 | 788 | Port ret; 789 | if (ffn == NULL) { 790 | ret = inject_io_err_name(net); 791 | } else { 792 | ret = ffn->func(net, book, argm); 793 | }; 794 | 795 | u32 lps = 0; 796 | u32 loc = node_alloc_1(net, tm[0], &lps); 797 | node_create(net, loc, new_pair(ret, ROOT)); 798 | boot_redex(net, new_pair(new_port(CON, loc), cont)); 799 | port = ROOT; 800 | 801 | continue; 802 | } 803 | 804 | case IO_DONE: { 805 | break; 806 | } 807 | } 808 | break; 809 | } 810 | } 811 | -------------------------------------------------------------------------------- /src/run.cu: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "hvm.cu" 5 | 6 | // Readback: λ-Encoded Ctr 7 | struct Ctr { 8 | u32 tag; 9 | u32 args_len; 10 | Port args_buf[16]; 11 | }; 12 | 13 | // Readback: Tuples 14 | struct Tup { 15 | u32 elem_len; 16 | Port elem_buf[8]; 17 | }; 18 | 19 | // Readback: λ-Encoded Str (UTF-32) 20 | // FIXME: this is actually ASCII :| 21 | struct Str { 22 | u32 len; 23 | char* buf; 24 | }; 25 | 26 | // Readback: λ-Encoded list of bytes 27 | typedef struct Bytes { 28 | u32 len; 29 | char *buf; 30 | } Bytes; 31 | 32 | // IO Magic Number 33 | #define IO_MAGIC_0 0xD0CA11 34 | #define IO_MAGIC_1 0xFF1FF1 35 | 36 | // IO Tags 37 | #define IO_DONE 0 38 | #define IO_CALL 1 39 | 40 | // Result Tags 41 | #define RESULT_OK 0 42 | #define RESULT_ERR 1 43 | 44 | // IOError = { 45 | // Type, -- a type error 46 | // Name, -- invalid io func name 47 | // Inner {val: T}, -- an error while calling an io func 48 | // } 49 | #define IO_ERR_TYPE 0 50 | #define IO_ERR_NAME 1 51 | #define IO_ERR_INNER 2 52 | 53 | typedef struct IOError { 54 | u32 tag; 55 | Port val; 56 | } IOError; 57 | 58 | // List Type 59 | #define LIST_NIL 0 60 | #define LIST_CONS 1 61 | 62 | // Readback 63 | // -------- 64 | 65 | // Reads back a λ-Encoded constructor from device to host. 66 | // Encoding: λt ((((t TAG) arg0) arg1) ...) 67 | Ctr gnet_readback_ctr(GNet* gnet, Port port) { 68 | Ctr ctr; 69 | ctr.tag = -1; 70 | ctr.args_len = 0; 71 | 72 | // Loads root lambda 73 | Port lam_port = gnet_expand(gnet, port); 74 | if (get_tag(lam_port) != CON) return ctr; 75 | Pair lam_node = gnet_node_load(gnet, get_val(lam_port)); 76 | 77 | // Loads first application 78 | Port app_port = gnet_expand(gnet, get_fst(lam_node)); 79 | if (get_tag(app_port) != CON) return ctr; 80 | Pair app_node = gnet_node_load(gnet, get_val(app_port)); 81 | 82 | // Loads first argument (as the tag) 83 | Port arg_port = gnet_expand(gnet, get_fst(app_node)); 84 | if (get_tag(arg_port) != NUM) return ctr; 85 | ctr.tag = get_u24(get_val(arg_port)); 86 | 87 | // Loads remaining arguments 88 | while (true) { 89 | app_port = gnet_expand(gnet, get_snd(app_node)); 90 | if (get_tag(app_port) != CON) break; 91 | app_node = gnet_node_load(gnet, get_val(app_port)); 92 | arg_port = gnet_expand(gnet, get_fst(app_node)); 93 | ctr.args_buf[ctr.args_len++] = arg_port; 94 | } 95 | 96 | return ctr; 97 | } 98 | 99 | // Reads back a tuple of at most `size` elements. Tuples are 100 | // (right-nested con nodes) (CON 1 (CON 2 (CON 3 (...)))) 101 | // The provided `port` should be `expanded` before calling. 102 | extern "C" Tup gnet_readback_tup(GNet* gnet, Port port, u32 size) { 103 | Tup tup; 104 | tup.elem_len = 0; 105 | 106 | // Loads remaining arguments 107 | while (get_tag(port) == CON && (tup.elem_len + 1 < size)) { 108 | Pair node = gnet_node_load(gnet, get_val(port)); 109 | tup.elem_buf[tup.elem_len++] = gnet_expand(gnet, get_fst(node)); 110 | 111 | port = gnet_expand(gnet, get_snd(node)); 112 | } 113 | 114 | tup.elem_buf[tup.elem_len++] = port; 115 | 116 | return tup; 117 | } 118 | 119 | 120 | // Converts a Port into a list of bytes. 121 | // Encoding: 122 | // - λt (t NIL) 123 | // - λt (((t CONS) head) tail) 124 | extern "C" Bytes gnet_readback_bytes(GNet* gnet, Port port) { 125 | // Result 126 | Bytes bytes; 127 | u32 capacity = 256; 128 | bytes.buf = (char*) malloc(sizeof(char) * capacity); 129 | bytes.len = 0; 130 | 131 | // Readback loop 132 | while (true) { 133 | // Normalizes the net 134 | gnet_normalize(gnet); 135 | 136 | // Reads the λ-Encoded Ctr 137 | Ctr ctr = gnet_readback_ctr(gnet, gnet_peek(gnet, port)); 138 | 139 | // Reads string layer 140 | switch (ctr.tag) { 141 | case LIST_NIL: { 142 | break; 143 | } 144 | case LIST_CONS: { 145 | if (ctr.args_len != 2) break; 146 | if (get_tag(ctr.args_buf[0]) != NUM) break; 147 | 148 | if (bytes.len == capacity - 1) { 149 | capacity *= 2; 150 | bytes.buf = (char*) realloc(bytes.buf, capacity); 151 | } 152 | 153 | bytes.buf[bytes.len++] = get_u24(get_val(ctr.args_buf[0])); 154 | gnet_boot_redex(gnet, new_pair(ctr.args_buf[1], ROOT)); 155 | port = ROOT; 156 | continue; 157 | } 158 | } 159 | break; 160 | } 161 | 162 | return bytes; 163 | } 164 | 165 | // Reads back a UTF-32 (truncated to 24 bits) string. 166 | // Since unicode scalars can fit in 21 bits, HVM's u24 167 | // integers can contain any unicode scalar value. 168 | // Encoding: 169 | // - λt (t NIL) 170 | // - λt (((t CONS) head) tail) 171 | extern "C" Str gnet_readback_str(GNet* gnet, Port port) { 172 | // gnet_readback_bytes is guaranteed to return a buffer with a capacity of at least one more 173 | // than the number of bytes read, so we can null-terminate it. 174 | Bytes bytes = gnet_readback_bytes(gnet, port); 175 | 176 | Str str; 177 | str.len = bytes.len; 178 | str.buf = bytes.buf; 179 | str.buf[str.len] = 0; 180 | 181 | return str; 182 | } 183 | 184 | 185 | /// Returns a λ-Encoded Ctr for a NIL: λt (t NIL) 186 | /// Should only be called within `inject_bytes`, as a previous call 187 | /// to `get_resources` is expected. 188 | __device__ Port inject_nil(Net* net, TM* tm) { 189 | u32 v1 = tm->vloc[0]; 190 | 191 | u32 n1 = tm->nloc[0]; 192 | u32 n2 = tm->nloc[1]; 193 | 194 | vars_create(net, v1, NONE); 195 | Port var = new_port(VAR, v1); 196 | 197 | node_create(net, n1, new_pair(new_port(NUM, new_u24(LIST_NIL)), var)); 198 | node_create(net, n2, new_pair(new_port(CON, n1), var)); 199 | 200 | return new_port(CON, n2); 201 | } 202 | 203 | /// Returns a λ-Encoded Ctr for a CONS: λt (((t CONS) head) tail) 204 | /// Should only be called within `inject_bytes`, as a previous call 205 | /// to `get_resources` is expected. 206 | /// The `char_idx` parameter is used to offset the vloc and nloc 207 | /// allocations, otherwise they would conflict with each other on 208 | /// subsequent calls. 209 | __device__ Port inject_cons(Net* net, TM* tm, Port head, Port tail) { 210 | u32 v1 = tm->vloc[0]; 211 | 212 | u32 n1 = tm->nloc[0]; 213 | u32 n2 = tm->nloc[1]; 214 | u32 n3 = tm->nloc[2]; 215 | u32 n4 = tm->nloc[3]; 216 | 217 | vars_create(net, v1, NONE); 218 | Port var = new_port(VAR, v1); 219 | 220 | node_create(net, n1, new_pair(tail, var)); 221 | node_create(net, n2, new_pair(head, new_port(CON, n1))); 222 | node_create(net, n3, new_pair(new_port(NUM, new_u24(LIST_CONS)), new_port(CON, n2))); 223 | node_create(net, n4, new_pair(new_port(CON, n3), var)); 224 | 225 | return new_port(CON, n4); 226 | } 227 | 228 | // Converts a list of bytes to a Port. 229 | // Encoding: 230 | // - λt (t NIL) 231 | // - λt (((t CONS) head) tail) 232 | __device__ Port inject_bytes(Net* net, TM* tm, Bytes *bytes) { 233 | // Allocate all resources up front: 234 | // - NIL needs 2 nodes & 1 var 235 | // - CONS needs 4 nodes & 1 var 236 | u32 len = bytes->len; 237 | 238 | if (!get_resources(net, tm, 0, 2, 1)) { 239 | return new_port(ERA, 0); 240 | } 241 | Port port = inject_nil(net, tm); 242 | 243 | for (u32 i = 0; i < len; i++) { 244 | if (!get_resources(net, tm, 0, 4, 1)) { 245 | return new_port(ERA, 0); 246 | } 247 | 248 | Port byte = new_port(NUM, new_u24(bytes->buf[len - i - 1])); 249 | port = inject_cons(net, tm, byte, port); 250 | } 251 | 252 | return port; 253 | } 254 | 255 | __global__ void make_bytes_port(GNet* gnet, Bytes bytes, Port* ret) { 256 | if (GID() == 0) { 257 | TM tm = tmem_new(); 258 | Net net = vnet_new(gnet, NULL, gnet->turn); 259 | *ret = inject_bytes(&net, &tm, &bytes); 260 | } 261 | } 262 | 263 | // Converts a list of bytes to a Port. 264 | // Encoding: 265 | // - λt (t NIL) 266 | // - λt (((t CONS) head) tail) 267 | extern "C" Port gnet_inject_bytes(GNet* gnet, Bytes *bytes) { 268 | Port* d_ret; 269 | cudaMalloc(&d_ret, sizeof(Port)); 270 | 271 | Bytes bytes_cu; 272 | bytes_cu.len = bytes->len; 273 | 274 | cudaMalloc(&bytes_cu.buf, sizeof(char) * bytes_cu.len); 275 | cudaMemcpy(bytes_cu.buf, bytes->buf, sizeof(char) * bytes_cu.len, cudaMemcpyHostToDevice); 276 | 277 | make_bytes_port<<<1,1>>>(gnet, bytes_cu, d_ret); 278 | 279 | Port ret; 280 | cudaMemcpy(&ret, d_ret, sizeof(Port), cudaMemcpyDeviceToHost); 281 | cudaFree(d_ret); 282 | cudaFree(bytes_cu.buf); 283 | 284 | return ret; 285 | } 286 | 287 | /// Returns a λ-Encoded Ctr for a RESULT_OK: λt ((t RESULT_OK) val) 288 | __device__ Port inject_ok(Net* net, TM* tm, Port val) { 289 | if (!get_resources(net, tm, 0, 3, 1)) { 290 | printf("inject_ok: failed to get resources\n"); 291 | return new_port(ERA, 0); 292 | } 293 | 294 | u32 v1 = tm->vloc[0]; 295 | 296 | u32 n1 = tm->nloc[0]; 297 | u32 n2 = tm->nloc[1]; 298 | u32 n3 = tm->nloc[2]; 299 | 300 | vars_create(net, v1, NONE); 301 | Port var = new_port(VAR, v1); 302 | 303 | node_create(net, n1, new_pair(val, var)); 304 | node_create(net, n2, new_pair(new_port(NUM, new_u24(RESULT_OK)), new_port(CON, n1))); 305 | node_create(net, n3, new_pair(new_port(CON, n2), var)); 306 | 307 | return new_port(CON, n3); 308 | } 309 | 310 | __global__ void make_ok_port(GNet* gnet, Port val, Port* ret) { 311 | if (GID() == 0) { 312 | TM tm = tmem_new(); 313 | Net net = vnet_new(gnet, NULL, gnet->turn); 314 | *ret = inject_ok(&net, &tm, val); 315 | } 316 | } 317 | 318 | extern "C" Port gnet_inject_ok(GNet* gnet, Port val) { 319 | Port* d_ret; 320 | cudaMalloc(&d_ret, sizeof(Port)); 321 | 322 | make_ok_port<<<1,1>>>(gnet, val, d_ret); 323 | 324 | Port ret; 325 | cudaMemcpy(&ret, d_ret, sizeof(Port), cudaMemcpyDeviceToHost); 326 | cudaFree(d_ret); 327 | 328 | return ret; 329 | } 330 | 331 | /// Returns a λ-Encoded Ctr for a RESULT_ERR: λt ((t RESULT_ERR) err) 332 | __device__ Port inject_err(Net* net, TM* tm, Port err) { 333 | if (!get_resources(net, tm, 0, 3, 1)) { 334 | printf("inject_err: failed to get resources\n"); 335 | return new_port(ERA, 0); 336 | } 337 | 338 | u32 v1 = tm->vloc[0]; 339 | 340 | u32 n1 = tm->nloc[0]; 341 | u32 n2 = tm->nloc[1]; 342 | u32 n3 = tm->nloc[2]; 343 | 344 | vars_create(net, v1, NONE); 345 | Port var = new_port(VAR, v1); 346 | 347 | node_create(net, n1, new_pair(err, var)); 348 | node_create(net, n2, new_pair(new_port(NUM, new_u24(RESULT_ERR)), new_port(CON, n1))); 349 | node_create(net, n3, new_pair(new_port(CON, n2), var)); 350 | 351 | return new_port(CON, n3); 352 | } 353 | 354 | 355 | __global__ void make_err_port(GNet* gnet, Port val, Port* ret) { 356 | if (GID() == 0) { 357 | TM tm = tmem_new(); 358 | Net net = vnet_new(gnet, NULL, gnet->turn); 359 | *ret = inject_err(&net, &tm, val); 360 | } 361 | } 362 | 363 | extern "C" Port gnet_inject_err(GNet* gnet, Port val) { 364 | Port* d_ret; 365 | cudaMalloc(&d_ret, sizeof(Port)); 366 | 367 | make_err_port<<<1,1>>>(gnet, val, d_ret); 368 | 369 | Port ret; 370 | cudaMemcpy(&ret, d_ret, sizeof(Port), cudaMemcpyDeviceToHost); 371 | cudaFree(d_ret); 372 | 373 | return ret; 374 | } 375 | 376 | /// Returns a λ-Encoded Ctr for a Result/Err(IOError(..)) 377 | __device__ Port inject_io_err(Net* net, TM* tm, IOError err) { 378 | if (err.tag <= IO_ERR_NAME) { 379 | if (!get_resources(net, tm, 0, 2, 1)) { 380 | return new_port(ERA, 0); 381 | } 382 | 383 | u32 v1 = tm->vloc[0]; 384 | 385 | u32 n1 = tm->nloc[0]; 386 | u32 n2 = tm->nloc[1]; 387 | 388 | vars_create(net, v1, NONE); 389 | Port var = new_port(VAR, v1); 390 | 391 | node_create(net, n1, new_pair(new_port(NUM, new_u24(err.tag)), var)); 392 | node_create(net, n2, new_pair(new_port(CON, n1), var)); 393 | 394 | return inject_err(net, tm, new_port(CON, n2)); 395 | } 396 | 397 | if (!get_resources(net, tm, 0, 3, 1)) { 398 | return new_port(ERA, 0); 399 | } 400 | 401 | u32 v1 = tm->vloc[0]; 402 | 403 | u32 n1 = tm->nloc[0]; 404 | u32 n2 = tm->nloc[1]; 405 | u32 n3 = tm->nloc[2]; 406 | 407 | vars_create(net, v1, NONE); 408 | Port var = new_port(VAR, v1); 409 | 410 | node_create(net, n1, new_pair(err.val, var)); 411 | node_create(net, n2, new_pair(new_port(NUM, new_u24(IO_ERR_INNER)), new_port(CON, n1))); 412 | node_create(net, n3, new_pair(new_port(CON, n2), var)); 413 | 414 | return inject_err(net, tm, new_port(CON, n3)); 415 | } 416 | 417 | __global__ void make_io_err_port(GNet* gnet, IOError err, Port* ret) { 418 | if (GID() == 0) { 419 | TM tm = tmem_new(); 420 | Net net = vnet_new(gnet, NULL, gnet->turn); 421 | *ret = inject_io_err(&net, &tm, err); 422 | } 423 | } 424 | 425 | extern "C" Port gnet_inject_io_err(GNet* gnet, IOError err) { 426 | Port* d_ret; 427 | cudaMalloc(&d_ret, sizeof(Port)); 428 | 429 | make_io_err_port<<<1,1>>>(gnet, err, d_ret); 430 | 431 | Port ret; 432 | cudaMemcpy(&ret, d_ret, sizeof(Port), cudaMemcpyDeviceToHost); 433 | cudaFree(d_ret); 434 | 435 | return ret; 436 | } 437 | 438 | /// Returns a λ-Encoded Ctr for a Result/Err(IOError/Type) 439 | extern "C" Port gnet_inject_io_err_type(GNet* gnet) { 440 | IOError io_error = { 441 | .tag = IO_ERR_TYPE, 442 | }; 443 | 444 | return gnet_inject_io_err(gnet, io_error); 445 | } 446 | 447 | /// Returns a λ-Encoded Ctr for a Result/Err(IOError/Name) 448 | extern "C" Port gnet_inject_io_err_name(GNet* gnet) { 449 | IOError io_error = { 450 | .tag = IO_ERR_NAME, 451 | }; 452 | 453 | return gnet_inject_io_err(gnet, io_error); 454 | } 455 | 456 | /// Returns a λ-Encoded Ctr for a Result/Err(IOError/Inner(val)) 457 | extern "C" Port gnet_inject_io_err_inner(GNet* gnet, Port val) { 458 | IOError io_error = { 459 | .tag = IO_ERR_INNER, 460 | .val = val, 461 | }; 462 | 463 | return gnet_inject_io_err(gnet, io_error); 464 | } 465 | 466 | /// Returns a λ-Encoded Ctr for an Result> 467 | /// `err` must be `NUL`-terminated. 468 | extern "C" Port gnet_inject_io_err_str(GNet* gnet, char* err) { 469 | Port* d_bytes_port; 470 | cudaMalloc(&d_bytes_port, sizeof(Port)); 471 | 472 | Bytes bytes_cu; 473 | bytes_cu.len = strlen(err); 474 | 475 | cudaMalloc(&bytes_cu.buf, sizeof(char) * bytes_cu.len); 476 | cudaMemcpy(bytes_cu.buf, err, sizeof(char) * bytes_cu.len, cudaMemcpyHostToDevice); 477 | 478 | make_bytes_port<<<1,1>>>(gnet, bytes_cu, d_bytes_port); 479 | 480 | Port bytes_port; 481 | cudaMemcpy(&bytes_port, d_bytes_port, sizeof(Port), cudaMemcpyDeviceToHost); 482 | cudaFree(d_bytes_port); 483 | 484 | cudaFree(bytes_cu.buf); 485 | 486 | return gnet_inject_io_err_inner(gnet, bytes_port); 487 | } 488 | 489 | 490 | // Primitive IO Fns 491 | // ----------------- 492 | 493 | // Open file pointers. Indices into this array 494 | // are used as "file descriptors". 495 | // Indices 0 1 and 2 are reserved. 496 | // - 0 -> stdin 497 | // - 1 -> stdout 498 | // - 2 -> stderr 499 | static FILE* FILE_POINTERS[256]; 500 | 501 | // Open dylibs handles. Indices into this array 502 | // are used as opaque loadedd object "handles". 503 | static void* DYLIBS[256]; 504 | 505 | // Converts a NUM port (file descriptor) to file pointer. 506 | FILE* readback_file(Port port) { 507 | if (get_tag(port) != NUM) { 508 | fprintf(stderr, "non-num where file descriptor was expected: %s\n", show_port(port).x); 509 | return NULL; 510 | } 511 | 512 | u32 idx = get_u24(get_val(port)); 513 | 514 | if (idx == 0) return stdin; 515 | if (idx == 1) return stdout; 516 | if (idx == 2) return stderr; 517 | 518 | FILE* fp = FILE_POINTERS[idx]; 519 | if (fp == NULL) { 520 | fprintf(stderr, "invalid file descriptor\n"); 521 | return NULL; 522 | } 523 | 524 | return fp; 525 | } 526 | 527 | // Converts a NUM port (dylib handle) to an opaque dylib object. 528 | void* readback_dylib(Port port) { 529 | if (get_tag(port) != NUM) { 530 | fprintf(stderr, "non-num where dylib handle was expected: %i\n", get_tag(port)); 531 | return NULL; 532 | } 533 | 534 | u32 idx = get_u24(get_val(port)); 535 | 536 | void* dl = DYLIBS[idx]; 537 | if (dl == NULL) { 538 | fprintf(stderr, "invalid dylib handle\n"); 539 | return NULL; 540 | } 541 | 542 | return dl; 543 | } 544 | 545 | // Reads from a file a specified number of bytes. 546 | // `argm` is a tuple of (file_descriptor, num_bytes). 547 | // Returns: Result> 548 | Port io_read(GNet* gnet, Port argm) { 549 | Tup tup = gnet_readback_tup(gnet, argm, 2); 550 | if (tup.elem_len != 2) { 551 | fprintf(stderr, "io_read: expected 2-tuple\n"); 552 | return gnet_inject_io_err_type(gnet); 553 | } 554 | 555 | FILE* fp = readback_file(tup.elem_buf[0]); 556 | u32 num_bytes = get_u24(get_val(tup.elem_buf[1])); 557 | 558 | if (fp == NULL) { 559 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(EBADF))); 560 | } 561 | 562 | /// Read a string. 563 | Bytes bytes; 564 | bytes.buf = (char*) malloc(sizeof(char) * num_bytes); 565 | bytes.len = fread(bytes.buf, sizeof(char), num_bytes, fp); 566 | 567 | if ((bytes.len != num_bytes) && ferror(fp)) { 568 | fprintf(stderr, "io_read: failed to read\n"); 569 | free(bytes.buf); 570 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(ferror(fp)))); 571 | } 572 | 573 | // Convert it to a port. 574 | Port ret = gnet_inject_bytes(gnet, &bytes); 575 | free(bytes.buf); 576 | 577 | return gnet_inject_ok(gnet, ret); 578 | } 579 | 580 | // Opens a file with the provided mode. 581 | // `argm` is a tuple (CON node) of the 582 | // file name and mode as strings. 583 | // Returns: Result> 584 | Port io_open(GNet* gnet, Port argm) { 585 | Tup tup = gnet_readback_tup(gnet, argm, 2); 586 | if (tup.elem_len != 2) { 587 | return gnet_inject_io_err_type(gnet); 588 | } 589 | 590 | Str name = gnet_readback_str(gnet, tup.elem_buf[0]); 591 | Str mode = gnet_readback_str(gnet, tup.elem_buf[1]); 592 | 593 | for (u32 fd = 3; fd < sizeof(FILE_POINTERS); fd++) { 594 | if (FILE_POINTERS[fd] == NULL) { 595 | FILE_POINTERS[fd] = fopen(name.buf, mode.buf); 596 | 597 | free(name.buf); 598 | free(mode.buf); 599 | 600 | if (FILE_POINTERS[fd] == NULL) { 601 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(errno))); 602 | } 603 | 604 | return gnet_inject_ok(gnet, new_port(NUM, new_u24(fd))); 605 | } 606 | } 607 | 608 | free(name.buf); 609 | free(mode.buf); 610 | 611 | // too many open files 612 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(EMFILE))); 613 | } 614 | 615 | // Closes a file, reclaiming the file descriptor. 616 | // Returns: Result<*, IOError> 617 | Port io_close(GNet* gnet, Port argm) { 618 | FILE* fp = readback_file(argm); 619 | if (fp == NULL) { 620 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(EBADF))); 621 | } 622 | 623 | if (fclose(fp) != 0) { 624 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(ferror(fp)))); 625 | } 626 | 627 | FILE_POINTERS[get_u24(get_val(argm))] = NULL; 628 | 629 | return gnet_inject_ok(gnet, new_port(ERA, 0)); 630 | } 631 | 632 | // Writes a list of bytes to a file. 633 | // `argm` is a tuple (CON node) of the 634 | // file descriptor and list of bytes to write. 635 | // Returns: Result<*, IOError> 636 | Port io_write(GNet* gnet, Port argm) { 637 | Tup tup = gnet_readback_tup(gnet, argm, 2); 638 | if (tup.elem_len != 2) { 639 | return gnet_inject_io_err_type(gnet); 640 | } 641 | 642 | FILE* fp = readback_file(tup.elem_buf[0]); 643 | Bytes bytes = gnet_readback_bytes(gnet, tup.elem_buf[1]); 644 | 645 | if (fp == NULL) { 646 | free(bytes.buf); 647 | 648 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(EBADF))); 649 | } 650 | 651 | if (fwrite(bytes.buf, sizeof(char), bytes.len, fp) != bytes.len) { 652 | free(bytes.buf); 653 | 654 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(ferror(fp)))); 655 | } 656 | 657 | free(bytes.buf); 658 | 659 | return gnet_inject_ok(gnet, new_port(ERA, 0)); 660 | } 661 | 662 | // Flushes an output stream. 663 | // Returns: Result<*, IOError> 664 | Port io_flush(GNet* gnet, Port argm) { 665 | FILE* fp = readback_file(argm); 666 | if (fp == NULL) { 667 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(EBADF))); 668 | } 669 | 670 | if (fflush(fp) != 0) { 671 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(ferror(fp)))); 672 | } 673 | 674 | return gnet_inject_ok(gnet, new_port(ERA, 0)); 675 | } 676 | 677 | // Seeks to a position in a file. 678 | // `argm` is a 3-tuple (CON fd (CON offset whence)), where 679 | // - fd is a file descriptor 680 | // - offset is a signed byte offset 681 | // - whence is what that offset is relative to: 682 | // - 0 (SEEK_SET): beginning of file 683 | // - 1 (SEEK_CUR): current position of the file pointer 684 | // - 2 (SEEK_END): end of the file 685 | // Returns: Result<*, IOError> 686 | Port io_seek(GNet* gnet, Port argm) { 687 | Tup tup = gnet_readback_tup(gnet, argm, 3); 688 | if (tup.elem_len != 3) { 689 | fprintf(stderr, "io_seek: expected 3-tuple\n"); 690 | return gnet_inject_io_err_type(gnet); 691 | } 692 | 693 | FILE* fp = readback_file(tup.elem_buf[0]); 694 | i32 offset = get_i24(get_val(tup.elem_buf[1])); 695 | u32 whence = get_i24(get_val(tup.elem_buf[2])); 696 | 697 | if (fp == NULL) { 698 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(EBADF))); 699 | } 700 | 701 | int cwhence; 702 | switch (whence) { 703 | case 0: cwhence = SEEK_SET; break; 704 | case 1: cwhence = SEEK_CUR; break; 705 | case 2: cwhence = SEEK_END; break; 706 | default: 707 | return gnet_inject_io_err_type(gnet); 708 | } 709 | 710 | if (fseek(fp, offset, cwhence) != 0) { 711 | return gnet_inject_io_err_inner(gnet, new_port(NUM, new_i24(ferror(fp)))); 712 | } 713 | 714 | return gnet_inject_ok(gnet, new_port(ERA, 0)); 715 | } 716 | 717 | // Returns the current time as a tuple of the high 718 | // and low 24 bits of a 48-bit nanosecond timestamp. 719 | // Returns: Result<(u24, u24), IOError<*>> 720 | Port io_get_time(GNet* gnet, Port argm) { 721 | // Get the current time in nanoseconds 722 | u64 time_ns = time64(); 723 | // Encode the time as a 64-bit unsigned integer 724 | u32 time_hi = (u32)(time_ns >> 24) & 0xFFFFFFF; 725 | u32 time_lo = (u32)(time_ns & 0xFFFFFFF); 726 | // Return the encoded time 727 | return gnet_make_node(gnet, CON, new_port(NUM, new_u24(time_hi)), new_port(NUM, new_u24(time_lo))); 728 | } 729 | 730 | // Sleeps. 731 | // `argm` is a tuple (CON node) of the high and low 732 | // 24 bits for a 48-bit duration in nanoseconds. 733 | // Returns: Result<*, IOError<*>> 734 | Port io_sleep(GNet* gnet, Port argm) { 735 | Tup tup = gnet_readback_tup(gnet, argm, 2); 736 | if (tup.elem_len != 2) { 737 | return gnet_inject_io_err_type(gnet); 738 | } 739 | 740 | // Get the sleep duration node 741 | Pair dur_node = gnet_node_load(gnet, get_val(argm)); 742 | // Get the high and low 24-bit parts of the duration 743 | u32 dur_hi = get_u24(get_val(tup.elem_buf[0])); 744 | u32 dur_lo = get_u24(get_val(tup.elem_buf[1])); 745 | // Combine into a 48-bit duration in nanoseconds 746 | u64 dur_ns = (((u64)dur_hi) << 24) | dur_lo; 747 | // Sleep for the specified duration 748 | struct timespec ts; 749 | ts.tv_sec = dur_ns / 1000000000; 750 | ts.tv_nsec = dur_ns % 1000000000; 751 | nanosleep(&ts, NULL); 752 | 753 | return gnet_inject_ok(gnet, new_port(ERA, 0)); 754 | } 755 | 756 | // Opens a dylib at the provided path. 757 | // `argm` is a tuple of `filename` and `lazy`. 758 | // `filename` is a λ-encoded string. 759 | // `lazy` is a `bool` indicating if functions should be lazily loaded. 760 | // Returns: Result> 761 | Port io_dl_open(GNet* gnet, Port argm) { 762 | Tup tup = gnet_readback_tup(gnet, argm, 2); 763 | Str str = gnet_readback_str(gnet, tup.elem_buf[0]); 764 | u32 lazy = get_u24(get_val(tup.elem_buf[1])); 765 | 766 | int flags = lazy ? RTLD_LAZY : RTLD_NOW; 767 | 768 | for (u32 dl = 0; dl < sizeof(DYLIBS); dl++) { 769 | if (DYLIBS[dl] == NULL) { 770 | DYLIBS[dl] = dlopen(str.buf, flags); 771 | 772 | free(str.buf); 773 | 774 | if (DYLIBS[dl] == NULL) { 775 | return gnet_inject_io_err_str(gnet, dlerror()); 776 | } 777 | 778 | return gnet_inject_ok(gnet, new_port(NUM, new_u24(dl))); 779 | } 780 | } 781 | 782 | return gnet_inject_io_err_str(gnet, "too many open dylibs"); 783 | } 784 | 785 | // Calls a function from a loaded dylib. 786 | // `argm` is a 3-tuple of `dylib_handle`, `symbol`, `args`. 787 | // `dylib_handle` is the numeric node returned from a `DL_OPEN` call. 788 | // `symbol` is a λ-encoded string of the symbol name. 789 | // `args` is the argument to be provided to the dylib symbol. 790 | // 791 | // This function returns a Result with an Ok variant containing an 792 | // arbitrary type. 793 | // 794 | // Returns Result> 795 | Port io_dl_call(GNet* gnet, Port argm) { 796 | Tup tup = gnet_readback_tup(gnet, argm, 3); 797 | if (tup.elem_len != 3) { 798 | fprintf(stderr, "io_dl_call: expected 3-tuple\n"); 799 | 800 | return gnet_inject_io_err_type(gnet); 801 | } 802 | 803 | void* dl = readback_dylib(tup.elem_buf[0]); 804 | Str symbol = gnet_readback_str(gnet, tup.elem_buf[1]); 805 | 806 | dlerror(); 807 | Port (*func)(GNet*, Port) = (Port (*)(GNet*, Port)) dlsym(dl, symbol.buf); 808 | char* error = dlerror(); 809 | if (error != NULL) { 810 | return gnet_inject_io_err_str(gnet, error); 811 | } 812 | 813 | return gnet_inject_ok(gnet, func(gnet, tup.elem_buf[2])); 814 | } 815 | 816 | // Closes a loaded dylib, reclaiming the handle. 817 | // 818 | // Returns: Result<*, IOError> 819 | Port io_dl_close(GNet* gnet, Book* book, Port argm) { 820 | void* dl = readback_dylib(argm); 821 | if (dl == NULL) { 822 | fprintf(stderr, "io_dl_close: invalid handle\n"); 823 | 824 | return gnet_inject_io_err_type(gnet); 825 | } 826 | 827 | int err = dlclose(dl) != 0; 828 | if (err != 0) { 829 | return gnet_inject_io_err_str(gnet, dlerror()); 830 | } 831 | 832 | DYLIBS[get_u24(get_val(argm))] = NULL; 833 | 834 | return gnet_inject_ok(gnet, new_port(ERA, 0)); 835 | } 836 | 837 | void book_init(Book* book) { 838 | book->ffns_buf[book->ffns_len++] = (FFn){"READ", io_read}; 839 | book->ffns_buf[book->ffns_len++] = (FFn){"OPEN", io_open}; 840 | book->ffns_buf[book->ffns_len++] = (FFn){"CLOSE", io_close}; 841 | book->ffns_buf[book->ffns_len++] = (FFn){"FLUSH", io_flush}; 842 | book->ffns_buf[book->ffns_len++] = (FFn){"WRITE", io_write}; 843 | book->ffns_buf[book->ffns_len++] = (FFn){"SEEK", io_seek}; 844 | book->ffns_buf[book->ffns_len++] = (FFn){"GET_TIME", io_get_time}; 845 | book->ffns_buf[book->ffns_len++] = (FFn){"SLEEP", io_sleep}; 846 | book->ffns_buf[book->ffns_len++] = (FFn){"DL_OPEN", io_dl_open}; 847 | book->ffns_buf[book->ffns_len++] = (FFn){"DL_CALL", io_dl_call}; 848 | book->ffns_buf[book->ffns_len++] = (FFn){"DL_CLOSE", io_dl_open}; 849 | 850 | cudaMemcpyToSymbol(BOOK, book, sizeof(Book)); 851 | } 852 | 853 | // Monadic IO Evaluator 854 | // --------------------- 855 | 856 | // Runs an IO computation. 857 | void do_run_io(GNet* gnet, Book* book, Port port) { 858 | book_init(book); 859 | 860 | setlinebuf(stdout); 861 | setlinebuf(stderr); 862 | 863 | // IO loop 864 | while (true) { 865 | // Normalizes the net 866 | gnet_normalize(gnet); 867 | 868 | // Reads the λ-Encoded Ctr 869 | Ctr ctr = gnet_readback_ctr(gnet, gnet_peek(gnet, port)); 870 | 871 | // Checks if IO Magic Number is a CON 872 | if (ctr.args_len < 1 || get_tag(ctr.args_buf[0]) != CON) { 873 | break; 874 | } 875 | 876 | // Checks the IO Magic Number 877 | Pair io_magic = gnet_node_load(gnet, get_val(ctr.args_buf[0])); 878 | //printf("%08x %08x\n", get_u24(get_val(get_fst(io_magic))), get_u24(get_val(get_snd(io_magic)))); 879 | if (get_val(get_fst(io_magic)) != new_u24(IO_MAGIC_0) || get_val(get_snd(io_magic)) != new_u24(IO_MAGIC_1)) { 880 | break; 881 | } 882 | 883 | switch (ctr.tag) { 884 | case IO_CALL: { 885 | if (ctr.args_len != 4) { 886 | fprintf(stderr, "invalid IO_CALL: args_len = %u\n", ctr.args_len); 887 | break; 888 | } 889 | 890 | Str func = gnet_readback_str(gnet, ctr.args_buf[1]); 891 | FFn* ffn = NULL; 892 | // FIXME: optimize this linear search 893 | for (u32 fid = 0; fid < book->ffns_len; ++fid) { 894 | if (strcmp(func.buf, book->ffns_buf[fid].name) == 0) { 895 | ffn = &book->ffns_buf[fid]; 896 | break; 897 | } 898 | } 899 | 900 | free(func.buf); 901 | 902 | Port argm = ctr.args_buf[2]; 903 | Port cont = ctr.args_buf[3]; 904 | 905 | Port ret; 906 | if (ffn == NULL) { 907 | ret = gnet_inject_io_err_name(gnet); 908 | } else { 909 | ret = ffn->func(gnet, argm); 910 | } 911 | 912 | Port p = gnet_make_node(gnet, CON, ret, ROOT); 913 | gnet_boot_redex(gnet, new_pair(p, cont)); 914 | port = ROOT; 915 | 916 | continue; 917 | } 918 | 919 | case IO_DONE: { 920 | break; 921 | } 922 | } 923 | break; 924 | } 925 | } 926 | -------------------------------------------------------------------------------- /tests/programs/empty.hvm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HigherOrderCO/HVM/654276018084b8f44a22b562dd68ab18583bfb5b/tests/programs/empty.hvm -------------------------------------------------------------------------------- /tests/programs/hello-world.hvm: -------------------------------------------------------------------------------- 1 | @String/Cons = (a (b ((@String/Cons/tag (a (b c))) c))) 2 | 3 | @String/Cons/tag = 1 4 | 5 | @String/Nil = ((@String/Nil/tag a) a) 6 | 7 | @String/Nil/tag = 0 8 | 9 | @main = l 10 | & @String/Cons ~ (104 (k l)) 11 | & @String/Cons ~ (101 (j k)) 12 | & @String/Cons ~ (108 (i j)) 13 | & @String/Cons ~ (108 (h i)) 14 | & @String/Cons ~ (111 (g h)) 15 | & @String/Cons ~ (44 (f g)) 16 | & @String/Cons ~ (32 (e f)) 17 | & @String/Cons ~ (119 (d e)) 18 | & @String/Cons ~ (111 (c d)) 19 | & @String/Cons ~ (114 (b c)) 20 | & @String/Cons ~ (108 (a b)) 21 | & @String/Cons ~ (100 (@String/Nil a)) 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/programs/io/basic.bend: -------------------------------------------------------------------------------- 1 | test-io = 1 2 | 3 | def unwrap(res): 4 | match res: 5 | case Result/Ok: 6 | return res.val 7 | case Result/Err: 8 | return res.val 9 | 10 | def open(): 11 | return call("OPEN", ("./LICENSE", "r")) 12 | 13 | def read(f): 14 | return call("READ", (f, 47)) 15 | 16 | def print(bytes): 17 | with IO: 18 | * <- call("WRITE", (1, bytes)) 19 | * <- call("WRITE", (1, "\n")) 20 | 21 | return wrap(*) 22 | 23 | def close(f): 24 | return call("CLOSE", f) 25 | 26 | def main(): 27 | with IO: 28 | f <- open() 29 | f = unwrap(f) 30 | bytes <- read(f) 31 | bytes = unwrap(bytes) 32 | * <- print(bytes) 33 | res <- close(f) 34 | 35 | return wrap(res) 36 | -------------------------------------------------------------------------------- /tests/programs/io/basic.hvm: -------------------------------------------------------------------------------- 1 | @IO/Call = (a (b (c (d ((@IO/Call/tag (a (b (c (d e))))) e))))) 2 | 3 | @IO/Call/tag = 1 4 | 5 | @IO/Done = (a (b ((@IO/Done/tag (a (b c))) c))) 6 | 7 | @IO/Done/tag = 0 8 | 9 | @IO/MAGIC = (13683217 16719857) 10 | 11 | @IO/bind = ((@IO/bind__C2 a) a) 12 | 13 | @IO/bind__C0 = (* (b (a c))) 14 | & @undefer ~ (a (b c)) 15 | 16 | @IO/bind__C1 = (* (* (a (b ((c d) (e g)))))) 17 | & @IO/Call ~ (@IO/MAGIC (a (b ((c f) g)))) 18 | & @IO/bind ~ (d (e f)) 19 | 20 | @IO/bind__C2 = (?((@IO/bind__C0 @IO/bind__C1) a) a) 21 | 22 | @IO/wrap = a 23 | & @IO/Done ~ (@IO/MAGIC a) 24 | 25 | @String/Cons = (a (b ((@String/Cons/tag (a (b c))) c))) 26 | 27 | @String/Cons/tag = 1 28 | 29 | @String/Nil = ((@String/Nil/tag a) a) 30 | 31 | @String/Nil/tag = 0 32 | 33 | @call = (a (b c)) 34 | & @IO/Call ~ (@IO/MAGIC (a (b (@call__C0 c)))) 35 | 36 | @call__C0 = a 37 | & @IO/Done ~ (@IO/MAGIC a) 38 | 39 | @close = f 40 | & @call ~ (e f) 41 | & @String/Cons ~ (67 (d e)) 42 | & @String/Cons ~ (76 (c d)) 43 | & @String/Cons ~ (79 (b c)) 44 | & @String/Cons ~ (83 (a b)) 45 | & @String/Cons ~ (69 (@String/Nil a)) 46 | 47 | @main = w 48 | & @IO/bind ~ (@open ((((s (a u)) (@IO/wrap v)) v) w)) 49 | & @IO/bind ~ (c ((((n (o (d q))) (r (s t))) t) u)) 50 | & @unwrap ~ (a {b r}) 51 | & @read ~ (b c) 52 | & @IO/bind ~ (f ((((g (k (* m))) (n (o p))) p) q)) 53 | & @print ~ (e f) 54 | & @unwrap ~ (d e) 55 | & @IO/bind ~ (h ((((i i) (k l)) l) m)) 56 | & @close ~ (g h) 57 | 58 | @open = o 59 | & @call ~ (d ((m n) o)) 60 | & @String/Cons ~ (79 (c d)) 61 | & @String/Cons ~ (80 (b c)) 62 | & @String/Cons ~ (69 (a b)) 63 | & @String/Cons ~ (78 (@String/Nil a)) 64 | & @String/Cons ~ (46 (l m)) 65 | & @String/Cons ~ (47 (k l)) 66 | & @String/Cons ~ (76 (j k)) 67 | & @String/Cons ~ (73 (i j)) 68 | & @String/Cons ~ (67 (h i)) 69 | & @String/Cons ~ (69 (g h)) 70 | & @String/Cons ~ (78 (f g)) 71 | & @String/Cons ~ (83 (e f)) 72 | & @String/Cons ~ (69 (@String/Nil e)) 73 | & @String/Cons ~ (114 (@String/Nil n)) 74 | 75 | @print = (f h) 76 | & @IO/bind ~ (g (@print__C3 h)) 77 | & @call ~ (e ((1 f) g)) 78 | & @String/Cons ~ (87 (d e)) 79 | & @String/Cons ~ (82 (c d)) 80 | & @String/Cons ~ (73 (b c)) 81 | & @String/Cons ~ (84 (a b)) 82 | & @String/Cons ~ (69 (@String/Nil a)) 83 | 84 | @print__C0 = ((* a) (* a)) 85 | 86 | @print__C1 = g 87 | & @call ~ (e ((1 f) g)) 88 | & @String/Cons ~ (87 (d e)) 89 | & @String/Cons ~ (82 (c d)) 90 | & @String/Cons ~ (73 (b c)) 91 | & @String/Cons ~ (84 (a b)) 92 | & @String/Cons ~ (69 (@String/Nil a)) 93 | & @String/Cons ~ (10 (@String/Nil f)) 94 | 95 | @print__C2 = (a (* c)) 96 | & @IO/bind ~ (@print__C1 (((@print__C0 (a b)) b) c)) 97 | 98 | @print__C3 = ((@print__C2 (@IO/wrap a)) a) 99 | 100 | @read = (e f) 101 | & @call ~ (d ((e 47) f)) 102 | & @String/Cons ~ (82 (c d)) 103 | & @String/Cons ~ (69 (b c)) 104 | & @String/Cons ~ (65 (a b)) 105 | & @String/Cons ~ (68 (@String/Nil a)) 106 | 107 | @test-io = 1 108 | 109 | @undefer = (((a a) b) b) 110 | 111 | @unwrap = ((@unwrap__C0 a) a) 112 | 113 | @unwrap__C0 = (?(((a a) (* (b b))) c) c) 114 | 115 | 116 | -------------------------------------------------------------------------------- /tests/programs/io/invalid-name.bend: -------------------------------------------------------------------------------- 1 | test-io = 1 2 | 3 | def main(): 4 | with IO: 5 | f <- call("INVALID-NAME", ("./README.md", "r")) 6 | 7 | return wrap(f) 8 | -------------------------------------------------------------------------------- /tests/programs/io/invalid-name.hvm: -------------------------------------------------------------------------------- 1 | @IO/Call = (a (b (c (d ((@IO/Call/tag (a (b (c (d e))))) e))))) 2 | 3 | @IO/Call/tag = 1 4 | 5 | @IO/Done = (a (b ((@IO/Done/tag (a (b c))) c))) 6 | 7 | @IO/Done/tag = 0 8 | 9 | @IO/MAGIC = (13683217 16719857) 10 | 11 | @IO/bind = ((@IO/bind__C2 a) a) 12 | 13 | @IO/bind__C0 = (* (b (a c))) 14 | & @undefer ~ (a (b c)) 15 | 16 | @IO/bind__C1 = (* (* (a (b ((c d) (e g)))))) 17 | & @IO/Call ~ (@IO/MAGIC (a (b ((c f) g)))) 18 | & @IO/bind ~ (d (e f)) 19 | 20 | @IO/bind__C2 = (?((@IO/bind__C0 @IO/bind__C1) a) a) 21 | 22 | @IO/wrap = a 23 | & @IO/Done ~ (@IO/MAGIC a) 24 | 25 | @String/Cons = (a (b ((@String/Cons/tag (a (b c))) c))) 26 | 27 | @String/Cons/tag = 1 28 | 29 | @String/Nil = ((@String/Nil/tag a) a) 30 | 31 | @String/Nil/tag = 0 32 | 33 | @call = (a (b c)) 34 | & @IO/Call ~ (@IO/MAGIC (a (b (@call__C0 c)))) 35 | 36 | @call__C0 = a 37 | & @IO/Done ~ (@IO/MAGIC a) 38 | 39 | @main = cb 40 | & @IO/bind ~ (y ((((z z) (@IO/wrap bb)) bb) cb)) 41 | & @call ~ (l ((w x) y)) 42 | & @String/Cons ~ (73 (k l)) 43 | & @String/Cons ~ (78 (j k)) 44 | & @String/Cons ~ (86 (i j)) 45 | & @String/Cons ~ (65 (h i)) 46 | & @String/Cons ~ (76 (g h)) 47 | & @String/Cons ~ (73 (f g)) 48 | & @String/Cons ~ (68 (e f)) 49 | & @String/Cons ~ (45 (d e)) 50 | & @String/Cons ~ (78 (c d)) 51 | & @String/Cons ~ (65 (b c)) 52 | & @String/Cons ~ (77 (a b)) 53 | & @String/Cons ~ (69 (@String/Nil a)) 54 | & @String/Cons ~ (46 (v w)) 55 | & @String/Cons ~ (47 (u v)) 56 | & @String/Cons ~ (82 (t u)) 57 | & @String/Cons ~ (69 (s t)) 58 | & @String/Cons ~ (65 (r s)) 59 | & @String/Cons ~ (68 (q r)) 60 | & @String/Cons ~ (77 (p q)) 61 | & @String/Cons ~ (69 (o p)) 62 | & @String/Cons ~ (46 (n o)) 63 | & @String/Cons ~ (109 (m n)) 64 | & @String/Cons ~ (100 (@String/Nil m)) 65 | & @String/Cons ~ (114 (@String/Nil x)) 66 | 67 | @test-io = 1 68 | 69 | @undefer = (((a a) b) b) 70 | 71 | 72 | -------------------------------------------------------------------------------- /tests/programs/io/open1.bend: -------------------------------------------------------------------------------- 1 | test-io = 1 2 | 3 | def main(): 4 | with IO: 5 | f <- call("OPEN", ("./LICENSE", "r")) 6 | 7 | return wrap(f) 8 | -------------------------------------------------------------------------------- /tests/programs/io/open1.hvm: -------------------------------------------------------------------------------- 1 | @IO/Call = (a (b (c (d ((@IO/Call/tag (a (b (c (d e))))) e))))) 2 | 3 | @IO/Call/tag = 1 4 | 5 | @IO/Done = (a (b ((@IO/Done/tag (a (b c))) c))) 6 | 7 | @IO/Done/tag = 0 8 | 9 | @IO/MAGIC = (13683217 16719857) 10 | 11 | @IO/bind = ((@IO/bind__C2 a) a) 12 | 13 | @IO/bind__C0 = (* (b (a c))) 14 | & @undefer ~ (a (b c)) 15 | 16 | @IO/bind__C1 = (* (* (a (b ((c d) (e g)))))) 17 | & @IO/Call ~ (@IO/MAGIC (a (b ((c f) g)))) 18 | & @IO/bind ~ (d (e f)) 19 | 20 | @IO/bind__C2 = (?((@IO/bind__C0 @IO/bind__C1) a) a) 21 | 22 | @IO/wrap = a 23 | & @IO/Done ~ (@IO/MAGIC a) 24 | 25 | @String/Cons = (a (b ((@String/Cons/tag (a (b c))) c))) 26 | 27 | @String/Cons/tag = 1 28 | 29 | @String/Nil = ((@String/Nil/tag a) a) 30 | 31 | @String/Nil/tag = 0 32 | 33 | @call = (a (b c)) 34 | & @IO/Call ~ (@IO/MAGIC (a (b (@call__C0 c)))) 35 | 36 | @call__C0 = a 37 | & @IO/Done ~ (@IO/MAGIC a) 38 | 39 | @main = s 40 | & @IO/bind ~ (o ((((p p) (@IO/wrap r)) r) s)) 41 | & @call ~ (d ((m n) o)) 42 | & @String/Cons ~ (79 (c d)) 43 | & @String/Cons ~ (80 (b c)) 44 | & @String/Cons ~ (69 (a b)) 45 | & @String/Cons ~ (78 (@String/Nil a)) 46 | & @String/Cons ~ (46 (l m)) 47 | & @String/Cons ~ (47 (k l)) 48 | & @String/Cons ~ (76 (j k)) 49 | & @String/Cons ~ (73 (i j)) 50 | & @String/Cons ~ (67 (h i)) 51 | & @String/Cons ~ (69 (g h)) 52 | & @String/Cons ~ (78 (f g)) 53 | & @String/Cons ~ (83 (e f)) 54 | & @String/Cons ~ (69 (@String/Nil e)) 55 | & @String/Cons ~ (114 (@String/Nil n)) 56 | 57 | @test-io = 1 58 | 59 | @undefer = (((a a) b) b) 60 | 61 | 62 | -------------------------------------------------------------------------------- /tests/programs/io/open2.bend: -------------------------------------------------------------------------------- 1 | test-io = 1 2 | 3 | def main(): 4 | with IO: 5 | f <- call("OPEN", ("fake-file", "r")) 6 | 7 | return wrap(f) 8 | -------------------------------------------------------------------------------- /tests/programs/io/open2.hvm: -------------------------------------------------------------------------------- 1 | @IO/Call = (a (b (c (d ((@IO/Call/tag (a (b (c (d e))))) e))))) 2 | 3 | @IO/Call/tag = 1 4 | 5 | @IO/Done = (a (b ((@IO/Done/tag (a (b c))) c))) 6 | 7 | @IO/Done/tag = 0 8 | 9 | @IO/MAGIC = (13683217 16719857) 10 | 11 | @IO/bind = ((@IO/bind__C2 a) a) 12 | 13 | @IO/bind__C0 = (* (b (a c))) 14 | & @undefer ~ (a (b c)) 15 | 16 | @IO/bind__C1 = (* (* (a (b ((c d) (e g)))))) 17 | & @IO/Call ~ (@IO/MAGIC (a (b ((c f) g)))) 18 | & @IO/bind ~ (d (e f)) 19 | 20 | @IO/bind__C2 = (?((@IO/bind__C0 @IO/bind__C1) a) a) 21 | 22 | @IO/wrap = a 23 | & @IO/Done ~ (@IO/MAGIC a) 24 | 25 | @String/Cons = (a (b ((@String/Cons/tag (a (b c))) c))) 26 | 27 | @String/Cons/tag = 1 28 | 29 | @String/Nil = ((@String/Nil/tag a) a) 30 | 31 | @String/Nil/tag = 0 32 | 33 | @call = (a (b c)) 34 | & @IO/Call ~ (@IO/MAGIC (a (b (@call__C0 c)))) 35 | 36 | @call__C0 = a 37 | & @IO/Done ~ (@IO/MAGIC a) 38 | 39 | @main = s 40 | & @IO/bind ~ (o ((((p p) (@IO/wrap r)) r) s)) 41 | & @call ~ (d ((m n) o)) 42 | & @String/Cons ~ (79 (c d)) 43 | & @String/Cons ~ (80 (b c)) 44 | & @String/Cons ~ (69 (a b)) 45 | & @String/Cons ~ (78 (@String/Nil a)) 46 | & @String/Cons ~ (102 (l m)) 47 | & @String/Cons ~ (97 (k l)) 48 | & @String/Cons ~ (107 (j k)) 49 | & @String/Cons ~ (101 (i j)) 50 | & @String/Cons ~ (45 (h i)) 51 | & @String/Cons ~ (102 (g h)) 52 | & @String/Cons ~ (105 (f g)) 53 | & @String/Cons ~ (108 (e f)) 54 | & @String/Cons ~ (101 (@String/Nil e)) 55 | & @String/Cons ~ (114 (@String/Nil n)) 56 | 57 | @test-io = 1 58 | 59 | @undefer = (((a a) b) b) 60 | 61 | 62 | -------------------------------------------------------------------------------- /tests/programs/io/open3.bend: -------------------------------------------------------------------------------- 1 | test-io = 1 2 | 3 | def main(): 4 | with IO: 5 | # calling open with an unexpected type of arg 6 | f <- call("OPEN", 123) 7 | 8 | return wrap(f) 9 | -------------------------------------------------------------------------------- /tests/programs/io/open3.hvm: -------------------------------------------------------------------------------- 1 | @IO/Call = (a (b (c (d ((@IO/Call/tag (a (b (c (d e))))) e))))) 2 | 3 | @IO/Call/tag = 1 4 | 5 | @IO/Done = (a (b ((@IO/Done/tag (a (b c))) c))) 6 | 7 | @IO/Done/tag = 0 8 | 9 | @IO/MAGIC = (13683217 16719857) 10 | 11 | @IO/bind = ((@IO/bind__C2 a) a) 12 | 13 | @IO/bind__C0 = (* (b (a c))) 14 | & @undefer ~ (a (b c)) 15 | 16 | @IO/bind__C1 = (* (* (a (b ((c d) (e g)))))) 17 | & @IO/Call ~ (@IO/MAGIC (a (b ((c f) g)))) 18 | & @IO/bind ~ (d (e f)) 19 | 20 | @IO/bind__C2 = (?((@IO/bind__C0 @IO/bind__C1) a) a) 21 | 22 | @IO/wrap = a 23 | & @IO/Done ~ (@IO/MAGIC a) 24 | 25 | @String/Cons = (a (b ((@String/Cons/tag (a (b c))) c))) 26 | 27 | @String/Cons/tag = 1 28 | 29 | @String/Nil = ((@String/Nil/tag a) a) 30 | 31 | @String/Nil/tag = 0 32 | 33 | @call = (a (b c)) 34 | & @IO/Call ~ (@IO/MAGIC (a (b (@call__C0 c)))) 35 | 36 | @call__C0 = a 37 | & @IO/Done ~ (@IO/MAGIC a) 38 | 39 | @main = i 40 | & @IO/bind ~ (e ((((f f) (@IO/wrap h)) h) i)) 41 | & @call ~ (d (123 e)) 42 | & @String/Cons ~ (79 (c d)) 43 | & @String/Cons ~ (80 (b c)) 44 | & @String/Cons ~ (69 (a b)) 45 | & @String/Cons ~ (78 (@String/Nil a)) 46 | 47 | @test-io = 1 48 | 49 | @undefer = (((a a) b) b) 50 | 51 | 52 | -------------------------------------------------------------------------------- /tests/programs/list.hvm: -------------------------------------------------------------------------------- 1 | @List/Cons = (a (b ((@List/Cons/tag (a (b c))) c))) 2 | 3 | @List/Cons/tag = 1 4 | 5 | @List/Nil = ((@List/Nil/tag a) a) 6 | 7 | @List/Nil/tag = 0 8 | 9 | @id = (a a) 10 | 11 | @list = c 12 | & @List/Cons ~ (1 (b c)) 13 | & @List/Cons ~ (2 (a b)) 14 | & @List/Cons ~ (3 (@List/Nil a)) 15 | 16 | @main = e 17 | & @map ~ ((a b) (d e)) 18 | & @map ~ (a (@list b)) 19 | & @List/Cons ~ (@id (@List/Nil d)) 20 | 21 | // @main__C0 = (a b) 22 | // & @map ~ (a (@list b)) 23 | 24 | @map = (a ((@map__C1 (a b)) b)) 25 | 26 | @map__C0 = (* (a (d ({(a b) c} f)))) 27 | & @List/Cons ~ (b (e f)) 28 | & @map ~ (c (d e)) 29 | 30 | @map__C1 = (?(((* @List/Nil) @map__C0) a) a) 31 | -------------------------------------------------------------------------------- /tests/programs/numeric-casts.hvm: -------------------------------------------------------------------------------- 1 | @main = x & @tu0 ~ (* x) 2 | 3 | // casting to u24 4 | @tu0 = (* {n x}) & @tu1 ~ (* x) & 0 ~ $([u24] n) // 0 5 | @tu1 = (* {n x}) & @tu2 ~ (* x) & 1234 ~ $([u24] n) // 1234 6 | @tu2 = (* {n x}) & @tu3 ~ (* x) & +4321 ~ $([u24] n) // 4321 7 | @tu3 = (* {n x}) & @tu4 ~ (* x) & -5678 ~ $([u24] n) // 16771538 (reinterprets bits) 8 | @tu4 = (* {n x}) & @tu5 ~ (* x) & 2.8 ~ $([u24] n) // 2 (rounds to zero) 9 | @tu5 = (* {n x}) & @tu6 ~ (* x) & -12.5 ~ $([u24] n) // 0 (saturates) 10 | @tu6 = (* {n x}) & @tu7 ~ (* x) & 16777216.0 ~ $([u24] n) // 16777215 (saturates) 11 | @tu7 = (* {n x}) & @tu8 ~ (* x) & +inf ~ $([u24] n) // 16777215 (saturates) 12 | @tu8 = (* {n x}) & @tu9 ~ (* x) & -inf ~ $([u24] n) // 0 (saturates) 13 | @tu9 = (* {n x}) & @ti0 ~ (* x) & +NaN ~ $([u24] n) // 0 14 | 15 | // casting to i24 16 | @ti0 = (* {n x}) & @ti1 ~ (* x) & 0 ~ $([i24] n) // +0 17 | @ti1 = (* {n x}) & @ti2 ~ (* x) & 1234 ~ $([i24] n) // +1234 18 | @ti2 = (* {n x}) & @ti3 ~ (* x) & +4321 ~ $([i24] n) // +4321 19 | @ti3 = (* {n x}) & @ti4 ~ (* x) & -5678 ~ $([i24] n) // -5678 20 | @ti4 = (* {n x}) & @ti5 ~ (* x) & 2.8 ~ $([i24] n) // +2 (rounds to zero) 21 | @ti5 = (* {n x}) & @ti6 ~ (* x) & -12.7 ~ $([i24] n) // -12 (rounds to zero) 22 | @ti6 = (* {n x}) & @ti7 ~ (* x) & 8388610.0 ~ $([i24] n) // +8388607 (saturates) 23 | @ti7 = (* {n x}) & @ti8 ~ (* x) & -8388610.0 ~ $([i24] n) // -8388608 (saturates) 24 | @ti8 = (* {n x}) & @ti9 ~ (* x) & +inf ~ $([i24] n) // +8388607 (saturates) 25 | @ti9 = (* {n x}) & @ti10 ~ (* x) & -inf ~ $([i24] n) // -8388608 (saturates) 26 | @ti10 = (* {n x}) & @tf0 ~ (* x) & +NaN ~ $([i24] n) // +0 27 | 28 | // casting to f24 29 | @tf0 = (* {n x}) & @tf1 ~ (* x) & +NaN ~ $([f24] n) // +NaN 30 | @tf1 = (* {n x}) & @tf2 ~ (* x) & +inf ~ $([f24] n) // +inf 31 | @tf2 = (* {n x}) & @tf3 ~ (* x) & -inf ~ $([f24] n) // -inf 32 | @tf3 = (* {n x}) & @tf4 ~ (* x) & 2.15 ~ $([f24] n) // 2.15 33 | @tf4 = (* {n x}) & @tf5 ~ (* x) & -2.15 ~ $([f24] n) // -2.15 34 | @tf5 = (* {n x}) & @tf6 ~ (* x) & 0.15 ~ $([f24] n) // 0.15 35 | @tf6 = (* {n x}) & @tf7 ~ (* x) & -1234 ~ $([f24] n) // -1234.0 36 | @tf7 = (* {n x}) & @tf8 ~ (* x) & +1234 ~ $([f24] n) // +1234.0 37 | @tf8 = (* {n x}) & @tf9 ~ (* x) & 123456 ~ $([f24] n) // 123456.0 38 | @tf9 = (* {n x}) & @tp0 ~ (* x) & 16775982 ~ $([f24] n) // 16775936.0 39 | 40 | // printing 41 | @tp0 = (* {n x}) & @tp1 ~ (* x) & n ~ [u24] // [u24] 42 | @tp1 = (* {n x}) & @tp2 ~ (* x) & n ~ [i24] // [i24] 43 | @tp2 = (* {n x}) & @t ~ (* x) & n ~ [f24] // [f24] 44 | 45 | @t = * 46 | -------------------------------------------------------------------------------- /tests/programs/numerics/f24.hvm: -------------------------------------------------------------------------------- 1 | @half = xN & 1.0 ~ $([/] $(2.0 xN)) 2 | @nan = xN & 0.0 ~ $([/] $(0.0 xN)) 3 | 4 | @main = x & @t0 ~ (* x) 5 | 6 | // nan and inf divisions 7 | @t0 = (* {n x}) & @t1 ~ (* x) & 1.0 ~ $([/] $(0.0 n)) // +inf 8 | @t1 = (* {n x}) & @t2 ~ (* x) & -1.0 ~ $([/] $(0.0 n)) // -inf 9 | @t2 = (* {n x}) & @t3 ~ (* x) & 0.0 ~ $([/] $(0.0 n)) // NaN 10 | 11 | // general operators 12 | @t3 = (* {n x}) & @t4 ~ (* x) & @half ~ $([+] $(2.0 n)) // 2.5 13 | @t4 = (* {n x}) & @t5 ~ (* x) & @half ~ $([-] $(2.0 n)) // -1.5 14 | @t5 = (* {n x}) & @t6 ~ (* x) & @half ~ $([*] $(2.3 n)) // 1.15 15 | @t6 = (* {n x}) & @t7 ~ (* x) & @half ~ $([/] $(2.0 n)) // 0.25 16 | @t7 = (* {n x}) & @t8 ~ (* x) & @half ~ $([%] $(2.0 n)) // 0.5 17 | 18 | // comparisons (returning ints) 19 | @t8 = (* {n x}) & @t9 ~ (* x) & @half ~ $([=] $(2.0 n)) // 0 20 | @t9 = (* {n x}) & @tA ~ (* x) & @half ~ $([!] $(2.0 n)) // 1 21 | @tA = (* {n x}) & @tB ~ (* x) & @half ~ $([<] $(2.0 n)) // 1 22 | @tB = (* {n x}) & @tC ~ (* x) & @half ~ $([>] $(2.0 n)) // 0 23 | 24 | // ieee nan comparisons 25 | @tC = (* {n x}) & @tD ~ (* x) & @nan ~ $([=] $(@nan n)) // 0 26 | @tD = (* {n x}) & @tE ~ (* x) & @nan ~ $([<] $(@nan n)) // 0 27 | @tE = (* {n x}) & @tF ~ (* x) & @nan ~ $([>] $(@nan n)) // 0 28 | 29 | // parsing 30 | @tF = (* {n x}) & @tG ~ (* x) & +NaN ~ $([+] $(0.0 n)) // NaN 31 | @tG = (* {n x}) & @tH ~ (* x) & +inf ~ $([+] $(0.0 n)) // inf 32 | @tH = (* {n x}) & @tI ~ (* x) & -inf ~ $([+] $(0.0 n)) // -inf 33 | @tI = (* {n x}) & @tJ ~ (* x) & 1.02 ~ $([+] $(0.0 n)) // 1.02 34 | 35 | // modulo 36 | @tJ = (* {n x}) & @tK ~ (* x) & +1.2 ~ $([%] $(+1.1 n)) // +0.1 37 | @tK = (* {n x}) & @tL ~ (* x) & +1.2 ~ $([%] $(-1.1 n)) // +0.1 38 | @tL = (* {n x}) & @tM ~ (* x) & -1.2 ~ $([%] $(+1.1 n)) // -0.1 39 | @tM = (* {n x}) & @tN ~ (* x) & -1.2 ~ $([%] $(-1.1 n)) // -0.1 40 | 41 | // modulo 42 | @tN = (* {n x}) & @tO ~ (* x) & +0.0 ~ $([<<] $(+3.14159265 n)) // ~0 43 | @tO = (* {n x}) & @tP ~ (* x) & +1.570796325 ~ $([<<] $(+0.0 n)) // 1.0 44 | @tP = (* {n x}) & @tQ ~ (* x) & +0.0 ~ $([>>] $(+3.14159265 n)) // ~0 45 | @tQ = (* {n x}) & @tR ~ (* x) & +0.785398162 ~ $([>>] $(+0.0 n)) // 1.0 46 | 47 | @tR = * 48 | -------------------------------------------------------------------------------- /tests/programs/numerics/i24.hvm: -------------------------------------------------------------------------------- 1 | @main = x & @t0 ~ (* x) 2 | 3 | // all ops 4 | @t0 = (* {n x}) & @t1 ~ (* x) & +10 ~ $([+] $(+2 n)) // 12 5 | @t1 = (* {n x}) & @t2 ~ (* x) & +10 ~ $([-] $(+2 n)) // 8 6 | @t2 = (* {n x}) & @t3 ~ (* x) & +10 ~ $([*] $(+2 n)) // 20 7 | @t3 = (* {n x}) & @t4 ~ (* x) & +10 ~ $([/] $(+2 n)) // 5 8 | @t4 = (* {n x}) & @t5 ~ (* x) & +10 ~ $([%] $(+2 n)) // 0 9 | @t5 = (* {n x}) & @t6 ~ (* x) & +10 ~ $([=] $(+2 n)) // 0 10 | @t6 = (* {n x}) & @t7 ~ (* x) & +10 ~ $([!] $(+2 n)) // 1 11 | @t7 = (* {n x}) & @t8 ~ (* x) & +10 ~ $([<] $(+2 n)) // 0 12 | @t8 = (* {n x}) & @t9 ~ (* x) & +10 ~ $([>] $(+2 n)) // 1 13 | @t9 = (* {n x}) & @tA ~ (* x) & +10 ~ $([&] $(+2 n)) // 2 14 | @tA = (* {n x}) & @tB ~ (* x) & +10 ~ $([|] $(+2 n)) // 10 15 | @tB = (* {n x}) & @tC ~ (* x) & +10 ~ $([^] $(+2 n)) // 8 16 | 17 | // underflow 18 | @tC = (* {n x}) & @tD ~ (* x) & -8388608 ~ $([-] $(+1 n)) // 8388607 19 | 20 | // overflow 21 | @tD = (* {n x}) & @tE ~ (* x) & +8388607 ~ $([+] $(+1 n)) // -8388608 22 | 23 | // modulo 24 | @tE = (* {n x}) & @tF ~ (* x) & +3 ~ $([%] $(+2 n)) // +1 25 | @tF = (* {n x}) & @tG ~ (* x) & +3 ~ $([%] $(-2 n)) // +1 26 | @tG = (* {n x}) & @tH ~ (* x) & -3 ~ $([%] $(+2 n)) // -1 27 | @tH = (* {n x}) & @tI ~ (* x) & -3 ~ $([%] $(-2 n)) // -1 28 | 29 | @tI = * 30 | 31 | -------------------------------------------------------------------------------- /tests/programs/numerics/u24.hvm: -------------------------------------------------------------------------------- 1 | @main = x & @t0 ~ (* x) 2 | 3 | // all ops 4 | @t0 = (* {n x}) & @t1 ~ (* x) & 10 ~ $([+] $(2 n)) // 12 5 | @t1 = (* {n x}) & @t2 ~ (* x) & 10 ~ $([-] $(2 n)) // 8 6 | @t2 = (* {n x}) & @t3 ~ (* x) & 10 ~ $([*] $(2 n)) // 20 7 | @t3 = (* {n x}) & @t4 ~ (* x) & 10 ~ $([/] $(2 n)) // 5 8 | @t4 = (* {n x}) & @t5 ~ (* x) & 10 ~ $([%] $(2 n)) // 0 9 | @t5 = (* {n x}) & @t6 ~ (* x) & 10 ~ $([=] $(2 n)) // 0 10 | @t6 = (* {n x}) & @t7 ~ (* x) & 10 ~ $([!] $(2 n)) // 1 11 | @t7 = (* {n x}) & @t8 ~ (* x) & 10 ~ $([<] $(2 n)) // 0 12 | @t8 = (* {n x}) & @t9 ~ (* x) & 10 ~ $([>] $(2 n)) // 1 13 | @t9 = (* {n x}) & @tA ~ (* x) & 10 ~ $([&] $(2 n)) // 2 14 | @tA = (* {n x}) & @tB ~ (* x) & 10 ~ $([|] $(2 n)) // 10 15 | @tB = (* {n x}) & @tC ~ (* x) & 10 ~ $([^] $(2 n)) // 8 16 | @tC = (* {n x}) & @tD ~ (* x) & 10 ~ $([<<] $(2 n)) // 40 17 | @tD = (* {n x}) & @tE ~ (* x) & 10 ~ $([>>] $(2 n)) // 2 18 | 19 | // underflow 20 | @tE = (* {n x}) & @tF ~ (* x) & 0 ~ $([-] $(1 n)) // 16777215 21 | 22 | // overflow 23 | @tF = (* {n x}) & @tG ~ (* x) & 16777215 ~ $([+] $(1 n)) // 0 24 | 25 | // no sign extension 26 | @tG = (* {n x}) & @tH ~ (* x) & 16777215 ~ $([>>] $(22 n)) // 3 27 | 28 | @tH = * 29 | -------------------------------------------------------------------------------- /tests/programs/safety-check.hvm: -------------------------------------------------------------------------------- 1 | @List/Cons = (a (b ((@List/Cons/tag (a (b c))) c))) 2 | 3 | @List/Cons/tag = 1 4 | 5 | @List/Nil = ((@List/Nil/tag a) a) 6 | 7 | @List/Nil/tag = 0 8 | 9 | @id = (a a) 10 | 11 | @list = c 12 | & @List/Cons ~ (1 (b c)) 13 | & @List/Cons ~ (2 (@List/Nil b)) 14 | 15 | @main = b 16 | & @map ~ (@main__C0 (a b)) 17 | & @List/Cons ~ (@id (@List/Nil a)) 18 | 19 | @main__C0 = (a b) 20 | & @map ~ (a (@list b)) 21 | 22 | @map = (a ((@map__C1 (a b)) b)) 23 | 24 | @map__C0 = (* (a (d ({(a b) c} f)))) 25 | & @List/Cons ~ (b (e f)) 26 | & @map ~ (c (d e)) 27 | 28 | @map__C1 = (?(((* @List/Nil) @map__C0) a) a) 29 | 30 | // Test flags 31 | @test-rust-only = 1 -------------------------------------------------------------------------------- /tests/run.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | error::Error, 4 | ffi::OsStr, 5 | fs, 6 | io::{Read, Write}, 7 | path::{Path, PathBuf}, 8 | process::{Command, Stdio}, 9 | }; 10 | 11 | use hvm::ast::Tree; 12 | use insta::assert_snapshot; 13 | use TSPL::Parser; 14 | 15 | #[test] 16 | fn test_run_programs() { 17 | test_dir(&manifest_relative("tests/programs/")); 18 | } 19 | 20 | #[test] 21 | fn test_run_examples() { 22 | test_dir(&manifest_relative("examples/")); 23 | } 24 | 25 | fn test_dir(dir: &Path) { 26 | insta::glob!(dir, "**/*.hvm", test_file) 27 | } 28 | 29 | fn manifest_relative(sub: &str) -> PathBuf { 30 | format!("{}/{}", env!("CARGO_MANIFEST_DIR"), sub).into() 31 | } 32 | 33 | fn test_file(path: &Path) { 34 | let contents = fs::read_to_string(path).unwrap(); 35 | if contents.contains("@test-skip = 1") { 36 | println!("skipping {path:?}"); 37 | return; 38 | } 39 | if contents.contains("@test-io = 1") { 40 | test_io_file(path); 41 | return; 42 | } 43 | 44 | println!("testing {path:?}..."); 45 | let rust_output = execute_hvm(&["run".as_ref(), path.as_os_str()], false).unwrap(); 46 | assert_snapshot!(rust_output); 47 | 48 | if contents.contains("@test-rust-only = 1") { 49 | println!("only testing rust implementation for {path:?}"); 50 | return; 51 | } 52 | 53 | println!(" testing {path:?}, C..."); 54 | let c_output = execute_hvm(&["run-c".as_ref(), path.as_os_str()], false).unwrap(); 55 | assert_eq!(c_output, rust_output, "{path:?}: C output does not match rust output"); 56 | 57 | if cfg!(feature = "cuda") { 58 | println!(" testing {path:?}, CUDA..."); 59 | let cuda_output = execute_hvm(&["run-cu".as_ref(), path.as_os_str()], false).unwrap(); 60 | assert_eq!( 61 | cuda_output, rust_output, 62 | "{path:?}: CUDA output does not match rust output" 63 | ); 64 | } 65 | } 66 | 67 | fn test_io_file(path: &Path) { 68 | println!(" testing (io) {path:?}, C..."); 69 | let c_output = execute_hvm(&["run-c".as_ref(), path.as_os_str()], true).unwrap(); 70 | assert_snapshot!(c_output); 71 | 72 | if cfg!(feature = "cuda") { 73 | println!(" testing (io) {path:?}, CUDA..."); 74 | let cuda_output = execute_hvm(&["run-cu".as_ref(), path.as_os_str()], true).unwrap(); 75 | assert_eq!(cuda_output, c_output, "{path:?}: CUDA output does not match C output"); 76 | } 77 | } 78 | 79 | fn execute_hvm(args: &[&OsStr], send_io: bool) -> Result> { 80 | // Spawn the command 81 | let mut child = Command::new(env!("CARGO_BIN_EXE_hvm")) 82 | .args(args) 83 | .stdin(Stdio::piped()) 84 | .stdout(Stdio::piped()) 85 | .stderr(Stdio::piped()) 86 | .spawn()?; 87 | 88 | // Capture the output of the command 89 | let mut stdout = child.stdout.take().ok_or("Couldn't capture stdout!")?; 90 | let mut stderr = child.stderr.take().ok_or("Couldn't capture stderr!")?; 91 | 92 | // Wait for the command to finish and get the exit status 93 | if send_io { 94 | let mut stdin = child.stdin.take().ok_or("Couldn't capture stdin!")?; 95 | stdin.write_all(b"io from the tests\n")?; 96 | drop(stdin); 97 | } 98 | let status = child.wait()?; 99 | 100 | // Read the output 101 | let mut output = String::new(); 102 | stdout.read_to_string(&mut output)?; 103 | stderr.read_to_string(&mut output)?; 104 | 105 | Ok(if !status.success() { 106 | format!("{status}\n{output}") 107 | } else { 108 | parse_output(&output).unwrap_or_else(|err| panic!("error parsing output:\n{err}\n\n{output}")) 109 | }) 110 | } 111 | 112 | fn parse_output(output: &str) -> Result { 113 | let mut lines = Vec::new(); 114 | 115 | for line in output.lines() { 116 | if line.starts_with("Result:") { 117 | let mut parser = hvm::ast::CoreParser::new(line); 118 | parser.consume("Result:")?; 119 | let mut tree = parser.parse_tree()?; 120 | normalize_vars(&mut tree, &mut HashMap::new()); 121 | lines.push(format!("Result: {}", tree.show())); 122 | } else if !line.starts_with("- ITRS:") 123 | && !line.starts_with("- TIME:") 124 | && !line.starts_with("- MIPS:") 125 | && !line.starts_with("- LEAK:") 126 | { 127 | // TODO: include iteration count in snapshot once consistent 128 | lines.push(line.to_string()) 129 | } 130 | } 131 | 132 | Ok(lines.join("\n")) 133 | } 134 | 135 | fn normalize_vars(tree: &mut Tree, vars: &mut HashMap) { 136 | match tree { 137 | Tree::Var { nam } => { 138 | let next_var = vars.len(); 139 | *nam = format!("x{}", vars.entry(std::mem::take(nam)).or_insert(next_var)); 140 | } 141 | Tree::Era | Tree::Ref { .. } | Tree::Num { .. } => {} 142 | Tree::Con { fst, snd } | Tree::Dup { fst, snd } | Tree::Opr { fst, snd } | Tree::Swi { fst, snd } => { 143 | normalize_vars(fst, vars); 144 | normalize_vars(snd, vars); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@empty.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: tests/programs/empty.hvm 5 | --- 6 | exit status: 101 7 | thread 'main' panicked at src/ast.rs:545:41: 8 | missing `@main` definition 9 | note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 10 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@hello-world.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: tests/programs/hello-world.hvm 5 | --- 6 | Result: ((@String/Cons/tag (104 (((@String/Cons/tag (101 (((@String/Cons/tag (108 (((@String/Cons/tag (108 (((@String/Cons/tag (111 (((@String/Cons/tag (44 (((@String/Cons/tag (32 (((@String/Cons/tag (119 (((@String/Cons/tag (111 (((@String/Cons/tag (114 (((@String/Cons/tag (108 (((@String/Cons/tag (100 (@String/Nil x0))) x0) x1))) x1) x2))) x2) x3))) x3) x4))) x4) x5))) x5) x6))) x6) x7))) x7) x8))) x8) x9))) x9) x10))) x10) x11))) x11) 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@list.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: tests/programs/list.hvm 5 | --- 6 | Result: ((@List/Cons/tag (((@List/Cons/tag (1 (((@List/Cons/tag (* (((@List/Cons/tag (* (@List/Nil x0))) x0) x1))) x1) x2))) x2) (@List/Nil x3))) x3) 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@numeric-casts.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: tests/programs/numeric-casts.hvm 5 | --- 6 | Result: {0 {1234 {4321 {16771538 {2 {0 {16777215 {16777215 {0 {0 {+0 {+1234 {+4321 {-5678 {+2 {-12 {+8388607 {-8388608 {+8388607 {-8388608 {+0 {+NaN {+inf {-inf {2.1500244 {-2.1500244 {0.15000153 {-1234.0 {1234.0 {123456.0 {16775936.0 {[u24] {[i24] {[f24] *}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@numerics__f24.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: tests/programs/numerics/f24.hvm 5 | --- 6 | Result: {+inf {-inf {+NaN {2.5 {-1.5 {1.1499939 {0.25 {0.5 {0 {1 {1 {0 {0 {0 {0 {+NaN {+inf {-inf {1.019989 {0.1000061 {0.1000061 {-0.1000061 {-0.1000061 {-8.908799e-6 {1.0 {8.908799e-6 {1.0 *}}}}}}}}}}}}}}}}}}}}}}}}}}} 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@numerics__i24.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: tests/programs/i24.hvm 5 | --- 6 | Result: {+12 {+8 {+20 {+5 {+0 {0 {1 {0 {1 {+2 {+10 {+8 {+8388607 {-8388608 {+1 {+1 {-1 {-1 *}}}}}}}}}}}}}}}}}} 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@numerics__u24.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: tests/programs/u24.hvm 5 | --- 6 | Result: {12 {8 {20 {5 {0 {0 {1 {0 {1 {2 {10 {8 {40 {2 {16777215 {0 {3 *}}}}}}}}}}}}}}}}} 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@safety-check.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: tests/programs/safety-check.hvm 5 | --- 6 | ERROR: attempt to clone a non-affine global reference. 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@sort_bitonic__main.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: examples/sort_bitonic/main.hvm 5 | --- 6 | Result: 8386560 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@sort_radix__main.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: examples/sort_radix/main.hvm 5 | --- 6 | Result: 16744448 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@stress__main.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: examples/stress/main.hvm 5 | --- 6 | Result: 0 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@sum_rec__main.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: examples/sum_rec/main.hvm 5 | --- 6 | Result: 16252928 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@sum_tree__main.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: examples/sum_tree/main.hvm 5 | --- 6 | Result: 1048576 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__file@tuples__tuples.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: rust_output 4 | input_file: examples/tuples/tuples.hvm 5 | --- 6 | Result: ((0 (3 (4 (5 (6 (7 (8 (1 (2 x0))))))))) x0) 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__io_file@demo_io__main.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: c_output 4 | input_file: examples/demo_io/main.hvm 5 | --- 6 | Apache License 7 | Result: ((@IO/Done/tag (@IO/MAGIC (((0 (* x0)) x0) x1))) x1) 8 | -------------------------------------------------------------------------------- /tests/snapshots/run__io_file@io__basic.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: c_output 4 | input_file: tests/programs/io/basic.hvm 5 | --- 6 | Apache License 7 | Result: ((@IO/Done/tag (@IO/MAGIC (((0 (* x0)) x0) x1))) x1) 8 | -------------------------------------------------------------------------------- /tests/snapshots/run__io_file@io__invalid-name.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: c_output 4 | input_file: tests/programs/io/invalid-name.hvm 5 | --- 6 | Result: ((@IO/Done/tag (@IO/MAGIC (((1 (((1 x0) x0) x1)) x1) x2))) x2) 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__io_file@io__open1.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: c_output 4 | input_file: tests/programs/io/open1.hvm 5 | --- 6 | Result: ((@IO/Done/tag (@IO/MAGIC (((0 (3 x0)) x0) x1))) x1) 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__io_file@io__open2.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: c_output 4 | input_file: tests/programs/io/open2.hvm 5 | --- 6 | Result: ((@IO/Done/tag (@IO/MAGIC (((1 (((2 (+2 x0)) x0) x1)) x1) x2))) x2) 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__io_file@io__open3.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: c_output 4 | input_file: tests/programs/io/open3.hvm 5 | --- 6 | Result: ((@IO/Done/tag (@IO/MAGIC (((1 (((0 x0) x0) x1)) x1) x2))) x2) 7 | -------------------------------------------------------------------------------- /tests/snapshots/run__io_file@io__read_and_print.hvm.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: tests/run.rs 3 | expression: c_output 4 | input_file: tests/programs/io/read_and_print.hvm 5 | --- 6 | What is your name? 7 | io fr from 8 | Result: 42 9 | --------------------------------------------------------------------------------