DevTalk.net

ActiveMesa's software development blog. MathSharp, R2P, X2C and more!

ReSharper SDK Adventures Part 1 – Math.Pow Inlining

with 4 comments

Seeing how the ReSharper SDK has been out for quite a while, I’d like to start a series of posts demonstrating how it can be used. And since this is the first post in a series, I’m going to explain a little about some of the practices related to plugin development, and illustrate a few concepts that are central to R# as a product.

Problem

Why does the plugin ecosystem exist in the first place? Why make the SDK? The motivation is, of course, the fact that individual features that you might need for your particular case aren’t available out of the box. For example, you are aware of a particular case where code is bad and you want to somehow correct this. (SSR helps in simple cases, but the one we’ll consider is a bit more complex.)

For example, the following is, arguably, bad code:

var y = Math.Pow(x, 2.0);

The performance overhead on calculating x² this way is massive. The reason is that Math.Pow() is tuned towards floating-point powers, which means that up to some extent, it is a lot more efficient to do one of the following:

  • Write the expression inline, i.e., Math.Pow(x, 2.0)x*x.

  • Substitute an expression with your own implementation where the power is an integer, i.e., Math.Pow(x, 2.0)Maths.Pow(x, 2), where Maths is your own utility class and Pow is a function that has an integer overload.

Now, the above is a problem, and I’d like this problem to be taken care of by using ReSharper’s extensibility mechanisms.

Identifying the Structure

If we are to somehow fix the above, we first need to be able to identify the ‘offending’ structure. We are going to assume the simplest possible case, Math.Pow(x, y) where x is an identifier and y is a numeric value that happens to be an integer.

We should immediately be able to realize the constraints that we are putting on this model. For example, we assume that x is an identifier and not, for example, a numeric value. In fact, if it was, we could precompute the whole thing. But what if it was a constant, something like Math.Pow(Math.PI, 2)? Clearly, in this particular case, we wouldn’t care whether the power was an integer or not, because in any case, the replacement would be a computed constant value.

So, let’s look at a typical call, Math.Pow(x, 2.0). Here’s how to get info about it:

  1. First, fire up Visual Studio with the /ReSharper.Internal switch to get all the internal debug menus.

  2. Make a bare-bones file that contains the Math.Pow() statement in it.

  3. In the top-level menu, choose ReSharper | Internal | Go to PSI Viewer.

What you should end up with is something like this:

The PSI viewer is a really neat tool for figuring out what the code you’re after actually is. For instance, the above basically shows us that Math.Pow(x, 2.0) is

  • An IInvocationExpression containing

  • A reference expression referring to Math.Pow

  • A list of arguments containing

  • A reference expression with an identifier x

  • A literal expression with a floating-point value of 2.0

And that’s just about all we need to know – for now. We can now create a simple problem analyzer to look for this particular scenario.

Problem Analyzer

A problem analyzer is a piece of code that ReSharper can use to find out if there’s a problem somewhere. Since we agree to consider integer-based Math.Pow expressions to be a performance problem, we are going to create an element problem analyzer that will help us identify it.

First of all, let us define a class called IntPowerProblemAnalyzer. This class is looking explicitly for IInvocationExpressions, thus it will be made to inherit from ElementProblemanalyzer<IInvocationExpression>. There’s also some metadata at the top that we’ll discuss in a moment.

[ElementProblemAnalyzer(new[]{typeof(IInvocationExpression)}, 
  HighlightingTypes=new[]{typeof(IntPowerHighlighting)})]
public class IntPowerProblemAnalyzer : ElementProblemAnalyzer<IInvocationExpression>
{
  protected override void Run(IInvocationExpression element, 
    ElementProblemAnalyzerData data, IHighlightingConsumer consumer)
  {
    // todo :)
  }
}

Okay, so the class we have right now has to implement a single method called Run(). This method is important because this is where we teach the plugin to identify and highlight code that conforms to our pattern. But how to actually identify the pattern? Using the PSI viewer above, we can deduce that a Math.Pow() call check would consist of two parts: checking that we are calling a Pow() function, and checking that the arguments are correct. The first condition is checked as follows:

bool functionIsCalledPow = false;
var e = element.InvokedExpression as IReferenceExpression;
if (e != null)
{
  if (e.Reference.GetName().Equals("Pow"))
    functionIsCalledPow = true;
}

Note that we’re not testing it was Math that this was called on – this is more complicated and a bit out-of-scope right now. The second part of the check is a little more convoluted:

bool firstArgIsIdentifier = false;
bool secondArgIsInteger = false;
if (element.Arguments.Count == 2)
{
  firstArgIsIdentifier = element.Arguments[0].Value is IReferenceExpression;
  var secondArg = element.Arguments[1].Value as ICSharpLiteralExpression;
  if (secondArg != null && (secondArg.IsConstantValue()))
  {
    double value = -1.0;
    var cv = secondArg.ConstantValue;
    if (cv.IsDouble())
      value = (double)cv.Value;
    else if (cv.IsInteger())
      value = (int) cv.Value;
    secondArgIsInteger = (value > 0.0) && value == Math.Floor(value);
  }
}

The above does a very basic, almost simplistic check on the structure of the code in question. Note that I’ve greatly simplified the identification of the reference, but it will do for now. The next thing that we have to discuss is how problematic code is highlighted.

Highlighting the Problem

We’ve got some code for finding the problem, but how to show it to the user? In the snippet above, we used the highlighting consumer and passed it an instance of a highlighting. So what is it?

A highlighting is simply a class that implements that IHighlighting interface and is decorated by appropriate attributes. All it does is define how a particular problem is highlighted by ReSharper. This highlighting is passed to the consumer and is also featured as part of the HighlightingTypes attribute parameter of the problem analyzer. We are currently using the following definition of IntPowerHighlighting:

[StaticSeverityHighlighting(Severity.WARNING, CSharpLanguage.Name)]
public class IntPowerHighlighting : IHighlighting
{
  private readonly IInvocationExpression expression;
  public int Power { get; private set; }
  public IntPowerHighlighting(IInvocationExpression expression, int power)
  {
    this.expression = expression;
    Power = power;
  }
  public bool IsValid()
  {
    return expression != null && expression.IsValid();
  }
  public string ToolTip { get { return "Inefficient use of integer-based power"; } }
  public string ErrorStripeToolTip { get { return ToolTip; } }
  public int NavigationOffsetPatch { get { return 0; } }
}

Together with the element problem analyzer, we can fire up our plugin and get a result similar to the following:

And that’s what we wanted in the first place, but just seeing the problem isn’t enough. How about letting the end user correct the problem?

Quick-Fix

A quick-fix is a fix associated with a highlighting. In our case, we want to let a user replace Math.Pow(x,2) with x*x, with the number of x’s corresponding to the power. Note that it is entirely unreasonable beyond a certain value, typically around 2-3, so our fix will only work for these values.

In order to implement an inlining fix, we need to create an additional class. This class will implement the IQuickFix interface and decorate it with a [QuickFix] attribute as follows:

[QuickFix]
public class IntPowerInliningFix : BulbItemImpl, IQuickFix
{
  private readonly IntPowerHighlighting highlighting;
  public IntPowerInliningFix(IntPowerHighlighting highlighting)
  {
    this.highlighting = highlighting;
  }
  protected override Action<ITextControl> ExecutePsiTransaction(ISolution solution, IProgressIndicator progress)
  {
    // todo
  }
  public override string Text
  {
    get { return "Inline integer power"; }
  }
  public bool IsAvailable(IUserDataHolder cache)
  {
    int power = highlighting.Power;
    return power == 2 || power == 3;
  }
}

Note how the fix is only available for powers 2 and 3. The ExecutePsiTransaction() method is where all interesting things occur. Specifically, this is where we actually replace the Math.Pow() call with an inlined multiplication. Since this is somewhat more complicated, let’s go through this step-by-step. First of all, we need to get our argument – the one we’re going to multiply a few times:

var arg = highlighting.Expression.Arguments[0];

Now we need to create an element factory to construct the replacement expression. We use the original expression to get at the PSI module:

var factory = CSharpElementFactory.GetInstance(highlighting.Expression.GetPsiModule());

Now, through a bit of LINQ magic, we create an expression that represents the multiplication. Essentially, we create lots of $0’s separated by multiplication signs and let CSharpElementFactory fill them in:

var replacement = factory.CreateExpression(
  Enumerable.Range(0, highlighting.Power).Select(i => "$0").Join("*"), arg.Value);

Finally, we replace the old expression with the new one:

ModificationUtil.ReplaceChild(highlighting.Expression, replacement);

And here is what it looks like:

Conclusion

This demonstration is, admittedly, very simple. I’ve taken a number of methodological shortcuts that would never be acceptable in production code, namely:

  • Only checked that the function is called Pow() without verifying that it’s part of System.Math

  • Only did a basic check against the first argument

  • Assumed that the power is either a double or an int, whereas in reality it could be e.g., a short

  • Didn’t write any tests

In the next part of the series we’ll continue with this theme and take a look at how the plugin can be enhanced and its operations can be more rigorously checked. Meanwhile, check out the source code. For more information on the ReSharper API, check out the ReSharper Plugin Development Guide. Happy coding!

Written by Dmitri Nesteruk

May 4th, 2012 at 4:30 pm

Posted in ReSharper