Exemple #1
0
        private bool AssertDecodeBranchArguments(
            IEnumerable <LNode> nodes,
            Dictionary <Symbol, ValueTag> valueTags,
            out IReadOnlyList <BranchArgument> args)
        {
            var results = new List <BranchArgument>();

            foreach (var argNode in nodes)
            {
                if (!FeedbackHelpers.AssertIsId(argNode, Log))
                {
                    args = null;
                    return(false);
                }

                var name = argNode.Name;
                if (name == CodeSymbols.Result)
                {
                    results.Add(BranchArgument.TryResult);
                }
                else if (name == EncoderState.tryFlowExceptionSymbol)
                {
                    results.Add(BranchArgument.TryException);
                }
                else
                {
                    results.Add(BranchArgument.FromValue(GetValueTag(argNode.Name, valueTags)));
                }
            }
            args = results;
            return(true);
        }
        public override void ReplaceInstruction(FlowGraph graph, IReadOnlyList <ValueTag> arguments)
        {
            if (!IsValid)
            {
                throw new InvalidOperationException("Cannot replace an invalid instruction reference.");
            }

            var tryFlow = (TryFlow)Flow;

            // Include `graph` in the defining graph. Wrap exception-throwing instructions
            // in 'try' flow.
            var entryTag = Block.Graph.Include(
                graph,
                (retFlow, enclosingBlock) =>
            {
                // Rewrite 'return' flow by replacing it with a branch to the success
                // block.
                var resultTag = enclosingBlock.AppendInstruction(retFlow.ReturnValue);
                return(new JumpFlow(
                           tryFlow.SuccessBranch.MapArguments(
                               arg => arg.IsTryResult ? BranchArgument.FromValue(resultTag) : arg)));
            },
                tryFlow.ExceptionBranch);

            // Jump to `graph`'s entry point.
            Block.Flow = new JumpFlow(entryTag, arguments);

            // Invalidate the instruction reference.
            Flow = null;
        }
Exemple #3
0
        /// <summary>
        /// Replaces a block's flow with an unconditional jump to an exception
        /// branch. Replaces 'try' flow exception arguments in that exception
        /// branch with a captured exception value.
        /// </summary>
        /// <param name="block">The block to rewrite.</param>
        /// <param name="exceptionBranch">The exception branch to jump to unconditionally.</param>
        /// <param name="capturedException">
        /// The captured exception to pass instead of a 'try' flow exception argument.
        /// </param>
        private static void JumpToExceptionBranch(
            BasicBlockBuilder block,
            Branch exceptionBranch,
            ValueTag capturedException)
        {
            var branch = exceptionBranch.WithArguments(
                exceptionBranch.Arguments
                .Select(arg => arg.IsTryException ? BranchArgument.FromValue(capturedException) : arg)
                .ToArray());

            block.Flow = new JumpFlow(branch);
        }
Exemple #4
0
        private BlockFlow AsThreadableFlow(Branch branch, FlowGraphBuilder graph, HashSet <BasicBlockTag> processedBlocks)
        {
            ThreadJumps(graph.GetBasicBlock(branch.Target), processedBlocks);

            // Block arguments are a bit of an obstacle for jump threading.
            //
            //   * We can easily thread jumps to blocks that have block parameters
            //     if those parameters are never used outside of the block: in that case,
            //     we just substitute arguments for parameters.
            //
            //   * Jumps to blocks that have block parameters that are used outside
            //     of the block that defines them are trickier to handle. We'll just bail
            //     when we encounter them, because these don't occur when the control-flow
            //     graph is in register forwarding form.

            var target = graph.GetBasicBlock(branch.Target);
            var uses   = graph.GetAnalysisResult <ValueUses>();

            // Only jump and switch flow are threadable. We don't jump-thread through
            // instructions.
            // TODO: consider copying instructions to enable more aggressive jump threading.
            if ((!(target.Flow is JumpFlow) && !(target.Flow is SwitchFlow)) ||
                target.InstructionTags.Count > 0 ||
                target.ParameterTags.Any(
                    tag => uses.GetInstructionUses(tag).Count > 0 ||
                    uses.GetFlowUses(tag).Any(block => block != target.Tag)))
            {
                return(null);
            }

            var valueSubstitutionMap = new Dictionary <ValueTag, ValueTag>();
            var argSubstitutionMap   = new Dictionary <BranchArgument, BranchArgument>();

            foreach (var kvPair in branch.ZipArgumentsWithParameters(graph))
            {
                if (kvPair.Value.IsValue)
                {
                    valueSubstitutionMap[kvPair.Key] = kvPair.Value.ValueOrNull;
                }
                argSubstitutionMap[BranchArgument.FromValue(kvPair.Key)] = kvPair.Value;
            }
            return(target.Flow
                   .MapArguments(argSubstitutionMap)
                   .MapValues(valueSubstitutionMap));
        }
        /// <inheritdoc/>
        public override FlowGraph Apply(FlowGraph graph)
        {
            var builder = graph.ToBuilder();

            // Analyze all local value definitions and find imported values.
            var definitions = new Dictionary <BasicBlockTag, Dictionary <ValueTag, ValueTag> >();
            var extraArgs   = new Dictionary <BasicBlockTag, List <ValueTag> >();
            var imports     = new Dictionary <BasicBlockTag, HashSet <ValueTag> >();

            foreach (var block in builder.BasicBlocks)
            {
                var localDefs    = new Dictionary <ValueTag, ValueTag>();
                var localImports = new HashSet <ValueTag>();
                foreach (var parameter in block.ParameterTags)
                {
                    localDefs[parameter] = parameter;
                }
                foreach (var instruction in block.NamedInstructions)
                {
                    localDefs[instruction] = instruction;
                    localImports.UnionWith(instruction.Arguments);
                }
                foreach (var instruction in block.Flow.Instructions)
                {
                    localImports.UnionWith(instruction.Arguments);
                }
                foreach (var branch in block.Flow.Branches)
                {
                    foreach (var arg in branch.Arguments)
                    {
                        if (arg.IsValue)
                        {
                            localImports.Add(arg.ValueOrNull);
                        }
                    }
                }
                definitions[block] = localDefs;
                imports[block]     = localImports;
                extraArgs[block]   = new List <ValueTag>();
            }

            var predecessors = graph.GetAnalysisResult <BasicBlockPredecessors>();

            // Now import definitions until we reach a fixpoint.
            var worklist = new HashSet <BasicBlockTag>(builder.BasicBlockTags);

            while (worklist.Count > 0)
            {
                var block = builder.GetBasicBlock(worklist.First());
                worklist.Remove(block);

                var blockDefs = definitions[block];
                var blockArgs = extraArgs[block];

                var blockImports = imports[block];
                blockImports.ExceptWith(blockDefs.Keys);
                imports[block] = new HashSet <ValueTag>();

                foreach (var value in blockImports)
                {
                    var rffName = value.Name + ".rff." + block.Tag.Name;

                    NamedInstruction valueInstruction;
                    if (graph.TryGetInstruction(value, out valueInstruction) &&
                        valueInstruction.Prototype is ConstantPrototype)
                    {
                        // Create duplicate definitions of constants instead of
                        // importing them using phis.
                        blockDefs[value] = block.InsertInstruction(0, valueInstruction.Instruction, rffName);
                        continue;
                    }

                    // Import a definition by introducing a new parameter and recursively
                    // importing it the value in predecessor blocks.
                    blockDefs[value] = block.AppendParameter(
                        builder.GetValueType(value),
                        rffName);
                    blockArgs.Add(value);

                    foreach (var pred in predecessors.GetPredecessorsOf(block))
                    {
                        if (!definitions[pred].ContainsKey(value))
                        {
                            imports[pred].Add(value);
                            worklist.Add(pred);
                        }
                    }
                }
            }

            // We have introduced basic block parameters and know which values are
            // used by blocks. We finish by replacing value uses and appending
            // branch arguments.
            foreach (var block in builder.BasicBlocks)
            {
                var blockDefs = definitions[block];
                foreach (var insn in block.NamedInstructions)
                {
                    insn.Instruction = insn.Instruction.MapArguments(blockDefs);
                }
                block.Flow = block.Flow
                             .MapValues(blockDefs)
                             .MapBranches(branch =>
                {
                    var args = new List <BranchArgument>(branch.Arguments);
                    foreach (var arg in extraArgs[branch.Target])
                    {
                        args.Add(BranchArgument.FromValue(blockDefs[arg]));
                    }
                    return(branch.WithArguments(args));
                });
            }

            return(builder.ToImmutable());
        }
Exemple #6
0
        private void ThreadJumps(BasicBlockBuilder block, HashSet <BasicBlockTag> processedBlocks)
        {
            if (!processedBlocks.Add(block))
            {
                return;
            }

            var flow = block.Flow;

            if (flow is JumpFlow)
            {
                // Jump flow is usually fairly straightforward to thread.
                var jumpFlow   = (JumpFlow)flow;
                var threadFlow = AsThreadableFlow(jumpFlow.Branch, block.Graph, processedBlocks);
                if (threadFlow != null && jumpFlow.Branch.Target != block.Tag)
                {
                    block.Flow = threadFlow;

                    if (block.Flow is SwitchFlow)
                    {
                        // If the jump flow gets replaced by switch flow, then we want to
                        // jump-thread the block again.
                        processedBlocks.Remove(block);
                        ThreadJumps(block, processedBlocks);
                        return;
                    }
                }

                // Now would also be a good time to try and fuse this block with
                // its successor, if this jump is the only branch to the successor.
                var           predecessors = block.Graph.GetAnalysisResult <BasicBlockPredecessors>();
                BasicBlockTag tail;
                if (BlockFusion.TryGetFusibleTail(block, block.Graph.ToImmutable(), predecessors, out tail))
                {
                    // Fuse the blocks.
                    FuseBlocks(block, tail);

                    // Fusing these blocks may have exposed additional information
                    // that will enable us to thread more jumps.
                    processedBlocks.Remove(block);
                    ThreadJumps(block, processedBlocks);
                }
            }
            else if (flow is TryFlow)
            {
                // We might be able to turn try flow into a jump, which we can thread
                // recursively.
                var tryFlow = (TryFlow)flow;

                // Start off by threading the try flow's branches.
                var successBranch = tryFlow.SuccessBranch;
                var failureBranch = tryFlow.ExceptionBranch;
                var successFlow   = AsThreadableFlow(tryFlow.SuccessBranch, block.Graph, processedBlocks);
                var failureFlow   = AsThreadableFlow(tryFlow.ExceptionBranch, block.Graph, processedBlocks);
                if (successFlow is JumpFlow)
                {
                    successBranch = ((JumpFlow)successFlow).Branch;
                }
                if (failureFlow is JumpFlow)
                {
                    failureBranch = ((JumpFlow)failureFlow).Branch;
                }

                var exceptionSpecs = block.Graph.GetAnalysisResult <InstructionExceptionSpecs>();
                if (!exceptionSpecs.GetExceptionSpecification(tryFlow.Instruction).CanThrowSomething ||
                    IsRethrowBranch(failureBranch, block.Graph, processedBlocks))
                {
                    // If the "risky" instruction absolutely cannot throw, then we can
                    // just rewrite the try as a jump.
                    // Similarly, if the exception branch simply re-throws the exception,
                    // then we are also free to make the same transformation, but for
                    // different reasons.
                    var value  = block.AppendInstruction(tryFlow.Instruction);
                    var branch = successBranch.WithArguments(
                        successBranch.Arguments
                        .Select(arg => arg.IsTryResult ? BranchArgument.FromValue(value) : arg)
                        .ToArray());
                    block.Flow = new JumpFlow(branch);

                    // Jump-thread this block again.
                    processedBlocks.Remove(block);
                    ThreadJumps(block, processedBlocks);
                }
                else if (IsRethrowIntrinsic(tryFlow.Instruction))
                {
                    // If the "risky" instruction is really just a 'rethrow' intrinsic,
                    // then we can branch directly to the exception branch.

                    var capturedException = tryFlow.Instruction.Arguments[0];
                    JumpToExceptionBranch(block, failureBranch, capturedException);

                    // Jump-thread this block again.
                    processedBlocks.Remove(block);
                    ThreadJumps(block, processedBlocks);
                }
                else if (IsThrowIntrinsic(tryFlow.Instruction))
                {
                    // If the "risky" instruction is really just a 'throw' intrinsic,
                    // then we can insert an instruction to capture the exception and
                    // jump directly to the exception block.
                    var exception = tryFlow.Instruction.Arguments[0];

                    var capturedExceptionParam = failureBranch
                                                 .ZipArgumentsWithParameters(block.Graph)
                                                 .FirstOrDefault(pair => pair.Value.IsTryException)
                                                 .Key;

                    if (capturedExceptionParam == null)
                    {
                        // If the exception branch does not pass an '#exception' parameter,
                        // then we can replace the try by a simple jump.
                        block.Flow = new JumpFlow(failureBranch);
                    }
                    else
                    {
                        // Otherwise, we actually need to capture the exception.
                        var capturedException = block.AppendInstruction(
                            Instruction.CreateCaptureIntrinsic(
                                block.Graph.GetValueType(capturedExceptionParam),
                                block.Graph.GetValueType(exception),
                                exception));

                        JumpToExceptionBranch(block, failureBranch, capturedException);
                    }

                    // Jump-thread this block again.
                    processedBlocks.Remove(block);
                    ThreadJumps(block, processedBlocks);
                }
                else
                {
                    // If we can't replace the 'try' flow with something better, then
                    // the least we can do is try and thread the 'try' flow's branches.
                    block.Flow = new TryFlow(tryFlow.Instruction, successBranch, failureBranch);
                }
            }
            else if (flow is SwitchFlow && IncludeSwitches)
            {
                // If we're allowed to, then we might be able to thread jumps in switch flow
                // branches.
                bool changed    = false;
                var  switchFlow = (SwitchFlow)flow;

                // Rewrite switch cases.
                var cases = new List <SwitchCase>();
                foreach (var switchCase in switchFlow.Cases)
                {
                    var caseFlow = AsThreadableFlow(switchCase.Branch, block.Graph, processedBlocks);
                    if (caseFlow != null &&
                        switchCase.Branch.Target != block.Tag)
                    {
                        if (caseFlow is JumpFlow)
                        {
                            cases.Add(
                                new SwitchCase(
                                    switchCase.Values,
                                    ((JumpFlow)caseFlow).Branch));
                            changed = true;
                            continue;
                        }
                        else if (caseFlow is SwitchFlow)
                        {
                            var threadedSwitch = (SwitchFlow)caseFlow;
                            if (threadedSwitch.SwitchValue == switchFlow.SwitchValue)
                            {
                                var valuesToBranches = threadedSwitch.ValueToBranchMap;
                                foreach (var value in switchCase.Values)
                                {
                                    Branch branchForValue;
                                    if (!valuesToBranches.TryGetValue(value, out branchForValue))
                                    {
                                        branchForValue = threadedSwitch.DefaultBranch;
                                    }
                                    cases.Add(
                                        new SwitchCase(
                                            ImmutableHashSet.Create(value),
                                            branchForValue));
                                }
                                changed = true;
                                continue;
                            }
                        }
                    }
                    cases.Add(switchCase);
                }

                // Rewrite default branch if possible.
                var defaultBranch = switchFlow.DefaultBranch;
                if (defaultBranch.Target != block.Tag)
                {
                    var defaultFlow = AsThreadableFlow(defaultBranch, block.Graph, processedBlocks);
                    if (defaultFlow is JumpFlow)
                    {
                        defaultBranch = ((JumpFlow)defaultFlow).Branch;
                        changed       = true;
                    }
                    else if (defaultFlow is SwitchFlow)
                    {
                        var threadedSwitch = (SwitchFlow)defaultFlow;
                        if (threadedSwitch.SwitchValue == switchFlow.SwitchValue)
                        {
                            var valueSet = ImmutableHashSet.CreateRange(switchFlow.ValueToBranchMap.Keys);
                            foreach (var switchCase in threadedSwitch.Cases)
                            {
                                cases.Add(
                                    new SwitchCase(
                                        switchCase.Values.Except(valueSet),
                                        switchCase.Branch));
                            }
                            defaultBranch = threadedSwitch.DefaultBranch;
                            changed       = true;
                        }
                    }
                }

                if (changed)
                {
                    block.Flow = new SwitchFlow(
                        switchFlow.SwitchValue,
                        cases,
                        defaultBranch);
                }

                // Also simplify the block's switch flow. If the switch
                // flow decays to jump flow, then we want to try threading
                // this block again.
                if (SwitchSimplification.TrySimplifySwitchFlow(block) &&
                    block.Flow is JumpFlow)
                {
                    processedBlocks.Remove(block);
                    ThreadJumps(block, processedBlocks);
                }
            }
        }