all groups > dotnet clr > october 2006 >
You're in the

dotnet clr

group:

Emit.Call failing on non-value types



Emit.Call failing on non-value types Johannes Hansen
10/11/2006 3:29:02 AM
dotnet clr: Im currently updating my dynamic comparer class (which you can read about on
codeproject) but I've run into a slight problem which I hope you guys can
help me with...

My current emit code looks similar to this:

il.EmitCall(OpCodes.Call, member.DefaultComparerProperty.GetGetMethod(),
null);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, member.FieldInfo);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldfld, member.FieldInfo);
il.EmitCall(OpCodes.Callvirt, member.DefaultComparerCompareMethod, null);

Now, "member" is just a custom class containing various properties for easy
access to defaul comparers, field types etc. for the member that needs to be
compared. "DefaultComparerProperty" is the PropertyInfo for the
Comparer<x>.Default property. "DefaultComparerCompareMethod" is the
MethodInfo for the "Compare" method on the default comparer for the member's
type.

My problem is that if the member is a value type this setup works, but if it
is a reference type it fails with the following exception:

System.InvalidOperationException: Failed to compare two elements in the
array. ---> System.MethodAccessException:
System.Collections.Generic.Comparer`1.Compare(System.__Canon, System.__Canon)
at DynamicComparison(Person , Person )
at DynamicComparer.DynamicComparerB`1.CompareMethodInvoker.Invoke(T
itemA, T itemB)
at DynamicComparer.DynamicComparerB`1.Compare(T itemA, T itemB) in
C:\SomePath\DynamicComparer.cs:line 44
at System.Array.FunctorComparer`1.Compare(T x, T y)
at System.Collections.Generic.ArraySortHelper`1.QuickSort[TValue](T[]
keys, TValue[] values, Int32 left, Int32 right, IComparer`1 comparer)
--- End of inner exception stack trace ---
at System.Collections.Generic.ArraySortHelper`1.QuickSort[TValue](T[]
keys, TValue[] values, Int32 left, Int32 right, IComparer`1 comparer)
at System.Collections.Generic.ArraySortHelper`1.Sort[TValue](T[] keys,
TValue[] values, Int32 index, Int32 length, IComparer`1 comparer)
at System.Collections.Generic.ArraySortHelper`1.Sort(T[] items, Int32
index, Int32 length, IComparer`1 comparer)
at System.Array.Sort[T](T[] array, Int32 index, Int32 length, IComparer`1
comparer)
at System.Collections.Generic.List`1.Sort(Comparison`1 comparison)
at DynamicMethod.Sample.Program.Main(String[] args) in
C:\somePath\Program.cs:line 69

Please note the first line which indicates that instead of calling the
default generic comparer's compare method using the type "Address" (or
whatever ref type is passed in) it is using the type "System.__Canon" (ie.
"Compare(System.__Canon, System.__Canon)" instead of "Compare(Address,
Address)")?! Where does this "__Canon" class come from. Please help!

--
Johannes Hansen

Re: Emit.Call failing on non-value types Johannes Hansen
10/11/2006 7:09:02 AM
Thanks for the reply Ben but the MethodInfo for the "Compare" method is taken
from the correct instance type of the comparer. I've extracted the IL for the
working hard-coded method body and the IL for the failing dynamic method
body. The dynamic method IL was extracted using Haibo Luo's Dynamic Method
Visualizer and the IL for the hard-coded comparer was extracted using Lutz
Roeder's Reflector, this might account for some of the syntactical
differences but I'm not sure.

Dynamic IL:
L_0005: call
System.Collections.Generic.Comparer`1[DynamicMethod.Sample.Address]
get_Default()/System.Collections.Generic.Comparer`1[DynamicMethod.Sample.Address]
L_000a: ldarg.0
L_000b: ldfld DynamicMethod.Sample.Address
address/DynamicMethod.Sample.Person
L_0010: ldarg.1
L_0011: ldfld DynamicMethod.Sample.Address
address/DynamicMethod.Sample.Person
L_0016: callvirt Int32 Compare(DynamicMethod.Sample.Address,
DynamicMethod.Sample.Address)/System.Collections.Generic.GenericComparer`1[DynamicMethod.Sample.Address]

Hard-coded (target) IL:
L_0005: call [mscorlib]System.Collections.Generic.Comparer`1<!0>
[mscorlib]System.Collections.Generic.Comparer`1<DynamicMethod.Sample.Address>::get_Default()
L_000a: ldarg.0
L_000b: ldfld DynamicMethod.Sample.Address
DynamicMethod.Sample.Person::address
L_0010: ldarg.1
L_0011: ldfld DynamicMethod.Sample.Address
DynamicMethod.Sample.Person::address
L_0016: callvirt instance int32
[mscorlib]System.Collections.Generic.Comparer`1<DynamicMethod.Sample.Address>::Compare(!0, !0)

Please remember that the failing code only fails when arg0 and arg1 is
reference types and not value types. I belive the error is thrown at the
final line (L_0016).

--
Johannes Hansen

System Consultant, frontAvenue A/S


[quoted text, click to view]
Re: Emit.Call failing on non-value types Johannes Hansen
10/11/2006 8:14:02 AM
I just saw that I'm apparently calling compare on a class called
"GenericComparer<T>"... However, all my declaring types and such tells me
that I am using Comparer<T>.

So when I'm using a value type the IL emitted looks like this:
IL_0005: call System.Collections.Generic.Comparer`1[System.Int32]
get_Default()/System.Collections.Generic.Comparer`1[System.Int32]
IL_000a: ldarg.0
IL_000b: ldfld Int32 age/DynamicMethod.Sample.Person
IL_0010: ldarg.1
IL_0011: ldfld Int32 age/DynamicMethod.Sample.Person
IL_0016: callvirt Int32 Compare(Int32,
Int32)/System.Collections.Generic.Comparer`1[System.Int32]

But when I'm using a reference type the emitted IL looks like this:
L_0005: call
System.Collections.Generic.Comparer`1[DynamicMethod.Sample.Address]
get_Default()/System.Collections.Generic.Comparer`1[DynamicMethod.Sample.Address]
L_000a: ldarg.0
L_000b: ldfld DynamicMethod.Sample.Address
address/DynamicMethod.Sample.Person
L_0010: ldarg.1
L_0011: ldfld DynamicMethod.Sample.Address
address/DynamicMethod.Sample.Person
L_0016: callvirt Int32 Compare(DynamicMethod.Sample.Address,
DynamicMethod.Sample.Address)/System.Collections.Generic.GenericComparer`1[DynamicMethod.Sample.Address]

Does anyone know what this is about?
--
Johannes Hansen

System Consultant, frontAvenue A/S


[quoted text, click to view]
Re: Emit.Call failing on non-value types Ben Voigt
10/11/2006 8:22:22 AM

"Johannes Hansen" <JohannesHansen@discussions.microsoft.com> wrote in
message news:55D373A7-C0C5-4F29-ADBE-5BF707098490@microsoft.com...
[quoted text, click to view]

Can you write the dynamic assembly to disk and look at it with .NET
Reflector?

Whenever I've had problems with a reference to the wrong method, it's been
because I either used a plain MemberInfo (i.e. result of GetMethod()) gotten
before I called CreateType, or used a MethodBuilder (ConstructorBuilder,
PropertyBuilder) after calling CreateType.

[quoted text, click to view]

Re: Emit.Call failing on non-value types Ben Voigt
10/11/2006 6:27:29 PM
"Johannes Hansen" <JohannesHansen@discussions.microsoft.com> wrote in
message news:34E3CAE2-6406-4FEC-8F04-CBBD8DE37159@microsoft.com...
[quoted text, click to view]

Again, can you use the Save method (on either AssemblyBuilder or
ModuleBuilder) to write the dynamic class to disk? Then you can open it
with .NET Reflector same as the one written by hand, and comparison will be
easier. Also, you can run PEVerify on it.

It certainly looks, though, like you're trying to invoke
GenericComparer<Address>.Compare on an object of type Comparer<Address>.
PEVerify will almost certainly confirm that you've a problem.

Don't all of these classes implement IComparer<T>? Try emitting a call to
typeof(IComparer<>).GetGenericType(member.FieldInfo.FieldType).GetMethod("Compare").

[quoted text, click to view]
Re: Emit.Call failing on non-value types Johannes Hansen
10/12/2006 2:46:02 AM
Calling through the IComparer<> interface did the job... Thanks Ben!
--
Johannes Hansen

System Consultant, frontAvenue A/S


[quoted text, click to view]
Re: Emit.Call failing on non-value types Johannes Hansen
10/12/2006 8:45:02 AM
Hi Ben

[quoted text, click to view]

No... :) I knew that I probably would get a slight performance penalty due
to the pointer lookup but initial perf. testing shows that the penalty is
within the acceptable range. The primary goal for using the
Comparer<>.Default/IComparer<>.Compare implementation was to get better
maintainability and reliability which we got.

[quoted text, click to view]

Yes, they are the same in both cases... So I guess I can't use that as a test.


--
Johannes Hansen

System Consultant, frontAvenue A/S


[quoted text, click to view]
Re: Emit.Call failing on non-value types Ben Voigt
10/12/2006 8:52:35 AM

"Johannes Hansen" <JohannesHansen@discussions.microsoft.com> wrote in
message news:E3767E07-8332-4817-9960-A1A4D14E0B42@microsoft.com...
[quoted text, click to view]

You're welcome.... though on second thought, was the whole reason for using
dynamic methods to avoid a v-table dispatch? Using the interface does cause
a pointer lookup at runtime and prevents jit inlining :(.

BTW, while emitting, was member.DefaultComparerProperty.PropertyType ==
member.DefaultComparerCompareMethod.DeclaringType?

This might be a quick test that allows you to use direct dispatch in most
cases and only fallback to IComparer<> when necessary.

[quoted text, click to view]
AddThis Social Bookmark Button