diff --git a/Cargo.lock b/Cargo.lock
index 094512f5ff716a7d89fe9617955bcee28a350309..8248c42585e4b33b0ea6d0f430d340614ca8f50e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -608,14 +608,6 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
-[[package]]
-name = "hercules_cg"
-version = "0.1.0"
-dependencies = [
- "bitvec",
- "hercules_ir",
-]
-
 [[package]]
 name = "hercules_driver"
 version = "0.1.0"
@@ -662,7 +654,6 @@ name = "hercules_opt"
 version = "0.1.0"
 dependencies = [
  "bitvec",
- "hercules_cg",
  "hercules_ir",
  "ordered-float",
  "postcard",
diff --git a/Cargo.toml b/Cargo.toml
index 936803f2646b209c4b0ac5269070318787932610..c6af575eb81f8ecdeaa43ddb13f837a4d5c0e74b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,6 @@
 [workspace]
 resolver = "2"
 members = [
-	"hercules_cg",
 	"hercules_ir",
 	"hercules_opt",
 	"hercules_rt",
diff --git a/hercules_ir/src/build.rs b/hercules_ir/src/build.rs
index 6fe2edf37aae217a50302b53fa61ef60b2ba3b0e..b451dcb8c3da2b7f0399a7613927357243e0b54a 100644
--- a/hercules_ir/src/build.rs
+++ b/hercules_ir/src/build.rs
@@ -394,9 +394,7 @@ impl<'a> Builder<'a> {
         Index::Position(idx)
     }
 
-    pub fn create_control_index(&self, idx: usize) -> Index {
-        Index::Control(idx)
-    }
+
 
     pub fn create_function(
         &mut self,
@@ -478,6 +476,10 @@ impl NodeBuilder {
         };
     }
 
+    pub fn build_projection(&mut self, control: NodeID, selection: usize) {
+        self.node = Node::Projection { control, selection };
+    }
+
     pub fn build_return(&mut self, control: NodeID, data: NodeID) {
         self.node = Node::Return { control, data };
     }
diff --git a/hercules_ir/src/dataflow.rs b/hercules_ir/src/dataflow.rs
index 5ad46a4c0c453a913c0cee5c8205644e14643b5a..c7462613847ab73aad421aa105a8e4a81b449040 100644
--- a/hercules_ir/src/dataflow.rs
+++ b/hercules_ir/src/dataflow.rs
@@ -332,7 +332,7 @@ pub fn control_output_flow(
     let node = &function.nodes[node_id.idx()];
 
     // Step 2: clear all bits, if applicable.
-    if node.is_strictly_control() || node.is_thread_id() || node.is_reduce() || node.is_phi() {
+    if node.is_control() || node.is_thread_id() || node.is_reduce() || node.is_phi() {
         out = UnionNodeSet::Empty;
     }
 
diff --git a/hercules_ir/src/def_use.rs b/hercules_ir/src/def_use.rs
index 73516ab571e4fda61b716cd061431030bedec6a6..36f21f03f3eb11ecae90ab716d7b9dc62e1eecc2 100644
--- a/hercules_ir/src/def_use.rs
+++ b/hercules_ir/src/def_use.rs
@@ -210,6 +210,10 @@ pub fn get_uses<'a>(node: &'a Node) -> NodeUses<'a> {
                 NodeUses::Two([*collect, *data])
             }
         }
+        Node::Projection { control, selection: _ } => {
+            NodeUses::One([*control])
+
+        }
     }
 }
 
@@ -291,5 +295,8 @@ pub fn get_uses_mut<'a>(node: &'a mut Node) -> NodeUsesMut<'a> {
                 NodeUsesMut::Two([collect, data])
             }
         }
+        Node::Projection { control, selection } => {
+            NodeUsesMut::One([control])
+        },
     }
 }
diff --git a/hercules_ir/src/ir.rs b/hercules_ir/src/ir.rs
index e25f4b7c1aed5e6f65e8a7768eb105499c92ad4a..ea9c07205029717d700700e8b97db76cd07e989f 100644
--- a/hercules_ir/src/ir.rs
+++ b/hercules_ir/src/ir.rs
@@ -128,15 +128,12 @@ pub enum DynamicConstant {
  * However, each of these types are indexed differently. Thus, these two nodes
  * operate on an index list, composing indices at different levels in a type
  * tree. Each type that can be indexed has a unique variant in the index enum.
- * Read nodes are overloaded to select between control successors of if and
- * match nodes.
  */
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub enum Index {
     Field(usize),
     Variant(usize),
     Position(Box<[NodeID]>),
-    Control(usize),
 }
 
 /*
@@ -225,6 +222,10 @@ pub enum Node {
         data: NodeID,
         indices: Box<[Index]>,
     },
+    Projection {
+        control: NodeID,
+        selection: usize,
+    }
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -862,7 +863,6 @@ macro_rules! define_pattern_predicate {
 }
 
 impl Index {
-    define_pattern_predicate!(is_control, Index::Control(_));
 
     pub fn try_field(&self) -> Option<usize> {
         if let Index::Field(field) = self {
@@ -880,20 +880,11 @@ impl Index {
         }
     }
 
-    pub fn try_control(&self) -> Option<usize> {
-        if let Index::Control(val) = self {
-            Some(*val)
-        } else {
-            None
-        }
-    }
-
     pub fn lower_case_name(&self) -> &'static str {
         match self {
             Index::Field(_) => "field",
             Index::Variant(_) => "variant",
             Index::Position(_) => "position",
-            Index::Control(_) => "control",
         }
     }
 }
@@ -958,6 +949,13 @@ impl Node {
         }
     );
     define_pattern_predicate!(is_match, Node::Match { control: _, sum: _ });
+    define_pattern_predicate!(
+        is_projection,
+        Node::Projection {
+            control: _, 
+            selection: _
+        }
+    );
 
     pub fn try_region(&self) -> Option<&[NodeID]> {
         if let Node::Region { preds } = self {
@@ -1017,17 +1015,6 @@ impl Node {
         }
     }
 
-    pub fn try_control_read(&self, branch: usize) -> Option<NodeID> {
-        if let Node::Read { collect, indices } = self
-            && indices.len() == 1
-            && indices[0] == Index::Control(branch)
-        {
-            Some(*collect)
-        } else {
-            None
-        }
-    }
-
     pub fn is_zero_constant(&self, constants: &Vec<Constant>) -> bool {
         if let Node::Constant { id } = self
             && constants[id.idx()].is_zero()
@@ -1038,19 +1025,14 @@ impl Node {
         }
     }
 
-    /*
-     * Read nodes can be considered control when following an if or match
-     * node. However, it is sometimes useful to exclude such nodes when
-     * considering control nodes.
-     */
-    pub fn is_strictly_control(&self) -> bool {
-        self.is_start()
-            || self.is_region()
-            || self.is_if()
-            || self.is_match()
-            || self.is_fork()
-            || self.is_join()
-            || self.is_return()
+    pub fn try_projection(&self, branch: usize) -> Option<NodeID> {
+        if let Node::Projection { control, selection } = self
+            && branch == *selection
+        {
+            Some(*control)
+        } else {
+            None
+        }
     }
 
     pub fn upper_case_name(&self) -> &'static str {
@@ -1110,6 +1092,10 @@ impl Node {
                 data: _,
                 indices: _,
             } => "Write",
+            Node::Projection { 
+                control: _, 
+                selection: _ 
+            } => "Projection",
         }
     }
 
@@ -1170,23 +1156,22 @@ impl Node {
                 data: _,
                 indices: _,
             } => "write",
+            Node::Projection { 
+                control: _, 
+                selection: _ 
+            } => "projection",
         }
     }
 
     pub fn is_control(&self) -> bool {
-        if self.is_strictly_control() {
-            return true;
-        }
-
-        if let Node::Read {
-            collect: _,
-            indices,
-        } = self
-        {
-            return indices.len() == 1 && indices[0].is_control();
-        }
-
-        false
+        self.is_start()
+            || self.is_region()
+            || self.is_if()
+            || self.is_match()
+            || self.is_fork()
+            || self.is_join()
+            || self.is_return()
+            || self.is_projection()
     }
 }
 
@@ -1491,6 +1476,9 @@ impl IRDisplay for Node {
                     third.0
                 )
             }
+            Node::Projection { control, selection } => {
+                write!(f, "projection({}, {})", control.0, selection)
+            }
         }
     }
 }
@@ -1500,7 +1488,6 @@ impl IRDisplay for Index {
         match self {
             Index::Field(idx) => write!(f, "field({})", idx),
             Index::Variant(idx) => write!(f, "variant({})", idx),
-            Index::Control(idx) => write!(f, "control({})", idx),
             Index::Position(indices) => {
                 write!(f, "position(")?;
                 for (i, idx) in indices.iter().enumerate() {
diff --git a/hercules_ir/src/parse.rs b/hercules_ir/src/parse.rs
index 63107c81d863934f101e859e7985b6585baee1e1..fc5e397bf4a6848d6e91969779f90906dbaa2021 100644
--- a/hercules_ir/src/parse.rs
+++ b/hercules_ir/src/parse.rs
@@ -288,6 +288,7 @@ fn parse_node<'a>(
         "return" => parse_return(ir_text, context)?,
         "constant" => parse_constant_node(ir_text, context)?,
         "dynamic_constant" => parse_dynamic_constant_node(ir_text, context)?,
+        "projection" => parse_projection(ir_text, context)?,
         // Unary and binary ops are spelled out in the textual format, but we
         // parse them into Unary or Binary node kinds.
         "not" => parse_unary(ir_text, context, UnaryOperator::Not)?,
@@ -619,24 +620,18 @@ fn parse_index<'a>(
                 )
             },
         ),
-        nom::combinator::map(
-            nom::sequence::tuple((
-                nom::character::complete::multispace0,
-                nom::bytes::complete::tag("control"),
-                nom::character::complete::multispace0,
-                nom::character::complete::char('('),
-                nom::character::complete::multispace0,
-                |x| parse_prim::<usize>(x, "1234567890"),
-                nom::character::complete::multispace0,
-                nom::character::complete::char(')'),
-                nom::character::complete::multispace0,
-            )),
-            |(_, _, _, _, _, x, _, _, _)| Index::Control(x),
-        ),
     ))(ir_text)?;
     Ok((ir_text, idx))
 }
 
+fn parse_projection<'a>(ir_text: &'a str, context: &RefCell<Context<'a>>,
+) -> nom::IResult<&'a str, Node> {
+    let parse_usize = |x| parse_prim::<usize>(x, "1234567890");
+    let (ir_text, (control, index)) = parse_tuple2(parse_identifier, parse_usize)(ir_text)?;
+    let control = context.borrow_mut().get_node_id(control);
+    Ok((ir_text, Node::Projection { control, selection: index }))
+}
+
 fn parse_read<'a>(ir_text: &'a str, context: &RefCell<Context<'a>>) -> nom::IResult<&'a str, Node> {
     let ir_text = nom::character::complete::multispace0(ir_text)?.0;
     let ir_text = nom::character::complete::char('(')(ir_text)?.0;
diff --git a/hercules_ir/src/typecheck.rs b/hercules_ir/src/typecheck.rs
index 3529c47b7034e4cd098214d1a48eb896da3529b9..f2e4dbaa9f85123eecaa7972181ea775fab2c74b 100644
--- a/hercules_ir/src/typecheck.rs
+++ b/hercules_ir/src/typecheck.rs
@@ -824,7 +824,6 @@ fn typeflow(
                             }
                             collect_id = *elem_ty_id;
                         }
-                        (Type::Control(_), Index::Control(_)) => {}
                         _ => {
                             return Error(String::from(
                                 "Read node has mismatched input type and indices.",
@@ -870,7 +869,6 @@ fn typeflow(
                             }
                             collect_id = *elem_ty_id;
                         }
-                        (Type::Control(_), Index::Control(_)) => {}
                         _ => {
                             return Error(String::from(
                                 "Write node has mismatched input type and indices.",
@@ -920,6 +918,10 @@ fn typeflow(
                 TypeSemilattice::Error(msg) => TypeSemilattice::Error(msg),
             }
         }
+        Node::Projection { control: _, selection: _ } => {
+            // Type is the type of the _if node
+            inputs[0].clone()
+        },
     }
 }
 
diff --git a/hercules_ir/src/verify.rs b/hercules_ir/src/verify.rs
index 1454a64b98fb50190f4107ed4efe2e6b9a4d0298..815e735606da3074f832786487a3eb49c064cedc 100644
--- a/hercules_ir/src/verify.rs
+++ b/hercules_ir/src/verify.rs
@@ -127,7 +127,7 @@ fn verify_structure(
                         | Node::Constant { id: _ }
                         | Node::DynamicConstant { id: _ } => {}
                         _ => {
-                            if function.nodes[user.idx()].is_strictly_control() {
+                            if function.nodes[user.idx()].is_control() {
                                 if found_control {
                                     Err("A start node must have exactly one control user.")?;
                                 } else {
@@ -154,7 +154,7 @@ fn verify_structure(
                             data: _,
                         } => {}
                         _ => {
-                            if function.nodes[user.idx()].is_strictly_control() {
+                            if function.nodes[user.idx()].is_control() {
                                 if found_control {
                                     Err("A region node must have exactly one control user.")?;
                                 } else {
@@ -181,7 +181,7 @@ fn verify_structure(
                     match function.nodes[user.idx()] {
                         Node::ThreadID { control: _ } => {}
                         _ => {
-                            if function.nodes[user.idx()].is_strictly_control() {
+                            if function.nodes[user.idx()].is_control() {
                                 if found_control {
                                     Err("A fork node must have exactly one control user.")?;
                                 } else {
@@ -209,7 +209,7 @@ fn verify_structure(
                             reduct: _,
                         } => {}
                         _ => {
-                            if function.nodes[user.idx()].is_strictly_control() {
+                            if function.nodes[user.idx()].is_control() {
                                 if found_control {
                                     Err("A join node must have exactly one control user.")?;
                                 } else {
@@ -235,23 +235,21 @@ fn verify_structure(
                     Err(format!("If node must have 2 users, not {}.", users.len()))?;
                 }
                 if let (
-                    Node::Read {
-                        collect: _,
-                        indices: indices1,
+                    Node::Projection { 
+                        control: _, 
+                        selection: result1,
                     },
-                    Node::Read {
-                        collect: _,
-                        indices: indices2,
+                    Node::Projection { 
+                        control: _, 
+                        selection: result2,
                     },
                 ) = (
                     &function.nodes[users[0].idx()],
                     &function.nodes[users[1].idx()],
                 ) {
-                    if indices1.len() != 1
-                        || indices2.len() != 1
-                        || !((indices1[0] == Index::Control(0) && indices2[0] == Index::Control(1))
-                            || (indices1[0] == Index::Control(1)
-                                && indices2[0] == Index::Control(0)))
+
+                    if 
+                        !((*result1 == 0 && *result2 == 1) || (*result2 == 0 && *result1 == 1))
                     {
                         Err("If node's user Read nodes must reference different elements of output product.")?;
                     }
@@ -315,21 +313,13 @@ fn verify_structure(
                     }
                     let mut users_covered = bitvec![u8, Lsb0; 0; users.len()];
                     for user in users {
-                        if let Node::Read {
-                            collect: _,
-                            ref indices,
+                        if let Node::Projection {
+                            control: _,
+                            ref selection,
                         } = function.nodes[user.idx()]
                         {
-                            if indices.len() != 1 {
-                                Err("Match node's user Read nodes must have a single index.")?;
-                            }
-                            let index = if let Index::Control(index) = indices[0] {
-                                index
-                            } else {
-                                Err("Match node's user Read node must use a control index.")?
-                            };
-                            assert!(index < users.len(), "Read child of match node reads from bad index, but ran after typecheck succeeded.");
-                            users_covered.set(index, true);
+                            assert!(*selection < users.len(), "Read child of match node reads from bad index, but ran after typecheck succeeded.");
+                            users_covered.set(*selection, true);
                         }
                     }
                     if users_covered.count_ones() != users.len() {
diff --git a/hercules_opt/Cargo.toml b/hercules_opt/Cargo.toml
index 6da77f447a0d1a2a1336eef499a57e6dd1d57534..1a903d6d63431d73f75b6494d13d43bd32aa7414 100644
--- a/hercules_opt/Cargo.toml
+++ b/hercules_opt/Cargo.toml
@@ -10,4 +10,3 @@ take_mut = "*"
 postcard = { version = "*", features = ["alloc"] }
 serde = { version = "*", features = ["derive"] }
 hercules_ir = { path = "../hercules_ir" }
-hercules_cg = { path = "../hercules_cg" }
diff --git a/hercules_opt/src/ccp.rs b/hercules_opt/src/ccp.rs
index a7bc60bf5fa94cb98a7b704a7bb4c5560f2c92a3..402e24757a014e5ace86ec6414f32d9fc7eacf23 100644
--- a/hercules_opt/src/ccp.rs
+++ b/hercules_opt/src/ccp.rs
@@ -732,22 +732,28 @@ fn ccp_flow_function(
             }),
             constant: ConstantLattice::bottom(),
         },
-        // Read handles reachability when following an if or match.
-        Node::Read { collect, indices } => match &function.nodes[collect.idx()] {
+        Node::Read { collect, indices: _ } => {
+            CCPLattice {
+                reachability: inputs[collect.idx()].reachability.clone(),
+                constant: ConstantLattice::bottom(),
+            }
+        }
+        // Projection handles reachability when following an if or match.
+        Node::Projection { control, selection } => match &function.nodes[control.idx()] {
             Node::If { control: _, cond } => {
                 let cond_constant = &inputs[cond.idx()].constant;
-                let if_reachability = &inputs[collect.idx()].reachability;
-                let if_constant = &inputs[collect.idx()].constant;
+                let if_reachability = &inputs[control.idx()].reachability;
+                let if_constant = &inputs[control.idx()].constant;
 
                 let new_reachability = if cond_constant.is_top() {
                     ReachabilityLattice::top()
                 } else if let ConstantLattice::Constant(cons) = cond_constant {
                     if let Constant::Boolean(val) = cons {
-                        if *val && indices[0] == Index::Control(0) {
+                        if *val && *selection == 0 {
                             // If condition is true and this is the false
                             // branch, then unreachable.
                             ReachabilityLattice::top()
-                        } else if !val && indices[0] == Index::Control(1) {
+                        } else if !val && *selection == 1 {
                             // If condition is true and this is the true branch,
                             // then unreachable.
                             ReachabilityLattice::top()
@@ -768,14 +774,14 @@ fn ccp_flow_function(
             }
             Node::Match { control: _, sum } => {
                 let sum_constant = &inputs[sum.idx()].constant;
-                let if_reachability = &inputs[collect.idx()].reachability;
-                let if_constant = &inputs[collect.idx()].constant;
+                let if_reachability = &inputs[control.idx()].reachability;
+                let if_constant = &inputs[control.idx()].constant;
 
                 let new_reachability = if sum_constant.is_top() {
                     ReachabilityLattice::top()
                 } else if let ConstantLattice::Constant(cons) = sum_constant {
                     if let Constant::Summation(_, variant, _) = cons {
-                        if Index::Control(*variant as usize) != indices[0] {
+                        if *variant as usize != *selection {
                             // If match variant is not the same as this branch,
                             // then unreachable.
                             ReachabilityLattice::top()
@@ -783,7 +789,7 @@ fn ccp_flow_function(
                             if_reachability.clone()
                         }
                     } else {
-                        panic!("Attempted to interpret Read node, where corresponding match node has a non-summation constant input. Did typechecking succeed?")
+                        panic!("Attempted to interpret projection node, where corresponding match node has a non-summation constant input. Did typechecking succeed?")
                     }
                 } else {
                     if_reachability.clone()
@@ -794,10 +800,7 @@ fn ccp_flow_function(
                     constant: if_constant.clone(),
                 }
             }
-            _ => CCPLattice {
-                reachability: inputs[collect.idx()].reachability.clone(),
-                constant: ConstantLattice::bottom(),
-            },
+            _ => panic!("Projection predecessor can only be an if or match node."),
         },
         // Write is uninterpreted for now.
         Node::Write {
diff --git a/hercules_opt/src/fork_guard_elim.rs b/hercules_opt/src/fork_guard_elim.rs
index 0ff3f785ed82da60316da94d2a63a3ffcf06dfd3..a996e67c593b9c470b7f64d9f684046ed0900852 100644
--- a/hercules_opt/src/fork_guard_elim.rs
+++ b/hercules_opt/src/fork_guard_elim.rs
@@ -42,19 +42,18 @@ fn guarded_fork(function: &Function,
     // Identify fork nodes
     let Node::Fork { control, factor } = node else { return None; };
     // Whose predecessor is a read from an if
-    let Node::Read { collect : if_node, ref indices }
+    let Node::Projection { control : if_node, ref selection }
         = function.nodes[control.idx()] else { return None; };
-    if indices.len() != 1 { return None; }
-    let Index::Control(branchIdx) = indices[0] else { return None; };
     let Node::If { control : if_pred, cond } = function.nodes[if_node.idx()]
         else { return None; };
 
     // Whose condition is appropriate
     let Node::Binary { left, right, op } = function.nodes[cond.idx()]
         else { return None; };
+    let branch_idx = *selection;
     // branchIdx == 1 means the true branch so we want the condition to be
     // 0 < n or n > 0
-    if branchIdx == 1
+    if branch_idx == 1
     && !((op == BinaryOperator::LT && function.nodes[left.idx()].is_zero_constant(constants) 
                 && function.nodes[right.idx()].try_dynamic_constant() == Some(*factor))
       || (op == BinaryOperator::GT && function.nodes[right.idx()].is_zero_constant(constants)
@@ -63,7 +62,7 @@ fn guarded_fork(function: &Function,
     }
     // branchIdx == 0 means the false branch so we want the condition to be
     // n < 0 or 0 > n
-    if branchIdx == 0
+    if branch_idx == 0
     && !((op == BinaryOperator::LT && function.nodes[left.idx()].try_dynamic_constant() == Some(*factor)
                 && function.nodes[right.idx()].is_zero_constant(constants))
        || (op == BinaryOperator::GT && function.nodes[right.idx()].try_dynamic_constant() == Some(*factor)
@@ -97,13 +96,12 @@ fn guarded_fork(function: &Function,
             return None;
         };
     // Other predecessor needs to be the other read from the guard's if
-    let Node::Read { collect : read_control, ref indices }
+    let Node::Projection { control : if_node2, ref selection }
         = function.nodes[other_pred.idx()]
         else { return None; };
-    if indices.len() != 1 { return None; }
-    let Index::Control(elseBranch) = indices[0] else { return None; };
-    if elseBranch == branchIdx { return None; }
-    if read_control != if_node { return None; }
+    let else_branch = *selection;
+    if else_branch == branch_idx { return None; }
+    if if_node2 != if_node { return None; }
 
     // Finally, identify the phi nodes associated with the region and match
     // them with the reduce nodes of the fork-join
diff --git a/hercules_opt/src/forkify.rs b/hercules_opt/src/forkify.rs
index 32e812a99eb394a78abe987225c46dd74ad78dd9..1b9e4d5b5b53aff5a0821a4a409c43b86b89de46 100644
--- a/hercules_opt/src/forkify.rs
+++ b/hercules_opt/src/forkify.rs
@@ -40,7 +40,7 @@ pub fn forkify(
             }
 
             // Check for a very particular loop indexing structure.
-            let if_ctrl = function.nodes[single_pred_loop.idx()].try_control_read(1)?;
+            let if_ctrl = function.nodes[single_pred_loop.idx()].try_projection(1)?;
             let (_, if_cond) = function.nodes[if_ctrl.idx()].try_if()?;
             let (idx, bound) = function.nodes[if_cond.idx()].try_binary(BinaryOperator::LT)?;
             let (phi, one) = function.nodes[idx.idx()].try_binary(BinaryOperator::Add)?;
@@ -123,13 +123,13 @@ pub fn forkify(
             .next()
             .unwrap();
         let loop_end = function.nodes[loop_true_read.idx()]
-            .try_control_read(1)
+            .try_projection(1)
             .unwrap();
         let loop_false_read = *def_use
             .get_users(loop_end)
             .iter()
             .filter_map(|id| {
-                if function.nodes[id.idx()].try_control_read(0).is_some() {
+                if function.nodes[id.idx()].try_projection(0).is_some() {
                     Some(id)
                 } else {
                     None
diff --git a/hercules_opt/src/pass.rs b/hercules_opt/src/pass.rs
index f2de27419dd81efafa03ea8388290c5844de03e0..58342c5e6107df626cca8f5f240f4d7e76808c05 100644
--- a/hercules_opt/src/pass.rs
+++ b/hercules_opt/src/pass.rs
@@ -1,4 +1,4 @@
-extern crate hercules_cg;
+// extern crate hercules_cg;
 extern crate hercules_ir;
 extern crate postcard;
 extern crate serde;
@@ -12,7 +12,7 @@ use std::process::*;
 
 use self::serde::Deserialize;
 
-use self::hercules_cg::*;
+// use self::hercules_cg::*;
 use self::hercules_ir::*;
 
 use crate::*;
@@ -434,6 +434,7 @@ impl PassManager {
                     continue;
                 }
                 Pass::Codegen(output_file_name) => {
+                    /* 
                     self.make_def_uses();
                     self.make_reverse_postorders();
                     self.make_typing();
@@ -485,8 +486,8 @@ impl PassManager {
                     file.write_all(&hbin_contents)
                         .expect("PANIC: Unable to write output file contents.");
 
-                    // Codegen doesn't require clearing analysis results.
-                    continue;
+                    // Codegen doesn't require clearing analysis results.*/
+                    continue; 
                 }
             }
 
diff --git a/hercules_opt/src/pred.rs b/hercules_opt/src/pred.rs
index 0bd7171def8fbf2e55be16fb1ddc1fc929e3f971..f8478dedc20c4dba3e7a3ee71816ba0675a3125b 100644
--- a/hercules_opt/src/pred.rs
+++ b/hercules_opt/src/pred.rs
@@ -104,16 +104,14 @@ pub fn predication(
                 }
                 // Introduce condition variables into sets, as this is where
                 // branching occurs.
-                Node::Read {
-                    collect,
-                    ref indices,
+                Node::Projection {
+                    control,
+                    ref selection,
                 } => {
-                    assert_eq!(indices.len(), 1);
-                    let truth_value = indices[0].try_control().unwrap();
-                    assert!(truth_value < 2);
-                    let mut sets = condition_valuations[&collect].clone();
-                    let condition = function.nodes[collect.idx()].try_if().unwrap().1;
-                    if truth_value == 0 {
+                    assert!(*selection < 2);
+                    let mut sets = condition_valuations[&control].clone();
+                    let condition = function.nodes[control.idx()].try_if().unwrap().1;
+                    if *selection == 0 {
                         sets.0.insert(condition);
                     } else {
                         sets.1.insert(condition);
diff --git a/hercules_samples/ccp_example.hir b/hercules_samples/ccp_example.hir
index 4a255f0df369a49ae1210dd1d8c4d72813d44af6..25b7379e19f488c2d5ecf8cb377161758c62bb98 100644
--- a/hercules_samples/ccp_example.hir
+++ b/hercules_samples/ccp_example.hir
@@ -6,14 +6,14 @@ fn tricky(x: i32) -> i32
   val = phi(loop, one, later_val)
   b = ne(one, val)
   if1 = if(loop, b)
-  if1_false = read(if1, control(0))
-  if1_true = read(if1, control(1))
+  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 = read(if2, control(0))
-  if2_true = read(if2, control(1))
+  if2_false = projection(if2, 0)
+  if2_true = projection(if2, 1)
   r = return(if2_false, later_val)
diff --git a/hercules_samples/simple2.hir b/hercules_samples/simple2.hir
index 31962317784c650156adf6ec1a8ac53123da0be8..af5ac284b0715a0d10de73571575fd8e2b8a1ac5 100644
--- a/hercules_samples/simple2.hir
+++ b/hercules_samples/simple2.hir
@@ -8,6 +8,6 @@ fn simple2(x: i32) -> i32
   fac_acc = mul(fac, idx_inc)
   in_bounds = lt(idx_inc, x)
   if = if(loop, in_bounds)
-  if_false = read(if, control(0))
-  if_true = read(if, control(1))
+  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_samples/strset.hir b/hercules_samples/strset.hir
index 2e3ba49225c314a5803e3e678dad2596253e6064..4c8b32eeb1ab3875e053818ec7745e74f7b502f1 100644
--- a/hercules_samples/strset.hir
+++ b/hercules_samples/strset.hir
@@ -12,6 +12,6 @@ fn strset<1>(str: array(u8, #0), byte: u8) -> array(u8, #0)
   continue = ne(read, byte)
   if_cond = and(continue, in_bounds)
   if = if(loop, if_cond)
-  if_false = read(if, control(0))
-  if_true = read(if, control(1))
+  if_false = projection(if, 0)
+  if_true = projection(if, 1)
   r = return(if_false, str_inc)
diff --git a/hercules_samples/sum_sample.hir b/hercules_samples/sum_sample.hir
index 2ff76749077ba5499b7eb94a747e6ffd25f73b32..94342e0dfd4c1c49ef9fdc4bb89205ad39e8f63f 100644
--- a/hercules_samples/sum_sample.hir
+++ b/hercules_samples/sum_sample.hir
@@ -11,8 +11,8 @@ fn sum<1>(a: array(f32, #0)) -> f32
   red_add = add(red, read)
   in_bounds = lt(idx_inc, bound)
   if = if(loop, in_bounds)
-  if_false = read(if, control(0))
-  if_true = read(if, control(1))
+  if_false = projection(if, 0)
+  if_true = projection(if, 1)
   r = return(if_false, red_add)
 
 fn alt_sum<1>(a: array(f32, #0)) -> f32
@@ -28,8 +28,8 @@ fn alt_sum<1>(a: array(f32, #0)) -> f32
   rem = rem(idx, two_idx)
   odd = eq(rem, one_idx)
   negate_if = if(loop, odd)
-  negate_if_false = read(negate_if, control(0))
-  negate_if_true = read(negate_if, control(1))
+  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)
@@ -37,6 +37,6 @@ fn alt_sum<1>(a: array(f32, #0)) -> f32
   red_add = add(red, read_phi)
   in_bounds = lt(idx_inc, bound)
   if = if(negate_bottom, in_bounds)
-  if_false = read(if, control(0))
-  if_true = read(if, control(1))
+  if_false = projection(if, 0)
+  if_true = projection(if, 1)
   r = return(if_false, red_add)
diff --git a/juno_frontend/src/ssa.rs b/juno_frontend/src/ssa.rs
index 0492fe4dde4c378c9b6eff55bb863995b23d31ff..924061ec6ef9b13192ba066f723d24525634aa99 100644
--- a/juno_frontend/src/ssa.rs
+++ b/juno_frontend/src/ssa.rs
@@ -42,12 +42,10 @@ impl SSA {
         let right_proj = right_builder.id();
 
         // True branch
-        let proj_left = builder.create_control_index(1);
-        left_builder.build_read(if_builder.id(), vec![proj_left].into());
+        left_builder.build_projection(if_builder.id(), 1);
 
         // False branch
-        let proj_right = builder.create_control_index(0);
-        right_builder.build_read(if_builder.id(), vec![proj_right].into());
+        right_builder.build_projection(if_builder.id(), 0);
 
         let _ = builder.add_node(left_builder);
         let _ = builder.add_node(right_builder);