diff --git a/juno_samples/fork_join_tests/src/cpu.sch b/juno_samples/fork_join_tests/src/cpu.sch
index abc2fde0fc3888d033dd2d2dc082aa8a9680f76a..7c416e904ad5d43a5297496b6de40037f5b9b553 100644
--- a/juno_samples/fork_join_tests/src/cpu.sch
+++ b/juno_samples/fork_join_tests/src/cpu.sch
@@ -14,6 +14,8 @@ cpu(auto.test5);
 cpu(auto.test7);
 cpu(auto.test8);
 
+let test1_cpu = auto.test1;
+rename["test1_cpu"](test1_cpu);
 
 ip-sroa(*);
 sroa(*);
@@ -39,7 +41,10 @@ dce(*);
 fixpoint panic after 20 {
   infer-schedules(*);
 }
-fork-split(auto.test1);
+
+let out = fork-split(test1_cpu);
+let first_fork = out.test1_cpu.fj1;
+
 fixpoint panic after 20 {
   unroll(auto.test1);
 }
diff --git a/juno_scheduler/src/compile.rs b/juno_scheduler/src/compile.rs
index e0e08e95899b73d50385b7ab47e73ccdf3480de0..0652f8f260929d3fd27147806520f02af28a0bd6 100644
--- a/juno_scheduler/src/compile.rs
+++ b/juno_scheduler/src/compile.rs
@@ -134,6 +134,7 @@ impl FromStr for Appliable {
             "phi-elim" => Ok(Appliable::Pass(ir::Pass::PhiElim)),
             "predication" => Ok(Appliable::Pass(ir::Pass::Predication)),
             "reduce-slf" => Ok(Appliable::Pass(ir::Pass::ReduceSLF)),
+            "rename" => Ok(Appliable::Pass(ir::Pass::Rename)),
             "reuse-products" => Ok(Appliable::Pass(ir::Pass::ReuseProducts)),
             "simplify-cfg" => Ok(Appliable::Pass(ir::Pass::SimplifyCFG)),
             "slf" | "store-load-forward" => Ok(Appliable::Pass(ir::Pass::SLF)),
@@ -433,6 +434,11 @@ fn compile_expr(
         parser::Expr::Boolean { span: _, val } => {
             Ok(ExprResult::Expr(ir::ScheduleExp::Boolean { val }))
         }
+        parser::Expr::String { span } => {
+            let string = lexer.span_str(span);
+            let val = string[1..string.len() - 1].to_string();
+            Ok(ExprResult::Expr(ir::ScheduleExp::String { val }))
+        }
         parser::Expr::Field {
             span: _,
             lhs,
diff --git a/juno_scheduler/src/ir.rs b/juno_scheduler/src/ir.rs
index 480fee643a7dbe4eafe4cfd317062fb74370edd7..e92f1d374705a72f090208a3073155507df5915c 100644
--- a/juno_scheduler/src/ir.rs
+++ b/juno_scheduler/src/ir.rs
@@ -28,6 +28,7 @@ pub enum Pass {
     PhiElim,
     Predication,
     ReduceSLF,
+    Rename,
     ReuseProducts,
     SLF,
     SROA,
@@ -43,6 +44,7 @@ impl Pass {
     pub fn is_valid_num_args(&self, num: usize) -> bool {
         match self {
             Pass::ArrayToProduct => num == 0 || num == 1,
+            Pass::Rename => num == 1,
             Pass::Xdot => num == 0 || num == 1,
             Pass::ForkChunk => num == 4,
             Pass::ForkFissionBufferize => num == 2,
@@ -54,6 +56,7 @@ impl Pass {
     pub fn valid_arg_nums(&self) -> &'static str {
         match self {
             Pass::ArrayToProduct => "0 or 1",
+            Pass::Rename => "1",
             Pass::Xdot => "0 or 1",
             Pass::ForkChunk => "4",
             Pass::ForkFissionBufferize => "2",
@@ -80,6 +83,9 @@ pub enum ScheduleExp {
     Boolean {
         val: bool,
     },
+    String {
+        val: String,
+    },
     Field {
         collect: Box<ScheduleExp>,
         field: String,
diff --git a/juno_scheduler/src/lang.l b/juno_scheduler/src/lang.l
index 9d4c34bf8f7e50aaef048c7eba1a22c6f5872a96..afe596b2f3a90fcfb9e8fd1d8c6f5224afc7131f 100644
--- a/juno_scheduler/src/lang.l
+++ b/juno_scheduler/src/lang.l
@@ -46,5 +46,6 @@ stop[\t \n\r]+after  "stop_after"
 [a-zA-Z][a-zA-Z0-9_\-]*! "MACRO"
 [a-zA-Z][a-zA-Z0-9_\-]*  "ID"
 [0-9]+                   "INT"
+\"[a-zA-Z0-9_\-\s\.]*\"  "STRING"
 
 .                     "UNMATCHED"
diff --git a/juno_scheduler/src/lang.y b/juno_scheduler/src/lang.y
index 9cb728428e08b0f77b77be526e2f3e0aa9c86a37..584bf2a4ef1a476669f10de115a5dda38213a695 100644
--- a/juno_scheduler/src/lang.y
+++ b/juno_scheduler/src/lang.y
@@ -1,6 +1,6 @@
 %start Schedule
 
-%avoid_insert "ID" "INT"
+%avoid_insert "ID" "INT" "STRING"
 %expect-unused Unmatched 'UNMATCHED'
 
 %%
@@ -47,6 +47,8 @@ Expr -> Expr
       { Expr::Boolean { span: $span, val: true } }
   | 'false'
       { Expr::Boolean { span: $span, val: false } }
+  | 'STRING'
+      { Expr::String { span: $span } }
   | Expr '.' 'ID'
       { Expr::Field { span: $span, lhs: Box::new($1), field: span_of_tok($3) } }
   | Expr '@' 'ID'
@@ -155,6 +157,7 @@ pub enum Expr {
   Variable    { span: Span },
   Integer     { span: Span },
   Boolean     { span: Span, val: bool },
+  String      { span: Span },
   Field       { span: Span, lhs: Box<Expr>, field: Span },
   BlockExpr   { span: Span, body: Box<OperationList> },
   Record      { span: Span, fields: Vec<(Span, Expr)> },
diff --git a/juno_scheduler/src/lib.rs b/juno_scheduler/src/lib.rs
index d4ab432aa9bbd75f67e8e3d07d74a43f03f0967f..03ad0111069673bec3f5e081e6d6e1e15eb5d749 100644
--- a/juno_scheduler/src/lib.rs
+++ b/juno_scheduler/src/lib.rs
@@ -1,6 +1,6 @@
 #![feature(exact_size_is_empty)]
+#![feature(let_chains)]
 
-use std::collections::{HashMap, HashSet};
 use std::fs::File;
 use std::io::Read;
 
diff --git a/juno_scheduler/src/pm.rs b/juno_scheduler/src/pm.rs
index 0e052026cb82c75277de17d663f89444f938abe7..9c51276b82aa52fde5aa8dd13295b69aea7fb064 100644
--- a/juno_scheduler/src/pm.rs
+++ b/juno_scheduler/src/pm.rs
@@ -29,6 +29,7 @@ pub enum Value {
     Selection { selection: Vec<Value> },
     Integer { val: usize },
     Boolean { val: bool },
+    String { val: String },
 }
 
 #[derive(Debug, Copy, Clone)]
@@ -70,6 +71,9 @@ impl Value {
             Value::Boolean { .. } => Err(SchedulerError::SemanticError(
                 "Expected labels, found boolean".to_string(),
             )),
+            Value::String { .. } => Err(SchedulerError::SemanticError(
+                "Expected labels, found string".to_string(),
+            )),
         }
     }
 
@@ -99,6 +103,9 @@ impl Value {
             Value::Boolean { .. } => Err(SchedulerError::SemanticError(
                 "Expected functions, found boolean".to_string(),
             )),
+            Value::String { .. } => Err(SchedulerError::SemanticError(
+                "Expected functions, found string".to_string(),
+            )),
         }
     }
 
@@ -130,6 +137,9 @@ impl Value {
             Value::Boolean { .. } => Err(SchedulerError::SemanticError(
                 "Expected code locations, found boolean".to_string(),
             )),
+            Value::String { .. } => Err(SchedulerError::SemanticError(
+                "Expected code locations, found string".to_string(),
+            )),
         }
     }
 }
@@ -998,6 +1008,7 @@ fn interp_expr(
         }
         ScheduleExp::Integer { val } => Ok((Value::Integer { val: *val }, false)),
         ScheduleExp::Boolean { val } => Ok((Value::Boolean { val: *val }, false)),
+        ScheduleExp::String { val } => Ok((Value::String { val: val.clone() }, false)),
         ScheduleExp::Field { collect, field } => {
             let (lhs, changed) = interp_expr(pm, collect, stringtab, env, functions)?;
             match lhs {
@@ -1005,7 +1016,8 @@ fn interp_expr(
                 | Value::Selection { .. }
                 | Value::Everything { .. }
                 | Value::Integer { .. }
-                | Value::Boolean { .. } => Err(SchedulerError::UndefinedField(field.clone())),
+                | Value::Boolean { .. }
+                | Value::String { .. } => Err(SchedulerError::UndefinedField(field.clone())),
                 Value::JunoFunction { func } => {
                     match pm.labels.borrow().iter().position(|s| s == field) {
                         None => Err(SchedulerError::UndefinedLabel(field.clone())),
@@ -1260,6 +1272,7 @@ fn update_value(
         Value::Everything {} => Some(Value::Everything {}),
         Value::Integer { val } => Some(Value::Integer { val }),
         Value::Boolean { val } => Some(Value::Boolean { val }),
+        Value::String { val } => Some(Value::String { val }),
     }
 }
 
@@ -2191,6 +2204,36 @@ fn run_pass(
                 changed |= func.modified();
             }
         }
+        Pass::Rename => {
+            assert!(args.len() == 1);
+            let new_name = match args[0] {
+                Value::String { ref val } => val.clone(),
+                _ => {
+                    return Err(SchedulerError::PassError {
+                        pass: "rename".to_string(),
+                        error: "expected string argument".to_string(),
+                    });
+                }
+            };
+            if pm.functions.iter().any(|f| f.name == new_name) {
+                return Err(SchedulerError::PassError {
+                    pass: "rename".to_string(),
+                    error: format!("function with name {} already exists", new_name),
+                });
+            }
+
+            if let Some(funcs) = selection_of_functions(pm, selection)
+                && funcs.len() == 1
+            {
+                let func = funcs[0];
+                pm.functions[func.idx()].name = new_name;
+            } else {
+                return Err(SchedulerError::PassError {
+                    pass: "rename".to_string(),
+                    error: "must be applied to the entirety of a single function".to_string(),
+                });
+            };
+        }
         Pass::ReuseProducts => {
             assert!(args.is_empty());
             pm.make_reverse_postorders();
@@ -2334,7 +2377,13 @@ fn run_pass(
                 let Some(mut func) = func else {
                     continue;
                 };
-                chunk_all_forks_unguarded(&mut func, fork_join_map, *dim_idx, *tile_size, *tile_order);
+                chunk_all_forks_unguarded(
+                    &mut func,
+                    fork_join_map,
+                    *dim_idx,
+                    *tile_size,
+                    *tile_order,
+                );
                 changed |= func.modified();
             }
             pm.delete_gravestones();