private CFGNode ProcessCFGNode(
            CFGNode calleeNode, CFGNode callerNode,
            CFGNode callerSuccessorNode,
            IExpression thisReplacement,
            IExpression returnValueTarget,
            MethodCompileInfo calleeMethodCompileInfo,
            MethodCompileInfo callerMethodCompileInfo,
            Dictionary<string, VariableDefinition> renamedLocalsMap,
            Dictionary<string, VariableReference> variableReplacements,
            Dictionary<string, IExpression> argumentReplacements,
            Dictionary<CFGNode, CFGNode> processedNodes,
            CFGNodeSet newNodes
            )
        {
            CFGNode inlinedNode;
            if (processedNodes.TryGetValue(calleeNode, out inlinedNode))
            {
                // we've already seen this node
                // don't inline it again otherwise we'll end up in an infinite recursion
                return inlinedNode;
            }

            CFG cfg = callerNode.Graph;
            inlinedNode = cfg.AddNode();
            inlinedNode.FlowControl = calleeNode.FlowControl;
            inlinedNode.BasicBlock = CodeUtility.CloneCode<BasicBlock>(calleeNode.BasicBlock);
            processedNodes[calleeNode] = inlinedNode;
            newNodes.Add(inlinedNode);
            Debug.Assert(
                inlinedNode.BasicBlock.Statements.Count == calleeNode.BasicBlock.Statements.Count,
                string.Format("Clone code produced {0} statements instead of {1}", inlinedNode.BasicBlock.Statements.Count, calleeNode.BasicBlock.Statements.Count)
            );

            // rename all variables in callee so they don't clash with variables in caller
            CodeUtility.MatchAndReplaceCode<IVariableReferenceExpression>(
                inlinedNode.BasicBlock,
                delegate(IVariableReferenceExpression expression)
                {
                    return true;
                },
                delegate(IVariableReferenceExpression expression)
                {
                    VariableReference variableReplacement;
                    if (!variableReplacements.TryGetValue(expression.Variable.Name, out variableReplacement))
                    {
                        if (callerMethodCompileInfo.IsTemporary(expression.Variable))
                        {
                            variableReplacement = callerMethodCompileInfo.NewTemporary(expression.Variable.VariableType);
                        }
                        else if (callerMethodCompileInfo.IsFrozen(expression.Variable))
                        {
                            variableReplacement = callerMethodCompileInfo.NewFrozen(expression.Variable.VariableType);
                        }
                        else
                        {
                            VariableDefinition variableDefinition;
                            if (!renamedLocalsMap.TryGetValue(expression.Variable.Name, out variableDefinition))
                            {
                                int index = callerMethodCompileInfo.Method.Body.Variables.Count;
                                string name = "V_" + index; // TODO: Extract into constant and helper method
                                TypeReference type = expression.Variable.VariableType;
                                variableDefinition = new VariableDefinition(name, index, callerMethodCompileInfo.Method, type);
                                callerMethodCompileInfo.Method.Body.Variables.Add(variableDefinition);
                                renamedLocalsMap[expression.Variable.Name] = variableDefinition;
                            }
                            variableReplacement = variableDefinition;
                        }
                        variableReplacements[expression.Variable.Name] = variableReplacement;
                    }
                    expression.Variable = variableReplacement;
                    return expression;
                },
                true
            );

            // replace all instances of "this" in callee with the target object
            if (thisReplacement != null)
            {
                CodeUtility.MatchAndReplaceCode<IExpression>(
                    inlinedNode.BasicBlock,
                    delegate(IExpression expression)
                    {
                        return expression is IThisReferenceExpression;
                    },
                    delegate(IExpression expression)
                    {
                        return thisReplacement;
                    },
                    true
                );
            }

            // replace all arguments references
            if (argumentReplacements != null)
            {
                CodeUtility.MatchAndReplaceCode<IExpression>(
                    inlinedNode.BasicBlock,
                    delegate(IExpression expression)
                    {
                        return expression is IArgumentReferenceExpression;
                    },
                    delegate(IExpression expression)
                    {
                        IArgumentReferenceExpression argumentReferenceExpression = (IArgumentReferenceExpression)expression;
                        IExpression replacementExpression;
                        if (!argumentReplacements.TryGetValue(argumentReferenceExpression.Parameter.Name, out replacementExpression))
                        {
                            throw new CompilerException(
                                string.Format(
                                    "Error while inlining call to {0}. Could not find argument replacement for parameter \"{1}\". Parameter sequence is {2}.",
                                    calleeMethodCompileInfo.Method,
                                    argumentReferenceExpression.Parameter.Name,
                                    argumentReferenceExpression.Parameter.Sequence
                                )
                            );
                        }
                        else
                        {
                            return replacementExpression;
                        }
                    },
                    true
                );
            }

            if (inlinedNode.FlowControl == FlowControl.Throw)
            {
                // TODO: fixup exception handling info
                throw new CannotInlineMethodException("Contain throw statement");
            }
            else if (inlinedNode.FlowControl == FlowControl.ConditionalBranch)
            {
                // TODO: Fix this so that conditioanl branches can be inlined
                throw new CannotInlineMethodException("Contains a conditional branch");
            }
            else if (inlinedNode.FlowControl == FlowControl.Return)
            {
                // replace return statement with assignment to variable being assigned to in caller
                Debug.Assert(inlinedNode.BasicBlock.Statements.Count == 1, string.Format("Return basic block has {0} statements", inlinedNode.BasicBlock.Statements.Count));
                Debug.Assert(inlinedNode.BasicBlock.Statements[0] is IMethodReturnStatement);
                IMethodReturnStatement methodReturnStatement = (IMethodReturnStatement)inlinedNode.BasicBlock.Statements[0];

                inlinedNode.BasicBlock.Statements.Clear();
                inlinedNode.BasicBlock.Statements.Add(new AssignStatement(returnValueTarget, methodReturnStatement.Expression));

                // add edge between this return node and the caller node's successor
                Debug.Assert(inlinedNode.SuccessorCount == 0);
                Debug.Assert(callerSuccessorNode != null);
                cfg.AddEdge(inlinedNode, callerSuccessorNode);

                // flow control is no longer return
                inlinedNode.FlowControl = FlowControl.Next;
            }

            foreach (CFGEdge outEdge in calleeNode.Graph.OutEdges(calleeNode))
            {
                CFGNode newSuccessorNode = ProcessCFGNode(
                    outEdge.Target, callerNode, callerSuccessorNode,
                    thisReplacement, returnValueTarget,
                    calleeMethodCompileInfo, callerMethodCompileInfo,
                    renamedLocalsMap,
                    variableReplacements,
                    argumentReplacements,
                    processedNodes,
                    newNodes
                );
                cfg.AddEdge(inlinedNode, newSuccessorNode, outEdge.BranchCondition);
            }

            return inlinedNode;
        }