OK, so I think that this is a problem with the AST to code generation,
though I really don’t understand it deeply enough to say exactly where.
If you dump the ASTs from the ruby code the body of the loop looks like
this:
{
(.bound i) = (Object)0;
.return .comma {
.try {
{
(.bound #rfc).InLoop = (Boolean)True;
{
(.bound #skip-condition) = (Boolean)False;
.for (; ; ) {
.try {
{
.if ((.bound #skip-condition) )
{(Void)(.bound #skip-condition) = (Boolean)False;
} .else {.if (.action (Boolean)
ConvertTo
System.Boolean( // ConvertTo to System.Boolean
.action (Object) InvokeMember <( //
InvokeMember <(Instance, Simple) ReturnNonCallable, IsCallWithThis
(.bound i)
1000
)
) ) {/*empty*/;
} .else {.break;
}}
{
(.bound i) = (Object).action
(Object)
InvokeMember +( // InvokeMember +(Instance, Simple) ReturnNonCallable,
IsCallWithThis
(.bound i)
1
);
}
}
} .catch ( BlockUnwinder #unwinder) {
(.bound #skip-condition) = (.bound
#unwinder).IsRedo;
} .catch ( EvalUnwinder #unwinder) {
.if (((.bound #unwinder).Reason ==
(BlockReturnReason)Break) ) {{
(.bound #loop-result) = (.bound
#unwinder).ReturnValue;
.break;
}
} .else {
.throw ()
}}
}
}
}
} .finally {
(.bound #rfc).InLoop = (Boolean)False;
}
(.bound #loop-result)
}
;
}
No mention of ConvertToInt32 there although it is noticeable that the
(.bound i) value is an object and not a Fixnum.
If you dump the generated code you get the following two methods for
invoking the Fixnum methods:
public static object $Ruby.Builtins.FixnumOps.LessThan(object[]
objArray1,
DynamicSite<object, int, object> site1, CodeContext context1, object
obj1,
int num1)
{
if (((obj1 != null) && (obj1.GetType() == typeof(int))) &&
(((RubyClass)
objArray1[0]).get_Version() == 0x3b3e))
{
bool flag;
bool flag1 = flag =
FixnumOps.LessThan(Converter.ConvertToInt32(obj1), num1);
return RuntimeHelpers.BooleanToObject(flag);
}
return site1.UpdateBindingAndInvoke(context1, obj1, num1);
}
public static object $Ruby.Builtins.FixnumOps.Add(object[] objArray1,
DynamicSite<object, int, object> site1, CodeContext context1, object
obj1,
int num1)
{
if (((obj1 != null) && (obj1.GetType() == typeof(int))) &&
(((RubyClass)
objArray1[0]).get_Version() == 0x3b3e))
{
return FixnumOps.Add((int) obj1, num1);
}
return site1.UpdateBindingAndInvoke(context1, obj1, num1);
}
Here as Wayne points out the code generator has added in the
ConvertToInt32
in the LessThan invocation, whereas in the Add invocation it has used a
direct cast. Somewhere in the innards of Ruby.dll this decision is
being
made and I haven’t tracked down where.
Clearly it is not necessary to use the conversion method: for a start
there
is a clause around the call to the method that tests whether obj1 (self)
is
an int.
As it happens I am generally doubtful about the inclusion of automated
Conversion in IronRuby at all, apart from perhaps for .NET classes that
don’t map directly to Ruby ones, such as byte and float. The design of
the
Ruby builtin classes manages the conversions directly, using such things
as
to_s, to_str, to_f, to_int and to_i. Getting the DLR to do hidden
conversions seems to be overkill and can upset the semantics of the
library
classes.
Pete
Without looking at the code I can’t comment on the main thrust of the
mail,
but shouldn’t the local variable i be a Fixnum from the word go and
therefore call the LessThan(int, int) method anyway?
Yes, LessThan(int, int) is called each time, but an unnecessary call is
also
made to method ConvertToInt32 every time around the loop to convert what
is
already an integer into an integer.
Cheers, Wayne.