/// <summary> /// Gauges a particular method body's inline gain. /// </summary> /// <param name="body">A method body that might be inlined.</param> /// <param name="arguments"> /// The list of arguments to feed to <paramref name="body"/>. /// </param> /// <param name="caller"> /// The control-flow graph of the caller, which defines the arguments. /// </param> /// <returns> /// A number that represents how much new information we expect to gain from inlining. /// </returns> protected virtual int GetInlineGain( MethodBody body, IReadOnlyList <ValueTag> arguments, FlowGraph caller) { // Inline method bodies containing up to ten instructions/blocks. // TODO: be smarter about arguments: // * Inlining a method that takes a more derived type as argument // may result in a direct call getting turned into an indirect // call. int gain = 10; foreach (var arg in arguments) { NamedInstruction argInstruction; if (caller.TryGetInstruction(arg, out argInstruction)) { if (argInstruction.Prototype is AllocaPrototype) { // Inlining a method that takes an `alloca` argument may result // in a level of memory indirection getting stripped away. gain += 4; continue; } } var type = caller.GetValueType(arg); // Inlining means that we don't have to pass around big arguments. // Encourage inlining methods that take hefty arguments. gain += EstimateTypeSize(type) / 4; } gain += EstimateTypeSize(body.ReturnParameter.Type) / 4; return(gain); }
/// <inheritdoc/> public override FlowGraph Apply(FlowGraph graph) { var builder = graph.ToBuilder(); foreach (var insn in builder.NamedInstructions) { ValueTag array; ClrFieldDefinition pseudoField; if (IsArrayInit(insn, out array, out pseudoField)) { array = ElideReinterpretCasts(array, builder.ToImmutable()); var arrayType = TypeHelpers.UnboxIfPossible(graph.GetValueType(array)); IType elementType; IReadOnlyList <Constant> data; int rank; if (ClrArrayType.TryGetArrayElementType(arrayType, out elementType) && ClrArrayType.TryGetArrayRank(arrayType, out rank) && rank == 1 && TryDecodePseudoFieldData(pseudoField, elementType, out data)) { // Generate instructions to fill the array. FillWith(array, data, elementType, insn); // Neuter the old array init function. insn.Instruction = Instruction.CreateConstant(DefaultConstant.Instance, insn.ResultType); } } } return(builder.ToImmutable()); }
private static bool TryExtractConstantAndValue( ValueTag leftHandSide, ValueTag rightHandSide, FlowGraph graph, out Constant constant, out ValueTag value) { var lhsInstruction = SimplifyInstruction( Instruction.CreateCopy( graph.GetValueType(leftHandSide), leftHandSide), graph); if (lhsInstruction.Prototype is ConstantPrototype) { constant = ((ConstantPrototype)lhsInstruction.Prototype).Value; value = rightHandSide; return(true); } var rhsInstruction = SimplifyInstruction( Instruction.CreateCopy( graph.GetValueType(rightHandSide), rightHandSide), graph); if (rhsInstruction.Prototype is ConstantPrototype) { constant = ((ConstantPrototype)rhsInstruction.Prototype).Value; value = leftHandSide; return(true); } else { constant = null; value = null; return(false); } }
private static IType GetActualType(ValueTag value, FlowGraph graph) { NamedInstruction insn; if (graph.TryGetInstruction(value, out insn)) { var proto = insn.Prototype; if (proto is ReinterpretCastPrototype || proto is CopyPrototype) { return(GetActualType(insn.Arguments[0], graph)); } } return(graph.GetValueType(value)); }
/// <inheritdoc/> protected override bool TryGetPreallocatedRegister( ValueTag value, FlowGraph graph, out CilCodegenRegister register) { ParameterDefinition parameter; if (paramRegisters.TryGetValue(value, out parameter)) { register = new CilCodegenRegister(parameter, graph.GetValueType(value)); return(true); } else { register = default(CilCodegenRegister); return(false); } }
/// <inheritdoc/> public override FlowGraph Apply(FlowGraph graph) { var builder = graph.ToBuilder(); foreach (var instruction in builder.Instructions) { var proto = instruction.Prototype; if (proto is IndirectCallPrototype) { // Flame IR has dedicated instructions for delegate calls, // but in CIL they are implemented by a virtual call to // a magic 'Invoke' method. var callProto = (IndirectCallPrototype)proto; var calleeValue = callProto.GetCallee(instruction.Instruction); var delegateType = TypeHelpers.UnboxIfPossible(graph.GetValueType(calleeValue)); IMethod invokeMethod; if (TypeHelpers.TryGetDelegateInvokeMethod(delegateType, out invokeMethod)) { instruction.Instruction = Instruction.CreateCall( invokeMethod, MethodLookup.Virtual, calleeValue, callProto.GetArgumentList(instruction.Instruction).ToArray()); } } else if (proto is NewDelegatePrototype && instruction is NamedInstructionBuilder) { // TODO: also lower NewDelegatePrototype for anonymous instructions! // CIL delegates are created by first loading a function pointer // onto the stack (using either `ldftn` or `ldvirtftn`) and then // constructing the actual delegate using a `newobj` opcode. var newDelegateProto = (NewDelegatePrototype)proto; var delegateType = TypeHelpers.UnboxIfPossible(newDelegateProto.ResultType); IMethod invokeMethod; if (!TypeHelpers.TryGetDelegateInvokeMethod(delegateType, out invokeMethod)) { continue; } var constructor = delegateType.Methods.Single(method => method.IsConstructor); bool isVirtual = newDelegateProto.Lookup == MethodLookup.Virtual; // First create an instruction that loads the function pointer. var namedInstruction = (NamedInstructionBuilder)instruction; var functionPointer = namedInstruction.InsertBefore( Instruction.CreateNewDelegate( constructor.Parameters[1].Type, newDelegateProto.Callee, isVirtual ? newDelegateProto.GetThisArgument(instruction.Instruction) : null, newDelegateProto.Lookup), namedInstruction.Tag.Name + "_fptr"); // CLR delegate constructors always take two parameters: a function // pointer and a 'this' argument (of type 'Object *box'). // The latter can be somewhat tricky to get right: // // * if the delegate has a 'this' argument then that 'this' argument // should be reinterpreted as an instance of 'Object *box'. // // * if the delegate does not have a 'this' argument, then we pass // a `null` constant as 'this' argument. ValueTag thisArgument; if (newDelegateProto.HasThisArgument) { thisArgument = newDelegateProto.GetThisArgument(instruction.Instruction); var thisType = builder.GetValueType(thisArgument); var expectedThisType = constructor.Parameters[0].Type; if (thisType != expectedThisType) { thisArgument = functionPointer.InsertBefore( Instruction.CreateReinterpretCast( (PointerType)expectedThisType, thisArgument)); } } else { thisArgument = functionPointer.InsertBefore( Instruction.CreateConstant( NullConstant.Instance, constructor.Parameters[0].Type)); } // And then create the actual delegate. To do so we must use the // delegate type's constructor. This constructor will always take // two parameters: a bound object (of type System.Object) and a // function pointer (of type natural int). instruction.Instruction = Instruction.CreateNewObject( constructor, new[] { thisArgument, functionPointer }); } } return(builder.ToImmutable()); }
/// <inheritdoc/> public RegisterAllocation <TRegister> Analyze( FlowGraph graph) { // Run the related values and interference graph analyses. var related = graph.GetAnalysisResult <RelatedValues>(); var interference = graph.GetAnalysisResult <InterferenceGraph>(); // Create a mapping of values to registers. This will become our // return value. var allocation = new Dictionary <ValueTag, TRegister>(); // Create a mapping of registers to the set of all values they // interfere with due to the registers getting allocated to values. var registerInterference = new Dictionary <TRegister, HashSet <ValueTag> >(); // Before we do any real register allocation, we should set up // preallocated registers. The reason for doing this now instead // of later on in the allocation loop is that preallocated registers // are "free:" they don't incur register allocations. At the same time, // preallocated registers can be recycled, so we'll have more // registers to recycle (and hopefully create fewer new ones) if we // handle preallocated registers first. foreach (var value in graph.ValueTags) { TRegister assignedRegister; if (TryGetPreallocatedRegister(value, graph, out assignedRegister)) { // If we have a preallocated register, then we should just accept it. allocation[value] = assignedRegister; registerInterference[assignedRegister] = new HashSet <ValueTag>( interference.GetInterferingValues(value)); } } // Iterate over all values in the graph. foreach (var value in graph.ValueTags) { if (!RequiresRegister(value, graph) || allocation.ContainsKey(value)) { // The value may not need a register or may already have one. // If so, then we shouldn't allocate one. continue; } // Compose a set of registers we might be able to recycle. // Specifically, we'll look for all registers that are not // allocated to values that interfere with the current value. var recyclable = new HashSet <TRegister>(); foreach (var pair in registerInterference) { if (!pair.Value.Contains(value)) { // If the value is not in the interference set of // the register, then we're good to go. recyclable.Add(pair.Key); } } // We would like to recycle a register that has been // allocated to a related but non-interfering value. // To do so, we'll build a set of candidate registers. var relatedRegisters = new HashSet <TRegister>(); foreach (var relatedValue in related.GetRelatedValues(value)) { TRegister reg; if (allocation.TryGetValue(relatedValue, out reg) && recyclable.Contains(reg)) { relatedRegisters.Add(reg); } } // If at all possible, try to recycle a related register. If that // doesn't work out, try to recycle a non-related register. If // that fails as well, then we'll create a new register. var valueType = graph.GetValueType(value); TRegister assignedRegister; if (!TryRecycleRegister(valueType, relatedRegisters, out assignedRegister) && !TryRecycleRegister(valueType, recyclable, out assignedRegister)) { assignedRegister = CreateRegister(valueType); registerInterference[assignedRegister] = new HashSet <ValueTag>(); } // Allocate the register we recycled or created to the value. allocation[value] = assignedRegister; registerInterference[assignedRegister].UnionWith( interference.GetInterferingValues(value)); } return(new RegisterAllocation <TRegister>(allocation)); }
private static BlockFlow SimplifySwitchFlow(SwitchFlow flow, FlowGraph graph) { var value = SimplifyInstruction(flow.SwitchValue, graph); if (value.Prototype is ConstantPrototype) { // Turn the switch into a jump. var constant = ((ConstantPrototype)value.Prototype).Value; var valuesToBranches = flow.ValueToBranchMap; return(new JumpFlow( valuesToBranches.ContainsKey(constant) ? valuesToBranches[constant] : flow.DefaultBranch)); } else if (ArithmeticIntrinsics.IsArithmeticIntrinsicPrototype(value.Prototype)) { var proto = (IntrinsicPrototype)value.Prototype; var intrinsicName = ArithmeticIntrinsics.ParseArithmeticIntrinsicName(proto.Name); if (intrinsicName == ArithmeticIntrinsics.Operators.Convert && proto.ParameterCount == 1 && flow.IsIntegerSwitch) { // We can eliminate instructions that extend integers // by changing the values in the list of cases. var operand = proto.GetArgumentList(value).Single(); var operandType = graph.GetValueType(operand); var convType = proto.ResultType; var operandSpec = operandType.GetIntegerSpecOrNull(); if (operandSpec == null) { // The operand of the conversion intrinsic is not an // integer. return(flow); } var convSpec = convType.GetIntegerSpecOrNull(); if (operandSpec.Size > convSpec.Size) { // We can't handle this case. To handle it anyway // would require us to introduce additional cases // and that's costly. return(flow); } var caseList = new List <SwitchCase>(); foreach (var switchCase in flow.Cases) { // Retain only those switch cases that have values // that are in the range of the conversion function. var values = ImmutableHashSet.CreateBuilder <Constant>(); foreach (var val in switchCase.Values.Cast <IntegerConstant>()) { var opVal = val.Cast(operandSpec); if (opVal.Cast(convSpec).Equals(val)) { values.Add(opVal); } } if (values.Count > 0) { caseList.Add(new SwitchCase(values.ToImmutableHashSet(), switchCase.Branch)); } } return(SimplifySwitchFlow( new SwitchFlow( Instruction.CreateCopy( operandType, operand), caseList, flow.DefaultBranch), graph)); } else if (intrinsicName == ArithmeticIntrinsics.Operators.IsEqualTo && proto.ParameterCount == 2 && proto.ResultType.IsIntegerType()) { var args = proto.GetArgumentList(value); var lhs = args[0]; var rhs = args[1]; Constant constant; ValueTag operand; if (TryExtractConstantAndValue(lhs, rhs, graph, out constant, out operand)) { // The 'arith.eq' intrinsic always either produces '0' or '1'. // Because of that property, we can safely rewrite switches // like so: // // switch arith.eq(value, constant) // 0 -> zeroBranch // 1 -> oneBranch // default -> defaultBranch // // --> // // switch value // constant -> oneBranch ?? defaultBranch // default -> zeroBranch ?? defaultBranch // var resultSpec = proto.ResultType.GetIntegerSpecOrNull(); var zeroVal = new IntegerConstant(0, resultSpec); var oneVal = new IntegerConstant(1, resultSpec); var valuesToBranches = flow.ValueToBranchMap; var zeroBranch = valuesToBranches.ContainsKey(zeroVal) ? valuesToBranches[zeroVal] : flow.DefaultBranch; var oneBranch = valuesToBranches.ContainsKey(oneVal) ? valuesToBranches[oneVal] : flow.DefaultBranch; return(SimplifySwitchFlow( new SwitchFlow( Instruction.CreateCopy( graph.GetValueType(operand), operand), new[] { new SwitchCase(ImmutableHashSet.Create(constant), oneBranch) }, zeroBranch), graph)); } } } return(flow); }