diff --git a/bootstrap/main.cpp b/bootstrap/main.cpp
index 00589ed..3c20e8e 100644
--- a/bootstrap/main.cpp
+++ b/bootstrap/main.cpp
@@ -1478,9 +1478,9 @@ struct NodeType
         INVALID,
         BOTTOM,
         TOP,
-        CONTROL,
+        LIVE_CONTROL,
+        DEAD_CONTROL,
         INTEGER,
-        VOID,
         MULTIVALUE,
         MEMORY,
         POINTER,
@@ -1497,7 +1497,7 @@ struct NodeType
         {
             u64 constant;
             u8 is_constant;
-        } integer;
+        } constant;
         struct
         {
             Slice<NodeType> types;
@@ -1512,7 +1512,8 @@ struct NodeType
                 trap();
             case Id::BOTTOM:
             case Id::TOP:
-            case Id::CONTROL:
+            case Id::LIVE_CONTROL:
+            case Id::DEAD_CONTROL:
                 return 1;
             default:
                 return 0;
@@ -1529,7 +1530,10 @@ struct NodeType
         switch (id)
         {
             case NodeType::Id::INTEGER:
-                return (payload.integer.is_constant == other.payload.integer.is_constant) & (payload.integer.constant == other.payload.integer.constant);
+                return (payload.constant.is_constant == other.payload.constant.is_constant) & (payload.constant.constant == other.payload.constant.constant);
+            case NodeType::Id::LIVE_CONTROL:
+            case NodeType::Id::DEAD_CONTROL:
+                return 1;
             default:
                 trap();
         }
@@ -1539,11 +1543,12 @@ struct NodeType
     {
         switch (id)
         {
-            case Id::VOID:
-                trap();
+            case Id::DEAD_CONTROL:
+            case Id::TOP:
+                return 1;
             case Id::INTEGER:
-                return payload.integer.is_constant;
-            case Id::CONTROL:
+                return payload.constant.is_constant;
+            case Id::LIVE_CONTROL:
             case Id::MULTIVALUE:
             case Id::BOTTOM:
                 return 0;
@@ -1557,22 +1562,67 @@ struct NodeType
 
     method NodeType meet(NodeType other)
     {
-        unused(other);
+        if (equal(other))
+        {
+            return *this;
+        }
 
+        if (id == other.id)
+        {
+            return x_meet(other);
+        }
+        
+        if (is_simple())
+        {
+            return x_meet(other);
+        }
+
+        if (other.is_simple())
+        {
+            return other.x_meet(*this);
+        }
+
+        return { .id = NodeType::Id::BOTTOM };
+    }
+
+    method NodeType x_meet(NodeType other)
+    {
         switch (id)
         {
-            case NodeType::Id::MULTIVALUE:
-                fail();
-            case NodeType::Id::INTEGER:
+            case Id::BOTTOM:
+            case Id::TOP:
+            case Id::LIVE_CONTROL:
+            case Id::DEAD_CONTROL:
+                {
+                    assert(is_simple());
+                    if ((id == Id::BOTTOM) | (other.id == Id::TOP))
+                    {
+                        return *this;
+                    }
+
+                    if ((id == Id::TOP) | (other.id == Id::BOTTOM))
+                    {
+                        return other;
+                    }
+
+                    if (!other.is_simple())
+                    {
+                        return NodeType{ .id = NodeType::Id::BOTTOM };
+                    }
+
+                    auto new_id = ((id == Id::LIVE_CONTROL) | (other.id == Id::LIVE_CONTROL)) ? Id::LIVE_CONTROL : Id::DEAD_CONTROL;
+                    return { .id = new_id };
+                }
+            case Id::INTEGER:
                 {
                     if (equal(other))
                     {
                         return *this;
                     }
 
-                    if (other.id != NodeType::Id::INTEGER)
+                    if (other.id != Id::INTEGER)
                     {
-                        return NodeType{ .id = NodeType::Id::BOTTOM };
+                        return meet(other);
                     }
 
                     if (is_bot())
@@ -1596,37 +1646,40 @@ struct NodeType
                     }
 
                     assert(is_constant() & other.is_constant());
-                    if (payload.integer.constant == other.payload.integer.constant)
+
+                    if (payload.constant.constant == other.payload.constant.constant)
                     {
-                        trap();
+                        return *this;
                     }
                     else
                     {
-                        trap();
+                        return { .id = Id::BOTTOM };
                     }
-                } break;
+                }
+            case Id::MULTIVALUE:
+                fail();
             default:
-                return NodeType{ .id = NodeType::Id::BOTTOM };
+                trap();
         }
     }
 
     method u8 is_bot()
     {
         assert(id == Id::INTEGER);
-        return !payload.integer.is_constant & (payload.integer.constant == 1);
+        return !payload.constant.is_constant & (payload.constant.constant == 1);
     }
 
     method u8 is_top()
     {
         assert(id == Id::INTEGER);
-        return !payload.integer.is_constant & (payload.integer.constant == 0);
+        return !payload.constant.is_constant & (payload.constant.constant == 0);
     }
 };
 
 may_be_unused global auto constexpr integer_top = NodeType{
     .id = NodeType::Id::INTEGER,
     .payload = {
-        .integer = {
+        .constant = {
             .constant = 0,
             .is_constant = 0,
         },
@@ -1636,7 +1689,7 @@ may_be_unused global auto constexpr integer_top = NodeType{
 may_be_unused global auto constexpr integer_bot = NodeType{
     .id = NodeType::Id::INTEGER,
     .payload = {
-        .integer = {
+        .constant = {
             .constant = 1,
             .is_constant = 0,
         },
@@ -1646,23 +1699,65 @@ may_be_unused global auto constexpr integer_bot = NodeType{
 may_be_unused global auto constexpr integer_zero = NodeType{
     .id = NodeType::Id::INTEGER,
     .payload = {
-        .integer = {
+        .constant = {
             .constant = 0,
             .is_constant = 1,
         },
     },
 };
 
-global NodeType type_if_types[2] = {
-    { .id = NodeType::Id::CONTROL },
-    { .id = NodeType::Id::CONTROL },
+global NodeType if_both_types[2] = {
+    { .id = NodeType::Id::LIVE_CONTROL },
+    { .id = NodeType::Id::LIVE_CONTROL },
 };
 
-global auto constexpr type_if = NodeType{
+global NodeType if_neither_types[2] = {
+    { .id = NodeType::Id::DEAD_CONTROL },
+    { .id = NodeType::Id::DEAD_CONTROL },
+};
+
+global NodeType if_true_types[2] = {
+    { .id = NodeType::Id::LIVE_CONTROL },
+    { .id = NodeType::Id::DEAD_CONTROL },
+};
+
+global NodeType if_false_types[2] = {
+    { .id = NodeType::Id::DEAD_CONTROL },
+    { .id = NodeType::Id::LIVE_CONTROL },
+};
+
+global auto constexpr type_if_both = NodeType{
     .id = NodeType::Id::MULTIVALUE,
     .payload = {
         .multi = {
-            .types = array_to_slice(type_if_types),
+            .types = array_to_slice(if_both_types),
+        },
+    },
+};
+
+global auto constexpr type_if_neither = NodeType{
+    .id = NodeType::Id::MULTIVALUE,
+    .payload = {
+        .multi = {
+            .types = array_to_slice(if_neither_types),
+        },
+    },
+};
+
+global auto constexpr type_if_true = NodeType{
+    .id = NodeType::Id::MULTIVALUE,
+    .payload = {
+        .multi = {
+            .types = array_to_slice(if_true_types),
+        },
+    },
+};
+
+global auto constexpr type_if_false = NodeType{
+    .id = NodeType::Id::MULTIVALUE,
+    .payload = {
+        .multi = {
+            .types = array_to_slice(if_false_types),
         },
     },
 };
@@ -1867,6 +1962,7 @@ struct Node
         RETURN,
         IF,
         CONSTANT_INT,
+        CONSTANT_CONTROL,
         SCOPE,
         SYMBOL_FUNCTION,
         CALL,
@@ -1891,6 +1987,7 @@ struct Node
     Array<Node*> outputs;
     u32 gvn;
     Id id;
+    s32 immediate_depth = 0;
 
     union
     {
@@ -1912,9 +2009,13 @@ struct Node
         {
             String label;
         } phi;
+        struct
+        {
+            Node* immediate_dominator = 0;
+        } region;
     } payload;
 
-    u8 padding[40] = {};
+    u8 padding[32] = {};
 
     method forceinline Slice<Node*> get_inputs()
     {
@@ -1967,13 +2068,6 @@ struct Node
         return node;
     }
 
-    method u8 remove_output(Node* output)
-    {
-        s32 index = outputs.slice().find_index(output);
-        assert(index != -1);
-        outputs.remove_swap(index);
-        return outputs.length == 0;
-    }
 
     method Node* add_output(Node* output)
     {
@@ -1991,17 +2085,17 @@ struct Node
         return input;
     }
 
-    method Node* set_input(Arena* arena, s32 index, Node* input)
+    method Node* set_input(Arena* arena, s32 index, Node* new_input)
     {
         Node* old_input = inputs[index];
-        if (old_input == input)
+        if (old_input == new_input)
         {
             return this;
         }
 
-        if (input)
+        if (new_input)
         {
-            input->add_output(this);
+            new_input->add_output(this);
         }
 
         if (old_input && old_input->remove_output(this))
@@ -2009,9 +2103,30 @@ struct Node
             old_input->kill(arena);
         }
 
-        inputs[index] = input;
+        inputs[index] = new_input;
 
-        return input;
+        return new_input;
+    }
+
+    method u8 remove_output(Node* output)
+    {
+        s32 index = outputs.slice().find_index(output);
+        assert(index != -1);
+        outputs.remove_swap(index);
+        return outputs.length == 0;
+    }
+
+    method void remove_input(Arena* arena, u32 index)
+    {
+        if (Node* old_input = inputs[index])
+        {
+            if (old_input->remove_output(this))
+            {
+                old_input->kill(arena);
+            }
+        }
+
+        inputs.remove_swap(index);
     }
 
     method Node* idealize(Thread* thread, Function* function)
@@ -2027,15 +2142,72 @@ struct Node
             {
                 return 0;
             }
-        case Id::ROOT:
-        case Id::STOP:
-        case Id::PROJECTION:
-        case Id::IF:
-        case Id::RETURN:
         case Id::CONSTANT_INT:
-        case Id::INTEGER_ADD:
-        case Id::REGION:
+        case Id::CONSTANT_CONTROL:
             return 0;
+        case Id::STOP:
+            {
+                auto input_count = inputs.length;
+                for (u32 i = 0; i < inputs.length; i += 1)
+                {
+                    if (inputs[i]->type.id == NodeType::Id::DEAD_CONTROL)
+                    {
+                        remove_input(thread->arena, i);
+                        i -= 1;
+                    }
+                }
+
+                if (input_count != inputs.length)
+                {
+                    return this;
+                }
+                else
+                {
+                    return 0;
+                }
+            }
+        case Id::PROJECTION:
+            {
+                auto* control = get_control();
+                if (control->type.id == NodeType::Id::MULTIVALUE)
+                {
+                    auto control_types = control->type.payload.multi.types;
+                    auto projection_index = payload.projection.index;
+                    if (control_types[projection_index].id == NodeType::Id::DEAD_CONTROL)
+                    {
+                        trap();
+                    }
+
+                    // TODO: fix
+
+                    // auto index = 1 - projection_index;
+                    // assert(index >= 0);
+                    // assert((u32)index < control_types.length);
+                    // if (control_types[index].id == NodeType::Id::DEAD_CONTROL)
+                    // {
+                    //     trap();
+                    // }
+                }
+                
+                return 0;
+            }
+        case Id::ROOT:
+        case Id::IF:
+        case Id::INTEGER_ADD:
+            return 0;
+        case Id::REGION:
+            {
+                // Find dead input
+                for (u32 i = 1; i < inputs.length; i += 1)
+                {
+                    if (inputs[i]->type.id == NodeType::Id::DEAD_CONTROL)
+                    {
+                        trap();
+                    }
+                }
+
+                return 0;
+            }
         case Id::SCOPE:
             trap();
         // TODO:
@@ -2101,6 +2273,17 @@ struct Node
                     }
                 }
             }
+        case Id::RETURN:
+            {
+                if (get_control()->type.id == Node::Type::Id::DEAD_CONTROL)
+                {
+                    trap();
+                }
+                else
+                {
+                    return 0;
+                }
+            }
         }
     }
 
@@ -2267,6 +2450,12 @@ struct Node
         }
     }
 
+    method Node* predicate()
+    {
+        assert(id == Id::IF);
+        return inputs[1];
+    }
+
     method Node::Type compute()
     {
         switch (id)
@@ -2276,7 +2465,30 @@ struct Node
             case Node::Id::STOP:
                 return { .id = Type::Id::BOTTOM };
             case Node::Id::IF:
-                return type_if;
+                {
+                    if (get_control()->type.id != NodeType::Id::LIVE_CONTROL)
+                    {
+                        trap();
+                    }
+
+                    auto* this_predicate = predicate();
+                    if ((this_predicate->type.id == Node::Type::Id::INTEGER) & this_predicate->type.is_constant())
+                    {
+                        trap();
+                    }
+
+                    for (Node* dom = get_immediate_dominator(), *prior = this; dom; prior = dom, dom = dom->get_immediate_dominator())
+                    {
+                        if ((dom->id == Id::IF) && dom->predicate() == this_predicate)
+                        {
+                            unused(prior);
+                            trap();
+                        }
+                    }
+
+                    return type_if_both;
+                }
+                // return type_if;
             case Node::Id::INTEGER_ADD:
             case Node::Id::INTEGER_SUB:
             case Node::Id::INTEGER_COMPARE_EQUAL:
@@ -2298,35 +2510,35 @@ struct Node
                                 default:
                                     trap();
                             case Id::INTEGER_ADD:
-                                result = left_type.payload.integer.constant + right_type.payload.integer.constant;
+                                result = left_type.payload.constant.constant + right_type.payload.constant.constant;
                                 break;
                             case Id::INTEGER_SUB:
-                                result = left_type.payload.integer.constant - right_type.payload.integer.constant;
+                                result = left_type.payload.constant.constant - right_type.payload.constant.constant;
                                 break;
                             case Id::INTEGER_COMPARE_EQUAL:
-                                result = left_type.payload.integer.constant == right_type.payload.integer.constant;
+                                result = left_type.payload.constant.constant == right_type.payload.constant.constant;
                                 break;
                             case Id::INTEGER_COMPARE_NOT_EQUAL:
-                                result = left_type.payload.integer.constant != right_type.payload.integer.constant;
+                                result = left_type.payload.constant.constant != right_type.payload.constant.constant;
                                 break;
                             case Id::INTEGER_COMPARE_LESS:
-                                result = left_type.payload.integer.constant < right_type.payload.integer.constant;
+                                result = left_type.payload.constant.constant < right_type.payload.constant.constant;
                                 break;
                             case Id::INTEGER_COMPARE_LESS_EQUAL:
-                                result = left_type.payload.integer.constant <= right_type.payload.integer.constant;
+                                result = left_type.payload.constant.constant <= right_type.payload.constant.constant;
                                 break;
                             case Id::INTEGER_COMPARE_GREATER:
-                                result = left_type.payload.integer.constant > right_type.payload.integer.constant;
+                                result = left_type.payload.constant.constant > right_type.payload.constant.constant;
                                 break;
                             case Id::INTEGER_COMPARE_GREATER_EQUAL:
-                                result = left_type.payload.integer.constant >= right_type.payload.integer.constant;
+                                result = left_type.payload.constant.constant >= right_type.payload.constant.constant;
                                 break;
                             }
 
                             return Node::Type{
                                 .id = Node::Type::Id::INTEGER,
                                 .payload = {
-                                    .integer = {
+                                    .constant = {
                                         .constant = result,
                                         // .bit_count = left_type.payload.integer.bit_count,
                                         .is_constant = 1,
@@ -2345,6 +2557,7 @@ struct Node
                     }
                 }
             case Node::Id::CONSTANT_INT:
+            case Node::Id::CONSTANT_CONTROL:
                 return type;
             case Node::Id::PROJECTION:
                 {
@@ -2381,7 +2594,15 @@ struct Node
                     };
                 }
             case Node::Id::REGION:
-                return { .id = Type::Id::CONTROL };
+                {
+                    Type ty =  { .id = Type::Id::DEAD_CONTROL };
+                    for (u32 i = 1; i < inputs.length; i += 1)
+                    {
+                        ty = ty.meet(inputs[i]->type);
+                    }
+
+                    return ty;
+                }
             case Node::Id::PHI:
                 return { .id = Type::Id::BOTTOM };
             default:
@@ -2389,6 +2610,22 @@ struct Node
         }
     }
 
+    method u8 is_associative()
+    {
+        switch (id)
+        {
+            case Id::INTEGER_ADD:
+                return 1;
+            default:
+                return 0;
+        }
+    }
+    method Node* associative_phi_constant(u8 should_rotate)
+    {
+        assert(is_associative());
+        trap();
+    }
+
     method Node* project(Thread* thread, Node* control, s32 index, String label)
     {
         assert(type.id == Node::Type::Id::MULTIVALUE);
@@ -2424,12 +2661,11 @@ struct Node
             trap();
         case NodeType::Id::TOP:
             trap();
-        case NodeType::Id::CONTROL:
+        case NodeType::Id::LIVE_CONTROL:
+        case NodeType::Id::DEAD_CONTROL:
             trap();
         case NodeType::Id::INTEGER:
             return unit->get_integer_type(32, 1);
-        case NodeType::Id::VOID:
-            trap();
         case NodeType::Id::MULTIVALUE:
             trap();
         case NodeType::Id::MEMORY:
@@ -2448,11 +2684,108 @@ struct Node
         switch (id)
         {
             case Node::Id::SCOPE:
+            case Node::Id::RETURN:
+            case Node::Id::IF:
+            case Node::Id::PROJECTION:
                 return inputs[0];
             default:
                 trap();
         }
     }
+
+    method Node* swap_inputs_1_2()
+    {
+        Node* temporal = inputs[1];
+        inputs[1] = inputs[2];
+        inputs[2] = temporal;
+        return this;
+    }
+
+    method u8 all_constants()
+    {
+        for (u32 i = 0; i < inputs.length; i += 1)
+        {
+            if (!inputs[i]->type.is_constant())
+            {
+                return 0;
+            }
+        }
+
+        return 1;
+    }
+
+    method Node* get_immediate_dominator()
+    {
+        switch (id)
+        {
+            case Id::ROOT:
+                return 0;
+            case Id::REGION:
+                if (payload.region.immediate_dominator)
+                {
+                    return payload.region.immediate_dominator;
+                }
+                else
+                {
+                    if (inputs.length == 3)
+                    {
+                        Node* left = inputs[1]->get_immediate_dominator();
+                        Node* right = inputs[2]->get_immediate_dominator();
+
+                        while (left != right)
+                        {
+                            if (!left || !right)
+                            {
+                                return 0;
+                            }
+                            else
+                            {
+                                auto comp = left->immediate_depth - right->immediate_depth;
+
+                                if (comp >= 0)
+                                {
+                                    left = left->get_immediate_dominator();
+                                }
+
+                                if (comp <= 0)
+                                {
+                                    right = right->get_immediate_dominator();
+                                }
+                            }
+                        }
+
+                        if (left)
+                        {
+                            immediate_depth = left->immediate_depth + 1;
+                            payload.region.immediate_dominator = left;
+                            return left;
+                        }
+                        else
+                        {
+                            return 0;
+                        }
+                    }
+                    else
+                    {
+                        trap();
+                    }
+                }
+            default:
+                {
+                    Node* result = inputs[0];
+                    if (result->immediate_depth == 0)
+                    {
+                        result->get_immediate_dominator();
+                    }
+                    if (immediate_depth == 0)
+                    {
+                        immediate_depth = result->immediate_depth + 1;
+                    }
+
+                    return result;
+                }
+        }
+    }
 };
 
 static_assert(sizeof(Node) == 128);
@@ -2465,7 +2798,7 @@ static_assert(page_size % sizeof(Node) == 0);
         {
             .id = Node::Type::Id::INTEGER,
             .payload = {
-                .integer = {
+                .constant = {
                     .constant = data.value,
                     // .bit_count = data.bit_count,
                     .is_constant = 1,
@@ -3027,7 +3360,13 @@ struct Analyzer
 
         auto* node = function->stop_node->add_input(return_node);
 
-        kill_control(thread->arena);
+        // Kill control
+        auto* dead_control = Node::add(thread, {
+            .type = { .id = Node::Type::Id::DEAD_CONTROL },
+            .inputs = { .pointer = &function->root_node, .length = 1 },
+            .id = Node::Id::CONSTANT_CONTROL,
+        })->peephole(thread, function);
+        set_control(thread->arena, dead_control);
 
         return node;
     }
@@ -3808,6 +4147,7 @@ fn Node* analyze_statement(Analyzer* analyzer, Parser* parser, Unit* unit, Threa
                     })->keep()->peephole(thread, function);
 
             Node* if_true = if_node->project(thread, if_node, 0, strlit("True"))->peephole(thread, function);
+            if_node->unkeep();
             Node* if_false = if_node->project(thread, if_node, 1, strlit("False"))->peephole(thread, function);
 
             u32 original_input_count = analyzer->scope->inputs.length;
@@ -4549,7 +4889,7 @@ fn Node* analyze_function(Parser* parser, Thread* thread, Unit* unit, File* file
 
                 Array<Node::Type> abi_argument_types = {};
                 Array<Node::Type> root_arg_types = {};
-                root_arg_types.append_one({ .id = Node::Type::Id::CONTROL });
+                root_arg_types.append_one({ .id = Node::Type::Id::LIVE_CONTROL });
 
                 for (u32 i = 0; i < argument_type_abis.length; i += 1)
                 {
@@ -4642,7 +4982,6 @@ fn Node* analyze_function(Parser* parser, Thread* thread, Unit* unit, File* file
                     case ABI_INFO_DIRECT:
                         {
                             auto* argument_node = function->root_node->project(thread, function->root_node, next_index, argument_name)->peephole(thread, function);
-                            assert(argument_node->type.id != Node::Type::Id::CONTROL);
                             define_variable(&analyzer, argument_name, argument_node);
                             next_index += 1;
                         } break;