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: ,

0 Comments:

Post a Comment

<< Home