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; }
/// <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); }
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()); }
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); } } }