/// <summary> /// Tells if syntactically equivalent instances of a particular intrinsic /// are semantically equivalent. /// </summary> /// <param name="intrinsic">An intrinsic to consider.</param> /// <returns> /// <c>true</c> if syntactically equivalent instances of /// <paramref name="intrinsic"/> are semantically equivalent; /// otherwise, <c>false</c>. /// </returns> private static bool IsCopyableIntrinsic(IntrinsicPrototype intrinsic) { return(ArithmeticIntrinsics.IsArithmeticIntrinsicPrototype(intrinsic) || ArrayIntrinsics.Namespace.IsIntrinsicPrototype(intrinsic, ArrayIntrinsics.Operators.GetLength) || ArrayIntrinsics.Namespace.IsIntrinsicPrototype(intrinsic, ArrayIntrinsics.Operators.GetElementPointer) || ExceptionIntrinsics.Namespace.IsIntrinsicPrototype(intrinsic, ExceptionIntrinsics.Operators.GetCapturedException)); }
private static bool IsLeftToRightReassociable( IntrinsicPrototype prototype, out IntrinsicPrototype rightPrototype) { string op; string rightOp; bool isChecked; if (ArithmeticIntrinsics.TryParseArithmeticIntrinsicName(prototype.Name, out op, out isChecked) && !isChecked && intArithLeftToRight.TryGetValue(op, out rightOp) && prototype.ParameterTypes.All(x => x.IsIntegerType())) { rightPrototype = ArithmeticIntrinsics.CreatePrototype( rightOp, isChecked, prototype.ResultType, prototype.ParameterTypes); return(true); } else { rightPrototype = null; return(false); } }
/// <summary> /// Creates an arithmetic intrinsic. /// </summary> /// <param name="operatorName"> /// The name of the arithmetic operator to apply. /// </param> /// <param name="resultType"> /// The type of value produced by the intrinsic. /// </param> /// <param name="parameterTypes"> /// The parameter types taken by the intrinsic. /// </param> /// <param name="arguments"> /// The argument list to pass to the instruction. /// </param> /// <returns> /// An arithmetic intrinsic. /// </returns> public static Instruction CreateArithmeticIntrinsic( string operatorName, IType resultType, IReadOnlyList <IType> parameterTypes, IReadOnlyList <ValueTag> arguments) { return(ArithmeticIntrinsics.CreatePrototype( operatorName, resultType, parameterTypes).Instantiate(arguments)); }
private static bool IsAssociative(IntrinsicPrototype prototype) { string op; bool isChecked; if (ArithmeticIntrinsics.TryParseArithmeticIntrinsicName(prototype.Name, out op, out isChecked) && !isChecked && assocIntArith.Contains(op)) { return(prototype.ParameterTypes.All(x => x.IsIntegerType())); } else { return(false); } }
/// <summary> /// The default constant instruction evaluation function. /// </summary> /// <param name="prototype"> /// The prorotype of the instruction to evaluate. /// </param> /// <param name="arguments"> /// A list of arguments to the instruction, all of which /// must be constants. /// </param> /// <returns> /// <c>null</c> if the instruction cannot be evaluated; otherwise, the constant /// to which the instruction evaluates. /// </returns> public static Constant EvaluateDefault( InstructionPrototype prototype, IReadOnlyList <Constant> arguments) { if (prototype is CopyPrototype || prototype is ReinterpretCastPrototype) { return(arguments[0]); } else if (prototype is ConstantPrototype) { var constProto = (ConstantPrototype)prototype; if (constProto.Value is DefaultConstant) { // Try to specialize 'default' constants. var intSpec = constProto.ResultType.GetIntegerSpecOrNull(); if (intSpec != null) { return(new IntegerConstant(0, intSpec)); } } return(constProto.Value); } else if (prototype is IntrinsicPrototype) { string arithOp; Constant result; if (ArithmeticIntrinsics.TryParseArithmeticIntrinsicName( ((IntrinsicPrototype)prototype).Name, out arithOp) && ArithmeticIntrinsics.TryEvaluate( arithOp, prototype.ResultType, arguments, out result)) { return(result); } } return(null); }
/// <summary> /// Looks for an instruction that is semantically /// equivalent to a given instruction but minimizes /// the number of layers of indirection erected by /// copies. /// </summary> /// <param name="instruction"> /// The instruction to simplify. /// </param> /// <param name="graph"> /// The graph that defines the instruction. /// </param> /// <returns> /// A semantically equivalent instruction.</returns> private static Instruction SimplifyInstruction( Instruction instruction, FlowGraph graph) { if (instruction.Prototype is CopyPrototype) { var copyProto = (CopyPrototype)instruction.Prototype; var copiedVal = copyProto.GetCopiedValue(instruction); if (graph.ContainsInstruction(copiedVal)) { var copiedInsn = graph.GetInstruction(copiedVal).Instruction; // Only simplify copies of arithmetic intriniscs and constants. // Those are the only instructions we're actually // interested in and they don't have any "funny" behavior. if (copiedInsn.Prototype is ConstantPrototype || copiedInsn.Prototype is CopyPrototype || ArithmeticIntrinsics.IsArithmeticIntrinsicPrototype(copiedInsn.Prototype)) { return(SimplifyInstruction(copiedInsn, graph)); } } } return(instruction); }
static RuleBasedPrototypeExceptionSpecs() { Default = new RuleBasedPrototypeExceptionSpecs(); // Instruction prototypes that never throw. Default.Register <AllocaArrayPrototype>(ExceptionSpecification.NoThrow); Default.Register <AllocaPrototype>(ExceptionSpecification.NoThrow); Default.Register <BoxPrototype>(ExceptionSpecification.NoThrow); Default.Register <ConstantPrototype>(ExceptionSpecification.NoThrow); Default.Register <CopyPrototype>(ExceptionSpecification.NoThrow); Default.Register <DynamicCastPrototype>(ExceptionSpecification.NoThrow); Default.Register <GetStaticFieldPointerPrototype>(ExceptionSpecification.NoThrow); Default.Register <LoadPrototype>(ExceptionSpecification.NoThrow); Default.Register <ReinterpretCastPrototype>(ExceptionSpecification.NoThrow); Default.Register <StorePrototype>(ExceptionSpecification.NoThrow); // Instruction prototypes that may throw because of implicit null checks. Default.Register <GetFieldPointerPrototype>( new NullCheckExceptionSpecification(0, ExceptionSpecification.ThrowAny)); Default.Register <NewDelegatePrototype>( proto => proto.Lookup == MethodLookup.Virtual ? new NullCheckExceptionSpecification(0, ExceptionSpecification.ThrowAny) : ExceptionSpecification.NoThrow); Default.Register <UnboxPrototype>( new NullCheckExceptionSpecification(0, ExceptionSpecification.ThrowAny)); // Call-like instruction prototypes. Default.Register <CallPrototype>( proto => proto.Lookup == MethodLookup.Static ? proto.Callee.GetExceptionSpecification() : ExceptionSpecification.ThrowAny); Default.Register <NewObjectPrototype>( proto => proto.Constructor.GetExceptionSpecification()); Default.Register <IndirectCallPrototype>(ExceptionSpecification.ThrowAny); // Unchecked arithmetic intrinsics never throw. foreach (var name in ArithmeticIntrinsics.Operators.All) { Default.Register( ArithmeticIntrinsics.GetArithmeticIntrinsicName(name, false), ExceptionSpecification.NoThrow); } // Array intrinsics are a little more complicated. // TODO: model bounds checks somehow. Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.GetElementPointer), ExceptionSpecification.ThrowAny); Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.LoadElement), ExceptionSpecification.ThrowAny); Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.StoreElement), ExceptionSpecification.ThrowAny); Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.GetLength), new NullCheckExceptionSpecification(0, ExceptionSpecification.ThrowAny)); Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.NewArray), ExceptionSpecification.NoThrow); // Exception intrinsics. Default.Register( ExceptionIntrinsics.Namespace.GetIntrinsicName(ExceptionIntrinsics.Operators.Capture), ExceptionSpecification.NoThrow); Default.Register( ExceptionIntrinsics.Namespace.GetIntrinsicName(ExceptionIntrinsics.Operators.GetCapturedException), ExceptionSpecification.NoThrow); Default.Register( ExceptionIntrinsics.Namespace.GetIntrinsicName(ExceptionIntrinsics.Operators.Rethrow), ExceptionSpecification.ThrowAny); Default.Register( ExceptionIntrinsics.Namespace.GetIntrinsicName(ExceptionIntrinsics.Operators.Throw), proto => ExceptionSpecification.Exact.Create(proto.ParameterTypes[0])); // Object intrinsics. // TODO: model exception thrown by type check. Default.Register( ObjectIntrinsics.Namespace.GetIntrinsicName(ObjectIntrinsics.Operators.UnboxAny), ExceptionSpecification.ThrowAny); // Memory intrinsics. Default.Register( MemoryIntrinsics.Namespace.GetIntrinsicName(MemoryIntrinsics.Operators.AllocaPinned), ExceptionSpecification.NoThrow); }
static RuleBasedPrototypeMemorySpecs() { Default = new RuleBasedPrototypeMemorySpecs(); Default.Register <AllocaArrayPrototype>(MemorySpecification.Nothing); Default.Register <AllocaPrototype>(MemorySpecification.Nothing); Default.Register <BoxPrototype>(MemorySpecification.Nothing); Default.Register <ConstantPrototype>(MemorySpecification.Nothing); Default.Register <CopyPrototype>(MemorySpecification.Nothing); Default.Register <DynamicCastPrototype>(MemorySpecification.Nothing); Default.Register <GetStaticFieldPointerPrototype>(MemorySpecification.Nothing); Default.Register <ReinterpretCastPrototype>(MemorySpecification.Nothing); Default.Register <GetFieldPointerPrototype>(MemorySpecification.Nothing); Default.Register <NewDelegatePrototype>(MemorySpecification.Nothing); Default.Register <UnboxPrototype>(MemorySpecification.Nothing); // Mark volatile loads and stores as unknown to ensure that they are never reordered // with regard to other memory operations. // TODO: is this really how we should represent volatility? Default.Register <LoadPrototype>(proto => proto.IsVolatile ? MemorySpecification.Unknown : MemorySpecification.ArgumentRead.Create(0)); Default.Register <StorePrototype>(proto => proto.IsVolatile ? MemorySpecification.Unknown : MemorySpecification.ArgumentWrite.Create(0)); // Call-like instruction prototypes. Default.Register <CallPrototype>( proto => proto.Lookup == MethodLookup.Static ? proto.Callee.GetMemorySpecification() : MemorySpecification.Unknown); Default.Register <NewObjectPrototype>( proto => proto.Constructor.GetMemorySpecification()); Default.Register <IndirectCallPrototype>(MemorySpecification.Unknown); // Arithmetic intrinsics never read or write. foreach (var name in ArithmeticIntrinsics.Operators.All) { Default.Register( ArithmeticIntrinsics.GetArithmeticIntrinsicName(name, false), MemorySpecification.Nothing); Default.Register( ArithmeticIntrinsics.GetArithmeticIntrinsicName(name, true), MemorySpecification.Nothing); } // Array intrinsics. Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.GetElementPointer), MemorySpecification.Nothing); Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.LoadElement), MemorySpecification.UnknownRead); Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.StoreElement), MemorySpecification.UnknownWrite); Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.GetLength), MemorySpecification.Nothing); Default.Register( ArrayIntrinsics.Namespace.GetIntrinsicName(ArrayIntrinsics.Operators.NewArray), MemorySpecification.Nothing); // Exception intrinsics. Default.Register( ExceptionIntrinsics.Namespace.GetIntrinsicName(ExceptionIntrinsics.Operators.GetCapturedException), MemorySpecification.UnknownRead); // Object intrinsics. Default.Register( ObjectIntrinsics.Namespace.GetIntrinsicName(ObjectIntrinsics.Operators.UnboxAny), MemorySpecification.UnknownRead); // Memory intrinsics. Default.Register( MemoryIntrinsics.Namespace.GetIntrinsicName(MemoryIntrinsics.Operators.AllocaPinned), MemorySpecification.Nothing); }
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); }