Sunday, January 15, 2006

More on interface delegations

As I explained here, Freya allows the programmer to implement an interface type by delegating the implementation of methods from the interface to an instance of an already existing implementation. This trick is borrowed from Delphi, and it's an effective and easy technique for reusing interface implementations. Then, I used this example:
// WARNING: THE SYNTAX HAS CHANGED!!!
public

Foo = class(IEnumerable)
end;

implementation for Foo is

// A private instance field.
var Items: array of String := ['Freya', 'rules'];

// Just an implementation detail!
interface IEnumerable is Items;

end.
Our Foo class must implement the IEnumerable interface type, and it does it by delegating the implementation to a string array; all array types implement that interface, don't they? The delegation member contains a mention to the private field Items... but our real interest is the value stored in that field and, since our field is already initialized, it's highly probable that Items won't be referenced from other methods in this class. Why, then, should we need to declare Items at all?
This is a new alternative technique for delegating interface implementation:
implementation for Foo is

interface IEnumerable := ['Freya', 'rules!'];

end.
Now we can merge all what we need for the delegation in a single clause! Just think in all that can be done with this new feature. We have used a string list in our example, but it will be more common to find a regular constructor call as initializer:
implementation for Foo is

interface IEnumerator := new DriveEnumerator;

end.
In short, now we have two kinds of delegation clauses. One of them refers to a field or property in the same class. In this case, you can initialize the field or property in a constructor, or using a field or property initializer. The second and shorter form assigns directly a value in the delegation clause using an initializer. In this case, the compiler declares a hidden field inside the class and takes care of its initialization.

Labels:

Friday, January 13, 2006

Enhancing code quality

It's an open question whether a compiler writer targeting the CLR should waste some time optimizing generated code. After all, the JIT compiler in .NET 2.0 is almost a miracle of software engineering. In any case, I think it pays, at least when we talk about very simple optimizations with an immediate impact in code.
If you're compiling a Pascal-like language as Freya, function result assignment may introduce extra bytes in the code stream. Consider this simple access method:
// From a generic stack implementation.
property Top: X;
begin
Result := Items[Count - 1];
end;
This was the output from the initial compiler implementation, as reported by Reflector:
  // Code size: 22 bytes
.maxstack 3
.locals init(!0 local1)
ldarg.0
ldfld !0[] Stack`1::Items
ldarg.0
ldfld int32 Stack`::Count
ldc.i4.1
sub
ldelem.any !0
stloc.0
ldloc.0
ret
What's that local1 local variable? That's simply a direct and naive implementation of the old and good Result! As you can see, there's a silly store/load code sequence at the end of the method.
I've just added to the Freya compiler a very simple and fast algorithm to detect proper function returns, in true C/C# fashion. Now, the compiler generates this code for the same Freya method:
  // Code size: 20 bytes
.maxstack 3
ldarg.0
ldfld !0[] Stack`1::Items
ldarg.0
ldfld int32 Stack`::Count
ldc.i4.1
sub
ldelem.any !0
ret
There's no need now for a local variable, and the code stream is two bytes shorter. How will this affect the JIT output? Well, it's highly probable that the JIT compiler would have detected the redundant sequence in the first example and removed it. However, with the new compiled code, the JIT compiler has less work to perform and the application will load faster. You should note that our algorithm is able to detect the return pattern in code like this:
method Silly(Value: Integer): String;
begin
Console.WriteLine(Value);
case Value of
v1..v2: Result := 'Whatever';
v3: Result := 'Oops';
v150..v300:
if Value = 200 then
Result := 'Yikes'
else
Result := 'Uh-oh';
else
Result := 'That''s all...'
end
end;
Let's see another simple and useful optimization. How would you translate this?
if Value <> 0 then
DoSomething;
My initial implementation would have missed the fact we are comparing for equality with the zero constant, and would have generated something like this:
  // Load Value...
ldc.i4.0
beq @@1
// DoSomething...
@@1:
Actually, this is a lot better, and it's just what we're doing now:
  // Load Value...
brtrue @@1
// DoSomething...
@@1:
Despite the fact that the CLR offers a "decent" type for Boolean, IL code still allows you to use a non null constant as an equivalent of true. In this case, we're saving just a byte. But remember: the shorter code, the faster it loads...

Labels: ,

Saturday, January 07, 2006

Debugging Freya applications

I have just added PDB file generation to the Freya compiler, to support debugging. Piece of cake! It has been extremely easy… so far.
  1. Mark the dynamic assembly with DebuggableAttribute, passing both the DisableOptimizations and Default constants to the attribute constructor.
  2. Create the dynamic module as "debuggable", passing true as the last parameter for AssemblyBuilder.DefineDynamicModule.
  3. Associate an ISymbolDocumentWriter to each source file in the project. Use the DefineDocument method from MethodBuilder for this task.
  4. Since every node in the Abstract Syntax Tree has a SourceRange field pointing to its source file, each AST node, including statement nodes, may access their corresponding ISymbolDocumentWriter instance.
  5. Code generation for statements is achieved by calling the Generate virtual method defined at the root AstStatement class. I just inserted a call to ILGenerator.MarkSequencePoint at the very beginning of the base Generate method. This Reflection.Emit method requires an ISymbolDocumentWriter as its first parameter, and a range inside the source file... and both things can be extracted from the SourceRange stored at the node.
  6. Additionally, local variables declared by the programmed must apply the SetLocalSymInfo method to their local variable builders, since IL code does not contain metadata about the names of local variables.
This is all what you need to load a Freya source file in Microsoft's CLR Debugger, as shown in this image:

A Freya debugging session with Microsoft's CLR Debugger

You can find more information on this topic at Mike Stall's .NET Debugging blog. Of course, this is just a first step towards integrating the compiler in the Visual Studio 2005 IDE.

Labels:

Monday, January 02, 2006

Object initializers

Take a look at this code:
// C# 3.0
var a = new Point { X = 0, Y = 1 };
This fragment has been copied from the C# 3.0 specification preview. According to that document, the above instruction should be interpreted as:
// C# 3.0
var a = new Point();
a.X = 0;
a.Y = 1;
So, we have a "normal" instance construction followed by property or field assignments. Does this remind you any other existing C# feature? Sure, that's how custom attributes are instantiated. Custom attributes mix positional parameters corresponding to constructor parameters, and named parameters, which must be translated as post-construction assignments. We could extend the syntax from custom attributes to object creation:
// A proposal
var a = new Point(X = 0, Y = 1);
Of course: I know object initializers were proposed for C# 3.0 as part of the support for anonymous types. In C# 3.o, you could drop the "constructor part" like this:
// Anonymous types in C# 3.0
var p1 = new {
Name = "Lawnmower", Price = 495.00 };
But then, why not this?
// Anonymous types in C# 3.0: a proposal
var p1 = new(
Name = "Lawnmower", Price = 495.00 );
Is this a good idea? I still have to check the 3.0 proposal, to see whether there is any other reason to prefer the curly braces initializers (nested initializers, for instance). In any case, I think it's better for Freya to adopt the "custom attribute initialization syntax" for object initializers: the literal translation of C#'s curly braces would be a begin/end pair. So, we could initialize a Point object in Freya this way:
// A proposal for Freya 1.0
var p := new Point(X := 0, Y :=0);
var tb := new DataTable(
'Customers', CaseSensitive := true);
How do you like it?

Labels: ,