diff --git a/hercules_opt/src/lib.rs b/hercules_opt/src/lib.rs
index 7187508a31240071849a26b85100e89607786b2f..a810dfbf5798de4c3bc6f11136ef1b8569243ccb 100644
--- a/hercules_opt/src/lib.rs
+++ b/hercules_opt/src/lib.rs
@@ -23,6 +23,7 @@ pub mod simplify_cfg;
 pub mod slf;
 pub mod sroa;
 pub mod unforkify;
+pub mod unroll;
 pub mod utils;
 
 pub use crate::ccp::*;
@@ -48,4 +49,5 @@ pub use crate::simplify_cfg::*;
 pub use crate::slf::*;
 pub use crate::sroa::*;
 pub use crate::unforkify::*;
+pub use crate::unroll::*;
 pub use crate::utils::*;
diff --git a/hercules_opt/src/unroll.rs b/hercules_opt/src/unroll.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f3c795ca951d51c7f095d813981938c998c3fd70
--- /dev/null
+++ b/hercules_opt/src/unroll.rs
@@ -0,0 +1,18 @@
+use bitvec::prelude::*;
+
+use hercules_ir::*;
+
+use crate::*;
+
+/*
+ * Run loop unrolling on all loops that are mutable in an editor.
+ */
+pub fn loop_unroll_all_loops(editor: &mut FunctionEditor, loops: &LoopTree) {
+    for (header, contents) in loops.bottom_up_loops() {
+        if editor.is_mutable(header) {
+            loop_unroll(editor, header, contents);
+        }
+    }
+}
+
+pub fn loop_unroll(editor: &mut FunctionEditor, header: NodeID, contents: &BitVec<u8, Lsb0>) {}
diff --git a/juno_scheduler/src/compile.rs b/juno_scheduler/src/compile.rs
index 1aaa10cddc350b703c22f734dbdf1f75fb3ef46c..9d5a86ccbac9e7b33e4c2a81a8d1633062edef24 100644
--- a/juno_scheduler/src/compile.rs
+++ b/juno_scheduler/src/compile.rs
@@ -114,6 +114,7 @@ impl FromStr for Appliable {
             "fork-interchange" => Ok(Appliable::Pass(ir::Pass::ForkInterchange)),
             "fork-chunk" | "fork-tile" => Ok(Appliable::Pass(ir::Pass::ForkChunk)),
             "lift-dc-math" => Ok(Appliable::Pass(ir::Pass::LiftDCMath)),
+            "loop-unroll" | "unroll" => Ok(Appliable::Pass(ir::Pass::LoopUnroll)),
             "outline" => Ok(Appliable::Pass(ir::Pass::Outline)),
             "phi-elim" => Ok(Appliable::Pass(ir::Pass::PhiElim)),
             "predication" => Ok(Appliable::Pass(ir::Pass::Predication)),
diff --git a/juno_scheduler/src/ir.rs b/juno_scheduler/src/ir.rs
index 0ecac39a19a5e364c0ab1185b162c0470f5b6a5b..1bb6cf1342c7b2d167e5e92e908f34ce54d0e4b5 100644
--- a/juno_scheduler/src/ir.rs
+++ b/juno_scheduler/src/ir.rs
@@ -23,6 +23,7 @@ pub enum Pass {
     Inline,
     InterproceduralSROA,
     LiftDCMath,
+    LoopUnroll,
     Outline,
     PhiElim,
     Predication,
diff --git a/juno_scheduler/src/pm.rs b/juno_scheduler/src/pm.rs
index 9c7391acd5686d95461ed562f61b3dbc9774651a..5c6aec5e6af2b48b84cdeff6abec0644975703dd 100644
--- a/juno_scheduler/src/pm.rs
+++ b/juno_scheduler/src/pm.rs
@@ -1665,6 +1665,24 @@ fn run_pass(
             pm.delete_gravestones();
             pm.clear_analyses();
         }
+        Pass::LoopUnroll => {
+            assert_eq!(args.len(), 0);
+
+            pm.make_loops();
+            let loops = pm.loops.take().unwrap();
+            for (func, loops) in build_selection(pm, selection, false)
+                .into_iter()
+                .zip(loops.iter())
+            {
+                let Some(mut func) = func else {
+                    continue;
+                };
+                loop_unroll_all_loops(&mut func, loops);
+                changed |= func.modified();
+            }
+            pm.delete_gravestones();
+            pm.clear_analyses();
+        }
         Pass::Forkify => {
             assert!(args.is_empty());
             loop {