Improving Sigil
Posted: 2013/03/04 Filed under: code | Tags: sigil 2 CommentsA few weeks ago I announced Sigil, a fail-fast validating wrapper around .NET’s ILGenerator. Since then I’ve pushed a number of updates and bug fixes, culminating in the fairly significant 2.0.0 release now on Nuget.
I figured now was a good time to write down what’s changed, and where I see this code going.
A Smarter Validator
The biggest change between 1.x and 2.x is that Sigil no longer requires stack type assertions after unconditional branches.
In version 1.x you had to do something like the following:
var emit = Emit<Func<int>>.NewDynamicMethod(); var label1 = emit.DefineLabel(); var label2 = emit.DefineLabel(); emit.LoadConstant(0); emit.Branch(label1); emit.MarkLabel(label2, new [] { typeof(int) }); // Sigil wasn't smart enough to know what the stack was here emit.Return(); emit.MarkLabel(label1); emit.Branch(label2);
Version 2.x removes the need to pass types to certain MarkLabel() calls entirely. This removes the most painful bit of busywork entailed in using the initial release of Sigil.
As a consequence of this new validator, a few breaking changes were made to Sigil’s interface. The obvious removal of stack assertions, plus the removal of GetStack (as the stack state is not always known) and the addition of some required parameters to array manipulation methods (as the array involved cannot always be inferred).
Better Debugging Information
Sigil is all about failing fast and helpfully, and several changes have been made to the string returned by SigilVerificationException.GetDebugInfo() since Sigil’s initial release.
Examples include:
- Non-returning paths indicated by the labels that path flows through
- Instructions that take locals are annotated with the names and types of the involved local
- The instruction that causes a verification error
- As in version 1.x the stack state at any error, now including all possible types
Optimizations
Since it’s initial release, Sigil has gained the ability to elide trivial casts (thanks to Marc Gravell for the first version of this feature).
Modest performance improvements have been made to rewriting branches into their short forms, and the ability to disable such rewriting has been added (credit to Jason Punyon for some of this).
Performance
Sigil has been tuned somewhat since it’s initial release, but the introduction of a smarter validator has come at a performance price under certain circumstances. As a rough guideline, if you’re generating methods with hundreds of labels and branches or tens of thousands of instructions Sigil may be too slow for you.
In addition to the aforementioned optimization options (passed as OptimizationOptions), the option to defer some validation has also been added (via the ValidationOptions enumeration). Between the two a great deal of Sigil’s utility can still be had even for very large or very branch intensive generated code.
Minor Improvements
- While debugging all declared labels and in scope locals are exposed as the Labels and Locals properties
- The WriteLine(string, params Locals[]) method
- Automatic insertion of the volatile prefix when able
- The ability to refer to locals and labels by name
The Future
I’m not really satisified with Sigil’s performance just yet. Profiling suggests that the slowest part of Sigil is gluing together instruction sequences for post-branch validation; I’m sure this can be done more quickly and probably skipped altogether in a lot of cases.
I’d also like to support the last four CIL instructions: Arglist, Mkrefany, Refanytype, and Refanyval. Sigil has focused mostly on the DynamicMethod case where these instructions are rarely useful, but for completion’s sake they should be available.
Sigil currently has .NET 4.5 and 3.5 builds, it’d be nice to get a .NET 2.0 build as well. This is non-trivial as Sigil makes heavy use of LINQ and extension methods.
A more long term goal is to abstract most of Sigil’s dependency on System.Reflection and System.Reflection.Emit so that its validation and optimization features can be used for a wider array of meta-programming tasks. Freely switching between standard .NET and IKVM.Reflection is probably the first place to start, as there’s already a similar feature in protobuf-net’s precompiler.
As before, Sigil’s source is on github and the latest stable releases are on Nuget.
Bitmap processing is difficult to do in both a pixel-format agnostic and performant way; runtime generation seems like it could solve this. (Generics are insufficent as the math changes).
For simple loops across unmanaged (or managed, unchecked) arrays, could Sigil produce code with standard performance characteristics?
Sigil is a helper for standard .NET meta-programming (ILGenerator specifically).
Anything you can do in .NET you can do with Sigil, and you will be using what is theoretically (and practically, in my experience) fastest meta-programming facilities.
So yes, Sigil generated code would be about as fast as possible with the caveat that it’s up to you to figure out what the best way to structure your code for performance is. Sigil makes ILGenerator less error prone and automates some tedious but trivial tasks, it doesn’t optimize your code for speed like a compiler does.