I was digging around in ILDASM and Reflector as found that:
- == is compiled to the "ceq" MSIL command
- object.Equals is left as is
- object.Equals calls object.InternalEquals
This question showed me how to find out how InternalEquals might be implemented i.e. in .cpp class (or whatever, somewhere in the CLR).
My question is:
What does ceq become? Another method in a different .cpp class? I.e. they are completely different peices of code? So although the default behaviour of == and Equals appears to be the same, it is different code?
The == operator doesn't always get translated to ceq. A type can overload it with operator==(). System.Decimal does this for example, it overloads all of the operators since their implementation is untrivial and the jitter doesn't have special knowledge of the type (the compiler does).
You'll find it back with Reflector as the Decimal.op_Equality() method. Which leads you to FCallCompare, a method that's attributed with MethodImplOptions.InternalCall. These kind of methods are special, the jitter has secret knowledge of them. You can find their implementation through the clr/src/vm/ecall.cpp source code file in Rotor. It contains a table of all internal call functions, the jitter looks up the table entry by the method name. Then compiles the address of the corresponding C++ function as provided in the table into the call instruction. Beware that the function name was changed since the Rotor release, search for FCallAdd, it it the next entry in the table. Which takes you to COMDecimal::Compare. Which takes you to the comdecimal.cpp source code file.
The x86 and x64 jitters know how to convert the ceq opcode to machine code directly without needing a helper function, it generates the native machine instructions inline. Actual generated code depends on the type of the values being compared. And the target, the x64 jitter uses SSE instructions, the x86 uses FPU instructions to compare floating point values. Other jitters will implement them differently yet of course.
A helper function like Object.InternalEquals() is also an internal method, just like FCallCompare. You'd use the same strategy to find the implementation.
You are seeing
ceq
because there is no overloaded==
- it is doing a direct reference compare. To do that, all it has to do is directly compare two numbers on the stack; this is just about the fastest thing it can do.object.Equals is ambiguous; there are two;
x.Equals(y) is a virtual method, so may well be overridden. Depending on the type, a virtual call, a static call or a constrained call will be issued, which may have a custom implementation.
object.Equals(x,y) is a static method, which checks first for nulls; 2 nulls = true, 1 null = false, 0 nulls - call x.Equals(y).
But to focus on the question, it is logically a == on native ints; in most JITs I would hope that this remains a == against two integer types (possibly pointers), but JITs vary (or may in fact not even exist - MF is an interpreter).
Yes, they run different code.
Both can be redefined for custom types.