From 3471b7f9d04eaf7b2d698036cf19d5607370ae73 Mon Sep 17 00:00:00 2001
From: Xavier Routh <xrouth2@illinois.edu>
Date: Tue, 10 Sep 2024 14:22:26 -0500
Subject: [PATCH] interpreter

---
 .gitignore                                    |   1 +
 Cargo.lock                                    | 732 ++-------------
 Cargo.toml                                    |   7 +-
 hercules_samples/call.hir                     |   7 +
 hercules_test/hercules_interpreter/Cargo.toml |  14 +
 .../hercules_interpreter/src/interpreter.rs   | 677 ++++++++++++++
 hercules_test/hercules_interpreter/src/lib.rs | 174 ++++
 .../hercules_interpreter/src/main.rs          |  33 +
 .../hercules_interpreter/src/value.rs         | 852 ++++++++++++++++++
 hercules_test/hercules_tests/Cargo.toml       |  14 +
 .../hercules_tests/tests/opt_tests.rs         | 201 +++++
 hercules_test/test_inputs/call.hir            |   7 +
 hercules_test/test_inputs/call_dc_params.hir  |   9 +
 hercules_test/test_inputs/ccp_example.hir     |  19 +
 hercules_test/test_inputs/fork_join.hir       |   8 +
 hercules_test/test_inputs/gvn_example.hir     |   8 +
 hercules_test/test_inputs/matmul.hir          |  58 ++
 hercules_test/test_inputs/matmul_int.hir      |  21 +
 hercules_test/test_inputs/simple1.hir         |   3 +
 hercules_test/test_inputs/simple2.hir         |  13 +
 hercules_test/test_inputs/simple3.hir         |  11 +
 hercules_test/test_inputs/strset.hir          |  17 +
 hercules_test/test_inputs/sum_int1.hir        |  16 +
 hercules_test/test_inputs/sum_int2.hir        |  25 +
 rust-toolchain.toml                           |   2 +
 25 files changed, 2251 insertions(+), 678 deletions(-)
 create mode 100644 hercules_samples/call.hir
 create mode 100644 hercules_test/hercules_interpreter/Cargo.toml
 create mode 100644 hercules_test/hercules_interpreter/src/interpreter.rs
 create mode 100644 hercules_test/hercules_interpreter/src/lib.rs
 create mode 100644 hercules_test/hercules_interpreter/src/main.rs
 create mode 100644 hercules_test/hercules_interpreter/src/value.rs
 create mode 100644 hercules_test/hercules_tests/Cargo.toml
 create mode 100644 hercules_test/hercules_tests/tests/opt_tests.rs
 create mode 100644 hercules_test/test_inputs/call.hir
 create mode 100644 hercules_test/test_inputs/call_dc_params.hir
 create mode 100644 hercules_test/test_inputs/ccp_example.hir
 create mode 100644 hercules_test/test_inputs/fork_join.hir
 create mode 100644 hercules_test/test_inputs/gvn_example.hir
 create mode 100644 hercules_test/test_inputs/matmul.hir
 create mode 100644 hercules_test/test_inputs/matmul_int.hir
 create mode 100644 hercules_test/test_inputs/simple1.hir
 create mode 100644 hercules_test/test_inputs/simple2.hir
 create mode 100644 hercules_test/test_inputs/simple3.hir
 create mode 100644 hercules_test/test_inputs/strset.hir
 create mode 100644 hercules_test/test_inputs/sum_int1.hir
 create mode 100644 hercules_test/test_inputs/sum_int2.hir
 create mode 100644 rust-toolchain.toml

diff --git a/.gitignore b/.gitignore
index 278d4690..feb7ba10 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@
 *.o
 *.hbin
 .*.swp
+.vscode
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 8248c425..c3138b1b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -47,7 +47,7 @@ version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
 dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys",
 ]
 
 [[package]]
@@ -57,7 +57,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
 dependencies = [
  "anstyle",
- "windows-sys 0.52.0",
+ "windows-sys",
 ]
 
 [[package]]
@@ -66,148 +66,6 @@ version = "1.0.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
 
-[[package]]
-name = "async-channel"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
-dependencies = [
- "concurrent-queue",
- "event-listener 2.5.3",
- "futures-core",
-]
-
-[[package]]
-name = "async-channel"
-version = "2.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
-dependencies = [
- "concurrent-queue",
- "event-listener-strategy",
- "futures-core",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-executor"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8828ec6e544c02b0d6691d21ed9f9218d0384a82542855073c2a3f58304aaf0"
-dependencies = [
- "async-task",
- "concurrent-queue",
- "fastrand 2.1.0",
- "futures-lite 2.3.0",
- "slab",
-]
-
-[[package]]
-name = "async-global-executor"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
-dependencies = [
- "async-channel 2.3.1",
- "async-executor",
- "async-io 2.3.3",
- "async-lock 3.4.0",
- "blocking",
- "futures-lite 2.3.0",
- "once_cell",
-]
-
-[[package]]
-name = "async-io"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
-dependencies = [
- "async-lock 2.8.0",
- "autocfg",
- "cfg-if",
- "concurrent-queue",
- "futures-lite 1.13.0",
- "log",
- "parking",
- "polling 2.8.0",
- "rustix 0.37.27",
- "slab",
- "socket2",
- "waker-fn",
-]
-
-[[package]]
-name = "async-io"
-version = "2.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964"
-dependencies = [
- "async-lock 3.4.0",
- "cfg-if",
- "concurrent-queue",
- "futures-io",
- "futures-lite 2.3.0",
- "parking",
- "polling 3.7.1",
- "rustix 0.38.34",
- "slab",
- "tracing",
- "windows-sys 0.52.0",
-]
-
-[[package]]
-name = "async-lock"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
-dependencies = [
- "event-listener 2.5.3",
-]
-
-[[package]]
-name = "async-lock"
-version = "3.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
-dependencies = [
- "event-listener 5.3.1",
- "event-listener-strategy",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-std"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
-dependencies = [
- "async-channel 1.9.0",
- "async-global-executor",
- "async-io 1.13.0",
- "async-lock 2.8.0",
- "crossbeam-utils",
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-lite 1.13.0",
- "gloo-timers",
- "kv-log-macro",
- "log",
- "memchr",
- "once_cell",
- "pin-project-lite",
- "pin-utils",
- "slab",
- "wasm-bindgen-futures",
-]
-
-[[package]]
-name = "async-task"
-version = "4.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
-
 [[package]]
 name = "atomic-polyfill"
 version = "1.0.3"
@@ -217,12 +75,6 @@ dependencies = [
  "critical-section",
 ]
 
-[[package]]
-name = "atomic-waker"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
-
 [[package]]
 name = "autocfg"
 version = "1.3.0"
@@ -271,25 +123,6 @@ dependencies = [
  "wyz",
 ]
 
-[[package]]
-name = "blocking"
-version = "1.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
-dependencies = [
- "async-channel 2.3.1",
- "async-task",
- "futures-io",
- "futures-lite 2.3.0",
- "piper",
-]
-
-[[package]]
-name = "bumpalo"
-version = "3.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
-
 [[package]]
 name = "byteorder"
 version = "1.5.0"
@@ -374,27 +207,12 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
 
-[[package]]
-name = "concurrent-queue"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
-dependencies = [
- "crossbeam-utils",
-]
-
 [[package]]
 name = "critical-section"
 version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216"
 
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
-
 [[package]]
 name = "deranged"
 version = "0.3.11"
@@ -405,68 +223,42 @@ dependencies = [
 ]
 
 [[package]]
-name = "embedded-io"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
-
-[[package]]
-name = "equivalent"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
-
-[[package]]
-name = "errno"
-version = "0.3.9"
+name = "derive_more"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
 dependencies = [
- "libc",
- "windows-sys 0.52.0",
+ "derive_more-impl",
 ]
 
 [[package]]
-name = "event-listener"
-version = "2.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
-
-[[package]]
-name = "event-listener"
-version = "5.3.1"
+name = "derive_more-impl"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
+checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
 dependencies = [
- "concurrent-queue",
- "parking",
- "pin-project-lite",
+ "proc-macro2",
+ "quote",
+ "syn",
 ]
 
 [[package]]
-name = "event-listener-strategy"
-version = "0.5.2"
+name = "either"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
-dependencies = [
- "event-listener 5.3.1",
- "pin-project-lite",
-]
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
 
 [[package]]
-name = "fastrand"
-version = "1.9.0"
+name = "embedded-io"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
-dependencies = [
- "instant",
-]
+checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
 
 [[package]]
-name = "fastrand"
-version = "2.1.0"
+name = "equivalent"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
 
 [[package]]
 name = "filetime"
@@ -477,7 +269,7 @@ dependencies = [
  "cfg-if",
  "libc",
  "redox_syscall",
- "windows-sys 0.52.0",
+ "windows-sys",
 ]
 
 [[package]]
@@ -492,55 +284,6 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
 
-[[package]]
-name = "futures-channel"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
-dependencies = [
- "futures-core",
-]
-
-[[package]]
-name = "futures-core"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
-
-[[package]]
-name = "futures-io"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
-
-[[package]]
-name = "futures-lite"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
-dependencies = [
- "fastrand 1.9.0",
- "futures-core",
- "futures-io",
- "memchr",
- "parking",
- "pin-project-lite",
- "waker-fn",
-]
-
-[[package]]
-name = "futures-lite"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
-dependencies = [
- "fastrand 2.1.0",
- "futures-core",
- "futures-io",
- "parking",
- "pin-project-lite",
-]
-
 [[package]]
 name = "getopts"
 version = "0.2.21"
@@ -561,18 +304,6 @@ dependencies = [
  "wasi",
 ]
 
-[[package]]
-name = "gloo-timers"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
-dependencies = [
- "futures-channel",
- "futures-core",
- "js-sys",
- "wasm-bindgen",
-]
-
 [[package]]
 name = "hash32"
 version = "0.2.1"
@@ -629,24 +360,28 @@ dependencies = [
 ]
 
 [[package]]
-name = "hercules_ir"
+name = "hercules_interpreter"
 version = "0.1.0"
 dependencies = [
  "bitvec",
- "nom",
+ "clap",
+ "derive_more",
+ "hercules_ir",
+ "hercules_opt",
+ "itertools",
  "ordered-float",
  "rand",
- "serde",
 ]
 
 [[package]]
-name = "hercules_matmul"
+name = "hercules_ir"
 version = "0.1.0"
 dependencies = [
- "async-std",
- "clap",
- "hercules_rt",
+ "bitvec",
+ "nom",
+ "ordered-float",
  "rand",
+ "serde",
 ]
 
 [[package]]
@@ -682,20 +417,19 @@ dependencies = [
 ]
 
 [[package]]
-name = "hercules_task_parallel"
+name = "hercules_tests"
 version = "0.1.0"
 dependencies = [
- "async-std",
+ "bitvec",
  "clap",
- "hercules_rt",
+ "hercules_interpreter",
+ "hercules_ir",
+ "hercules_opt",
+ "itertools",
+ "ordered-float",
+ "rand",
 ]
 
-[[package]]
-name = "hermit-abi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
-
 [[package]]
 name = "indexmap"
 version = "2.2.6"
@@ -707,46 +441,26 @@ dependencies = [
 ]
 
 [[package]]
-name = "instant"
-version = "0.1.13"
+name = "is_terminal_polyfill"
+version = "1.70.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
-dependencies = [
- "cfg-if",
-]
+checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
 
 [[package]]
-name = "io-lifetimes"
-version = "1.0.11"
+name = "itertools"
+version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
 dependencies = [
- "hermit-abi",
- "libc",
- "windows-sys 0.48.0",
+ "either",
 ]
 
-[[package]]
-name = "is_terminal_polyfill"
-version = "1.70.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
-
 [[package]]
 name = "itoa"
 version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
-[[package]]
-name = "js-sys"
-version = "0.3.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
-dependencies = [
- "wasm-bindgen",
-]
-
 [[package]]
 name = "juno_frontend"
 version = "0.1.0"
@@ -763,15 +477,6 @@ dependencies = [
  "phf",
 ]
 
-[[package]]
-name = "kv-log-macro"
-version = "1.0.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
-dependencies = [
- "log",
-]
-
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
@@ -784,18 +489,6 @@ version = "0.2.155"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
 
-[[package]]
-name = "linux-raw-sys"
-version = "0.3.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
-
 [[package]]
 name = "lock_api"
 version = "0.4.12"
@@ -806,15 +499,6 @@ dependencies = [
  "scopeguard",
 ]
 
-[[package]]
-name = "log"
-version = "0.4.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
-dependencies = [
- "value-bag",
-]
-
 [[package]]
 name = "lrlex"
 version = "0.13.6"
@@ -945,12 +629,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "once_cell"
-version = "1.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
-
 [[package]]
 name = "ordered-float"
 version = "4.2.0"
@@ -970,12 +648,6 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "parking"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
-
 [[package]]
 name = "phf"
 version = "0.11.2"
@@ -1018,60 +690,6 @@ dependencies = [
  "siphasher",
 ]
 
-[[package]]
-name = "pin-project-lite"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
-
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
-
-[[package]]
-name = "piper"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391"
-dependencies = [
- "atomic-waker",
- "fastrand 2.1.0",
- "futures-io",
-]
-
-[[package]]
-name = "polling"
-version = "2.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
-dependencies = [
- "autocfg",
- "bitflags 1.3.2",
- "cfg-if",
- "concurrent-queue",
- "libc",
- "log",
- "pin-project-lite",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "polling"
-version = "3.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e6a007746f34ed64099e88783b0ae369eaa3da6392868ba262e2af9b8fbaea1"
-dependencies = [
- "cfg-if",
- "concurrent-queue",
- "hermit-abi",
- "pin-project-lite",
- "rustix 0.38.34",
- "tracing",
- "windows-sys 0.52.0",
-]
-
 [[package]]
 name = "postcard"
 version = "1.0.8"
@@ -1209,33 +827,6 @@ dependencies = [
  "semver",
 ]
 
-[[package]]
-name = "rustix"
-version = "0.37.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
-dependencies = [
- "bitflags 1.3.2",
- "errno",
- "io-lifetimes",
- "libc",
- "linux-raw-sys 0.3.8",
- "windows-sys 0.48.0",
-]
-
-[[package]]
-name = "rustix"
-version = "0.38.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
-dependencies = [
- "bitflags 2.5.0",
- "errno",
- "libc",
- "linux-raw-sys 0.4.14",
- "windows-sys 0.52.0",
-]
-
 [[package]]
 name = "rustversion"
 version = "1.0.17"
@@ -1280,25 +871,6 @@ version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
 
-[[package]]
-name = "slab"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "socket2"
-version = "0.4.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
-dependencies = [
- "libc",
- "winapi",
-]
-
 [[package]]
 name = "sparsevec"
 version = "0.2.0"
@@ -1394,22 +966,6 @@ dependencies = [
  "time-core",
 ]
 
-[[package]]
-name = "tracing"
-version = "0.1.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
-dependencies = [
- "pin-project-lite",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-core"
-version = "0.1.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
-
 [[package]]
 name = "unicode-ident"
 version = "1.0.12"
@@ -1428,12 +984,6 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
-[[package]]
-name = "value-bag"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101"
-
 [[package]]
 name = "vergen"
 version = "8.3.1"
@@ -1456,147 +1006,19 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "waker-fn"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
-
 [[package]]
 name = "wasi"
 version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
-dependencies = [
- "cfg-if",
- "wasm-bindgen-macro",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
-dependencies = [
- "bumpalo",
- "log",
- "once_cell",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-futures"
-version = "0.4.42"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
-dependencies = [
- "cfg-if",
- "js-sys",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.92"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
-
-[[package]]
-name = "web-sys"
-version = "0.3.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "windows-sys"
-version = "0.48.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
-dependencies = [
- "windows-targets 0.48.5",
-]
-
 [[package]]
 name = "windows-sys"
 version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.5",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
-dependencies = [
- "windows_aarch64_gnullvm 0.48.5",
- "windows_aarch64_msvc 0.48.5",
- "windows_i686_gnu 0.48.5",
- "windows_i686_msvc 0.48.5",
- "windows_x86_64_gnu 0.48.5",
- "windows_x86_64_gnullvm 0.48.5",
- "windows_x86_64_msvc 0.48.5",
+ "windows-targets",
 ]
 
 [[package]]
@@ -1605,46 +1027,28 @@ version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.5",
- "windows_aarch64_msvc 0.52.5",
- "windows_i686_gnu 0.52.5",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
  "windows_i686_gnullvm",
- "windows_i686_msvc 0.52.5",
- "windows_x86_64_gnu 0.52.5",
- "windows_x86_64_gnullvm 0.52.5",
- "windows_x86_64_msvc 0.52.5",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
 ]
 
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
-
 [[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
 
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
-
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
 
-[[package]]
-name = "windows_i686_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
-
 [[package]]
 name = "windows_i686_gnu"
 version = "0.52.5"
@@ -1657,48 +1061,24 @@ version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
 
-[[package]]
-name = "windows_i686_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
-
 [[package]]
 name = "windows_i686_msvc"
 version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
 
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
-
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
 
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
-
 [[package]]
 name = "windows_x86_64_gnullvm"
 version = "0.52.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
 
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.48.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
-
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.52.5"
diff --git a/Cargo.toml b/Cargo.toml
index c6af575e..14184efa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,11 +6,14 @@ members = [
 	"hercules_rt",
 	"hercules_rt_proc",
 
+	"hercules_test/hercules_interpreter",
+	"hercules_test/hercules_tests",
+	
 	"hercules_tools/hercules_driver",
 	"hercules_tools/hercules_hbin_dump",
 
 	"juno_frontend",
 
-	"hercules_samples/matmul",
-	"hercules_samples/task_parallel"
+	#"hercules_samples/matmul",
+	#"hercules_samples/task_parallel"
 ]
diff --git a/hercules_samples/call.hir b/hercules_samples/call.hir
new file mode 100644
index 00000000..44748934
--- /dev/null
+++ b/hercules_samples/call.hir
@@ -0,0 +1,7 @@
+fn myfunc(x: i32) -> i32
+  y = call(add, x, x)
+  r = return(start, y)
+
+fn add(x: i32, y: i32) -> i32
+  w = add(x, y)
+  r = return(start, w)
\ No newline at end of file
diff --git a/hercules_test/hercules_interpreter/Cargo.toml b/hercules_test/hercules_interpreter/Cargo.toml
new file mode 100644
index 00000000..d41caff8
--- /dev/null
+++ b/hercules_test/hercules_interpreter/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "hercules_interpreter"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+bitvec = "*"
+clap = { version = "*", features = ["derive"] }
+rand = "*"
+hercules_ir = { path = "../../hercules_ir" }
+hercules_opt = { path = "../../hercules_opt" }
+itertools = "*"
+ordered-float = "*"
+derive_more = {version = "*", features = ["from"]}
\ No newline at end of file
diff --git a/hercules_test/hercules_interpreter/src/interpreter.rs b/hercules_test/hercules_interpreter/src/interpreter.rs
new file mode 100644
index 00000000..a166427f
--- /dev/null
+++ b/hercules_test/hercules_interpreter/src/interpreter.rs
@@ -0,0 +1,677 @@
+extern crate itertools;
+extern crate ordered_float;
+
+use crate::value;
+
+use self::itertools::Itertools;
+use std::collections::HashMap;
+use std::panic;
+use std::collections::hash_map::Entry::Occupied;
+
+use value::*;
+
+extern crate hercules_ir;
+extern crate hercules_opt;
+
+use self::hercules_ir::*;
+
+/* High level design details / discussion for this:
+ *
+ * This crate includes tools for interpreting a hercules IR module. Execution model / flow is based on
+ * "A Simple Graph-Based Intermediate Representation" Section 3 by Cliff Click.
+ *
+ * In the future we would an *interactive* interpreter. I.e to be able to step forward and backwards
+ * throughout the IR (idealy visually) and analyze results at certain steps in the computation.
+ */
+pub struct FunctionExecutionState<'a> {
+    args: Vec<InterpreterVal>, // parameters
+
+    // Map a vec of (thread indicies, reduce_node) -> Value
+    // Reduction values are shared across the fork / join pair that drives them, so they can't be local to a control token.
+    reduce_values: HashMap<(Vec<usize>, NodeID), InterpreterVal>, // Map (thread indicies, reduction node) -> value
+    join_counters: HashMap<(Vec<usize>, NodeID), usize>, // Map (thread indicies, join_node) -> join_counter. Counters until the joins are done!
+    dynamic_constant_params: Vec<usize>,
+
+    // Per module State.
+    module: &'a Module,
+    function_contexts: &'a Vec<FunctionContext<'a>>,
+
+    function_id: FunctionID,
+}
+
+pub struct FunctionContext<'a> {
+    control_subgraph: &'a Subgraph,
+    def_use: &'a ImmutableDefUseMap,
+    fork_join_map: &'a HashMap<NodeID, NodeID>, // Map forks -> joins
+    fork_join_nest: &'a HashMap<NodeID, Vec<NodeID>>,
+}
+
+impl <'a> FunctionContext<'a> {
+    pub fn new  (
+        control_subgraph: &'a Subgraph,
+        def_use: &'a ImmutableDefUseMap,
+        fork_join_map: &'a HashMap<NodeID, NodeID>, // Map forks -> joins
+        fork_join_nest: &'a HashMap<NodeID, Vec<NodeID>>,
+    ) -> FunctionContext<'a> {
+        FunctionContext {
+            control_subgraph,
+            def_use,
+            fork_join_map,
+            fork_join_nest,
+        }
+    }
+}
+
+pub fn dyn_const_value(dc: &DynamicConstant, dyn_const_params: &[usize]) -> usize {
+    match dc {
+        DynamicConstant::Constant(v) => *v,
+        DynamicConstant::Parameter(v) => dyn_const_params[*v],
+    }
+}
+// Each control token stores a current position, and also a mapping of fork nodes -> thread idx.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct ControlToken {
+    pub curr: NodeID,
+    pub prev: NodeID, // Used for properly latching PHI nodes, as they depend on their control predecessor.
+    pub thread_indicies: Vec<usize>, // Stack of thread_indicies, can use fork_join nests to recover thread indicies per fork.
+
+    // Map phis -> values
+    // Oddly, these are stored per control token, when some other per-token data is stored in the FunctionExecutionState
+    // we could move this `phi_values` to be consistent, however that would make CoW behvaior optimization more difficult.
+    pub phi_values: HashMap<NodeID, InterpreterVal>, // TODO: Optimize this to a Cow<HashMap>, or optimize this whole thing.
+}
+
+impl ControlToken {
+    pub fn moved_to(&self, next: NodeID) -> ControlToken {
+        ControlToken { curr: next, prev: self.curr, thread_indicies: self.thread_indicies.clone(), phi_values: self.phi_values.clone() }
+    }
+}
+impl<'a> FunctionExecutionState<'a> {
+    pub fn new(
+        args: Vec<InterpreterVal>,
+        module: &'a Module,
+        function_id: FunctionID,
+        function_contexts: &'a Vec<FunctionContext>,
+        dynamic_constant_params: Vec<usize>,
+    ) -> Self {
+        assert_eq!(args.len(), module.functions[function_id.idx()].param_types.len());
+
+        FunctionExecutionState {
+            args,
+            reduce_values: HashMap::new(),
+            join_counters: HashMap::new(),
+            module,
+            function_contexts,
+            function_id,
+            dynamic_constant_params,
+        }
+    }
+
+    pub fn get_control_subgraph(&self) -> &Subgraph {
+        self.function_contexts[self.function_id.idx()].control_subgraph
+    }
+
+    pub fn get_fork_join_map(&self) -> &HashMap<NodeID, NodeID> {
+        self.function_contexts[self.function_id.idx()].fork_join_map
+    }
+
+    pub fn get_fork_join_nest(&self) -> &HashMap<NodeID, Vec<NodeID>> {
+        self.function_contexts[self.function_id.idx()].fork_join_nest
+    }
+
+    pub fn get_def_use(&self) -> &ImmutableDefUseMap {
+        self.function_contexts[self.function_id.idx()].def_use
+    }
+
+    pub fn get_function(&self) -> &Function {
+        &self.module.functions[self.function_id.idx()]
+    }
+
+    /* Drives PHI values of this region for a control token, returns the next control node. */
+    pub fn handle_region(
+        &mut self,
+        token: &mut ControlToken,
+        preds: &Box<[NodeID]>,
+    ) -> NodeID {
+
+        let prev = token.prev;
+        let node = token.curr;
+        
+        // Gather PHI nodes for this region node.
+        let phis: Vec<NodeID> = self
+            .get_def_use()
+            .get_users(node)
+            .into_iter()
+            .filter_map(|n| {
+                if self.get_function().nodes[n.idx()].is_phi() {
+                    Some(*n)
+                } else {
+                    None
+                }
+            })
+            .collect();
+
+        // Find incoming edge of this region.
+        let (edge_num, _) = preds
+            .into_iter()
+            .find_position(|pred| **pred == prev)
+            .expect("PANIC: invalid incoming edge.");
+
+        let mut to_latch = vec![];
+
+        // Get values to latch at this point.
+        for phi in phis {
+            to_latch.push(self.handle_phi(token, edge_num, phi));
+        }
+
+        // Write values into map.
+        for (phi, value) in to_latch {
+            token.phi_values.insert(phi, value);
+        }
+
+        // Return the (single) control successor of the region node
+        self.get_control_subgraph().succs(node).next().unwrap()
+    }
+
+    pub fn handle_phi(
+        &mut self,
+        token: &ControlToken,
+        edge: usize,
+        phi: NodeID,
+    ) -> (NodeID, InterpreterVal) {
+        let (_control, data) = &self.get_function().nodes[phi.idx()]
+            .try_phi()
+            .expect("PANIC: handle_phi on non-phi node.");
+        let value_node = data[edge];
+        // println!("Latching PHI value of node {:?}", value_node.idx());
+        let value = self.handle_data(token, value_node);
+        (phi, value)
+    }
+
+    // Drive all reductions for this join.
+    // TODO (@xrouth): Change all these to return vecs of ControlTokens, so this is consistent with other handle_* functions.
+    pub fn handle_join(&mut self, mut token: ControlToken, join: NodeID) -> Option<ControlToken> {
+        let reduces: Vec<NodeID> = self
+            .get_def_use()
+            .get_users(join)
+            .into_iter()
+            .filter_map(|n| {
+                if self.get_function().nodes[n.idx()].is_reduce() {
+                    Some(*n)
+                } else {
+                    None
+                }
+            })
+            .collect();
+
+        for reduction in reduces {
+            self.handle_reduction(&token, reduction);
+        }
+
+        let thread_values = self.get_thread_factors(&token, join);
+        // dbg!(thread_values.clone());
+        // This and_modify doesn't do aynthing??
+        self.join_counters
+            .entry((thread_values.clone(), join))
+            .and_modify(|v| *v -= 1);
+
+        if *self
+            .join_counters
+            .get(&(thread_values, join))
+            .expect("PANIC: join counter not initialized")
+            == 0
+        {
+            let curr = token.curr;
+            token.prev = curr;
+            token.thread_indicies.pop(); // Get rid of this thread index.
+            token.curr = self.get_control_subgraph().succs(join).next().unwrap();
+            Some(token)
+        } else {
+            None
+        }
+    }
+
+    /* Get top N-1 thread factors, where N is the depth of the control token. */
+    pub fn get_thread_factors(&self, token: &ControlToken, control_node: NodeID) -> Vec<usize> {
+        let nested_forks = self
+            .get_fork_join_nest()
+            .get(&control_node)
+            .expect("PANIC: invalid node for fork join nest.")
+            .clone();
+        // Take the top N entries such that it matches the length of the TRF in the control token.
+
+        // Get the depth of the control token that is requesting this reduction node.
+        let fork_levels = nested_forks.len();
+
+        let len = fork_levels - 1;
+        let mut thread_values = token.thread_indicies.clone();
+        thread_values.truncate(len);
+        thread_values
+    }
+
+    pub fn intialize_reduction(&mut self, token_at_fork: &ControlToken, reduce: NodeID) {
+
+        let token = token_at_fork;
+
+        let (control, init, _) = &self.get_function().nodes[reduce.idx()]
+            .try_reduce()
+            .expect("PANIC: handle_reduction on non-reduce node.");
+
+        let thread_values = self.get_thread_factors(token, *control);
+
+        let init = self.handle_data(&token, *init);
+        // Q (@xrouth): It is UB to have the initializer depend on things within the fork-join section? do we check for that?
+        // A: Should be done in verify (TODO).
+
+        self.reduce_values
+            .entry((thread_values.clone(), reduce))
+            .or_insert(init);
+    }
+
+    // Drive the reduction, this will be invoked for each control token.
+    pub fn handle_reduction(&mut self, token: &ControlToken, reduce: NodeID) {
+        let (control, _, reduct) = &self.get_function().nodes[reduce.idx()]
+            .try_reduce()
+            .expect("PANIC: handle_reduction on non-reduce node.");
+
+        let thread_values = self.get_thread_factors(token, *control);
+
+        // If empty set to default (figure out how to not repeat this check)
+        // TODO: (Can we do it upon entry to the fork node?) (YES!)
+
+        let data = self.handle_data(&token, *reduct);
+        /* 
+        println!(
+            "reduction write: {:?}, {:?}, {:?}",
+            thread_values, reduce, data
+        ); */
+
+        self.reduce_values.insert((thread_values, reduce), data);
+    }
+
+    pub fn handle_data(&mut self, token: &ControlToken, node: NodeID) -> InterpreterVal {
+        // println!("Data Node: {} {:?}", node.idx(), &self.get_function().nodes[node.idx()]);
+
+        // Partial borrow complaint. :/
+        match &self.module.functions[self.function_id.idx()].nodes[node.idx()]{
+            Node::Phi { control: _, data: _ } => (*token
+                .phi_values
+                .get(&node)
+                .expect("PANIC: Phi value not latched."))
+            .clone(),
+            Node::ThreadID { control } => {
+                // `control` is the fork that drives this node.
+                let nesting_level = self
+                    .get_fork_join_nest()
+                    .get(control)
+                    .expect("PANIC: No nesting information for thread index!")
+                    .len();
+                let v = token.thread_indicies[nesting_level - 1]; // Might have to -1?
+                InterpreterVal::DynamicConstant((v).into())
+            }
+            // If we read from a reduction that is the same depth as this thread, we need to write back to it before anyone else reads from it.
+            // This probably isn't the exact condition, but somethign similar. Anyways, we achieve correctness by iterating control nodes recursively.
+            Node::Reduce {
+                control,
+                init,
+                reduct: _,
+            } => {
+                let thread_values = self.get_thread_factors(token, *control);
+
+                // println!("reduction read: {:?}, {:?}", thread_values, node);
+                let entry = self
+                    .reduce_values
+                    .entry((thread_values, node));
+                
+                let val = match entry {
+                    Occupied(v) => v.get().clone(),
+                    std::collections::hash_map::Entry::Vacant(_) => panic!("Reduce has not been initialized!"),
+                };
+                // println!("value: {:?}", val.clone());
+                val
+            }
+            Node::Parameter { index } => self.args[*index].clone(),
+            Node::Constant { id } => {
+                let con = &self.module.constants[id.idx()];
+                InterpreterVal::from_constant(
+                    con,
+                    &self.module.constants,
+                    &self.module.types,
+                    &self.module.dynamic_constants,
+                    &self.dynamic_constant_params
+                )
+            }
+            Node::DynamicConstant { id } => {
+                let dyn_con = &self.module.dynamic_constants[id.idx()];
+                let v = match dyn_con {
+                    DynamicConstant::Constant(v) => v,
+                    DynamicConstant::Parameter(v) => &self.dynamic_constant_params[*v],
+                };
+                // TODO: Figure out what type / semantics are of thread ID and dynamic const.
+                InterpreterVal::DynamicConstant((*v).into())
+            }
+            Node::Unary { input, op } => {
+                let val = self.handle_data(token, *input);
+                InterpreterVal::unary_op(*op, val)
+            }
+            Node::Binary { left, right, op } => {
+                let left = self.handle_data(token, *left);
+                let right = self.handle_data(token, *right);
+
+                InterpreterVal::binary_op(*op, left, right)
+            }
+            Node::Ternary {
+                first,
+                second,
+                third,
+                op,
+            } => {
+                let first = self.handle_data(token, *first);
+                let second = self.handle_data(token, *second);
+                let third = self.handle_data(token, *third);
+
+                match op {
+                    TernaryOperator::Select => {
+                        if first == InterpreterVal::Boolean(true) {
+                            second
+                        } else {
+                            third
+                        }
+                    }
+                }
+            }
+            Node::Call {
+                function,
+                dynamic_constants,
+                args,
+            } => {
+
+                let args = args.into_iter()
+                            .map(|arg_node| self.handle_data(token, *arg_node))
+                            .collect();
+
+
+                let dynamic_constant_params = dynamic_constants.into_iter()
+                            .map(|id| {
+                                let dyn_con = &self.module.dynamic_constants[id.idx()];
+                                let v = match dyn_con {
+                                    DynamicConstant::Constant(v) => *v,
+                                    DynamicConstant::Parameter(v) => self.dynamic_constant_params[*v],
+                                };
+                                v
+                            }).collect_vec();
+
+                let mut state = FunctionExecutionState::new(
+                    args,
+                    self.module,
+                    *function,
+                    self.function_contexts,
+                    dynamic_constant_params,
+                );
+
+                state.run()
+            }
+            Node::Read { collect, indices } => {
+                let collection = self.handle_data(token, *collect);
+
+                self.handle_read(token, collection, indices)
+            }
+            Node::Write {
+                collect,
+                data,
+                indices,
+            } => {
+                let collection = self.handle_data(token, *collect);
+                let data = self.handle_data(token, *data);
+                self.handle_write(token, collection, data, indices)
+            }
+            _ => todo!(),
+        }
+    }
+
+    pub fn handle_write(
+        &mut self,
+        token: &ControlToken,
+        collection: InterpreterVal,
+        data: InterpreterVal,
+        indices: &[Index],
+    ) -> InterpreterVal {
+        let index = &indices[0];
+
+        // TODO (@xrouth): Recurse on writes correctly
+        let val = match index {
+            Index::Field(_) => todo!(),
+            Index::Variant(_) => todo!(),
+            Index::Position(array_indices) => {
+                // Arrays also have inner indices...
+                // Recover dimensional data from types.
+                let array_indices: Vec<_> = array_indices
+                    .into_iter()
+                    .map(|idx| self.handle_data(token, *idx).as_usize())
+                    .collect();
+                // TODO: Implemenet . try_array() and other try_conversions on the InterpreterVal type
+                if let InterpreterVal::Array(type_id, mut vals) = collection {
+                    // TODO: Make this its own funciton to reuse w/ array_size
+                    let extents: Vec<_> = self.module.types[type_id.idx()]
+                        .try_extents()
+                        .expect("PANIC: wrong type for array")
+                        .into_iter()
+                        .map(|extent| dyn_const_value(&self.module.dynamic_constants[extent.idx()], &self.dynamic_constant_params))
+                        .collect();
+                    let idx = InterpreterVal::array_idx(&extents, &array_indices);
+                    //println!("idx: {:?}", idx);
+                    vals[idx] = data;
+                    InterpreterVal::Array(type_id, vals)
+                } else {
+                    panic!("PANIC: Position index on not an array")
+                }
+            }
+        };
+        val
+    }
+
+    pub fn handle_read(
+        &mut self,
+        token: &ControlToken,
+        collection: InterpreterVal,
+        indices: &[Index],
+    ) -> InterpreterVal {
+        let index = &indices[0];
+
+        let val = match index {
+            Index::Field(_) => todo!(),
+            Index::Variant(_) => todo!(),
+            Index::Position(array_indices) => {
+                // Arrays also have inner indices...
+                // Recover dimensional data from types.
+                let array_indices: Vec<_> = array_indices
+                    .into_iter()
+                    .map(|idx| self.handle_data(token, *idx).as_usize())
+                    .collect();
+
+                // TODO: Implemenet . try_array() and other try_conversions on the InterpreterVal type
+                if let InterpreterVal::Array(type_id, vals) = collection {
+                    // TODO: Make this its own funciton to reuse w/ array_size
+                    let extents: Vec<_> = self.module.types[type_id.idx()]
+                        .try_extents()
+                        .expect("PANIC: wrong type for array")
+                        .into_iter()
+                        .map(|extent| dyn_const_value(&self.module.dynamic_constants[extent.idx()], &self.dynamic_constant_params))
+                        .collect();
+                    vals[InterpreterVal::array_idx(&extents, &array_indices)].clone()
+                } else {
+                    panic!("PANIC: Position index on not an array")
+                }
+            }
+        };
+
+        // Recurse
+        if indices.len() == 1 {
+            val
+        } else {
+            self.handle_read(token, val, &indices[1..])
+        }
+    }
+
+    pub fn run(&mut self) -> InterpreterVal {
+        let start_node: NodeID = NodeID::new(0);
+
+        let start_token = ControlToken {
+            curr: start_node,
+            prev: start_node,
+            thread_indicies: Vec::new(),
+            phi_values: HashMap::new(),
+        };
+
+        let mut live_tokens: Vec<ControlToken> = Vec::new();
+        live_tokens.push(start_token);
+
+
+        // To do reduction nodes correctly we have to traverse control tokens in a depth-first fashion (i.e immediately handle spawned threads).
+        'outer: loop {
+            let mut ctrl_token = live_tokens.pop().expect("PANIC: Interpreter ran out of control tokens without returning.");
+
+            /* println!(
+                "\n\nNew Token at: Control State: {} threads: {:?}, {:?}",
+                ctrl_token.curr.idx(),
+                ctrl_token.thread_indicies.clone(),
+                &self.get_function().nodes[ctrl_token.curr.idx()]
+            ); */
+
+            // TODO: Rust is annoying and can't recognize that this is a partial borrow.
+            // Can't partial borrow, so need a clone. 
+            let node = &self.get_function().nodes[ctrl_token.curr.idx()].clone();
+            let new_tokens = match node {
+                Node::Start => {
+                    let next: NodeID = self.get_control_subgraph().succs(ctrl_token.curr).next().unwrap();
+
+                    let ctrl_token = ctrl_token.moved_to(next);
+    
+                    vec![ctrl_token]
+                }
+                Node::Region { preds } => {
+
+                    // Updates 
+                    let next = self.handle_region(&mut ctrl_token, &preds);
+                    let ctrl_token = ctrl_token.moved_to(next);
+
+                    vec![ctrl_token]
+                }
+                Node::If { control: _, cond } => {
+                    let cond = self.handle_data(&ctrl_token, *cond);
+
+                    // Convert condition to usize
+                    let cond: usize = match cond {
+                        InterpreterVal::Boolean(v) => v.into(),
+                        _ => panic!("PANIC: Invalid condition for IF, please typecheck."),
+                    };
+
+                    // Find control read succesor that matches the condition.
+                    let next = self
+                        .get_control_subgraph()
+                        .succs(ctrl_token.curr)
+                        .find(|n| {
+                            self.get_function().nodes[n.idx()]
+                                .try_projection(cond)
+                                .is_some()
+                        })
+                        .expect("PANIC: No outgoing valid outgoing edge.");
+
+                    let ctrl_token = ctrl_token.moved_to(next);
+                    vec![ctrl_token]
+                }
+                Node::Projection { .. } => {
+                    let next: NodeID = self.get_control_subgraph().succs(ctrl_token.curr).next().unwrap();
+
+                    let ctrl_token = ctrl_token.moved_to(next);
+
+                    vec![ctrl_token]
+                }
+
+                Node::Match { control: _, sum: _ } => todo!(),
+                Node::Fork { control: _, factor } => {
+                    let fork = ctrl_token.curr;
+                    let dyn_con = &self.module.dynamic_constants[factor.idx()];
+
+                    let thread_factor = match dyn_con {
+                        DynamicConstant::Constant(v) => v,
+                        DynamicConstant::Parameter(v) => &self.dynamic_constant_params[*v],
+                    }.clone();
+
+                    // Update control token 
+                    let next = self.get_control_subgraph().succs(ctrl_token.curr).nth(0).unwrap();
+                    let ctrl_token = ctrl_token.moved_to(next);
+
+                    let mut tokens_to_add = Vec::with_capacity(thread_factor);
+
+                    assert_ne!(thread_factor, 0);
+
+                    // Token is at its correct sontrol succesor already.
+                    // Add the new thread index.
+                    for i in 0..(thread_factor) {
+                        let mut new_token = ctrl_token.clone(); // Copy map, curr, prev, etc.
+                        new_token.thread_indicies.push(i); // Stack of thread indicies
+                        tokens_to_add.push(new_token);
+                    }
+
+                    let thread_factors = self.get_thread_factors(&ctrl_token, ctrl_token.curr);
+
+                    // Find join and initialize them, and set their reduction counters as well.
+                    let join = self
+                        .get_fork_join_map()
+                        .get(&fork)
+                        .expect("PANIC: fork missing a join.")
+                        .clone();
+
+                    let reduces: Vec<NodeID> = self
+                        .get_def_use()
+                        .get_users(join)
+                        .into_iter()
+                        .filter_map(|n| {
+                            if self.get_function().nodes[n.idx()].is_reduce() {
+                                Some(*n)
+                            } else {
+                                None
+                            }
+                        })
+                        .collect();
+        
+                    for reduction in reduces {
+                        // TODO: Is this the correct reduction?
+                        self.intialize_reduction(&ctrl_token, reduction);
+                    }
+
+
+                    self.join_counters.insert((thread_factors, join), thread_factor);
+
+                    tokens_to_add
+                }
+                Node::Join { control: _ } => {
+                    let join = ctrl_token.curr;
+
+                    // Only make a control token if the join is finished, checked in handle_join.
+                    if let Some(next) = self.handle_join(ctrl_token, join) {
+                        vec![next]
+                    } else {
+                        vec![]
+                    }
+                }
+                Node::Return { control: _, data } => {
+                    let result = self.handle_data(&ctrl_token, *data);
+                    // println!("result = {:?}", result);
+                    break 'outer result;
+                }
+                _ => {
+                    panic!("PANIC: Unexpected node in control subgraph {:?}", node);
+                }
+            };
+
+            for i in new_tokens {
+                live_tokens.push(i);
+            }
+
+        }
+    }
+}
+
+
diff --git a/hercules_test/hercules_interpreter/src/lib.rs b/hercules_test/hercules_interpreter/src/lib.rs
new file mode 100644
index 00000000..89fae51a
--- /dev/null
+++ b/hercules_test/hercules_interpreter/src/lib.rs
@@ -0,0 +1,174 @@
+pub mod interpreter;
+pub mod value;
+
+use std::fs::File;
+
+use hercules_ir::Module;
+use hercules_ir::TypeID;
+
+pub use crate::interpreter::*;
+pub use crate::value::*;
+
+// Get a vec of 
+pub fn into_interp_val(module: &Module, wrapper: InterpreterWrapper, target_ty_id: TypeID) -> InterpreterVal 
+{
+    match wrapper {
+        InterpreterWrapper::Boolean(v) => InterpreterVal::Boolean(v),
+        InterpreterWrapper::Integer8(v) => InterpreterVal::Integer8(v),
+        InterpreterWrapper::Integer16(v) => InterpreterVal::Integer16(v),
+        InterpreterWrapper::Integer32(v) => InterpreterVal::Integer32(v),
+        InterpreterWrapper::Integer64(v) => InterpreterVal::Integer64(v),
+        InterpreterWrapper::UnsignedInteger8(v) => InterpreterVal::UnsignedInteger8(v),
+        InterpreterWrapper::UnsignedInteger16(v) => InterpreterVal::UnsignedInteger16(v),
+        InterpreterWrapper::UnsignedInteger32(v) => InterpreterVal::UnsignedInteger32(v),
+        InterpreterWrapper::UnsignedInteger64(v) => InterpreterVal::UnsignedInteger64(v),
+        InterpreterWrapper::Float32(v) => InterpreterVal::Float32(v),
+        InterpreterWrapper::Float64(v) => InterpreterVal::Float64(v),
+        InterpreterWrapper::Product(_) => todo!(),
+        InterpreterWrapper::Summation(_, _) => todo!(),
+
+        InterpreterWrapper::Array(array) => {
+            let ty = &module.types[target_ty_id.idx()];
+            let ele_type = ty.try_element_type().expect("PANIC: Type ID");
+            // unwrap -> map to rust type, check 
+        
+            let mut values = vec![];
+        
+            for i in 0..array.len() {
+                values.push(into_interp_val(module, array[i].clone(), TypeID::new(0)));
+            }
+        
+            InterpreterVal::Array(target_ty_id, values.into_boxed_slice())
+        }
+    }
+} 
+
+pub fn array_from_interp_val<T: Clone>(module: &Module, interp_val: InterpreterVal) -> Vec<T> 
+    where value::InterpreterVal: Into<T>
+{
+     vec![]
+}
+
+// Recursively turns rt args into interpreter wrappers.
+#[macro_export]
+macro_rules! parse_rt_args {
+    ($arg:expr) => {
+        {   
+
+            let mut values: Vec<InterpreterWrapper> = vec![];
+
+            values.push(($arg).into());
+
+            values
+        }
+    };
+    ( $arg:expr, $($tail_args:expr), +) => {
+        {   
+            let mut values: Vec<InterpreterWrapper> = vec![];
+
+            values.push($arg.into());
+
+            let mut recurse = parse_rt_args!($($tail_args), +); // How do you pass the rest?
+            values.append(&mut recurse);
+            values
+        }
+    };
+}
+
+pub fn parse_file(path: &str) -> Module {
+    let mut file = File::open(path).expect("PANIC: Unable to open input file.");
+    let mut contents = String::new();
+    std::io::Read::read_to_string(&mut file, &mut contents)
+        .expect("PANIC: Unable to read input file contents.");
+    let module =
+        hercules_ir::parse::parse(&contents).expect("PANIC: Failed to parse Hercules IR file.");
+    module
+}
+
+#[macro_export]
+macro_rules! interp_module {
+    ($module:ident, $dynamic_constants:expr, $($args:expr), *) => {
+        {
+            //let hir_file = String::from($path);
+
+            let wrappers = parse_rt_args!( $($args), *);
+
+            let dynamic_constants: Vec<usize> = $dynamic_constants.into();
+            let module = $module.clone(); //parse_file(hir_file);
+
+            let mut pm = hercules_opt::pass::PassManager::new(module);
+            pm.add_pass(hercules_opt::pass::Pass::Verify);
+
+            pm.run_passes();
+            pm.make_reverse_postorders();
+            pm.make_doms();
+            pm.make_fork_join_maps();
+            pm.make_fork_join_nests();
+            pm.make_control_subgraphs();
+            pm.make_plans();
+
+            let reverse_postorders = pm.reverse_postorders.as_ref().unwrap().clone();
+            let doms = pm.doms.as_ref().unwrap().clone();
+            let fork_join_maps = pm.fork_join_maps.as_ref().unwrap().clone();
+            let fork_join_nests = pm.fork_join_nests.as_ref().unwrap().clone();
+            let plans = pm.plans.as_ref().unwrap().clone();
+            let control_subgraphs = pm.control_subgraphs.as_ref().unwrap().clone();
+            let def_uses = pm.def_uses.as_ref().unwrap().clone();
+
+            let module = pm.get_module();
+
+            let mut function_contexts = vec![];
+
+            for idx in 0..module.functions.len() {
+                let context = FunctionContext::new(
+                    &control_subgraphs[idx],
+                    &def_uses[idx],
+                    &fork_join_maps[idx],
+                    &fork_join_nests[idx],
+                );
+                function_contexts.push(context);
+            }
+
+            let function_number = 0;
+
+            let parameter_types = &module.functions[function_number].param_types;
+
+            let args = wrappers.into_iter().enumerate().map(|(idx, val)| {into_interp_val(&module, val, parameter_types[idx])}).collect();
+
+            let mut state = FunctionExecutionState::new(
+                args,
+                &module,
+                hercules_ir::FunctionID::new(function_number),
+                &function_contexts,
+                dynamic_constants,
+            );
+
+            state.run()
+        }
+    };
+}
+
+
+#[macro_export]
+macro_rules! interp_file_with_passes {
+    ($path:literal, $dynamic_constants:expr, $passes:expr, $($args:expr), *) => {
+        {
+            let module = parse_file($path);
+            
+            let result_before = interp_module!(module, $dynamic_constants, $($args), *);
+
+            let mut pm = hercules_opt::pass::PassManager::new(module.clone());
+
+            for pass in $passes {
+                pm.add_pass(pass);
+            }
+
+            pm.run_passes();
+
+            let module = pm.get_module();
+            let result_after = interp_module!(module, $dynamic_constants, $($args), *); 
+
+            assert_eq!(result_after, result_before);
+        }
+    };
+}
\ No newline at end of file
diff --git a/hercules_test/hercules_interpreter/src/main.rs b/hercules_test/hercules_interpreter/src/main.rs
new file mode 100644
index 00000000..4d2ebd31
--- /dev/null
+++ b/hercules_test/hercules_interpreter/src/main.rs
@@ -0,0 +1,33 @@
+extern crate clap;
+extern crate hercules_ir;
+extern crate hercules_opt;
+extern crate rand;
+
+
+use hercules_interpreter::interpreter::*;
+use hercules_interpreter::*;
+use hercules_interpreter::value;
+use std::fs::File;
+use std::io::prelude::*;
+
+use self::hercules_ir::*;
+
+use clap::Parser;
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+struct Args {
+    hir_file: String,
+
+    #[arg(short, long, default_value_t = String::new())]
+    output: String,
+}
+
+fn main() {
+    let args = Args::parse();
+    let module = parse_file(&args.hir_file);
+    let ret_val = interp_module!(module, [2, 3, 4], 1, 3);
+    
+    println!("ret val: {:?}", ret_val);
+}
+
diff --git a/hercules_test/hercules_interpreter/src/value.rs b/hercules_test/hercules_interpreter/src/value.rs
new file mode 100644
index 00000000..1c52fea8
--- /dev/null
+++ b/hercules_test/hercules_interpreter/src/value.rs
@@ -0,0 +1,852 @@
+#![allow(unused)]
+
+extern crate derive_more;
+use derive_more::{From};
+
+/* Defines semantic meaning of IR operations. */
+extern crate itertools;
+
+use crate::dyn_const_value;
+
+use self::itertools::Itertools;
+use std::clone;
+use std::convert::TryInto;
+use std::panic;
+
+extern crate hercules_ir;
+extern crate hercules_opt;
+
+use self::hercules_ir::*;
+use self::hercules_opt::*;
+
+#[derive(PartialEq, Debug, Clone, Eq)]
+pub enum InterpreterVal {
+    Boolean(bool),
+    Integer8(i8),
+    Integer16(i16),
+    Integer32(i32),
+    Integer64(i64),
+    UnsignedInteger8(u8),
+    UnsignedInteger16(u16),
+    UnsignedInteger32(u32),
+    UnsignedInteger64(u64),
+
+    Float32(ordered_float::OrderedFloat<f32>),
+    Float64(ordered_float::OrderedFloat<f64>),
+
+    Product(TypeID, Box<[InterpreterVal]>),
+    Summation(TypeID, u32, Box<[InterpreterVal]>),
+    Array(TypeID, Box<[InterpreterVal]>), // TypeID of the array Type (not the element type)
+    // These can be freely? casted
+    DynamicConstant(usize),
+    ThreadID(usize),
+}
+
+#[derive(Clone)]
+pub enum InterpreterWrapper {
+    Boolean(bool),
+    Integer8(i8),
+    Integer16(i16),
+    Integer32(i32),
+    Integer64(i64),
+    UnsignedInteger8(u8),
+    UnsignedInteger16(u16),
+    UnsignedInteger32(u32),
+    UnsignedInteger64(u64),
+
+    Float32(ordered_float::OrderedFloat<f32>),
+    Float64(ordered_float::OrderedFloat<f64>),
+
+    Product(Box<[InterpreterWrapper]>),
+    Summation(u32, Box<[InterpreterWrapper]>),
+    Array(Box<[InterpreterWrapper]>), // TypeID of the array Type (not the element type)
+}
+
+
+
+impl <T> From<&Vec<T>> for InterpreterWrapper 
+    where T: Into<InterpreterWrapper> + Clone
+{
+    
+    fn from(value: &Vec<T>) -> Self {
+        let mut values = vec![];
+        for i in 0..value.len() {
+            values[i] = value[i].clone().into()
+        }
+        InterpreterWrapper::Array(values.into_boxed_slice())
+    }
+}
+
+impl <T> From<Vec<T>> for InterpreterWrapper 
+    where T: Into<InterpreterWrapper> + Clone
+{
+    fn from(value: Vec<T>) -> Self {
+        let mut values = vec![];
+        for i in 0..value.len() {
+            values.push(value[i].clone().into());
+        }
+        InterpreterWrapper::Array(values.into_boxed_slice())
+    }
+}
+
+
+impl <T> From<&[T]> for InterpreterWrapper 
+    where T: Into<InterpreterWrapper> + Clone
+{
+    
+    fn from(value: &[T]) -> Self {
+        let mut values = vec![];
+        for i in 0..value.len() {
+            values[i] = value[i].clone().into()
+        }
+        InterpreterWrapper::Array(values.into_boxed_slice())
+    }
+}
+
+// Map rust types to interpreter values. 
+macro_rules! from_impl {
+    ($rust:ty, $variant:tt) => {
+        impl From<$rust> for InterpreterWrapper {
+            fn from(value: $rust) -> Self {
+                InterpreterWrapper::$variant(value)
+            }
+        }
+    };
+}
+
+from_impl!(bool, Boolean);
+from_impl!(i8, Integer8);
+from_impl!(i16, Integer16);
+from_impl!(i32, Integer32);
+from_impl!(i64, Integer64);
+from_impl!(u8, UnsignedInteger8);
+from_impl!(u16, UnsignedInteger16);
+from_impl!(u32, UnsignedInteger32);
+from_impl!(u64, UnsignedInteger64);
+
+impl<'a> InterpreterVal {
+    pub fn from_constant(
+        constant: &Constant,
+        constants: &'a Vec<Constant>,
+        types: &'a Vec<Type>,
+        dynamic_constants: &'a [DynamicConstant],
+        dynamic_constant_params: &'a [usize],
+    ) -> Self {
+        match *constant {
+            Constant::Boolean(v) => Self::Boolean(v),
+            Constant::Integer8(v) => Self::Integer8(v),
+            Constant::Integer16(v) => Self::Integer16(v),
+            Constant::Integer32(v) => Self::Integer32(v),
+            Constant::Integer64(v) => Self::Integer64(v),
+            Constant::UnsignedInteger8(v) => Self::UnsignedInteger8(v),
+            Constant::UnsignedInteger16(v) => Self::UnsignedInteger16(v),
+            Constant::UnsignedInteger32(v) => Self::UnsignedInteger32(v),
+            Constant::UnsignedInteger64(v) => Self::UnsignedInteger64(v),
+            Constant::Float32(v) => Self::Float32(v),
+            Constant::Float64(v) => Self::Float64(v),
+
+            Constant::Product(_, _) => todo!(),
+            Constant::Summation(_, _, _) => todo!(),
+            Constant::Array(type_id) => {
+                // TODO: This is currently only implemented for arrays of primitive types, implement zero initializers for other types.
+                let ty = &types[type_id.idx()];
+
+                let extents: Vec<_> = ty
+                    .try_extents()
+                    .expect("PANIC: wrong type for array")
+                    .into_iter()
+                    .map(|extent| dyn_const_value(&dynamic_constants[extent.idx()], &dynamic_constant_params))
+                    .collect();
+
+                let size = InterpreterVal::array_size(&extents);
+
+                // Need to recurse here, ugh.
+                let element_type_id = ty.try_element_type().expect("PANIC: no element type");
+                let element_type = &types[element_type_id.idx()];
+
+                // TODO Need a seperate funciton to handle this, as arrays w/ non-primitive elements should use Constant::Zero(element_id),
+                // and arrays w/ primitive elements should use the zero-initializer for those.
+
+                let element_zeroed = InterpreterVal::zero_initialized(&element_type);
+                let backing_array = vec![element_zeroed; size];
+                Self::Array(type_id, backing_array.into_boxed_slice())
+            }
+        }
+    }
+
+    /* Zero initialized interpreter value of a hercules type, only supported for primitive types. */
+    pub fn zero_initialized(ty: &'a Type) -> InterpreterVal {
+        match ty {
+            Type::Boolean => Self::Boolean(false),
+            Type::Integer8 => Self::Integer8(0),
+            Type::Integer16 => Self::Integer16(0),
+            Type::Integer32 => Self::Integer32(0),
+            Type::Integer64 => Self::Integer64(0),
+            Type::UnsignedInteger8 => Self::UnsignedInteger8(0),
+            Type::UnsignedInteger16 => Self::UnsignedInteger16(0),
+            Type::UnsignedInteger32 => Self::UnsignedInteger32(0),
+            Type::UnsignedInteger64 => Self::UnsignedInteger64(0),
+            //Type::Float32 => Self::Float32(0.0),
+            //Type::Float64 => Self::Float64(0.0),
+            _ => todo!("Zero initializer not implemented yet for this type"),
+        }
+    }
+
+    // TODO: Find some way to reuse this code and conditional constant prop code.
+    pub fn binary_op(
+        op: BinaryOperator,
+        left: InterpreterVal,
+        right: InterpreterVal,
+    ) -> InterpreterVal {
+
+        // Do some type conversion first.
+        let left = match left {
+            InterpreterVal::DynamicConstant(v) => match right {
+                InterpreterVal::Integer8(_) => InterpreterVal::Integer8(v.try_into().unwrap()),
+                InterpreterVal::Integer16(_) => InterpreterVal::Integer16(v.try_into().unwrap()),
+                InterpreterVal::Integer32(_) => InterpreterVal::Integer32(v.try_into().unwrap()),
+                InterpreterVal::Integer64(_) => InterpreterVal::Integer64(v.try_into().unwrap()),
+                InterpreterVal::UnsignedInteger8(_) => {
+                    InterpreterVal::UnsignedInteger8(v.try_into().unwrap())
+                }
+                InterpreterVal::UnsignedInteger16(_) => {
+                    InterpreterVal::UnsignedInteger16(v.try_into().unwrap())
+                }
+                InterpreterVal::UnsignedInteger32(_) => {
+                    InterpreterVal::UnsignedInteger64(v.try_into().unwrap())
+                }
+                InterpreterVal::UnsignedInteger64(_) => {
+                    InterpreterVal::UnsignedInteger64(v.try_into().unwrap())
+                }
+                InterpreterVal::DynamicConstant(_) => {
+                    panic!("PANIC: Some math on dynamic constants is unimplemented")
+                }
+                // InterpreterVal::ThreadID(_) => InterpreterVal::Boolean(v),
+                _ => panic!("PANIC: Some math on dynamic constants is unimplemented"),
+            },
+            // InterpreterVal::ThreadID(v) => todo!(),
+            _ => left,
+        };
+
+        let right = match right {
+            InterpreterVal::DynamicConstant(v) => match left {
+                InterpreterVal::Integer8(_) => InterpreterVal::Integer8(v.try_into().unwrap()),
+                InterpreterVal::Integer16(_) => InterpreterVal::Integer16(v.try_into().unwrap()),
+                InterpreterVal::Integer32(_) => InterpreterVal::Integer32(v.try_into().unwrap()),
+                InterpreterVal::Integer64(_) => InterpreterVal::Integer64(v.try_into().unwrap()),
+                InterpreterVal::UnsignedInteger8(_) => {
+                    InterpreterVal::UnsignedInteger8(v.try_into().unwrap())
+                }
+                InterpreterVal::UnsignedInteger16(_) => {
+                    InterpreterVal::UnsignedInteger16(v.try_into().unwrap())
+                }
+                InterpreterVal::UnsignedInteger32(_) => {
+                    InterpreterVal::UnsignedInteger32(v.try_into().unwrap())
+                }
+                InterpreterVal::UnsignedInteger64(_) => {
+                    InterpreterVal::UnsignedInteger64(v.try_into().unwrap())
+                }
+                InterpreterVal::DynamicConstant(_) => {
+                    panic!("PANIC: Some math on dynamic constants is unimplemented")
+                }
+                _ => panic!("PANIC: Some math on dynamic constants is unimplemented"),
+            },
+            // InterpreterVal::ThreadID(v) => todo!(),
+            _ => right,
+        };
+
+        match (op, left.clone(), right.clone()) {
+            (BinaryOperator::Add, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val + right_val)
+            }
+            (BinaryOperator::Add, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val + right_val)
+            }
+            (BinaryOperator::Add, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val + right_val)
+            }
+            (BinaryOperator::Add, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val + right_val)
+            }
+            (
+                BinaryOperator::Add,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val + right_val),
+            (
+                BinaryOperator::Add,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val + right_val),
+            (
+                BinaryOperator::Add,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val + right_val),
+            (
+                BinaryOperator::Add,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val + right_val),
+            (BinaryOperator::Add, Self::Float32(left_val), Self::Float32(right_val)) => {
+                Self::Float32(left_val + right_val)
+            }
+            (BinaryOperator::Add, Self::Float64(left_val), Self::Float64(right_val)) => {
+                Self::Float64(left_val + right_val)
+            }
+            (BinaryOperator::Sub, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val - right_val)
+            }
+            (BinaryOperator::Sub, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val - right_val)
+            }
+            (BinaryOperator::Sub, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val - right_val)
+            }
+            (BinaryOperator::Sub, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val - right_val)
+            }
+            (
+                BinaryOperator::Sub,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val - right_val),
+            (
+                BinaryOperator::Sub,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val - right_val),
+            (
+                BinaryOperator::Sub,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val - right_val),
+            (
+                BinaryOperator::Sub,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val - right_val),
+            (BinaryOperator::Sub, Self::Float32(left_val), Self::Float32(right_val)) => {
+                Self::Float32(left_val - right_val)
+            }
+            (BinaryOperator::Sub, Self::Float64(left_val), Self::Float64(right_val)) => {
+                Self::Float64(left_val - right_val)
+            }
+            (BinaryOperator::Mul, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val * right_val)
+            }
+            (BinaryOperator::Mul, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val * right_val)
+            }
+            (BinaryOperator::Mul, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val * right_val)
+            }
+            (BinaryOperator::Mul, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val * right_val)
+            }
+            (
+                BinaryOperator::Mul,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val * right_val),
+            (
+                BinaryOperator::Mul,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val * right_val),
+            (
+                BinaryOperator::Mul,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val * right_val),
+            (
+                BinaryOperator::Mul,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val * right_val),
+            (BinaryOperator::Mul, Self::Float32(left_val), Self::Float32(right_val)) => {
+                Self::Float32(left_val * right_val)
+            }
+            (BinaryOperator::Mul, Self::Float64(left_val), Self::Float64(right_val)) => {
+                Self::Float64(left_val * right_val)
+            }
+            (BinaryOperator::Div, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val / right_val)
+            }
+            (BinaryOperator::Div, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val / right_val)
+            }
+            (BinaryOperator::Div, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val / right_val)
+            }
+            (BinaryOperator::Div, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val / right_val)
+            }
+            (
+                BinaryOperator::Div,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val / right_val),
+            (
+                BinaryOperator::Div,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val / right_val),
+            (
+                BinaryOperator::Div,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val / right_val),
+            (
+                BinaryOperator::Div,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val / right_val),
+            (BinaryOperator::Div, Self::Float32(left_val), Self::Float32(right_val)) => {
+                Self::Float32(left_val / right_val)
+            }
+            (BinaryOperator::Div, Self::Float64(left_val), Self::Float64(right_val)) => {
+                Self::Float64(left_val / right_val)
+            }
+            (BinaryOperator::Rem, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val % right_val)
+            }
+            (BinaryOperator::Rem, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val % right_val)
+            }
+            (BinaryOperator::Rem, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val % right_val)
+            }
+            (BinaryOperator::Rem, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val % right_val)
+            }
+            (
+                BinaryOperator::Rem,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val % right_val),
+            (
+                BinaryOperator::Rem,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val % right_val),
+            (
+                BinaryOperator::Rem,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val % right_val),
+            (
+                BinaryOperator::Rem,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val % right_val),
+            (BinaryOperator::Rem, Self::Float32(left_val), Self::Float32(right_val)) => {
+                Self::Float32(left_val % right_val)
+            }
+            (BinaryOperator::Rem, Self::Float64(left_val), Self::Float64(right_val)) => {
+                Self::Float64(left_val % right_val)
+            }
+            (BinaryOperator::LT, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Boolean(left_val < right_val)
+            }
+            (BinaryOperator::LT, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Boolean(left_val < right_val)
+            }
+            (BinaryOperator::LT, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Boolean(left_val < right_val)
+            }
+            (BinaryOperator::LT, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Boolean(left_val < right_val)
+            }
+            (
+                BinaryOperator::LT,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::Boolean(left_val < right_val),
+            (
+                BinaryOperator::LT,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::Boolean(left_val < right_val),
+            (
+                BinaryOperator::LT,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::Boolean(left_val < right_val),
+            (
+                BinaryOperator::LT,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::Boolean(left_val < right_val),
+            (BinaryOperator::LT, Self::Float32(left_val), Self::Float32(right_val)) => {
+                Self::Boolean(*left_val < *right_val)
+            }
+            (BinaryOperator::LT, Self::Float64(left_val), Self::Float64(right_val)) => {
+                Self::Boolean(*left_val < *right_val)
+            }
+            (BinaryOperator::LTE, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Boolean(left_val <= right_val)
+            }
+            (BinaryOperator::LTE, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Boolean(left_val <= right_val)
+            }
+            (BinaryOperator::LTE, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Boolean(left_val <= right_val)
+            }
+            (BinaryOperator::LTE, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Boolean(left_val <= right_val)
+            }
+            (
+                BinaryOperator::LTE,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::Boolean(left_val <= right_val),
+            (
+                BinaryOperator::LTE,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::Boolean(left_val <= right_val),
+            (
+                BinaryOperator::LTE,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::Boolean(left_val <= right_val),
+            (
+                BinaryOperator::LTE,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::Boolean(left_val <= right_val),
+            (BinaryOperator::LTE, Self::Float32(left_val), Self::Float32(right_val)) => {
+                Self::Boolean(*left_val <= *right_val)
+            }
+            (BinaryOperator::LTE, Self::Float64(left_val), Self::Float64(right_val)) => {
+                Self::Boolean(*left_val <= *right_val)
+            }
+            (BinaryOperator::GT, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Boolean(left_val > right_val)
+            }
+            (BinaryOperator::GT, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Boolean(left_val > right_val)
+            }
+            (BinaryOperator::GT, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Boolean(left_val > right_val)
+            }
+            (BinaryOperator::GT, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Boolean(left_val > right_val)
+            }
+            (
+                BinaryOperator::GT,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::Boolean(left_val > right_val),
+            (
+                BinaryOperator::GT,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::Boolean(left_val > right_val),
+            (
+                BinaryOperator::GT,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::Boolean(left_val > right_val),
+            (
+                BinaryOperator::GT,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::Boolean(left_val > right_val),
+            (BinaryOperator::GT, Self::Float32(left_val), Self::Float32(right_val)) => {
+                Self::Boolean(*left_val > *right_val)
+            }
+            (BinaryOperator::GT, Self::Float64(left_val), Self::Float64(right_val)) => {
+                Self::Boolean(*left_val > *right_val)
+            }
+            (BinaryOperator::GTE, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Boolean(left_val >= right_val)
+            }
+            (BinaryOperator::GTE, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Boolean(left_val >= right_val)
+            }
+            (BinaryOperator::GTE, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Boolean(left_val >= right_val)
+            }
+            (BinaryOperator::GTE, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Boolean(left_val >= right_val)
+            }
+            (
+                BinaryOperator::GTE,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::Boolean(left_val >= right_val),
+            (
+                BinaryOperator::GTE,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::Boolean(left_val >= right_val),
+            (
+                BinaryOperator::GTE,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::Boolean(left_val >= right_val),
+            (
+                BinaryOperator::GTE,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::Boolean(left_val >= right_val),
+            (BinaryOperator::GTE, Self::Float32(left_val), Self::Float32(right_val)) => {
+                Self::Boolean(*left_val >= *right_val)
+            }
+            (BinaryOperator::GTE, Self::Float64(left_val), Self::Float64(right_val)) => {
+                Self::Boolean(*left_val >= *right_val)
+            }
+            // EQ and NE can be implemented more easily, since we don't
+            // need to unpack the left and right values.
+            (BinaryOperator::EQ, left_val, right_val) => Self::Boolean(left_val == right_val),
+            (BinaryOperator::NE, left_val, right_val) => Self::Boolean(left_val != right_val),
+            (BinaryOperator::Or, Self::Boolean(left_val), Self::Boolean(right_val)) => {
+                Self::Boolean(left_val || right_val)
+            }
+            (BinaryOperator::Or, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val | right_val)
+            }
+            (BinaryOperator::Or, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val | right_val)
+            }
+            (BinaryOperator::Or, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val | right_val)
+            }
+            (BinaryOperator::Or, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val | right_val)
+            }
+            (
+                BinaryOperator::Or,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val | right_val),
+            (
+                BinaryOperator::Or,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val | right_val),
+            (
+                BinaryOperator::Or,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val | right_val),
+            (
+                BinaryOperator::Or,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val | right_val),
+            (BinaryOperator::And, Self::Boolean(left_val), Self::Boolean(right_val)) => {
+                Self::Boolean(left_val && right_val)
+            }
+            (BinaryOperator::And, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val & right_val)
+            }
+            (BinaryOperator::And, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val & right_val)
+            }
+            (BinaryOperator::And, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val & right_val)
+            }
+            (BinaryOperator::And, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val & right_val)
+            }
+            (
+                BinaryOperator::And,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val & right_val),
+            (
+                BinaryOperator::And,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val & right_val),
+            (
+                BinaryOperator::And,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val & right_val),
+            (
+                BinaryOperator::And,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val & right_val),
+            (BinaryOperator::Xor, Self::Boolean(left_val), Self::Boolean(right_val)) => {
+                Self::Boolean(left_val ^ right_val)
+            }
+            (BinaryOperator::Xor, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val ^ right_val)
+            }
+            (BinaryOperator::Xor, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val ^ right_val)
+            }
+            (BinaryOperator::Xor, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val ^ right_val)
+            }
+            (BinaryOperator::Xor, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val ^ right_val)
+            }
+            (
+                BinaryOperator::Xor,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val ^ right_val),
+            (
+                BinaryOperator::Xor,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val ^ right_val),
+            (
+                BinaryOperator::Xor,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val ^ right_val),
+            (
+                BinaryOperator::Xor,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val ^ right_val),
+            (BinaryOperator::LSh, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val << right_val)
+            }
+            (BinaryOperator::LSh, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val << right_val)
+            }
+            (BinaryOperator::LSh, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val << right_val)
+            }
+            (BinaryOperator::LSh, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val << right_val)
+            }
+            (
+                BinaryOperator::LSh,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val << right_val),
+            (
+                BinaryOperator::LSh,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val << right_val),
+            (
+                BinaryOperator::LSh,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val << right_val),
+            (
+                BinaryOperator::LSh,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val << right_val),
+            (BinaryOperator::RSh, Self::Integer8(left_val), Self::Integer8(right_val)) => {
+                Self::Integer8(left_val >> right_val)
+            }
+            (BinaryOperator::RSh, Self::Integer16(left_val), Self::Integer16(right_val)) => {
+                Self::Integer16(left_val >> right_val)
+            }
+            (BinaryOperator::RSh, Self::Integer32(left_val), Self::Integer32(right_val)) => {
+                Self::Integer32(left_val >> right_val)
+            }
+            (BinaryOperator::RSh, Self::Integer64(left_val), Self::Integer64(right_val)) => {
+                Self::Integer64(left_val >> right_val)
+            }
+            (
+                BinaryOperator::RSh,
+                Self::UnsignedInteger8(left_val),
+                Self::UnsignedInteger8(right_val),
+            ) => Self::UnsignedInteger8(left_val >> right_val),
+            (
+                BinaryOperator::RSh,
+                Self::UnsignedInteger16(left_val),
+                Self::UnsignedInteger16(right_val),
+            ) => Self::UnsignedInteger16(left_val >> right_val),
+            (
+                BinaryOperator::RSh,
+                Self::UnsignedInteger32(left_val),
+                Self::UnsignedInteger32(right_val),
+            ) => Self::UnsignedInteger32(left_val >> right_val),
+            (
+                BinaryOperator::RSh,
+                Self::UnsignedInteger64(left_val),
+                Self::UnsignedInteger64(right_val),
+            ) => Self::UnsignedInteger64(left_val >> right_val),
+            _ => {
+                println!("{:?}, {:?}", left, right);
+                panic!("Unsupported combination of binary operation and constant values. Did typechecking succeed?")
+            }
+        }
+    }
+
+    pub fn unary_op(op: UnaryOperator, val: InterpreterVal) -> Self {
+        match (op, val) {
+            (UnaryOperator::Not, Self::Boolean(val)) => Self::Boolean(!val),
+            (UnaryOperator::Not, Self::Integer8(val)) => Self::Integer8(!val),
+            (UnaryOperator::Not, Self::Integer16(val)) => Self::Integer16(!val),
+            (UnaryOperator::Not, Self::Integer32(val)) => Self::Integer32(!val),
+            (UnaryOperator::Not, Self::Integer64(val)) => Self::Integer64(!val),
+            (UnaryOperator::Neg, Self::Integer8(val)) => Self::Integer8(-val),
+            (UnaryOperator::Neg, Self::Integer16(val)) => Self::Integer16(-val),
+            (UnaryOperator::Neg, Self::Integer32(val)) => Self::Integer32(-val),
+            (UnaryOperator::Neg, Self::Integer64(val)) => Self::Integer64(-val),
+            (UnaryOperator::Neg, Self::Float32(val)) => Self::Float32(-val),
+            (UnaryOperator::Neg, Self::Float64(val)) => Self::Float64(-val),
+            (UnaryOperator::Cast(_), _) => todo!("Write cast impl"),
+            _ => panic!("Unsupported combination of unary operation and constant value. Did typechecking succeed?")
+        }
+    }
+
+    pub fn as_usize(&self) -> usize {
+        match *self {
+            InterpreterVal::Boolean(v) => v.try_into().unwrap(),
+            InterpreterVal::Integer8(v) => v.try_into().unwrap(),
+            InterpreterVal::Integer16(v) => v.try_into().unwrap(),
+            InterpreterVal::Integer32(v) => v.try_into().unwrap(),
+            InterpreterVal::Integer64(v) => v.try_into().unwrap(),
+            InterpreterVal::UnsignedInteger8(v) => v.try_into().unwrap(),
+            InterpreterVal::UnsignedInteger16(v) => v.try_into().unwrap(),
+            InterpreterVal::UnsignedInteger32(v) => v.try_into().unwrap(),
+            InterpreterVal::UnsignedInteger64(v) => v.try_into().unwrap(),
+            InterpreterVal::DynamicConstant(v) => v,
+            InterpreterVal::ThreadID(v) => v,
+            _ => panic!("PANIC: Value not castable to usize"),
+        }
+    }
+
+    // Defines row major / how we layout our arrays
+    pub fn array_idx(extents: &[usize], indices: &[usize]) -> usize {
+        let a = extents
+            .into_iter()
+            .enumerate()
+            .map(|(i, n)| {
+                extents[i + 1..]
+                    .into_iter()
+                    .cloned()
+                    .reduce(|a, b| a * b)
+                    .unwrap_or(1)
+                    * indices[i]
+            })
+            .sum();
+        a
+    }
+
+    pub fn array_size(extents: &[usize]) -> usize {
+        extents
+            .into_iter()
+            .cloned()
+            .reduce(|a, b| a * b)
+            .unwrap_or(1)
+    }
+}
+
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_array_idx() {
+        let extents = [10, 5];
+        let indices = [1, 1];
+        let idx = InterpreterVal::array_idx(&extents, &indices);
+        assert_eq!(idx, 6)
+    }
+}
diff --git a/hercules_test/hercules_tests/Cargo.toml b/hercules_test/hercules_tests/Cargo.toml
new file mode 100644
index 00000000..9bd6fe7b
--- /dev/null
+++ b/hercules_test/hercules_tests/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "hercules_tests"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+bitvec = "*"
+clap = { version = "*", features = ["derive"] }
+rand = "*"
+hercules_ir = { path = "../../hercules_ir" }
+hercules_opt = { path = "../../hercules_opt" }
+hercules_interpreter = { path = "../hercules_interpreter" }
+itertools = "*"
+ordered-float = "*"
diff --git a/hercules_test/hercules_tests/tests/opt_tests.rs b/hercules_test/hercules_tests/tests/opt_tests.rs
new file mode 100644
index 00000000..c14d4db5
--- /dev/null
+++ b/hercules_test/hercules_tests/tests/opt_tests.rs
@@ -0,0 +1,201 @@
+use std::env;
+
+use hercules_interpreter::*;
+use hercules_opt::pass::Pass;
+
+extern crate rand;
+use rand::Rng;
+
+#[test]
+fn matmul_int() {
+    let module = parse_file("../test_inputs/matmul_int.hir");
+    let dyn_consts = [2, 2, 2];
+    let m1 = vec![3, 4, 5, 6];
+    let m2 = vec![7, 8, 9, 10];
+    let result_1 = interp_module!(module, dyn_consts, m1.clone(), m2.clone());
+
+    let mut pm = hercules_opt::pass::PassManager::new(module.clone());
+
+    let passes = vec![
+        Pass::Verify,
+        Pass::CCP,
+        Pass::DCE,
+        Pass::GVN,
+        Pass::DCE,
+        Pass::Forkify,
+        Pass::DCE,
+        Pass::Predication,
+        Pass::DCE,
+    ];
+
+    for pass in passes {
+        pm.add_pass(pass);
+    }
+    pm.run_passes();
+
+    let module = pm.get_module();
+    let result_2 = interp_module!(module, dyn_consts, m1, m2);
+    assert_eq!(result_1, result_2)
+}
+
+#[test]
+fn ccp_example() {
+    let module = parse_file("../test_inputs/ccp_example.hir");
+    let dyn_consts = [];
+    let x = 34;
+    let result_1 = interp_module!(module, dyn_consts, x);
+
+    let mut pm = hercules_opt::pass::PassManager::new(module.clone());
+
+    let passes = vec![
+        Pass::Verify,
+        Pass::CCP,
+        Pass::DCE,
+        Pass::GVN,
+        Pass::DCE,
+        Pass::Forkify,
+        Pass::DCE,
+        Pass::Predication,
+        Pass::DCE,
+    ];
+
+    for pass in passes {
+        pm.add_pass(pass);
+    }
+    pm.run_passes();
+
+    let module = pm.get_module();
+    let result_2 = interp_module!(module, dyn_consts, x);
+    assert_eq!(result_1, result_2)
+}
+
+#[test]
+fn gvn_example() {
+    let module = parse_file("../test_inputs/gvn_example.hir");
+
+    let dyn_consts = [];
+    let x: i32 = rand::random();
+    let x = x / 32;
+    let y: i32 = rand::random();
+    let y = y / 32; // prevent overflow, 
+    let result_1 = interp_module!(module, dyn_consts, x, y);
+
+    let mut pm = hercules_opt::pass::PassManager::new(module.clone());
+
+    let passes = vec![
+        Pass::Verify,
+        Pass::CCP,
+        Pass::DCE,
+        Pass::GVN,
+        Pass::DCE,
+        Pass::Forkify,
+        Pass::DCE,
+        Pass::Predication,
+        Pass::DCE,
+    ];
+
+    for pass in passes {
+        pm.add_pass(pass);
+    }
+    pm.run_passes();
+
+    let module = pm.get_module();
+    let result_2 = interp_module!(module, dyn_consts, x, y);
+    assert_eq!(result_1, result_2)
+}
+
+#[test]
+fn sum_int() {
+    let module = parse_file("../test_inputs/sum_int1.hir");
+
+    let size = 100;
+    let dyn_consts = [size];
+    let mut vec = vec![0; size];
+    let mut rng = rand::thread_rng();
+
+    for x in vec.iter_mut() {
+        *x = rng.gen::<i32>() / 100;
+    }
+
+    let result_1 = interp_module!(module, dyn_consts, vec.clone());
+
+    let mut pm = hercules_opt::pass::PassManager::new(module.clone());
+
+    let passes = vec![
+        Pass::Verify,
+        Pass::CCP,
+        Pass::DCE,
+        Pass::GVN,
+        Pass::DCE,
+        Pass::Forkify,
+        Pass::DCE,
+        Pass::Predication,
+        Pass::DCE,
+    ];
+
+    for pass in passes {
+        pm.add_pass(pass);
+    }
+    pm.run_passes();
+
+    let module = pm.get_module();
+    let result_2 = interp_module!(module, dyn_consts, vec);
+    assert_eq!(result_1, result_2)
+}
+
+#[test]
+fn sum_int2() {
+    let module = parse_file("../test_inputs/sum_int2.hir");
+
+    let size = 100;
+    let dyn_consts = [size];
+    let mut vec = vec![0; size];
+    let mut rng = rand::thread_rng();
+
+    for x in vec.iter_mut() {
+        *x = rng.gen::<i32>() / 100;
+    }
+
+    let result_1 = interp_module!(module, dyn_consts, vec.clone());
+
+    let mut pm = hercules_opt::pass::PassManager::new(module.clone());
+
+    let passes = vec![
+        Pass::Verify,
+        Pass::CCP,
+        Pass::DCE,
+        Pass::GVN,
+        Pass::DCE,
+        Pass::Forkify,
+        Pass::DCE,
+        Pass::Predication,
+        Pass::DCE,
+    ];
+
+    for pass in passes {
+        pm.add_pass(pass);
+    }
+    pm.run_passes();
+
+    let module = pm.get_module();
+    let result_2 = interp_module!(module, dyn_consts, vec);
+    assert_eq!(result_1, result_2)
+}
+
+#[test]
+fn sum_int2_smaller() {
+    interp_file_with_passes!("../test_inputs/sum_int2.hir", 
+    [100], 
+    vec![
+        Pass::Verify,
+        Pass::CCP,
+        Pass::DCE,
+        Pass::GVN,
+        Pass::DCE,
+        Pass::Forkify,
+        Pass::DCE,
+        Pass::Predication,
+        Pass::DCE,
+    ],
+    vec![1; 100]);
+}
\ No newline at end of file
diff --git a/hercules_test/test_inputs/call.hir b/hercules_test/test_inputs/call.hir
new file mode 100644
index 00000000..44748934
--- /dev/null
+++ b/hercules_test/test_inputs/call.hir
@@ -0,0 +1,7 @@
+fn myfunc(x: i32) -> i32
+  y = call(add, x, x)
+  r = return(start, y)
+
+fn add(x: i32, y: i32) -> i32
+  w = add(x, y)
+  r = return(start, w)
\ No newline at end of file
diff --git a/hercules_test/test_inputs/call_dc_params.hir b/hercules_test/test_inputs/call_dc_params.hir
new file mode 100644
index 00000000..5ccf2686
--- /dev/null
+++ b/hercules_test/test_inputs/call_dc_params.hir
@@ -0,0 +1,9 @@
+fn myfunc(x: u64) -> u64
+  y = call<10, 4>(add, x, x)
+  r = return(start, y)
+
+fn add<2>(x: u64, y: u64) -> u64
+  b = dynamic_constant(#1)
+  r = return(start, z)
+  w = add(x, y)
+  z = add(b, w)
\ No newline at end of file
diff --git a/hercules_test/test_inputs/ccp_example.hir b/hercules_test/test_inputs/ccp_example.hir
new file mode 100644
index 00000000..25b7379e
--- /dev/null
+++ b/hercules_test/test_inputs/ccp_example.hir
@@ -0,0 +1,19 @@
+fn tricky(x: i32) -> i32
+  one = constant(i32, 1)
+  two = constant(i32, 2)
+  loop = region(start, if2_true)
+  idx = phi(loop, x, idx_dec)
+  val = phi(loop, one, later_val)
+  b = ne(one, val)
+  if1 = if(loop, b)
+  if1_false = projection(if1, 0)
+  if1_true = projection(if1, 1)
+  middle = region(if1_false, if1_true)
+  inter_val = sub(two, val)
+  later_val = phi(middle, inter_val, two)
+  idx_dec = sub(idx, one)
+  cond = gte(idx_dec, one)
+  if2 = if(middle, cond)
+  if2_false = projection(if2, 0)
+  if2_true = projection(if2, 1)
+  r = return(if2_false, later_val)
diff --git a/hercules_test/test_inputs/fork_join.hir b/hercules_test/test_inputs/fork_join.hir
new file mode 100644
index 00000000..99e95829
--- /dev/null
+++ b/hercules_test/test_inputs/fork_join.hir
@@ -0,0 +1,8 @@
+fn fork_join<1>() -> u64
+  f_ctrl = fork(start, #0)
+  j_ctrl = join(f_ctrl)
+  zero = constant(u64, 0)
+  x = thread_id(f_ctrl)
+  data = reduce(j_ctrl, zero, sum)
+  sum = add(data, x)
+  r = return(j_ctrl, data)
diff --git a/hercules_test/test_inputs/gvn_example.hir b/hercules_test/test_inputs/gvn_example.hir
new file mode 100644
index 00000000..b5e3c8aa
--- /dev/null
+++ b/hercules_test/test_inputs/gvn_example.hir
@@ -0,0 +1,8 @@
+fn tricky(x: i32, y: i32) -> i32
+  zero = constant(i32, 0)
+  xx = add(x, zero)
+  a = add(xx, y)
+  b = add(x, y)
+  bb = add(zero, b)
+  c = add(a, bb)
+  r = return(start, c)
diff --git a/hercules_test/test_inputs/matmul.hir b/hercules_test/test_inputs/matmul.hir
new file mode 100644
index 00000000..2f0fb67a
--- /dev/null
+++ b/hercules_test/test_inputs/matmul.hir
@@ -0,0 +1,58 @@
+fn matmul<3>(a: array(f32, #0, #1), b: array(f32, #1, #2)) -> array(f32, #0, #2)
+  c = constant(array(f32, #0, #2), zero)
+  i_ctrl = fork(start, #0)
+  i_idx = thread_id(i_ctrl)
+  j_ctrl = fork(i_ctrl, #2)
+  j_idx = thread_id(j_ctrl)
+  k_ctrl = fork(j_ctrl, #1)
+  k_idx = thread_id(k_ctrl)
+  k_join_ctrl = join(k_ctrl)
+  j_join_ctrl = join(k_join_ctrl)
+  i_join_ctrl = join(j_join_ctrl)
+  r = return(i_join_ctrl, update_i_c)
+  zero = constant(f32, 0)
+  a_val = read(a, position(i_idx, k_idx))
+  b_val = read(b, position(k_idx, j_idx))
+  mul = mul(a_val, b_val)
+  add = add(mul, dot)
+  dot = reduce(k_join_ctrl, zero, add)
+  updated_c = write(update_j_c, dot, position(i_idx, j_idx))
+  update_j_c = reduce(j_join_ctrl, update_i_c, updated_c)
+  update_i_c = reduce(i_join_ctrl, c, update_j_c)
+  
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hercules_test/test_inputs/matmul_int.hir b/hercules_test/test_inputs/matmul_int.hir
new file mode 100644
index 00000000..1e496bab
--- /dev/null
+++ b/hercules_test/test_inputs/matmul_int.hir
@@ -0,0 +1,21 @@
+fn matmul<3>(a: array(i32, #0, #1), b: array(i32, #1, #2)) -> array(i32, #0, #2)
+  c = constant(array(i32, #0, #2), [])
+  i_ctrl = fork(start, #0)
+  i_idx = thread_id(i_ctrl)
+  j_ctrl = fork(i_ctrl, #2)
+  j_idx = thread_id(j_ctrl)
+  k_ctrl = fork(j_ctrl, #1)
+  k_idx = thread_id(k_ctrl)
+  k_join_ctrl = join(k_ctrl)
+  j_join_ctrl = join(k_join_ctrl)
+  i_join_ctrl = join(j_join_ctrl)
+  r = return(i_join_ctrl, update_i_c)
+  zero = constant(i32, 0)
+  a_val = read(a, position(i_idx, k_idx))
+  b_val = read(b, position(k_idx, j_idx))
+  mul = mul(a_val, b_val)
+  add = add(mul, dot)
+  dot = reduce(k_join_ctrl, zero, add)
+  updated_c = write(update_j_c, dot, position(i_idx, j_idx))
+  update_j_c = reduce(j_join_ctrl, update_i_c, updated_c)
+  update_i_c = reduce(i_join_ctrl, c, update_j_c)
diff --git a/hercules_test/test_inputs/simple1.hir b/hercules_test/test_inputs/simple1.hir
new file mode 100644
index 00000000..902c1206
--- /dev/null
+++ b/hercules_test/test_inputs/simple1.hir
@@ -0,0 +1,3 @@
+fn simple1(a: i32, b: i32) -> i32
+  c = add(a, b)
+  r = return(start, c)
diff --git a/hercules_test/test_inputs/simple2.hir b/hercules_test/test_inputs/simple2.hir
new file mode 100644
index 00000000..af5ac284
--- /dev/null
+++ b/hercules_test/test_inputs/simple2.hir
@@ -0,0 +1,13 @@
+fn simple2(x: i32) -> i32
+  zero = constant(i32, 0)
+  one = constant(i32, 1)
+  loop = region(start, if_true)
+  idx = phi(loop, zero, idx_inc)
+  idx_inc = add(idx, one)
+  fac = phi(loop, one, fac_acc)
+  fac_acc = mul(fac, idx_inc)
+  in_bounds = lt(idx_inc, x)
+  if = if(loop, in_bounds)
+  if_false = projection(if, 0)
+  if_true = projection(if, 1)
+  r = return(if_false, fac_acc)
\ No newline at end of file
diff --git a/hercules_test/test_inputs/simple3.hir b/hercules_test/test_inputs/simple3.hir
new file mode 100644
index 00000000..33a8e1b4
--- /dev/null
+++ b/hercules_test/test_inputs/simple3.hir
@@ -0,0 +1,11 @@
+fn simple3<1>(a: array(f32, #0), b: array(f32, #0)) -> f32
+  zero = constant(f32, 0.0)
+  fork = fork(start, #0)
+  id = thread_id(fork)
+  join = join(fork)
+  r = return(join, dot_red)
+  aval = read(a, position(id))
+  bval = read(b, position(id))
+  mul = mul(aval, bval)
+  dot = add(mul, dot_red)
+  dot_red = reduce(join, zero, dot)
\ No newline at end of file
diff --git a/hercules_test/test_inputs/strset.hir b/hercules_test/test_inputs/strset.hir
new file mode 100644
index 00000000..4c8b32ee
--- /dev/null
+++ b/hercules_test/test_inputs/strset.hir
@@ -0,0 +1,17 @@
+fn strset<1>(str: array(u8, #0), byte: u8) -> array(u8, #0)
+  zero = constant(u64, 0)
+  one = constant(u64, 1)
+  bound = dynamic_constant(#0)
+  loop = region(start, if_true)
+  idx = phi(loop, zero, idx_inc)
+  str_inc = phi(loop, str, write)
+  idx_inc = add(idx, one)
+  in_bounds = lt(idx_inc, bound)
+  read = read(str_inc, position(idx))
+  write = write(str_inc, byte, position(idx))
+  continue = ne(read, byte)
+  if_cond = and(continue, in_bounds)
+  if = if(loop, if_cond)
+  if_false = projection(if, 0)
+  if_true = projection(if, 1)
+  r = return(if_false, str_inc)
diff --git a/hercules_test/test_inputs/sum_int1.hir b/hercules_test/test_inputs/sum_int1.hir
new file mode 100644
index 00000000..4a9ba015
--- /dev/null
+++ b/hercules_test/test_inputs/sum_int1.hir
@@ -0,0 +1,16 @@
+fn sum<1>(a: array(i32, #0)) -> i32
+  zero_idx = constant(u64, 0)
+  one_idx = constant(u64, 1)
+  zero_inc = constant(i32, 0)
+  bound = dynamic_constant(#0)
+  loop = region(start, if_true)
+  idx = phi(loop, zero_idx, idx_inc)
+  idx_inc = add(idx, one_idx)
+  red = phi(loop, zero_inc, red_add)
+  read = read(a, position(idx))
+  red_add = add(red, read)
+  in_bounds = lt(idx_inc, bound)
+  if = if(loop, in_bounds)
+  if_false = projection(if, 0)
+  if_true = projection(if, 1)
+  r = return(if_false, red_add)
\ No newline at end of file
diff --git a/hercules_test/test_inputs/sum_int2.hir b/hercules_test/test_inputs/sum_int2.hir
new file mode 100644
index 00000000..b5e9a5c0
--- /dev/null
+++ b/hercules_test/test_inputs/sum_int2.hir
@@ -0,0 +1,25 @@
+fn alt_sum<1>(a: array(i32, #0)) -> i32
+  zero_idx = constant(u64, 0)
+  one_idx = constant(u64, 1)
+  two_idx = constant(u64, 2)
+  zero_inc = constant(i32, 0)
+  bound = dynamic_constant(#0)
+  loop = region(start, if_true)
+  idx = phi(loop, zero_idx, idx_inc)
+  idx_inc = add(idx, one_idx)
+  red = phi(loop, zero_inc, red_add)
+  rem = rem(idx, two_idx)
+  odd = eq(rem, one_idx)
+  negate_if = if(loop, odd)
+  negate_if_false = projection(negate_if, 0)
+  negate_if_true = projection(negate_if, 1)
+  negate_bottom = region(negate_if_false, negate_if_true)
+  read = read(a, position(idx))
+  read_neg = neg(read)
+  read_phi = phi(negate_bottom, read, read_neg)
+  red_add = add(red, read_phi)
+  in_bounds = lt(idx_inc, bound)
+  if = if(negate_bottom, in_bounds)
+  if_false = projection(if, 0)
+  if_true = projection(if, 1)
+  r = return(if_false, red_add)
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 00000000..271800cb
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
\ No newline at end of file
-- 
GitLab