├── .gitignore ├── .vscode └── settings.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── calc.rs ├── list.rs ├── tarjan.rs └── tree.rs ├── examples └── blog_expr.rs └── src ├── bin ├── ackermann.rs └── blog_post.rs ├── lib.rs └── tests ├── ackermann.rs ├── binomial.rs ├── list.rs ├── mod.rs └── triangular.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Ackermann", 4 | "eval", 5 | "Kont", 6 | "lowlinks", 7 | "sccs", 8 | "tarjan", 9 | "uninit", 10 | "Unresumed" 11 | ], 12 | "cSpell.ignorePaths": [ 13 | "*.mir" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /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 = "ansi_term" 7 | version = "0.11.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 10 | dependencies = [ 11 | "winapi", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.0.1" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "bstr" 39 | version = "0.2.17" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 42 | dependencies = [ 43 | "lazy_static", 44 | "memchr", 45 | "regex-automata", 46 | "serde", 47 | ] 48 | 49 | [[package]] 50 | name = "bumpalo" 51 | version = "3.8.0" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 54 | 55 | [[package]] 56 | name = "cast" 57 | version = "0.2.7" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" 60 | dependencies = [ 61 | "rustc_version", 62 | ] 63 | 64 | [[package]] 65 | name = "cfg-if" 66 | version = "1.0.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 69 | 70 | [[package]] 71 | name = "clap" 72 | version = "2.33.3" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 75 | dependencies = [ 76 | "ansi_term", 77 | "atty", 78 | "bitflags", 79 | "strsim", 80 | "textwrap", 81 | "unicode-width", 82 | "vec_map", 83 | ] 84 | 85 | [[package]] 86 | name = "criterion" 87 | version = "0.3.5" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" 90 | dependencies = [ 91 | "atty", 92 | "cast", 93 | "clap", 94 | "criterion-plot", 95 | "csv", 96 | "itertools", 97 | "lazy_static", 98 | "num-traits", 99 | "oorandom", 100 | "plotters", 101 | "rayon", 102 | "regex", 103 | "serde", 104 | "serde_cbor", 105 | "serde_derive", 106 | "serde_json", 107 | "tinytemplate", 108 | "walkdir", 109 | ] 110 | 111 | [[package]] 112 | name = "criterion-plot" 113 | version = "0.4.4" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" 116 | dependencies = [ 117 | "cast", 118 | "itertools", 119 | ] 120 | 121 | [[package]] 122 | name = "crossbeam-channel" 123 | version = "0.5.1" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" 126 | dependencies = [ 127 | "cfg-if", 128 | "crossbeam-utils", 129 | ] 130 | 131 | [[package]] 132 | name = "crossbeam-deque" 133 | version = "0.8.1" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" 136 | dependencies = [ 137 | "cfg-if", 138 | "crossbeam-epoch", 139 | "crossbeam-utils", 140 | ] 141 | 142 | [[package]] 143 | name = "crossbeam-epoch" 144 | version = "0.9.5" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" 147 | dependencies = [ 148 | "cfg-if", 149 | "crossbeam-utils", 150 | "lazy_static", 151 | "memoffset", 152 | "scopeguard", 153 | ] 154 | 155 | [[package]] 156 | name = "crossbeam-utils" 157 | version = "0.8.5" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" 160 | dependencies = [ 161 | "cfg-if", 162 | "lazy_static", 163 | ] 164 | 165 | [[package]] 166 | name = "csv" 167 | version = "1.1.6" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" 170 | dependencies = [ 171 | "bstr", 172 | "csv-core", 173 | "itoa", 174 | "ryu", 175 | "serde", 176 | ] 177 | 178 | [[package]] 179 | name = "csv-core" 180 | version = "0.1.10" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 183 | dependencies = [ 184 | "memchr", 185 | ] 186 | 187 | [[package]] 188 | name = "either" 189 | version = "1.6.1" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 192 | 193 | [[package]] 194 | name = "getrandom" 195 | version = "0.2.3" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 198 | dependencies = [ 199 | "cfg-if", 200 | "libc", 201 | "wasi", 202 | ] 203 | 204 | [[package]] 205 | name = "half" 206 | version = "1.8.2" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" 209 | 210 | [[package]] 211 | name = "hermit-abi" 212 | version = "0.1.19" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 215 | dependencies = [ 216 | "libc", 217 | ] 218 | 219 | [[package]] 220 | name = "itertools" 221 | version = "0.10.1" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" 224 | dependencies = [ 225 | "either", 226 | ] 227 | 228 | [[package]] 229 | name = "itoa" 230 | version = "0.4.8" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 233 | 234 | [[package]] 235 | name = "js-sys" 236 | version = "0.3.55" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 239 | dependencies = [ 240 | "wasm-bindgen", 241 | ] 242 | 243 | [[package]] 244 | name = "lazy_static" 245 | version = "1.4.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 248 | 249 | [[package]] 250 | name = "libc" 251 | version = "0.2.107" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" 254 | 255 | [[package]] 256 | name = "log" 257 | version = "0.4.14" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 260 | dependencies = [ 261 | "cfg-if", 262 | ] 263 | 264 | [[package]] 265 | name = "memchr" 266 | version = "2.4.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 269 | 270 | [[package]] 271 | name = "memoffset" 272 | version = "0.6.4" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" 275 | dependencies = [ 276 | "autocfg", 277 | ] 278 | 279 | [[package]] 280 | name = "num-traits" 281 | version = "0.2.14" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 284 | dependencies = [ 285 | "autocfg", 286 | ] 287 | 288 | [[package]] 289 | name = "num_cpus" 290 | version = "1.13.0" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 293 | dependencies = [ 294 | "hermit-abi", 295 | "libc", 296 | ] 297 | 298 | [[package]] 299 | name = "oorandom" 300 | version = "11.1.3" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" 303 | 304 | [[package]] 305 | name = "plotters" 306 | version = "0.3.1" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" 309 | dependencies = [ 310 | "num-traits", 311 | "plotters-backend", 312 | "plotters-svg", 313 | "wasm-bindgen", 314 | "web-sys", 315 | ] 316 | 317 | [[package]] 318 | name = "plotters-backend" 319 | version = "0.3.2" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" 322 | 323 | [[package]] 324 | name = "plotters-svg" 325 | version = "0.3.1" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" 328 | dependencies = [ 329 | "plotters-backend", 330 | ] 331 | 332 | [[package]] 333 | name = "ppv-lite86" 334 | version = "0.2.15" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" 337 | 338 | [[package]] 339 | name = "proc-macro2" 340 | version = "1.0.32" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 343 | dependencies = [ 344 | "unicode-xid", 345 | ] 346 | 347 | [[package]] 348 | name = "quote" 349 | version = "1.0.10" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 352 | dependencies = [ 353 | "proc-macro2", 354 | ] 355 | 356 | [[package]] 357 | name = "rand" 358 | version = "0.8.4" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 361 | dependencies = [ 362 | "libc", 363 | "rand_chacha", 364 | "rand_core", 365 | "rand_hc", 366 | ] 367 | 368 | [[package]] 369 | name = "rand_chacha" 370 | version = "0.3.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 373 | dependencies = [ 374 | "ppv-lite86", 375 | "rand_core", 376 | ] 377 | 378 | [[package]] 379 | name = "rand_core" 380 | version = "0.6.3" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 383 | dependencies = [ 384 | "getrandom", 385 | ] 386 | 387 | [[package]] 388 | name = "rand_hc" 389 | version = "0.3.1" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 392 | dependencies = [ 393 | "rand_core", 394 | ] 395 | 396 | [[package]] 397 | name = "rayon" 398 | version = "1.5.1" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" 401 | dependencies = [ 402 | "autocfg", 403 | "crossbeam-deque", 404 | "either", 405 | "rayon-core", 406 | ] 407 | 408 | [[package]] 409 | name = "rayon-core" 410 | version = "1.9.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" 413 | dependencies = [ 414 | "crossbeam-channel", 415 | "crossbeam-deque", 416 | "crossbeam-utils", 417 | "lazy_static", 418 | "num_cpus", 419 | ] 420 | 421 | [[package]] 422 | name = "regex" 423 | version = "1.5.4" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 426 | dependencies = [ 427 | "regex-syntax", 428 | ] 429 | 430 | [[package]] 431 | name = "regex-automata" 432 | version = "0.1.10" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 435 | 436 | [[package]] 437 | name = "regex-syntax" 438 | version = "0.6.25" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 441 | 442 | [[package]] 443 | name = "rustc_version" 444 | version = "0.4.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 447 | dependencies = [ 448 | "semver", 449 | ] 450 | 451 | [[package]] 452 | name = "ryu" 453 | version = "1.0.5" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 456 | 457 | [[package]] 458 | name = "same-file" 459 | version = "1.0.6" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 462 | dependencies = [ 463 | "winapi-util", 464 | ] 465 | 466 | [[package]] 467 | name = "scopeguard" 468 | version = "1.1.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 471 | 472 | [[package]] 473 | name = "semver" 474 | version = "1.0.4" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" 477 | 478 | [[package]] 479 | name = "serde" 480 | version = "1.0.130" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 483 | 484 | [[package]] 485 | name = "serde_cbor" 486 | version = "0.11.2" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" 489 | dependencies = [ 490 | "half", 491 | "serde", 492 | ] 493 | 494 | [[package]] 495 | name = "serde_derive" 496 | version = "1.0.130" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 499 | dependencies = [ 500 | "proc-macro2", 501 | "quote", 502 | "syn", 503 | ] 504 | 505 | [[package]] 506 | name = "serde_json" 507 | version = "1.0.70" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3" 510 | dependencies = [ 511 | "itoa", 512 | "ryu", 513 | "serde", 514 | ] 515 | 516 | [[package]] 517 | name = "stack-safe" 518 | version = "0.0.1" 519 | dependencies = [ 520 | "clap", 521 | "criterion", 522 | "rand", 523 | "static_assertions", 524 | ] 525 | 526 | [[package]] 527 | name = "static_assertions" 528 | version = "1.1.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 531 | 532 | [[package]] 533 | name = "strsim" 534 | version = "0.8.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 537 | 538 | [[package]] 539 | name = "syn" 540 | version = "1.0.81" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" 543 | dependencies = [ 544 | "proc-macro2", 545 | "quote", 546 | "unicode-xid", 547 | ] 548 | 549 | [[package]] 550 | name = "textwrap" 551 | version = "0.11.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 554 | dependencies = [ 555 | "unicode-width", 556 | ] 557 | 558 | [[package]] 559 | name = "tinytemplate" 560 | version = "1.2.1" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 563 | dependencies = [ 564 | "serde", 565 | "serde_json", 566 | ] 567 | 568 | [[package]] 569 | name = "unicode-width" 570 | version = "0.1.9" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 573 | 574 | [[package]] 575 | name = "unicode-xid" 576 | version = "0.2.2" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 579 | 580 | [[package]] 581 | name = "vec_map" 582 | version = "0.8.2" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 585 | 586 | [[package]] 587 | name = "walkdir" 588 | version = "2.3.2" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" 591 | dependencies = [ 592 | "same-file", 593 | "winapi", 594 | "winapi-util", 595 | ] 596 | 597 | [[package]] 598 | name = "wasi" 599 | version = "0.10.2+wasi-snapshot-preview1" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 602 | 603 | [[package]] 604 | name = "wasm-bindgen" 605 | version = "0.2.78" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 608 | dependencies = [ 609 | "cfg-if", 610 | "wasm-bindgen-macro", 611 | ] 612 | 613 | [[package]] 614 | name = "wasm-bindgen-backend" 615 | version = "0.2.78" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 618 | dependencies = [ 619 | "bumpalo", 620 | "lazy_static", 621 | "log", 622 | "proc-macro2", 623 | "quote", 624 | "syn", 625 | "wasm-bindgen-shared", 626 | ] 627 | 628 | [[package]] 629 | name = "wasm-bindgen-macro" 630 | version = "0.2.78" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 633 | dependencies = [ 634 | "quote", 635 | "wasm-bindgen-macro-support", 636 | ] 637 | 638 | [[package]] 639 | name = "wasm-bindgen-macro-support" 640 | version = "0.2.78" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 643 | dependencies = [ 644 | "proc-macro2", 645 | "quote", 646 | "syn", 647 | "wasm-bindgen-backend", 648 | "wasm-bindgen-shared", 649 | ] 650 | 651 | [[package]] 652 | name = "wasm-bindgen-shared" 653 | version = "0.2.78" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 656 | 657 | [[package]] 658 | name = "web-sys" 659 | version = "0.3.55" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" 662 | dependencies = [ 663 | "js-sys", 664 | "wasm-bindgen", 665 | ] 666 | 667 | [[package]] 668 | name = "winapi" 669 | version = "0.3.9" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 672 | dependencies = [ 673 | "winapi-i686-pc-windows-gnu", 674 | "winapi-x86_64-pc-windows-gnu", 675 | ] 676 | 677 | [[package]] 678 | name = "winapi-i686-pc-windows-gnu" 679 | version = "0.4.0" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 682 | 683 | [[package]] 684 | name = "winapi-util" 685 | version = "0.1.5" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 688 | dependencies = [ 689 | "winapi", 690 | ] 691 | 692 | [[package]] 693 | name = "winapi-x86_64-pc-windows-gnu" 694 | version = "0.4.0" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 697 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stack-safe" 3 | version = "0.0.1" 4 | authors = ["Martin Huschenbett "] 5 | edition = "2021" 6 | publish = false 7 | description = "An attribute macro to make recursive functions stack-safe" 8 | homepage = "https://github.com/hurryabit/stack-safe#readme" 9 | repository = "https://github.com/hurryabit/stack-safe.git" 10 | license = "Apache 2.0" 11 | 12 | [dependencies] 13 | static_assertions = "1.1.0" 14 | clap = "~2.33.3" 15 | rand = "0.8.4" 16 | 17 | [dev-dependencies] 18 | criterion = { version = "0.3.5", features = ["html_reports"] } 19 | 20 | [profile.release] 21 | panic = "abort" 22 | 23 | [[bench]] 24 | name = "tarjan" 25 | harness = false 26 | 27 | [[bench]] 28 | name = "list" 29 | harness = false 30 | 31 | [[bench]] 32 | name = "tree" 33 | harness = false 34 | 35 | [[bench]] 36 | name = "calc" 37 | harness = false 38 | -------------------------------------------------------------------------------- /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 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stack-safety for free? 2 | 3 | This repository contains my experiments with the idea outlined in the blog post [Stack-safety for free?](https://hurryabit.github.io/blog/stack-safety-for-free/). Should I decide to create a package implementing the demonstrated technique in a reusable fashion, it will live here. 4 | -------------------------------------------------------------------------------- /benches/calc.rs: -------------------------------------------------------------------------------- 1 | #![feature(generators, generator_trait)] 2 | #![allow(clippy::unnecessary_cast)] 3 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 4 | use std::ops::{Generator, GeneratorState}; 5 | use std::pin::Pin; 6 | use std::time::Duration; 7 | 8 | pub type Num = f64; 9 | 10 | pub enum Expr { 11 | Num(Num), 12 | Add(Box, Box), 13 | Mul(Box, Box), 14 | } 15 | 16 | impl Drop for Expr { 17 | fn drop(&mut self) { 18 | std::mem::forget(std::mem::take(self)); 19 | } 20 | } 21 | 22 | impl Default for Expr { 23 | fn default() -> Self { 24 | Self::Num(0 as Num) 25 | } 26 | } 27 | 28 | impl Expr { 29 | pub fn eval_recursive(&self) -> Num { 30 | match self { 31 | Self::Num(num) => *num, 32 | Self::Add(expr1, expr2) => expr1.eval_recursive() + expr2.eval_recursive(), 33 | Self::Mul(expr1, expr2) => expr1.eval_recursive() * expr2.eval_recursive(), 34 | } 35 | } 36 | } 37 | 38 | impl Expr { 39 | pub fn eval_trampolined<'a>(&'a self) -> Num { 40 | trampoline(|e: &'a Self| { 41 | move |_| match e { 42 | Self::Num(n) => *n, 43 | Self::Add(e1, e2) => (yield e1.as_ref()) + (yield e2.as_ref()), 44 | Self::Mul(e1, e2) => (yield e1.as_ref()) * (yield e2.as_ref()), 45 | } 46 | })(self) 47 | } 48 | 49 | pub fn eval_trampolined_opt(&self) -> Num { 50 | pub enum Gen<'a> { 51 | Init { expr: &'a Expr }, 52 | Add1 { rhs: &'a Expr }, 53 | Add2 { lhs: Num }, 54 | Mul1 { rhs: &'a Expr }, 55 | Mul2 { lhs: Num }, 56 | Done, 57 | } 58 | 59 | impl<'a> Gen<'a> { 60 | pub fn init(expr: &'a Expr) -> Self { 61 | Self::Init { expr } 62 | } 63 | } 64 | 65 | impl<'a> Generator for Gen<'a> { 66 | type Yield = &'a Expr; 67 | type Return = Num; 68 | 69 | fn resume( 70 | self: std::pin::Pin<&mut Self>, 71 | val: Num, 72 | ) -> std::ops::GeneratorState { 73 | let this = self.get_mut(); 74 | match this { 75 | Self::Init { expr } => match expr { 76 | Expr::Num(num) => { 77 | *this = Self::Done; 78 | GeneratorState::Complete(*num) 79 | } 80 | Expr::Add(lhs, rhs) => { 81 | *this = Self::Add1 { rhs }; 82 | GeneratorState::Yielded(lhs) 83 | } 84 | Expr::Mul(lhs, rhs) => { 85 | *this = Self::Mul1 { rhs }; 86 | GeneratorState::Yielded(lhs) 87 | } 88 | }, 89 | Self::Add1 { rhs } => { 90 | let rhs = *rhs; 91 | *this = Self::Add2 { lhs: val }; 92 | GeneratorState::Yielded(rhs) 93 | } 94 | Self::Add2 { lhs } => { 95 | let lhs = *lhs; 96 | *this = Self::Done; 97 | GeneratorState::Complete(lhs + val) 98 | } 99 | Self::Mul1 { rhs } => { 100 | let rhs = *rhs; 101 | *this = Self::Mul2 { lhs: val }; 102 | GeneratorState::Yielded(rhs) 103 | } 104 | Self::Mul2 { lhs } => { 105 | let lhs = *lhs; 106 | *this = Self::Done; 107 | GeneratorState::Complete(lhs * val) 108 | } 109 | Self::Done => panic!("resuming finished generator"), 110 | } 111 | } 112 | } 113 | 114 | trampoline(Gen::init)(self) 115 | } 116 | } 117 | 118 | impl Expr { 119 | pub fn eval_iterative_cps(&self) -> Num { 120 | enum Cont<'a> { 121 | AddLhs { rhs: &'a Expr }, 122 | AddRhs { lhs: Num }, 123 | MulLhs { rhs: &'a Expr }, 124 | MulRhs { lhs: Num }, 125 | } 126 | 127 | let mut cont_chain = Vec::new(); 128 | let mut expr = self; 129 | loop { 130 | match expr { 131 | Self::Num(num) => { 132 | let mut val = *num; 133 | loop { 134 | if let Some(cont) = cont_chain.pop() { 135 | match cont { 136 | Cont::AddLhs { rhs } => { 137 | cont_chain.push(Cont::AddRhs { lhs: val }); 138 | expr = rhs; 139 | break; 140 | } 141 | Cont::AddRhs { lhs } => { 142 | val += lhs; 143 | } 144 | Cont::MulLhs { rhs } => { 145 | cont_chain.push(Cont::MulRhs { lhs: val }); 146 | expr = rhs; 147 | break; 148 | } 149 | Cont::MulRhs { lhs } => { 150 | val *= lhs; 151 | } 152 | } 153 | } else { 154 | return val; 155 | } 156 | } 157 | } 158 | Self::Add(lhs, rhs) => { 159 | cont_chain.push(Cont::AddLhs { rhs }); 160 | expr = lhs; 161 | } 162 | Self::Mul(lhs, rhs) => { 163 | cont_chain.push(Cont::MulLhs { rhs }); 164 | expr = lhs; 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | impl Expr { 172 | pub fn eval_iterative_rpn(&self) -> Num { 173 | enum Item<'a> { 174 | Operand(&'a Expr), 175 | Add, 176 | Mul, 177 | } 178 | 179 | let mut current_expr = self; 180 | let mut item_stack = Vec::new(); 181 | let mut value_stack = Vec::new(); 182 | let mut current_val = 0 as Num; 183 | 184 | loop { 185 | match current_expr { 186 | Expr::Num(val) => { 187 | value_stack.push(current_val); 188 | current_val = *val; 189 | loop { 190 | if let Some(item) = item_stack.pop() { 191 | match item { 192 | Item::Operand(expr) => { 193 | current_expr = expr; 194 | break; 195 | } 196 | Item::Add => { 197 | current_val += value_stack.pop().unwrap(); 198 | } 199 | Item::Mul => { 200 | current_val *= value_stack.pop().unwrap(); 201 | } 202 | } 203 | } else { 204 | return current_val; 205 | } 206 | } 207 | } 208 | Expr::Add(lhs, rhs) => { 209 | item_stack.push(Item::Add); 210 | item_stack.push(Item::Operand(rhs)); 211 | current_expr = lhs; 212 | } 213 | Expr::Mul(lhs, rhs) => { 214 | item_stack.push(Item::Mul); 215 | item_stack.push(Item::Operand(rhs)); 216 | current_expr = lhs; 217 | } 218 | } 219 | } 220 | } 221 | } 222 | 223 | mod examples { 224 | use std::fmt::Display; 225 | 226 | use super::*; 227 | use rand::random; 228 | 229 | pub struct Case { 230 | pub expr: Expr, 231 | pub eval: Num, 232 | pub size: usize, 233 | } 234 | 235 | #[derive(Clone, Copy)] 236 | pub enum Ops { 237 | Add, 238 | Mul, 239 | Rnd, 240 | } 241 | 242 | impl Display for Ops { 243 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 244 | match self { 245 | Self::Add => write!(f, "add"), 246 | Self::Mul => write!(f, "mul"), 247 | Self::Rnd => write!(f, "rnd"), 248 | } 249 | } 250 | } 251 | 252 | // impl Ops { 253 | // fn random(self) -> (&'static dyn Fn (Box, Box) -> Expr, fn (Num, Num) -> Num) { 254 | // use std::ops::Add; 255 | 256 | // let add = (&Expr::Add, Num::add); 257 | // match self { 258 | // Ops::Add => add, 259 | // Ops::Mul => todo!(), 260 | // Ops::Both => todo!(), 261 | // } 262 | // } 263 | // } 264 | 265 | pub fn simple() -> (Expr, Num) { 266 | let expr = { 267 | Expr::Add( 268 | Box::new(Expr::Num(1 as Num)), 269 | Box::new(Expr::Mul( 270 | Box::new(Expr::Num(2 as Num)), 271 | Box::new(Expr::Num(3 as Num)), 272 | )), 273 | ) 274 | }; 275 | (expr, 7 as Num) 276 | } 277 | 278 | fn random_num() -> Case { 279 | let num = random(); 280 | Case { 281 | expr: Expr::Num(num), 282 | eval: num, 283 | size: 1, 284 | } 285 | } 286 | 287 | fn random_bin(ops: Ops, lhs: Case, rhs: Case) -> Case { 288 | let lhs_expr = Box::new(lhs.expr); 289 | let rhs_expr = Box::new(rhs.expr); 290 | let add = match ops { 291 | Ops::Add => true, 292 | Ops::Mul => false, 293 | Ops::Rnd => random(), 294 | }; 295 | let (expr, eval) = if add { 296 | (Expr::Add(lhs_expr, rhs_expr), lhs.eval + rhs.eval) 297 | } else { 298 | (Expr::Mul(lhs_expr, rhs_expr), lhs.eval * rhs.eval) 299 | }; 300 | let size = 1 + lhs.size + rhs.size; 301 | Case { expr, eval, size } 302 | } 303 | 304 | fn tree_with(ops: Ops, n: usize, leaf: &dyn Fn() -> Case) -> Case { 305 | if n == 0 { 306 | leaf() 307 | } else { 308 | random_bin( 309 | ops, 310 | tree_with(ops, n - 1, leaf), 311 | tree_with(ops, n - 1, leaf), 312 | ) 313 | } 314 | } 315 | 316 | fn branch_with(ops: Ops, n: usize, leaf: &dyn Fn() -> Case) -> Case { 317 | let mut expr = leaf(); 318 | for _ in 0..n { 319 | expr = if random::() { 320 | random_bin(ops, expr, leaf()) 321 | } else { 322 | random_bin(ops, leaf(), expr) 323 | } 324 | } 325 | expr 326 | } 327 | 328 | pub fn one_branch(ops: Ops) -> Case { 329 | branch_with(ops, 512 * 1024 - 1, &random_num) 330 | } 331 | 332 | pub fn many_trees(ops: Ops) -> Case { 333 | branch_with(ops, 1023, &|| tree_with(ops, 9, &random_num)) 334 | } 335 | 336 | pub fn one_tree(ops: Ops) -> Case { 337 | tree_with(ops, 19, &random_num) 338 | } 339 | 340 | pub fn many_branches(ops: Ops) -> Case { 341 | tree_with(ops, 10, &|| branch_with(ops, 511, &random_num)) 342 | } 343 | } 344 | 345 | fn bench_expr_eval(c: &mut Criterion) { 346 | #![allow(clippy::type_complexity)] 347 | use examples::*; 348 | 349 | let implementations: &[(&str, fn(&Expr) -> Num)] = &[ 350 | ("recursive", Expr::eval_recursive), 351 | ("trampolined", Expr::eval_trampolined), 352 | ("trampolined_opt", Expr::eval_trampolined_opt), 353 | ("iterative_cps", Expr::eval_iterative_cps), 354 | // ("iterative_rpn", Expr::eval_iterative_rpn), 355 | ]; 356 | 357 | let (simple, simple_eval) = examples::simple(); 358 | for (_, impl_func) in implementations { 359 | assert_eq!(impl_func(&simple), simple_eval); 360 | } 361 | 362 | let cases: &[(&str, fn(examples::Ops) -> examples::Case)] = &[ 363 | ("one_tree", examples::one_tree), 364 | ("one_branch", examples::one_branch), 365 | ("many_trees", examples::many_trees), 366 | ("many_branches", examples::many_branches), 367 | ]; 368 | 369 | let group_name = format!("expr_{}", std::any::type_name::()); 370 | let mut group = c.benchmark_group(&group_name); 371 | for (case_name, case_func) in cases { 372 | for ops in [Ops::Add, Ops::Rnd] { 373 | let case_name = format!("{}_{}", case_name, ops); 374 | let case1 = case_func(ops); 375 | let case2 = case_func(ops); 376 | println!("{}/{} has size {}", group_name, case_name, case1.size); 377 | 378 | assert_eq!(case1.expr.eval_recursive(), case1.eval); 379 | stack_safe::with_stack_size(10 * 1024, || { 380 | for (impl_name, impl_func) in &implementations[1..] { 381 | assert_eq!( 382 | impl_func(&case1.expr), 383 | case1.eval, 384 | "testing implementation {} on case {}", 385 | impl_name, 386 | case_name, 387 | ); 388 | } 389 | }) 390 | .unwrap(); 391 | 392 | let cases = (case1, case2); 393 | for (impl_name, impl_func) in implementations { 394 | group.bench_with_input( 395 | BenchmarkId::new(*impl_name, &case_name), 396 | &cases, 397 | |b, (case1, case2)| { 398 | b.iter(|| { 399 | assert_eq!(impl_func(&case1.expr), case1.eval); 400 | assert_eq!(impl_func(&case2.expr), case2.eval); 401 | }) 402 | }, 403 | ); 404 | } 405 | } 406 | } 407 | group.finish(); 408 | } 409 | 410 | criterion_group! { 411 | name = benches; 412 | config = Criterion::default() 413 | .measurement_time(Duration::from_secs(60)) 414 | .warm_up_time(Duration::from_secs(5)) 415 | .sample_size(50) 416 | .configure_from_args(); 417 | targets = bench_expr_eval 418 | } 419 | criterion_main!(benches); 420 | 421 | pub fn trampoline(f: impl Fn(Arg) -> Gen) -> impl Fn(Arg) -> Res 422 | where 423 | Res: Default, 424 | Gen: Generator + Unpin, 425 | { 426 | move |arg: Arg| { 427 | let mut stack = Vec::new(); 428 | let mut current = f(arg); 429 | let mut res = Res::default(); 430 | 431 | loop { 432 | match Pin::new(&mut current).resume(res) { 433 | GeneratorState::Yielded(arg) => { 434 | stack.push(current); 435 | current = f(arg); 436 | res = Res::default(); 437 | } 438 | GeneratorState::Complete(real_res) => match stack.pop() { 439 | None => return real_res, 440 | Some(top) => { 441 | current = top; 442 | res = real_res; 443 | } 444 | }, 445 | } 446 | } 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /benches/list.rs: -------------------------------------------------------------------------------- 1 | #![feature(generators, generator_trait, step_trait)] 2 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 3 | use std::time::Duration; 4 | 5 | mod list { 6 | use std::ops::Range; 7 | 8 | pub enum List { 9 | Nil, 10 | Cons { head: T, tail: Box> }, 11 | } 12 | 13 | impl Drop for List { 14 | fn drop(&mut self) { 15 | if let Self::Cons { .. } = self { 16 | let mut list = std::mem::replace(self, List::Nil); 17 | while let Self::Cons { head, tail } = &mut list { 18 | let next = std::mem::replace(tail.as_mut(), Self::Nil); 19 | unsafe { 20 | std::ptr::drop_in_place(head as *mut T); 21 | std::ptr::drop_in_place(tail as *mut Box>); 22 | } 23 | std::mem::forget::>(list); 24 | list = next; 25 | } 26 | std::mem::forget::>(list); 27 | } 28 | } 29 | } 30 | 31 | impl List { 32 | pub fn len_recursive(&self) -> usize { 33 | match self { 34 | Self::Nil => 0, 35 | Self::Cons { head: _, tail } => 1 + tail.len_recursive(), 36 | } 37 | } 38 | 39 | pub fn len_stack_safe(&self) -> usize { 40 | stack_safe::trampoline(|list: &List| { 41 | move |_: usize| match list { 42 | Self::Nil => 0, 43 | Self::Cons { head: _, tail } => { 44 | let tail_len = yield tail.as_ref(); 45 | 1 + tail_len 46 | } 47 | } 48 | })(self) 49 | } 50 | } 51 | 52 | impl From> for List { 53 | fn from(range: Range) -> Self { 54 | let mut result = Self::Nil; 55 | for value in range.rev() { 56 | result = Self::Cons { 57 | head: value, 58 | tail: Box::new(result), 59 | } 60 | } 61 | result 62 | } 63 | } 64 | } 65 | 66 | pub fn bench_list_len(c: &mut Criterion) { 67 | use list::*; 68 | 69 | let cases: [(&str, usize); 1] = [("L_{size}", 1_000_000)]; 70 | 71 | let mut group = c.benchmark_group("list_len"); 72 | for (label, size) in cases { 73 | let label = label.replace("{size}", &size.to_string()); 74 | let list = List::from(0..size); 75 | 76 | assert_eq!(list.len_recursive(), size); 77 | assert_eq!( 78 | stack_safe::with_stack_size(1024, move || List::from(0..size).len_stack_safe()) 79 | .unwrap(), 80 | size, 81 | ); 82 | 83 | group.bench_with_input(BenchmarkId::new("recursive", &label), &list, |b, list| { 84 | b.iter(|| { 85 | assert_eq!(list.len_recursive(), size); 86 | }) 87 | }); 88 | group.bench_with_input(BenchmarkId::new("stack_safe", &label), &list, |b, list| { 89 | b.iter(|| { 90 | assert_eq!(list.len_stack_safe(), size); 91 | }) 92 | }); 93 | } 94 | group.finish(); 95 | } 96 | 97 | criterion_group! { 98 | name = benches; 99 | config = Criterion::default() 100 | .measurement_time(Duration::from_secs(10)) 101 | .warm_up_time(Duration::from_secs(2)) 102 | .sample_size(20) 103 | .configure_from_args(); 104 | targets = bench_list_len 105 | } 106 | criterion_main!(benches); 107 | -------------------------------------------------------------------------------- /benches/tarjan.rs: -------------------------------------------------------------------------------- 1 | #![feature(destructuring_assignment, generators, generator_trait)] 2 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 3 | use std::time::Duration; 4 | 5 | mod tarjan { 6 | use std::collections::HashSet; 7 | 8 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 9 | pub struct Node { 10 | id: usize, 11 | } 12 | 13 | impl Node { 14 | pub const fn new(id: usize) -> Self { 15 | Self { id } 16 | } 17 | } 18 | 19 | pub type Graph = Vec>; 20 | 21 | pub type SCCs = Vec>; 22 | 23 | #[derive(Default)] 24 | struct State { 25 | index: usize, 26 | indices: Vec, 27 | lowlinks: Vec, 28 | components: SCCs, 29 | stack: Vec, 30 | on_stack: HashSet, 31 | } 32 | 33 | pub fn recursive(graph: &Graph) -> SCCs { 34 | use std::cmp::min; 35 | 36 | let n = graph.len(); 37 | let mut s = State { 38 | index: 0, 39 | indices: Vec::with_capacity(n), 40 | lowlinks: Vec::with_capacity(n), 41 | components: Vec::new(), 42 | stack: Vec::new(), 43 | on_stack: HashSet::new(), 44 | }; 45 | s.indices.resize(n, usize::MAX); 46 | s.lowlinks.resize(n, usize::MAX); 47 | 48 | fn dfs(v: Node, graph: &Graph, s: &mut State) { 49 | s.indices[v.id] = s.index; 50 | s.lowlinks[v.id] = s.index; 51 | s.index += 1; 52 | s.stack.push(v); 53 | s.on_stack.insert(v); 54 | 55 | for &w in &graph[v.id] { 56 | if s.indices[w.id] == usize::MAX { 57 | dfs(w, graph, s); 58 | s.lowlinks[v.id] = min(s.lowlinks[v.id], s.lowlinks[w.id]); 59 | } else if s.on_stack.contains(&w) { 60 | s.lowlinks[v.id] = min(s.lowlinks[v.id], s.indices[w.id]); 61 | } 62 | } 63 | 64 | if s.lowlinks[v.id] == s.indices[v.id] { 65 | let mut component = Vec::new(); 66 | let mut w = Node { id: usize::MAX }; 67 | while w != v { 68 | w = s.stack.pop().unwrap(); 69 | s.on_stack.remove(&w); 70 | component.push(w) 71 | } 72 | s.components.push(component); 73 | } 74 | } 75 | 76 | for id in 0..n { 77 | let v = Node { id }; 78 | if s.indices[v.id] == usize::MAX { 79 | dfs(v, graph, &mut s); 80 | } 81 | } 82 | 83 | s.components 84 | } 85 | 86 | pub fn stack_safe(graph: &Graph) -> SCCs { 87 | use stack_safe::trampoline_mut; 88 | use std::cmp::min; 89 | 90 | let n = graph.len(); 91 | let mut s = State { 92 | index: 0, 93 | indices: Vec::with_capacity(n), 94 | lowlinks: Vec::with_capacity(n), 95 | components: Vec::new(), 96 | stack: Vec::new(), 97 | on_stack: HashSet::new(), 98 | }; 99 | s.indices.resize(n, usize::MAX); 100 | s.lowlinks.resize(n, usize::MAX); 101 | 102 | #[allow(clippy::needless_lifetimes)] 103 | fn dfs<'a>(v: Node, graph: &'a Graph, s: &'a mut State) { 104 | let gen = |(v, graph): (Node, &'a Graph)| { 105 | move |(_, mut s): ((), &'a mut State)| { 106 | s.indices[v.id] = s.index; 107 | s.lowlinks[v.id] = s.index; 108 | s.index += 1; 109 | s.stack.push(v); 110 | s.on_stack.insert(v); 111 | 112 | for &w in &graph[v.id] { 113 | if s.indices[w.id] == usize::MAX { 114 | ((), s) = yield ((w, graph), s); 115 | s.lowlinks[v.id] = min(s.lowlinks[v.id], s.lowlinks[w.id]); 116 | } else if s.on_stack.contains(&w) { 117 | s.lowlinks[v.id] = min(s.lowlinks[v.id], s.indices[w.id]); 118 | } 119 | } 120 | 121 | if s.lowlinks[v.id] == s.indices[v.id] { 122 | let mut component = Vec::new(); 123 | let mut w = Node { id: usize::MAX }; 124 | while w != v { 125 | w = s.stack.pop().unwrap(); 126 | s.on_stack.remove(&w); 127 | component.push(w) 128 | } 129 | s.components.push(component); 130 | } 131 | ((), s) 132 | } 133 | }; 134 | static_assertions::assert_eq_size_val!(gen((v, graph)), [0u8; 72]); 135 | trampoline_mut(gen)((v, graph), s) 136 | } 137 | 138 | for id in 0..n { 139 | let v = Node { id }; 140 | if s.indices[v.id] == usize::MAX { 141 | dfs(v, graph, &mut s); 142 | } 143 | } 144 | 145 | s.components 146 | } 147 | 148 | pub mod manual { 149 | use super::{Graph, Node, SCCs, State}; 150 | use std::cmp::min; 151 | use std::collections::HashSet; 152 | use std::ops::{Generator, GeneratorState}; 153 | 154 | enum DfsGen<'a> { 155 | Init { 156 | v: Node, 157 | graph: &'a Graph, 158 | }, 159 | Call { 160 | v: Node, 161 | graph: &'a Graph, 162 | ws: std::slice::Iter<'a, Node>, 163 | w: Node, 164 | }, 165 | Done, 166 | } 167 | 168 | static_assertions::assert_eq_size!(DfsGen, [u8; 48]); 169 | 170 | impl<'a> DfsGen<'a> { 171 | pub fn init((v, graph): (Node, &'a Graph)) -> Self { 172 | Self::Init { v, graph } 173 | } 174 | } 175 | 176 | impl<'a> Generator<((), &'a mut State)> for DfsGen<'a> { 177 | type Yield = ((Node, &'a Graph), &'a mut State); 178 | type Return = ((), &'a mut State); 179 | 180 | fn resume( 181 | self: std::pin::Pin<&mut Self>, 182 | ((), s): ((), &'a mut State), 183 | ) -> GeneratorState { 184 | let this = self.get_mut(); 185 | match std::mem::replace(this, Self::Done) { 186 | Self::Init { v, graph } => { 187 | s.indices[v.id] = s.index; 188 | s.lowlinks[v.id] = s.index; 189 | s.index += 1; 190 | s.stack.push(v); 191 | s.on_stack.insert(v); 192 | 193 | let mut ws = graph[v.id].iter(); 194 | #[allow(clippy::while_let_on_iterator)] 195 | while let Some(&w) = ws.next() { 196 | if s.indices[w.id] == usize::MAX { 197 | *this = Self::Call { v, graph, ws, w }; 198 | return GeneratorState::Yielded(((w, graph), s)); 199 | } else if s.on_stack.contains(&w) { 200 | s.lowlinks[v.id] = min(s.lowlinks[v.id], s.indices[w.id]); 201 | } 202 | } 203 | 204 | if s.lowlinks[v.id] == s.indices[v.id] { 205 | let mut component = Vec::new(); 206 | let mut w = Node { id: usize::MAX }; 207 | while w != v { 208 | w = s.stack.pop().unwrap(); 209 | s.on_stack.remove(&w); 210 | component.push(w) 211 | } 212 | s.components.push(component); 213 | } 214 | *this = Self::Done; 215 | GeneratorState::Complete(((), s)) 216 | } 217 | Self::Call { 218 | v, 219 | graph, 220 | mut ws, 221 | w, 222 | } => { 223 | s.lowlinks[v.id] = min(s.lowlinks[v.id], s.lowlinks[w.id]); 224 | 225 | while let Some(&w) = ws.next() { 226 | if s.indices[w.id] == usize::MAX { 227 | *this = Self::Call { v, graph, ws, w }; 228 | return GeneratorState::Yielded(((w, graph), s)); 229 | } else if s.on_stack.contains(&w) { 230 | s.lowlinks[v.id] = min(s.lowlinks[v.id], s.indices[w.id]); 231 | } 232 | } 233 | 234 | if s.lowlinks[v.id] == s.indices[v.id] { 235 | let mut component = Vec::new(); 236 | let mut w = Node { id: usize::MAX }; 237 | while w != v { 238 | w = s.stack.pop().unwrap(); 239 | s.on_stack.remove(&w); 240 | component.push(w) 241 | } 242 | s.components.push(component); 243 | } 244 | *this = Self::Done; 245 | GeneratorState::Complete(((), s)) 246 | } 247 | Self::Done => panic!("Trying to resume finished DfsGen."), 248 | } 249 | } 250 | } 251 | 252 | pub fn stack_safe(graph: &Graph) -> SCCs { 253 | use stack_safe::trampoline_mut; 254 | 255 | let n = graph.len(); 256 | let mut s = State { 257 | index: 0, 258 | indices: Vec::with_capacity(n), 259 | lowlinks: Vec::with_capacity(n), 260 | components: Vec::new(), 261 | stack: Vec::new(), 262 | on_stack: HashSet::new(), 263 | }; 264 | s.indices.resize(n, usize::MAX); 265 | s.lowlinks.resize(n, usize::MAX); 266 | 267 | fn dfs(v: Node, graph: &Graph, s: &mut State) { 268 | trampoline_mut(DfsGen::init)((v, graph), s) 269 | } 270 | 271 | for id in 0..n { 272 | let v = Node { id }; 273 | if s.indices[v.id] == usize::MAX { 274 | dfs(v, graph, &mut s); 275 | } 276 | } 277 | 278 | s.components 279 | } 280 | } 281 | 282 | pub mod examples { 283 | #![allow(non_upper_case_globals)] 284 | use super::*; 285 | 286 | const v0: Node = Node::new(0); 287 | const v1: Node = Node::new(1); 288 | const v2: Node = Node::new(2); 289 | const v3: Node = Node::new(3); 290 | const v4: Node = Node::new(4); 291 | 292 | pub fn simple() -> Graph { 293 | vec![vec![v1], vec![v2, v3], vec![v1, v4], vec![v2], vec![]] 294 | } 295 | 296 | pub fn simple_sccs() -> SCCs { 297 | vec![vec![v4], vec![v3, v2, v1], vec![v0]] 298 | } 299 | 300 | pub fn path(n: usize) -> Graph { 301 | let mut graph: Vec<_> = (1..n).map(|id| vec![Node::new(id)]).collect(); 302 | graph.push(vec![]); 303 | graph 304 | } 305 | 306 | pub fn path_sccs(n: usize) -> SCCs { 307 | (0..n).rev().map(|id| vec![Node::new(id)]).collect() 308 | } 309 | 310 | pub fn path_rev(n: usize) -> Graph { 311 | let mut graph = vec![vec![]]; 312 | graph.extend((0..(n - 1)).map(|id| vec![Node::new(id)])); 313 | graph 314 | } 315 | 316 | pub fn path_rev_sccs(n: usize) -> SCCs { 317 | (0..n).map(|id| vec![Node::new(id)]).collect() 318 | } 319 | 320 | pub fn complete(n: usize) -> Graph { 321 | let outgoing: Vec<_> = (0..n).map(Node::new).collect(); 322 | (0..n).map(|_| outgoing.clone()).collect() 323 | } 324 | 325 | pub fn complete_sccs(n: usize) -> SCCs { 326 | vec![(0..n).rev().map(Node::new).collect()] 327 | } 328 | } 329 | } 330 | 331 | pub fn bench_tarjan(c: &mut Criterion) { 332 | use tarjan::*; 333 | 334 | assert_eq!(recursive(&examples::simple()), examples::simple_sccs()); 335 | assert_eq!(stack_safe(&examples::simple()), examples::simple_sccs()); 336 | 337 | #[allow(clippy::type_complexity)] 338 | let cases: [(&str, fn(usize) -> Graph, fn(usize) -> SCCs, usize); 3] = [ 339 | ("P_{size}", examples::path, examples::path_sccs, 10_000), 340 | ( 341 | "P_{size}_rev", 342 | examples::path_rev, 343 | examples::path_rev_sccs, 344 | 10_000, 345 | ), 346 | ("K_{size}", examples::complete, examples::complete_sccs, 500), 347 | ]; 348 | 349 | let mut group = c.benchmark_group("tarjan"); 350 | for (label, graph, sccs, size) in cases { 351 | let label = label.replace("{size}", &size.to_string()); 352 | let graph = graph(size); 353 | let sccs = sccs(size); 354 | 355 | assert_eq!(recursive(&graph), sccs); 356 | let graph_clone = graph.clone(); 357 | assert_eq!( 358 | stack_safe::with_stack_size(10 * 1024, move || stack_safe(&graph_clone)).unwrap(), 359 | sccs, 360 | ); 361 | let graph_clone = graph.clone(); 362 | assert_eq!( 363 | stack_safe::with_stack_size(10 * 1024, move || manual::stack_safe(&graph_clone)) 364 | .unwrap(), 365 | sccs, 366 | ); 367 | 368 | group.bench_with_input(BenchmarkId::new("recursive", &label), &graph, |b, graph| { 369 | b.iter(|| { 370 | recursive(graph); 371 | }) 372 | }); 373 | group.bench_with_input( 374 | BenchmarkId::new("stack_safe", &label), 375 | &graph, 376 | |b, graph| { 377 | b.iter(|| { 378 | stack_safe(graph); 379 | }) 380 | }, 381 | ); 382 | group.bench_with_input(BenchmarkId::new("manual", &label), &graph, |b, graph| { 383 | b.iter(|| { 384 | manual::stack_safe(graph); 385 | }) 386 | }); 387 | } 388 | group.finish(); 389 | } 390 | 391 | criterion_group! { 392 | name = benches; 393 | config = Criterion::default() 394 | .measurement_time(Duration::from_secs(10)) 395 | .warm_up_time(Duration::from_secs(2)) 396 | .sample_size(20) 397 | .configure_from_args(); 398 | targets = bench_tarjan 399 | } 400 | criterion_main!(benches); 401 | -------------------------------------------------------------------------------- /benches/tree.rs: -------------------------------------------------------------------------------- 1 | #![feature(destructuring_assignment, generators, generator_trait)] 2 | use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; 3 | use std::time::Duration; 4 | 5 | mod tree { 6 | use std::cmp::max; 7 | 8 | #[derive(Clone, Debug)] 9 | pub struct Tree { 10 | pub value: i64, 11 | pub children: Vec, 12 | } 13 | 14 | impl Tree { 15 | pub fn new(id: i64) -> Self { 16 | Self { 17 | value: id, 18 | children: Vec::new(), 19 | } 20 | } 21 | } 22 | 23 | impl Drop for Tree { 24 | fn drop(&mut self) { 25 | if !self.children.is_empty() { 26 | let mut stack = std::mem::take(&mut self.children); 27 | while let Some(mut node) = stack.pop() { 28 | stack.append(&mut node.children); 29 | } 30 | } 31 | } 32 | } 33 | 34 | impl Tree { 35 | pub fn depth_recursive(&self) -> usize { 36 | let mut max_child_depth = 0; 37 | for child in &self.children { 38 | max_child_depth = max(max_child_depth, child.depth_recursive()); 39 | } 40 | max_child_depth + 1 41 | } 42 | 43 | pub fn depth_stack_safe(&self) -> usize { 44 | stack_safe::trampoline(|tree: &Self| { 45 | move |_: usize| { 46 | let mut max_child_depth = 0; 47 | for child in &tree.children { 48 | let child_depth = yield child; 49 | max_child_depth = max(max_child_depth, child_depth); 50 | } 51 | max_child_depth + 1 52 | } 53 | })(self) 54 | } 55 | 56 | pub fn depth_manual(&self) -> usize { 57 | stack_safe::trampoline(manual::DepthGen::init)(self) 58 | } 59 | 60 | pub fn sum_recursive(&self) -> i64 { 61 | let mut result = self.value; 62 | for child in &self.children { 63 | result += child.sum_recursive(); 64 | } 65 | result 66 | } 67 | 68 | pub fn sum_stack_safe(&self) -> i64 { 69 | stack_safe::trampoline(|tree: &Self| { 70 | move |_: i64| { 71 | let mut result = tree.value; 72 | for child in &tree.children { 73 | result += yield child; 74 | } 75 | result 76 | } 77 | })(self) 78 | } 79 | 80 | pub fn sum_manual(&self) -> i64 { 81 | stack_safe::trampoline(manual::SumGen::init)(self) 82 | } 83 | 84 | pub fn sum_loop(&self) -> i64 { 85 | let mut sum = self.value; 86 | let mut forest = self.children.iter(); 87 | let mut stack = Vec::new(); 88 | 89 | loop { 90 | if let Some(tree) = forest.next() { 91 | sum += tree.value; 92 | stack.push(forest); 93 | forest = tree.children.iter(); 94 | } else if let Some(top) = stack.pop() { 95 | forest = top; 96 | } else { 97 | break sum; 98 | } 99 | } 100 | } 101 | } 102 | 103 | mod manual { 104 | use super::*; 105 | use std::ops::{Generator, GeneratorState}; 106 | 107 | pub enum DepthGen<'a> { 108 | Init { 109 | tree: &'a Tree, 110 | }, 111 | Call { 112 | max_child_depth: usize, 113 | children: std::slice::Iter<'a, Tree>, 114 | }, 115 | Done, 116 | } 117 | 118 | static_assertions::const_assert_eq!(std::mem::size_of::(), 32); 119 | 120 | impl<'a> DepthGen<'a> { 121 | #[inline(always)] 122 | pub fn init(tree: &'a Tree) -> Self { 123 | Self::Init { tree } 124 | } 125 | } 126 | 127 | impl<'a> Generator for DepthGen<'a> { 128 | type Yield = &'a Tree; 129 | type Return = usize; 130 | 131 | #[inline(always)] 132 | fn resume( 133 | self: std::pin::Pin<&mut Self>, 134 | child_depth: usize, 135 | ) -> GeneratorState { 136 | let this = self.get_mut(); 137 | match this { 138 | Self::Init { tree } => { 139 | let max_child_depth = 0; 140 | let mut children = tree.children.iter(); 141 | if let Some(child) = children.next() { 142 | *this = Self::Call { 143 | max_child_depth, 144 | children, 145 | }; 146 | GeneratorState::Yielded(child) 147 | } else { 148 | *this = Self::Done; 149 | GeneratorState::Complete(max_child_depth + 1) 150 | } 151 | } 152 | Self::Call { 153 | max_child_depth, 154 | children, 155 | } => { 156 | *max_child_depth = max(*max_child_depth, child_depth); 157 | if let Some(child) = children.next() { 158 | GeneratorState::Yielded(child) 159 | } else { 160 | let depth = *max_child_depth + 1; 161 | *this = Self::Done; 162 | GeneratorState::Complete(depth) 163 | } 164 | } 165 | Self::Done => panic!("Trying to resume completed DepthGen"), 166 | } 167 | } 168 | } 169 | 170 | pub enum SumGen<'a> { 171 | Init { 172 | tree: &'a Tree, 173 | }, 174 | Call { 175 | sum: i64, 176 | children: std::slice::Iter<'a, Tree>, 177 | }, 178 | Done, 179 | } 180 | 181 | static_assertions::const_assert_eq!(std::mem::size_of::(), 32); 182 | 183 | impl<'a> SumGen<'a> { 184 | #[inline(always)] 185 | pub fn init(tree: &'a Tree) -> Self { 186 | Self::Init { tree } 187 | } 188 | } 189 | 190 | impl<'a> Generator for SumGen<'a> { 191 | type Yield = &'a Tree; 192 | type Return = i64; 193 | 194 | #[inline(always)] 195 | fn resume( 196 | self: std::pin::Pin<&mut Self>, 197 | child_sum: i64, 198 | ) -> GeneratorState { 199 | let this = self.get_mut(); 200 | match this { 201 | Self::Init { tree } => { 202 | let sum = tree.value; 203 | let mut children = tree.children.iter(); 204 | if let Some(child) = children.next() { 205 | *this = Self::Call { sum, children }; 206 | GeneratorState::Yielded(child) 207 | } else { 208 | *this = Self::Done; 209 | GeneratorState::Complete(sum) 210 | } 211 | } 212 | Self::Call { sum, children } => { 213 | *sum += child_sum; 214 | if let Some(child) = children.next() { 215 | GeneratorState::Yielded(child) 216 | } else { 217 | let sum = *sum; 218 | *this = Self::Done; 219 | GeneratorState::Complete(sum) 220 | } 221 | } 222 | Self::Done => panic!("Trying to resume completed DepthGen"), 223 | } 224 | } 225 | } 226 | } 227 | 228 | pub mod examples { 229 | use super::*; 230 | 231 | pub fn simple() -> (Tree, usize, i64) { 232 | let mut v0 = Tree::new(0); 233 | let v1 = Tree::new(1); 234 | let mut v2 = Tree::new(2); 235 | let v3 = Tree::new(3); 236 | v2.children = vec![v3]; 237 | v0.children = vec![v1, v2]; 238 | (v0, 3, 6) 239 | } 240 | 241 | pub fn path(n: usize) -> (Tree, usize, i64) { 242 | let mut tree = Tree::new(n as i64 - 1); 243 | for k in (0..(n - 1)).rev() { 244 | let mut parent = Tree::new(k as i64); 245 | parent.children.push(tree); 246 | tree = parent; 247 | } 248 | (tree, n, (n * (n - 1) / 2) as i64) 249 | } 250 | 251 | pub fn binary(n: usize) -> (Tree, usize, i64) { 252 | if n <= 1 { 253 | (Tree::new(1), 1, 1) 254 | } else { 255 | let mut tree = Tree::new(1); 256 | tree.children = vec![binary(n - 1).0, binary(n - 1).0]; 257 | (tree, n, 2i64.pow(n as u32) - 1) 258 | } 259 | } 260 | } 261 | } 262 | 263 | fn bench_tree_depth(c: &mut Criterion) { 264 | use tree::*; 265 | 266 | let (simple, simple_depth, _simple_sum) = examples::simple(); 267 | assert_eq!(simple.depth_recursive(), simple_depth); 268 | assert_eq!(simple.depth_stack_safe(), simple_depth); 269 | 270 | #[allow(clippy::type_complexity)] 271 | let cases: [(&str, fn(usize) -> (Tree, usize, i64), usize); 2] = [ 272 | ("P_{size}", examples::path, 100_000), 273 | ("B_{size}", examples::binary, 20), 274 | ]; 275 | 276 | let mut group = c.benchmark_group("tree_depth"); 277 | for (label, tree_f, size) in cases { 278 | let label = label.replace("{size}", &size.to_string()); 279 | let (tree, tree_depth, _tree_sum) = tree_f(size); 280 | 281 | assert_eq!(tree.depth_recursive(), tree_depth); 282 | assert_eq!( 283 | stack_safe::with_stack_size(1024, move || tree_f(size).0.depth_stack_safe()).unwrap(), 284 | tree_depth, 285 | ); 286 | assert_eq!( 287 | stack_safe::with_stack_size(1024, move || tree_f(size).0.depth_manual()).unwrap(), 288 | tree_depth, 289 | ); 290 | 291 | group.bench_with_input(BenchmarkId::new("recursive", &label), &tree, |b, tree| { 292 | b.iter(|| { 293 | assert_eq!(tree.depth_recursive(), tree_depth); 294 | }) 295 | }); 296 | group.bench_with_input(BenchmarkId::new("stack_safe", &label), &tree, |b, tree| { 297 | b.iter(|| { 298 | assert_eq!(tree.depth_stack_safe(), tree_depth); 299 | }) 300 | }); 301 | group.bench_with_input(BenchmarkId::new("manual", &label), &tree, |b, tree| { 302 | b.iter(|| { 303 | assert_eq!(tree.depth_manual(), tree_depth); 304 | }) 305 | }); 306 | } 307 | group.finish(); 308 | } 309 | 310 | fn bench_tree_sum(c: &mut Criterion) { 311 | use tree::*; 312 | 313 | let (simple, _simple_depth, simple_sum) = examples::simple(); 314 | assert_eq!(simple.sum_recursive(), simple_sum); 315 | assert_eq!(simple.sum_stack_safe(), simple_sum); 316 | 317 | #[allow(clippy::type_complexity)] 318 | let cases: [(&str, fn(usize) -> (Tree, usize, i64), usize); 2] = [ 319 | ("P_{size}", examples::path, 100_000), 320 | ("B_{size}", examples::binary, 20), 321 | ]; 322 | 323 | let mut group = c.benchmark_group("tree_sum"); 324 | for (label, tree_f, size) in cases { 325 | let label = label.replace("{size}", &size.to_string()); 326 | let (tree, _tree_depth, tree_sum) = tree_f(size); 327 | 328 | assert_eq!(tree.sum_recursive(), tree_sum); 329 | assert_eq!( 330 | stack_safe::with_stack_size(1024, move || tree_f(size).0.sum_stack_safe()).unwrap(), 331 | tree_sum, 332 | ); 333 | assert_eq!( 334 | stack_safe::with_stack_size(1024, move || tree_f(size).0.sum_manual()).unwrap(), 335 | tree_sum, 336 | ); 337 | assert_eq!( 338 | stack_safe::with_stack_size(1024, move || tree_f(size).0.sum_loop()).unwrap(), 339 | tree_sum, 340 | ); 341 | 342 | group.bench_with_input(BenchmarkId::new("recursive", &label), &tree, |b, tree| { 343 | b.iter(|| { 344 | assert_eq!(tree.sum_recursive(), tree_sum); 345 | }) 346 | }); 347 | group.bench_with_input(BenchmarkId::new("stack_safe", &label), &tree, |b, tree| { 348 | b.iter(|| { 349 | assert_eq!(tree.sum_stack_safe(), tree_sum); 350 | }) 351 | }); 352 | group.bench_with_input(BenchmarkId::new("manual", &label), &tree, |b, tree| { 353 | b.iter(|| { 354 | assert_eq!(tree.sum_manual(), tree_sum); 355 | }) 356 | }); 357 | group.bench_with_input(BenchmarkId::new("loop", &label), &tree, |b, tree| { 358 | b.iter(|| { 359 | assert_eq!(tree.sum_loop(), tree_sum); 360 | }) 361 | }); 362 | } 363 | group.finish(); 364 | } 365 | 366 | criterion_group! { 367 | name = benches; 368 | config = Criterion::default() 369 | .measurement_time(Duration::from_secs(10)) 370 | .warm_up_time(Duration::from_secs(2)) 371 | .sample_size(20) 372 | .configure_from_args(); 373 | targets = bench_tree_sum, bench_tree_depth 374 | } 375 | criterion_main!(benches); 376 | -------------------------------------------------------------------------------- /examples/blog_expr.rs: -------------------------------------------------------------------------------- 1 | #![feature(generators, generator_trait)] 2 | #![allow( 3 | clippy::borrowed_box, 4 | clippy::needless_return, 5 | clippy::while_let_on_iterator 6 | )] 7 | use stack_safe::trampoline; 8 | use std::mem::MaybeUninit; 9 | use std::ops::{Generator, GeneratorState}; 10 | use std::pin::Pin; 11 | 12 | #[derive(Debug)] 13 | pub enum Exp { 14 | Num(f64), 15 | Add(Vec), 16 | Mul(Box, Box), 17 | } 18 | 19 | impl Exp { 20 | fn eval_recursive(&self) -> f64 { 21 | match self { 22 | Exp::Num(val) => *val, 23 | Exp::Add(exps) => { 24 | let mut sum = 0.0; 25 | for exp in exps { 26 | let val = exp.eval_recursive(); 27 | sum += val; 28 | } 29 | sum 30 | } 31 | Exp::Mul(exp1, exp2) => { 32 | let val1 = exp1.eval_recursive(); 33 | let val2 = exp2.eval_recursive(); 34 | val1 * val2 35 | } 36 | } 37 | } 38 | } 39 | 40 | impl Exp { 41 | fn eval_generator(&self) -> impl Generator { 42 | move |_| match self { 43 | Exp::Num(val) => { 44 | return *val; 45 | } 46 | Exp::Add(exps) => { 47 | let mut sum = 0.0; 48 | let mut iter = exps.iter(); 49 | while let Some(exp) = iter.next() { 50 | let val = yield exp; // Gen::Add 51 | sum += val; 52 | } 53 | return sum; 54 | } 55 | Exp::Mul(exp1, exp2) => { 56 | let val1 = yield exp1; // Gen::Mul1 57 | let val2 = yield exp2; // Gen::Mul2 58 | return val1 * val2; 59 | } 60 | } 61 | } 62 | } 63 | 64 | pub enum SimpleGen<'a> { 65 | Unresumed { 66 | init: &'a Exp, 67 | }, 68 | Returned, 69 | Add { 70 | sum: f64, 71 | iter: std::slice::Iter<'a, Exp>, 72 | }, 73 | Mul1 { 74 | exp2: &'a Box, 75 | }, 76 | Mul2 { 77 | val1: f64, 78 | }, 79 | } 80 | 81 | impl<'a> SimpleGen<'a> { 82 | pub fn new(init: &'a Exp) -> Self { 83 | SimpleGen::Unresumed { init } 84 | } 85 | } 86 | 87 | impl<'a> Generator for SimpleGen<'a> { 88 | type Yield = &'a Exp; 89 | type Return = f64; 90 | 91 | fn resume(self: Pin<&mut Self>, arg: f64) -> GeneratorState { 92 | let this = self.get_mut(); 93 | match this { 94 | SimpleGen::Unresumed { init } => match init { 95 | Exp::Num(val) => { 96 | *this = SimpleGen::Returned; 97 | GeneratorState::Complete(*val) 98 | } 99 | Exp::Add(exps) => { 100 | let sum = 0.0; 101 | let mut iter = exps.iter(); 102 | if let Some(exp) = iter.next() { 103 | *this = SimpleGen::Add { sum, iter }; 104 | GeneratorState::Yielded(exp) 105 | } else { 106 | *this = SimpleGen::Returned; 107 | GeneratorState::Complete(sum) 108 | } 109 | } 110 | Exp::Mul(exp1, exp2) => { 111 | *this = SimpleGen::Mul1 { exp2 }; 112 | GeneratorState::Yielded(exp1) 113 | } 114 | }, 115 | SimpleGen::Returned => panic!("resuming returned generator"), 116 | SimpleGen::Add { sum, iter } => { 117 | let val = arg; 118 | *sum += val; 119 | if let Some(exp) = iter.next() { 120 | // We don't need to change `this` here because we have 121 | // updated `sum` and `iter` in place. 122 | GeneratorState::Yielded(exp) 123 | } else { 124 | let sum = *sum; 125 | *this = SimpleGen::Returned; 126 | GeneratorState::Complete(sum) 127 | } 128 | } 129 | SimpleGen::Mul1 { exp2 } => { 130 | let val1 = arg; 131 | let exp2 = *exp2; 132 | *this = SimpleGen::Mul2 { val1 }; 133 | GeneratorState::Yielded(exp2) 134 | } 135 | SimpleGen::Mul2 { val1 } => { 136 | let val2 = arg; 137 | let val1 = *val1; 138 | *this = SimpleGen::Returned; 139 | GeneratorState::Complete(val1 * val2) 140 | } 141 | } 142 | } 143 | } 144 | 145 | pub enum ActualGenDiscriminator { 146 | Unresumed, 147 | Returned, 148 | Panicked, 149 | Add, 150 | Mul1, 151 | Mul2, 152 | } 153 | 154 | union SumOrExp2<'a> { 155 | sum: f64, 156 | exp2: &'a Box, 157 | } 158 | 159 | pub struct ActualGen<'a> { 160 | discriminator: ActualGenDiscriminator, 161 | init: &'a Exp, 162 | sum_or_exp2: MaybeUninit>, 163 | val1: MaybeUninit, 164 | iter: MaybeUninit>, 165 | } 166 | 167 | impl<'a> ActualGen<'a> { 168 | pub fn new(init: &'a Exp) -> Self { 169 | ActualGen { 170 | discriminator: ActualGenDiscriminator::Unresumed, 171 | init, 172 | val1: MaybeUninit::uninit(), 173 | sum_or_exp2: MaybeUninit::uninit(), 174 | iter: MaybeUninit::uninit(), 175 | } 176 | } 177 | } 178 | 179 | impl<'a> Generator for ActualGen<'a> { 180 | type Yield = &'a Exp; 181 | type Return = f64; 182 | 183 | fn resume(self: Pin<&mut Self>, arg: f64) -> GeneratorState { 184 | unsafe { 185 | let this = self.get_mut(); 186 | match this.discriminator { 187 | ActualGenDiscriminator::Unresumed => match this.init { 188 | Exp::Num(val) => { 189 | this.discriminator = ActualGenDiscriminator::Returned; 190 | GeneratorState::Complete(*val) 191 | } 192 | Exp::Add(exps) => { 193 | this.sum_or_exp2 = MaybeUninit::new(SumOrExp2 { sum: 0.0 }); 194 | this.iter = MaybeUninit::new(exps.iter()); 195 | if let Some(exp) = this.iter.assume_init_mut().next() { 196 | this.discriminator = ActualGenDiscriminator::Add; 197 | 198 | GeneratorState::Yielded(exp) 199 | } else { 200 | this.discriminator = ActualGenDiscriminator::Returned; 201 | GeneratorState::Complete(this.sum_or_exp2.assume_init_ref().sum) 202 | } 203 | } 204 | Exp::Mul(exp1, exp2) => { 205 | this.discriminator = ActualGenDiscriminator::Mul1; 206 | this.sum_or_exp2 = MaybeUninit::new(SumOrExp2 { exp2 }); 207 | GeneratorState::Yielded(exp1) 208 | } 209 | }, 210 | ActualGenDiscriminator::Returned => panic!("resuming returned generator"), 211 | ActualGenDiscriminator::Panicked => panic!("resuming panicked generator"), 212 | ActualGenDiscriminator::Add => { 213 | let val = arg; 214 | this.sum_or_exp2.assume_init_mut().sum += val; 215 | if let Some(exp) = this.iter.assume_init_mut().next() { 216 | GeneratorState::Yielded(exp) 217 | } else { 218 | this.discriminator = ActualGenDiscriminator::Returned; 219 | GeneratorState::Complete(this.sum_or_exp2.assume_init_ref().sum) 220 | } 221 | } 222 | ActualGenDiscriminator::Mul1 => { 223 | let val1 = arg; 224 | this.discriminator = ActualGenDiscriminator::Mul2; 225 | this.val1 = MaybeUninit::new(val1); 226 | GeneratorState::Yielded(this.sum_or_exp2.assume_init_ref().exp2) 227 | } 228 | ActualGenDiscriminator::Mul2 => { 229 | let val2 = arg; 230 | this.discriminator = ActualGenDiscriminator::Returned; 231 | GeneratorState::Complete(this.val1.assume_init_ref() * val2) 232 | } 233 | } 234 | } 235 | } 236 | } 237 | 238 | fn main() { 239 | let exp = { 240 | use Exp::*; 241 | Mul(Box::new(Num(2.0)), Box::new(Add(vec![Num(3.0)]))) 242 | }; 243 | dbg!(std::mem::size_of_val(&exp.eval_generator())); 244 | dbg!(std::mem::size_of_val(&SimpleGen::new(&exp))); 245 | 246 | dbg!(exp.eval_recursive()); 247 | dbg!(trampoline(Exp::eval_generator)(&exp)); 248 | dbg!(trampoline(SimpleGen::new)(&exp)); 249 | dbg!(trampoline(ActualGen::new)(&exp)); 250 | } 251 | -------------------------------------------------------------------------------- /src/bin/ackermann.rs: -------------------------------------------------------------------------------- 1 | /* 2 | results & timings: 3 | 4 | +─────────────+─────────+───────+────────────+─────────────+─────────+────────────+────────+ 5 | | | result | loop | recursive | manual-tco | manual | yield-tco | yield | 6 | +─────────────+─────────+───────+────────────+─────────────+─────────+────────────+────────+ 7 | | "A(3, 12)" | 32765 | 0.8 | 1.2 | 1.2 | 1.6 | 1.4 | 5.0 | 8 | | "A(3, 13)" | 65533 | 3.0 | 4.8 | 4.7 | 6.4 | 5.8 | 20.0 | 9 | | "A(3, 14)" | 131069 | | 20.9 | | | | 99.3 | 10 | | "A(3, 15)" | 262141 | | SO | | | | 403 | 11 | | "A(3, 16)" | 524285 | | SO | | | | 1650 | 12 | +─────────────+─────────+───────+────────────+─────────────+─────────+────────────+────────+ 13 | */ 14 | 15 | #![feature(generators, generator_trait)] 16 | 17 | mod ackermann { 18 | use stack_safe::{trampoline, trampoline_tco, Call}; 19 | 20 | pub fn recursive(m: u64, n: u64) -> u64 { 21 | if m == 0 { 22 | n + 1 23 | } else if n == 0 { 24 | recursive(m - 1, 1) 25 | } else { 26 | recursive(m - 1, recursive(m, n - 1)) 27 | } 28 | } 29 | 30 | pub fn r#loop(mut m: u64, mut n: u64) -> u64 { 31 | let mut stack = Vec::new(); 32 | while !(m == 0 && stack.is_empty()) { 33 | if m == 0 { 34 | m = stack.pop().unwrap(); 35 | n += 1; 36 | } else if n == 0 { 37 | m -= 1; 38 | n = 1; 39 | } else { 40 | stack.push(m - 1); 41 | n -= 1; 42 | } 43 | } 44 | n + 1 45 | } 46 | 47 | pub fn r#yield(m: u64, n: u64) -> u64 { 48 | trampoline(|(m, n): (u64, u64)| { 49 | move |_: u64| { 50 | if m == 0 { 51 | n + 1 52 | } else if n == 0 { 53 | yield (m - 1, 1) 54 | } else { 55 | let k = yield (m, n - 1); 56 | yield (m - 1, k) 57 | } 58 | } 59 | })((m, n)) 60 | } 61 | 62 | pub fn yield_tco(m: u64, n: u64) -> u64 { 63 | trampoline_tco(|(m, n): (u64, u64)| { 64 | move |_: u64| { 65 | if m == 0 { 66 | n + 1 67 | } else if n == 0 { 68 | yield Call::tail((m - 1, 1)) 69 | } else { 70 | let k = yield Call::normal((m, n - 1)); 71 | yield Call::tail((m - 1, k)) 72 | } 73 | } 74 | })((m, n)) 75 | } 76 | 77 | pub mod manual { 78 | use std::ops::{Generator, GeneratorState}; 79 | use std::pin::Pin; 80 | 81 | pub enum Kont { 82 | A { m: u64, n: u64 }, 83 | B, 84 | C { m: u64 }, 85 | D, 86 | E, 87 | } 88 | 89 | impl Kont { 90 | pub fn init(m: u64, n: u64) -> Self { 91 | Self::A { m, n } 92 | } 93 | } 94 | 95 | impl Generator for Kont { 96 | type Yield = (u64, u64); 97 | type Return = u64; 98 | 99 | fn resume(self: Pin<&mut Self>, r: u64) -> GeneratorState { 100 | match *self { 101 | Self::A { m, n } => { 102 | if m == 0 { 103 | GeneratorState::Complete(n + 1) 104 | } else if n == 0 { 105 | *self.get_mut() = Self::B; 106 | GeneratorState::Yielded((m - 1, 1)) 107 | } else { 108 | *self.get_mut() = Self::C { m }; 109 | GeneratorState::Yielded((m, n - 1)) 110 | } 111 | } 112 | Self::B => GeneratorState::Complete(r), 113 | Self::C { m } => { 114 | *self.get_mut() = Self::D; 115 | GeneratorState::Yielded((m - 1, r)) 116 | } 117 | Self::D => { 118 | *self.get_mut() = Self::E; 119 | GeneratorState::Complete(r) 120 | } 121 | Self::E => panic!("Trying to resume finished generator."), 122 | } 123 | } 124 | } 125 | } 126 | 127 | pub fn manual(m: u64, n: u64) -> u64 { 128 | trampoline(|(m, n)| manual::Kont::init(m, n))((m, n)) 129 | } 130 | 131 | pub mod manual_tco { 132 | use stack_safe::Call; 133 | use std::ops::{Generator, GeneratorState}; 134 | use std::pin::Pin; 135 | 136 | pub enum Kont { 137 | A { m: u64, n: u64 }, 138 | C { m: u64 }, 139 | E, 140 | } 141 | 142 | impl Kont { 143 | pub fn init(m: u64, n: u64) -> Self { 144 | Self::A { m, n } 145 | } 146 | } 147 | 148 | impl Generator for Kont { 149 | type Yield = Call<(u64, u64)>; 150 | type Return = u64; 151 | 152 | fn resume(self: Pin<&mut Self>, r: u64) -> GeneratorState { 153 | match *self { 154 | Self::A { m, n } => { 155 | if m == 0 { 156 | GeneratorState::Complete(n + 1) 157 | } else if n == 0 { 158 | GeneratorState::Yielded(Call::tail((m - 1, 1))) 159 | } else { 160 | *self.get_mut() = Self::C { m }; 161 | GeneratorState::Yielded(Call::normal((m, n - 1))) 162 | } 163 | } 164 | Self::C { m } => { 165 | *self.get_mut() = Self::E; 166 | GeneratorState::Yielded(Call::tail((m - 1, r))) 167 | } 168 | Self::E => panic!("Trying to resume finished generator."), 169 | } 170 | } 171 | } 172 | } 173 | 174 | pub fn manual_tco(m: u64, n: u64) -> u64 { 175 | trampoline_tco(|(m, n)| manual_tco::Kont::init(m, n))((m, n)) 176 | } 177 | } 178 | 179 | fn validate_u64_arg(arg: String) -> Result<(), String> { 180 | match arg.parse::() { 181 | Ok(_) => Ok(()), 182 | Err(_) => Err(String::from("expected integer")), 183 | } 184 | } 185 | 186 | fn main() { 187 | use clap::{value_t, App, Arg}; 188 | 189 | let matches = App::new("Ackermann computer") 190 | .version("0.0.1") 191 | .about("Computes the Ackermann function") 192 | .arg( 193 | Arg::with_name("IMPL") 194 | .help("implementation to use") 195 | .required(true) 196 | .possible_values(&[ 197 | "recursive", 198 | "loop", 199 | "yield", 200 | "yield-tco", 201 | "manual", 202 | "manual-tco", 203 | ]), 204 | ) 205 | .arg( 206 | Arg::with_name("M") 207 | .help("integer to pass as first argument") 208 | .required(true) 209 | .validator(validate_u64_arg), 210 | ) 211 | .arg( 212 | Arg::with_name("N") 213 | .help("integer to pass as second argument") 214 | .required(true) 215 | .validator(validate_u64_arg), 216 | ) 217 | .get_matches(); 218 | 219 | let implementation = match matches.value_of("IMPL").unwrap() { 220 | "recursive" => ackermann::recursive, 221 | "loop" => ackermann::r#loop, 222 | "yield" => ackermann::r#yield, 223 | "yield-tco" => ackermann::yield_tco, 224 | "manual" => ackermann::manual, 225 | "manual-tco" => ackermann::manual_tco, 226 | _ => panic!("Impossible value for IMPL."), 227 | }; 228 | let m = value_t!(matches.value_of("M"), u64).unwrap_or_else(|e| e.exit()); 229 | let n = value_t!(matches.value_of("N"), u64).unwrap_or_else(|e| e.exit()); 230 | 231 | println!("{}", implementation(m, n)); 232 | } 233 | -------------------------------------------------------------------------------- /src/bin/blog_post.rs: -------------------------------------------------------------------------------- 1 | // This file contains the code from the blog post 2 | // https://hurryabit.github.io/blog/stack-safety-for-free/ 3 | #![feature(generators, generator_trait)] 4 | use std::ops::{Generator, GeneratorState}; 5 | use std::pin::Pin; 6 | 7 | fn triangular(n: u64) -> u64 { 8 | if n == 0 { 9 | 0 10 | } else { 11 | n + triangular(n - 1) 12 | } 13 | } 14 | 15 | fn triangular_safe(n: u64) -> u64 { 16 | recurse(|n| move |_| { 17 | if n == 0 { 18 | 0 19 | } else { 20 | n + yield (n - 1) 21 | } 22 | })(n) 23 | } 24 | 25 | fn recurse( 26 | f: impl Fn(Arg) -> Gen 27 | ) -> impl Fn(Arg) -> Res 28 | where 29 | Res: Default, 30 | Gen: Generator + Unpin, 31 | { 32 | move |arg: Arg| { 33 | let mut stack = Vec::new(); 34 | let mut current = f(arg); 35 | let mut res = Res::default(); 36 | 37 | loop { 38 | match Pin::new(&mut current).resume(res) { 39 | GeneratorState::Yielded(arg) => { 40 | stack.push(current); 41 | current = f(arg); 42 | res = Res::default(); 43 | } 44 | GeneratorState::Complete(real_res) => { 45 | match stack.pop() { 46 | None => return real_res, 47 | Some(top) => { 48 | current = top; 49 | res = real_res; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | fn main() { 59 | const LARGE: u64 = 1_000_000; 60 | 61 | assert_eq!(triangular_safe(LARGE), LARGE * (LARGE + 1) / 2); 62 | println!("`triangular_safe` has not overflowed its stack."); 63 | 64 | println!("`triangular` will overflow its stack soon..."); 65 | assert_eq!(triangular(LARGE), LARGE * (LARGE + 1) / 2); 66 | } 67 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature( 2 | destructuring_assignment, 3 | generators, 4 | generator_trait, 5 | step_trait, 6 | thread_spawn_unchecked 7 | )] 8 | use std::ops::{Generator, GeneratorState}; 9 | use std::pin::Pin; 10 | use std::thread; 11 | 12 | pub fn trampoline(f: impl Fn(Arg) -> Gen) -> impl Fn(Arg) -> Res 13 | where 14 | Res: Default, 15 | Gen: Generator + Unpin, 16 | { 17 | move |arg: Arg| { 18 | let mut stack = Vec::new(); 19 | let mut current = f(arg); 20 | let mut res = Res::default(); 21 | 22 | loop { 23 | match Pin::new(&mut current).resume(res) { 24 | GeneratorState::Yielded(arg) => { 25 | stack.push(current); 26 | current = f(arg); 27 | res = Res::default(); 28 | } 29 | GeneratorState::Complete(real_res) => match stack.pop() { 30 | None => return real_res, 31 | Some(top) => { 32 | current = top; 33 | res = real_res; 34 | } 35 | }, 36 | } 37 | } 38 | } 39 | } 40 | 41 | pub struct Call { 42 | arg: T, 43 | is_tail: bool, 44 | } 45 | 46 | impl Call { 47 | pub fn normal(arg: T) -> Self { 48 | Self { 49 | arg, 50 | is_tail: false, 51 | } 52 | } 53 | 54 | pub fn tail(arg: T) -> Self { 55 | Self { arg, is_tail: true } 56 | } 57 | } 58 | 59 | pub fn trampoline_tco(f: impl Fn(Arg) -> Gen) -> impl Fn(Arg) -> Res 60 | where 61 | Res: Default, 62 | Gen: Generator, Return = Res> + Unpin, 63 | { 64 | move |arg: Arg| { 65 | let mut stack = Vec::new(); 66 | let mut gen = f(arg); 67 | let mut res = Res::default(); 68 | 69 | loop { 70 | match Pin::new(&mut gen).resume(res) { 71 | GeneratorState::Yielded(call) => { 72 | if !call.is_tail { 73 | stack.push(gen); 74 | } 75 | gen = f(call.arg); 76 | res = Res::default(); 77 | } 78 | GeneratorState::Complete(res1) => match stack.pop() { 79 | None => return res1, 80 | Some(top) => { 81 | gen = top; 82 | res = res1; 83 | } 84 | }, 85 | } 86 | } 87 | } 88 | } 89 | 90 | pub fn trampoline_mut<'a, Arg, MutArg, Res, Gen>( 91 | f: impl Fn(Arg) -> Gen, 92 | ) -> impl Fn(Arg, &'a mut MutArg) -> Res 93 | where 94 | MutArg: 'a, 95 | Res: Default, 96 | Gen: Generator< 97 | (Res, &'a mut MutArg), 98 | Yield = (Arg, &'a mut MutArg), 99 | Return = (Res, &'a mut MutArg), 100 | > + Unpin, 101 | { 102 | move |arg: Arg, mut mut_arg: &mut MutArg| { 103 | let mut stack = Vec::new(); 104 | let mut gen = f(arg); 105 | let mut res = Res::default(); 106 | 107 | loop { 108 | match Pin::new(&mut gen).resume((res, mut_arg)) { 109 | GeneratorState::Yielded((arg, new_mut_arg)) => { 110 | mut_arg = new_mut_arg; 111 | stack.push(gen); 112 | gen = f(arg); 113 | res = Res::default(); 114 | } 115 | GeneratorState::Complete((new_res, new_mut_arg)) => { 116 | mut_arg = new_mut_arg; 117 | match stack.pop() { 118 | None => return new_res, 119 | Some(new_gen) => { 120 | gen = new_gen; 121 | res = new_res; 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | pub fn with_stack_size(size: usize, f: F) -> thread::Result 131 | where 132 | T: Send, 133 | F: FnOnce() -> T + Send, 134 | { 135 | let result = unsafe { 136 | std::thread::Builder::new() 137 | .stack_size(size) 138 | .spawn_unchecked(f) 139 | }; 140 | result.unwrap().join() 141 | } 142 | 143 | #[cfg(test)] 144 | mod tests; 145 | -------------------------------------------------------------------------------- /src/tests/ackermann.rs: -------------------------------------------------------------------------------- 1 | use crate::trampoline; 2 | 3 | fn recursive(m: u64, n: u64) -> u64 { 4 | if m == 0 { 5 | n + 1 6 | } else if n == 0 { 7 | recursive(m - 1, 1) 8 | } else { 9 | recursive(m - 1, recursive(m, n - 1)) 10 | } 11 | } 12 | 13 | fn stack_safe(m: u64, n: u64) -> u64 { 14 | trampoline(|(m, n): (u64, u64)| { 15 | move |_: u64| { 16 | if m == 0 { 17 | n + 1 18 | } else if n == 0 { 19 | yield (m - 1, 1) 20 | } else { 21 | let k = yield (m, n - 1); 22 | yield (m - 1, k) 23 | } 24 | } 25 | })((m, n)) 26 | } 27 | 28 | 29 | #[test] 30 | #[should_panic] 31 | #[ignore = "stack overflow is not an unwinding panic"] 32 | fn recursive_is_unsafe() { 33 | assert_eq!(recursive(3, 12), 32765); 34 | } 35 | 36 | #[test] 37 | fn stack_safe_is_safe() { 38 | assert_eq!(stack_safe(3, 10), 8189); 39 | } 40 | -------------------------------------------------------------------------------- /src/tests/binomial.rs: -------------------------------------------------------------------------------- 1 | use crate::trampoline; 2 | 3 | fn binomial_stack_safe(n: u64, k: u64) -> u64 { 4 | trampoline(|(n, k)| move |_| { 5 | if k == 0 || k == n { 6 | 1 7 | } else { 8 | (yield (n - 1, k - 1)) + (yield (n - 1, k)) 9 | } 10 | })((n, k)) 11 | } 12 | 13 | 14 | 15 | 16 | #[test] 17 | fn binomial_10_3() { 18 | assert_eq!(binomial_stack_safe(10, 3), 120); 19 | } 20 | -------------------------------------------------------------------------------- /src/tests/list.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use crate::{trampoline, with_stack_size}; 4 | 5 | enum List { 6 | Nil, 7 | Cons { head: T, tail: Box> }, 8 | } 9 | 10 | impl Drop for List { 11 | fn drop(&mut self) { 12 | if let Self::Cons { .. } = self { 13 | let mut list = std::mem::replace(self, List::Nil); 14 | while let Self::Cons { head, tail } = &mut list { 15 | let next = std::mem::replace(tail.as_mut(), Self::Nil); 16 | unsafe { 17 | std::ptr::drop_in_place(head as *mut T); 18 | std::ptr::drop_in_place(tail as *mut Box>); 19 | } 20 | std::mem::forget::>(list); 21 | list = next; 22 | } 23 | std::mem::forget::>(list); 24 | } 25 | } 26 | } 27 | 28 | impl List { 29 | fn len_recursive(&self) -> usize { 30 | match self { 31 | Self::Nil => 0, 32 | Self::Cons { head: _, tail } => 1 + tail.len_recursive(), 33 | } 34 | } 35 | 36 | fn len_stack_safe(&self) -> usize { 37 | trampoline(|list: &List| { 38 | move |_: usize| match list { 39 | Self::Nil => 0, 40 | Self::Cons { head: _, tail } => { 41 | let tail_len = yield tail.as_ref(); 42 | 1 + tail_len 43 | } 44 | } 45 | })(self) 46 | } 47 | } 48 | 49 | impl From> for List { 50 | fn from(range: Range) -> Self { 51 | let mut result = Self::Nil; 52 | for value in range.rev() { 53 | result = Self::Cons { 54 | head: value, 55 | tail: Box::new(result), 56 | } 57 | } 58 | result 59 | } 60 | } 61 | 62 | const LARGE: usize = 10_000; 63 | 64 | #[test] 65 | #[ignore = "stack overflow is not an unwinding panic"] 66 | fn len_recursive_is_unsafe() { 67 | let result = with_stack_size(10 * 1024, || List::from(0..LARGE).len_recursive()); 68 | assert!(result.is_err()); 69 | } 70 | 71 | #[test] 72 | fn len_stack_safe_is_safe() { 73 | let result = with_stack_size(1024, || List::from(0..LARGE).len_stack_safe()); 74 | assert_eq!(result.unwrap(), LARGE); 75 | } 76 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod ackermann; 2 | mod binomial; 3 | mod list; 4 | mod triangular; 5 | -------------------------------------------------------------------------------- /src/tests/triangular.rs: -------------------------------------------------------------------------------- 1 | use crate::{trampoline, with_stack_size}; 2 | 3 | fn recursive(n: u64) -> u64 { 4 | if n == 0 { 5 | 0 6 | } else { 7 | n + recursive(n - 1) 8 | } 9 | } 10 | 11 | fn stack_safe(n: u64) -> u64 { 12 | trampoline(|n: u64| { 13 | move |_: u64| { 14 | if n == 0 { 15 | 0 16 | } else { 17 | n + yield (n - 1) 18 | } 19 | } 20 | })(n) 21 | } 22 | 23 | const LARGE: u64 = 10_000; 24 | 25 | #[test] 26 | #[ignore = "stack overflow is not an unwinding panic"] 27 | fn recursive_is_unsafe() { 28 | let result = with_stack_size(10 * 1024, || recursive(LARGE)); 29 | assert!(result.is_err()); 30 | } 31 | 32 | #[test] 33 | fn stack_safe_is_safe() { 34 | let result = with_stack_size(512, || stack_safe(LARGE)); 35 | assert_eq!(result.unwrap(), LARGE * (LARGE + 1) / 2); 36 | } 37 | --------------------------------------------------------------------------------