dotnet clr:
I am calling a method on a member field. The declaring type of the method appears in my inheritance hierarchy, and I am making the call from an override of the same method. Here is the MSIL with the project path and classname replaced by ellipses (read via Reflector, I've used Reflection.Emit to create the assembly): ..method public hidebysig virtual instance bool Equals(object obj) cil managed { .override object::Equals .maxstack 2 L_0000: ldarg.0 L_0001: ldfld [mscorlib]System.Collections.BitArray .......::internalValue L_0006: ldarg.1 L_0007: call instance bool object::Equals(object) L_000c: ret } For my troubles, I get the following message from PEVerify: [IL]: Error: [......Types.dll : ......::Equals][offset 0x00000007] The 'this' parameter to the call must be the calling method's 'this' parameter. My automated algorithm has determined that a direct call can be made -- since BitArray is sealed, the type is known statically. The most derived implementation of Equals in BitArray is Object::BitArray. I cannot understand why PEVerify would believe this is an error.
[quoted text, click to view] "Ben Voigt" <rbv@nospam.nospam> wrote in message news:%23zQedHwlGHA.492@TK2MSFTNGP05.phx.gbl... >I am calling a method on a member field. The declaring type of the method >appears in my inheritance hierarchy, and I am making the call from an >override of the same method. Here is the MSIL with the project path and >classname replaced by ellipses (read via Reflector, I've used >Reflection.Emit to create the assembly): > > .method public hidebysig virtual instance bool Equals(object obj) cil > managed > { > .override object::Equals > .maxstack 2 > L_0000: ldarg.0 > L_0001: ldfld [mscorlib]System.Collections.BitArray > ......::internalValue > L_0006: ldarg.1 > L_0007: call instance bool object::Equals(object) > L_000c: ret > } > > For my troubles, I get the following message from PEVerify: > [IL]: Error: [......Types.dll : ......::Equals][offset 0x00000007] The > 'this' parameter to the call must be the calling method's 'this' > parameter. > > My automated algorithm has determined that a direct call can be made -- > since BitArray is sealed, the type is known statically. The most derived > implementation of Equals in BitArray is Object::BitArray. I cannot garrr -- that's supposed to say Object::Equals > understand why PEVerify would believe this is an error. >
[quoted text, click to view] "Ben Voigt" <rbv@nospam.nospam> wrote: > "Ben Voigt" <rbv@nospam.nospam> wrote in message > news:%23zQedHwlGHA.492@TK2MSFTNGP05.phx.gbl...
I can't find the above message. Did you cancel it? I think I understand your problem, but can you post a complete .IL file which compiles to this unverifiable sequence? [quoted text, click to view] > > .method public hidebysig virtual instance bool Equals(object obj) cil > > managed [...] > > L_0001: ldfld [mscorlib]System.Collections.BitArray > > ......::internalValue > > L_0006: ldarg.1 > > L_0007: call instance bool object::Equals(object)
Here, I don't think you can do this. As far as I know, the only IL sequence that permits you to statically call a particular version of a virtual method (i.e. not using callvirt) is: ldarg.0 /* possibly load arguments */ call instance /* immediate ancestor */ and you can only do this in the overridden method. [quoted text, click to view] > > For my troubles, I get the following message from PEVerify: > > [IL]: Error: [......Types.dll : ......::Equals][offset 0x00000007] The > > 'this' parameter to the call must be the calling method's 'this' > > parameter.
That sounds correct to me. Can you find something in the CLI spec that allows what you're trying to do? [quoted text, click to view] > > My automated algorithm has determined that a direct call can be made -- > > since BitArray is sealed, the type is known statically. The most derived > > implementation of Equals in BitArray is Object::Equals. I cannot > > understand why PEVerify would believe this is an error.
The BitArray in the other assembly may change its implementation in the future (the assembly may be upgraded or changed), without a requirement for the client assembly (i.e. your assembly) to be recompiled. If BitArray became non-sealed at some time in the future, and a new descendant with an overridden Equals() method happened to get into your field, then you'd be avoiding calling the correct overridden version. The CLR and JIT could make the leap of faith you're trying to make at runtime, but I don't think compilers can at compile time. -- Barry --
Hi Ben, Thanks for your post! Can you tell me which version of CLR you are using? In Whidbey, our CLR team introduced a new rule regarding making non-virtual calls to virtual methods, which only permits it if the target method is being called on the caller's 'this' pointer. For more information, please refer to the link below: "Static calls to virtual methods and verifiability " http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=c33b0dbc-a696-4b3d- a136-4bee2d86be2a Hope this helps! Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== When responding to posts, please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
[quoted text, click to view] ""Jeffrey Tan[MSFT]"" <jetan@online.microsoft.com> wrote in message news:cJ20SuPmGHA.4928@TK2MSFTNGXA01.phx.gbl... > Hi Ben, > > Thanks for your post! > > Can you tell me which version of CLR you are using? In Whidbey, our CLR > team introduced a new rule regarding making non-virtual calls to virtual > methods, which only permits it if the target method is being called on the > caller's 'this' pointer.
..NET 2.0 -- all my system assemblies are version 2.0.0.0 [quoted text, click to view] > > For more information, please refer to the link below: > "Static calls to virtual methods and verifiability " > http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=c33b0dbc-a696-4b3d- > a136-4bee2d86be2a Hmmm.... I think the verifier should allow this when the target type can be statically inferred, because the object was created in the same method using Newobj (i.e. not via reflection), or the type is sealed.... especially when the only virtual methods on the class are GetHashCode and ToString. Or will the JIT perform this optimization in all cases? [quoted text, click to view] > > Hope this helps! > > Best regards, > Jeffrey Tan > Microsoft Online Community Support > ================================================== > When responding to posts, please "Reply to Group" via your newsreader so > that others may learn and benefit from your issue. > ================================================== > This posting is provided "AS IS" with no warranties, and confers no > rights. >
[quoted text, click to view] > The BitArray in the other assembly may change its implementation in the > future (the assembly may be upgraded or changed), without a requirement > for the client assembly (i.e. your assembly) to be recompiled. If > BitArray became non-sealed at some time in the future, and a new > descendant with an overridden Equals() method happened to get into your > field, then you'd be avoiding calling the correct overridden version.
Becoming non-sealed is in most cases an interface-breaking change. I see essentially two scenarios: (1) The class introduces new non-virtual methods. All existing clients are using direct call to invoke these methods. These methods (at least some fraction) will be changed to virtual, requiring clients to recompile. (2) The class overrides existing virtual methods, and adds no new publicly visible methods. Existing clients will continue to make virtual calls using the type of the abstract base class or interface. No recompilation needed. (3) The third scenario is that the class added new virtual methods in anticipation of later overrides, but this is incompatible with being sealed in the first place: warning CS0628: 'XYZ.Test()': new protected member declared in sealed class error CS0549: 'XYZ.Test()' is a new virtual member in sealed class 'XYZ' [quoted text, click to view] > > The CLR and JIT could make the leap of faith you're trying to make at > runtime, but I don't think compilers can at compile time.
You betcha: TimeSpanConverter tsc = new TimeSpanConverter(); tsc.ConvertTo(null, null, DateTime.Now(), typeof(string)); While this example is a little contrived, it should be quite obvious that the compiler knows exactly which method is called, even though the method has a virtual slot. [quoted text, click to view]
[quoted text, click to view] "Ben Voigt" <rbv@nospam.nospam> wrote in message news:uQl%23SlRmGHA.3300@TK2MSFTNGP05.phx.gbl... > ""Jeffrey Tan[MSFT]"" <jetan@online.microsoft.com> wrote in message > news:cJ20SuPmGHA.4928@TK2MSFTNGXA01.phx.gbl... >> Hi Ben, >> >> Thanks for your post! >> >> Can you tell me which version of CLR you are using? In Whidbey, our CLR >> team introduced a new rule regarding making non-virtual calls to virtual >> methods, which only permits it if the target method is being called on >> the >> caller's 'this' pointer. > > .NET 2.0 -- all my system assemblies are version 2.0.0.0 > >> >> For more information, please refer to the link below: >> "Static calls to virtual methods and verifiability " >> http://www.bluebytesoftware.com/blog/PermaLink.aspx?guid=c33b0dbc-a696-4b3d- >> a136-4bee2d86be2a > > Hmmm.... I think the verifier should allow this when the target type can > be statically inferred, because the object was created in the same method > using Newobj (i.e. not via reflection), or the type is sealed.... > especially when the only virtual methods on the class are GetHashCode and > ToString. Or will the JIT perform this optimization in all cases? After taking out the check for IsSealed, I now receive these verification errors instead: [IL]: Error: [.....dll : .....::ToString][offset 0x0000000C] Callvirt on a value type method. [IL]: Error: [.....dll : ......::get_DebugDisplay][offset 0x00000026][found address of Byte][expected ref 'System.Object'] Unexpected type on the stack. [IL]: Error: [......dll : .....::get_DebugDisplay][offset 0x00000026] Call to base type of valuetype. So it seems a direct call is expected on a value type, because all its methods are implicitly sealed (but reflection says they aren't marked final), but not on a sealed ref class? I finally came up with this rule, which produces verifiable code: public static bool ShouldCallDirect(Type targetType, MethodInfo method) { return method.IsPublic && (/*targetType.IsSealed ||*/ targetType.IsValueType || method.IsFinal); } [quoted text, click to view] > >> >> Hope this helps! >> >> Best regards, >> Jeffrey Tan >> Microsoft Online Community Support >> ================================================== >> When responding to posts, please "Reply to Group" via your newsreader so >> that others may learn and benefit from your issue. >> ================================================== >> This posting is provided "AS IS" with no warranties, and confers no >> rights. >> > >
[quoted text, click to view] "Barry Kelly" <barry.j.kelly@gmail.com> wrote in message news:l750a2tvoclgve9t33cqreh7nvhk73d8vs@4ax.com... > "Ben Voigt" <rbv@nospam.nospam> wrote: > >> > The CLR and JIT could make the leap of faith you're trying to make at >> > runtime, but I don't think compilers can at compile time. >> >> You betcha: >> >> TimeSpanConverter tsc = new TimeSpanConverter(); >> tsc.ConvertTo(null, null, DateTime.Now(), typeof(string)); >> >> While this example is a little contrived, it should be quite obvious that >> the compiler knows exactly which method is called, even though the method >> has a virtual slot. > > I'm afraid you've lost me :) Which virtual method is being called > statically?
Sorry for not clarifying that. There's only one virtual method: ConvertTo. The compiler knows without question the exact runtime type of tsc, so there's no need to use callvirt. Or maybe callvirt doesn't mean "call a method through the v-table" but "call a virtual method the most efficient way possible"? And the JIT will substitute a direct call instead of v-table lookup when the exact method is known? [quoted text, click to view]
[quoted text, click to view] "Barry Kelly" <barry.j.kelly@gmail.com> wrote in message news:hj60a2t2k2cjgr33cvr8r3qq3m7bd80l7l@4ax.com... > "Ben Voigt" <rbv@nospam.nospam> wrote: > >> "Barry Kelly" <barry.j.kelly@gmail.com> wrote in message >> news:l750a2tvoclgve9t33cqreh7nvhk73d8vs@4ax.com... >> > "Ben Voigt" <rbv@nospam.nospam> wrote: >> > >> >> > The CLR and JIT could make the leap of faith you're trying to make >> >> > at >> >> > runtime, but I don't think compilers can at compile time. >> >> >> >> You betcha: >> >> >> >> TimeSpanConverter tsc = new TimeSpanConverter(); >> >> tsc.ConvertTo(null, null, DateTime.Now(), typeof(string)); >> >> >> >> While this example is a little contrived, it should be quite obvious >> >> that >> >> the compiler knows exactly which method is called, even though the >> >> method >> >> has a virtual slot. >> > >> > I'm afraid you've lost me :) Which virtual method is being called >> > statically? >> >> Sorry for not clarifying that. There's only one virtual method: >> ConvertTo. >> The compiler knows without question the exact runtime type of tsc, so >> there's no need to use callvirt. > > Sure - but the C# compiler doesn't, it uses callvirt. > >> Or maybe callvirt doesn't mean "call a method through the v-table" but >> "call >> a virtual method the most efficient way possible"? And the JIT will >> substitute a direct call instead of v-table lookup when the exact method >> is >> known? > > I think that's the general pattern. The CLR uses extremely late binding. > If the C# compiler used 'call', and if some point in the future the > assembly containing TimeSpanConverter was changed so that it no longer > overrode ConvertTo and instead implemented an ancestor implementation, > the code would break. > > I know you said in an earlier post that "Becoming non-sealed is in most > cases an interface-breaking change.". I think that could be true in a > library in the unmanaged world, but I have difficulty seeing how it > would be a breaking change in the managed world - unless it's to break > your optimization, which I reckon is probably most safely left to the > JIT.
But the C# compiler is using call, not callvirt, to call into your sealed class, except for overriden methods.... you aren't allowed to define new virtual members in a sealed class. Basically if the JIT is making the decision, there should be only one call instruction, no distinction between call and callvirt. [quoted text, click to view] > > The C# compiler isn't aggressive at all in the optimizations it does, > and sometimes what it produces even looks sub-optimal (I'm thinking of > boolean expressions inside if statements - it often creates spurious > locals, IIRC). It appears that most of the focus is on JIT compilation. > > -- Barry > > -- > http://barrkel.blogspot.com/
[quoted text, click to view] "Barry Kelly" <barry.j.kelly@gmail.com> wrote in message news:7fh0a2h0bdt86c2o377kbochmunv1puhl1@4ax.com... > "Ben Voigt" <rbv@nospam.nospam> wrote: > >> > I think that's the general pattern. The CLR uses extremely late >> > binding. >> > If the C# compiler used 'call', and if some point in the future the >> > assembly containing TimeSpanConverter was changed so that it no longer >> > overrode ConvertTo and instead implemented an ancestor implementation, >> > the code would break. >> > >> > I know you said in an earlier post that "Becoming non-sealed is in most >> > cases an interface-breaking change.". I think that could be true in a >> > library in the unmanaged world, but I have difficulty seeing how it >> > would be a breaking change in the managed world - unless it's to break >> > your optimization, which I reckon is probably most safely left to the >> > JIT. >> >> But the C# compiler is using call, not callvirt, to call into your sealed >> class, except for overriden methods.... > > No it isn't. The program: > >> you aren't allowed to define new >> virtual members in a sealed class. > > The class could be unsealed, a virtual method overridden, and the > assembly recompiled long after your code has baked in a static call to > the wrong method. > >> Basically if the JIT is making the decision, there should be only one >> call >> instruction, no distinction between call and callvirt. > > Actually, call and callvirt have different semantics even independent of > whether or not the method in question is virtual - callvirt checks for a > null instance, even if the method is static. I've written about it on by > blog: > > http://barrkel.blogspot.com/2006/05/call-vs-callvirt-for-c-non-virtual.html > > The C# compiler appears to only uses 'call' for calling ancestor > methods, static methods and constructors. not exactly... I see scattered throughout the class libraries, and my own C# compiled code: call instance string int32::ToString() Despite the fact that ToString is a virtual method override. When I tried calling all virtual methods virtually, I quickly ran into trouble with value classes -- it seems that only call is valid for use with value classes (unless called through an interface reference I suppose). I'm about to go read your blog article. I'm hoping that the JIT is generating static calls for a lot of callvirt MSIL. [quoted text, click to view]
[quoted text, click to view] "Ben Voigt" <rbv@nospam.nospam> wrote: > > The CLR and JIT could make the leap of faith you're trying to make at > > runtime, but I don't think compilers can at compile time. > > You betcha: > > TimeSpanConverter tsc = new TimeSpanConverter(); > tsc.ConvertTo(null, null, DateTime.Now(), typeof(string)); > > While this example is a little contrived, it should be quite obvious that > the compiler knows exactly which method is called, even though the method > has a virtual slot.
I'm afraid you've lost me :) Which virtual method is being called statically? -- Barry --
[quoted text, click to view] "Ben Voigt" <rbv@nospam.nospam> wrote: > "Barry Kelly" <barry.j.kelly@gmail.com> wrote in message > news:l750a2tvoclgve9t33cqreh7nvhk73d8vs@4ax.com... > > "Ben Voigt" <rbv@nospam.nospam> wrote: > > > >> > The CLR and JIT could make the leap of faith you're trying to make at > >> > runtime, but I don't think compilers can at compile time. > >> > >> You betcha: > >> > >> TimeSpanConverter tsc = new TimeSpanConverter(); > >> tsc.ConvertTo(null, null, DateTime.Now(), typeof(string)); > >> > >> While this example is a little contrived, it should be quite obvious that > >> the compiler knows exactly which method is called, even though the method > >> has a virtual slot. > > > > I'm afraid you've lost me :) Which virtual method is being called > > statically? > > Sorry for not clarifying that. There's only one virtual method: ConvertTo. > The compiler knows without question the exact runtime type of tsc, so > there's no need to use callvirt.
Sure - but the C# compiler doesn't, it uses callvirt. [quoted text, click to view] > Or maybe callvirt doesn't mean "call a method through the v-table" but "call > a virtual method the most efficient way possible"? And the JIT will > substitute a direct call instead of v-table lookup when the exact method is > known?
I think that's the general pattern. The CLR uses extremely late binding. If the C# compiler used 'call', and if some point in the future the assembly containing TimeSpanConverter was changed so that it no longer overrode ConvertTo and instead implemented an ancestor implementation, the code would break. I know you said in an earlier post that "Becoming non-sealed is in most cases an interface-breaking change.". I think that could be true in a library in the unmanaged world, but I have difficulty seeing how it would be a breaking change in the managed world - unless it's to break your optimization, which I reckon is probably most safely left to the JIT. The C# compiler isn't aggressive at all in the optimizations it does, and sometimes what it produces even looks sub-optimal (I'm thinking of boolean expressions inside if statements - it often creates spurious locals, IIRC). It appears that most of the focus is on JIT compilation. -- Barry --
[quoted text, click to view] "Ben Voigt" <rbv@nospam.nospam> wrote: > > I think that's the general pattern. The CLR uses extremely late binding. > > If the C# compiler used 'call', and if some point in the future the > > assembly containing TimeSpanConverter was changed so that it no longer > > overrode ConvertTo and instead implemented an ancestor implementation, > > the code would break. > > > > I know you said in an earlier post that "Becoming non-sealed is in most > > cases an interface-breaking change.". I think that could be true in a > > library in the unmanaged world, but I have difficulty seeing how it > > would be a breaking change in the managed world - unless it's to break > > your optimization, which I reckon is probably most safely left to the > > JIT. > > But the C# compiler is using call, not callvirt, to call into your sealed > class, except for overriden methods....
No it isn't. The program: ---8<--- using System; using System.ComponentModel; class App { static void Main() { TimeSpanConverter tsc = new TimeSpanConverter(); tsc.ConvertTo(null, null, DateTime.Now, typeof(string)); } } --->8--- Produces code like this for Main(): ---8<--- .method private hidebysig static void Main() cil managed { .entrypoint // Code size 36 (0x24) .maxstack 5 .locals init ([0] class [System]System.ComponentModel.TimeSpanConverter tsc) IL_0000: newobj instance void [System]System.ComponentModel.TimeSpanConverter::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: ldnull IL_0008: ldnull IL_0009: call valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Now() IL_000e: box [mscorlib]System.DateTime IL_0013: ldtoken [mscorlib]System.String IL_0018: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle) IL_001d: callvirt instance object [System]System.ComponentModel.TypeConverter::ConvertTo(class [System]System.ComponentModel.ITypeDescriptorContext, class [mscorlib]System.Globalization.CultureInfo, object, class [mscorlib]System.Type) IL_0022: pop IL_0023: ret } // end of method App::Main --->8--- You can see there at IL_001d that TypeConverter::ConvertTo is clearly called via callvirt. [quoted text, click to view] > you aren't allowed to define new > virtual members in a sealed class.
The class could be unsealed, a virtual method overridden, and the assembly recompiled long after your code has baked in a static call to the wrong method. [quoted text, click to view] > Basically if the JIT is making the decision, there should be only one call > instruction, no distinction between call and callvirt.
Actually, call and callvirt have different semantics even independent of whether or not the method in question is virtual - callvirt checks for a null instance, even if the method is static. I've written about it on by blog: http://barrkel.blogspot.com/2006/05/call-vs-callvirt-for-c-non-virtual.html The C# compiler appears to only uses 'call' for calling ancestor methods, static methods and constructors. -- Barry --
Hi Ben, Thanks for your feedback! Yes, I see your concern. However, within Joe Duffy's blog we can see that some other folkers have different concerns and our CLR team finally chooses this decision. If you have strong suggestion and concern regarding this rule, I recommend you feedback it in the link below: http://connect.microsoft.com/Main/content/content.aspx?ContentID=2220 Our developer team will receive your feedback and follow up with you. Thanks for your understanding. [quoted text, click to view] > So it seems a direct call is expected on a value type
Yes, "callvirt requires an object reference as the this pointer and will not accept a managed pointer to a value type instance". This is documented in "Calling Methods" chapter of "Serge Lidin"'s book "Inside Microsoft .NET IL Assembler". Hope this helps. Best regards, Jeffrey Tan Microsoft Online Community Support ================================================== When responding to posts, please "Reply to Group" via your newsreader so that others may learn and benefit from your issue. ================================================== This posting is provided "AS IS" with no warranties, and confers no rights.
[quoted text, click to view] "Ben Voigt" <rbv@nospam.nospam> wrote: > > The C# compiler appears to only uses 'call' for calling ancestor > > methods, static methods and constructors. > > not exactly... I see scattered throughout the class libraries, and my own C# > compiled code: > call instance string int32::ToString()
Yes, I'll have to add instance calls on value types by way of ldarga / ldloca (i.e. not boxed) to my list. [quoted text, click to view] > Despite the fact that ToString is a virtual method override. When I tried > calling all virtual methods virtually, I quickly ran into trouble with value > classes -- it seems that only call is valid for use with value classes > (unless called through an interface reference I suppose).
If you called ToString() on a boxed value type rather than on the unboxed version (via object.ToString() or whatever), I think you'll end up with callvirt again. [quoted text, click to view] > I'm about to go read your blog article. I'm hoping that the JIT is > generating static calls for a lot of callvirt MSIL.
The generated calls appear to be indirect much of the time, even after the target methods have been compiled. I'm not certain of the circumstances that makes them static (though I'm somewhat sure that NGEN will make method addresses (and thus calls) inside the assembly static, based on the assembly dumps I've seen). I don't know if the JIT ever revisits a method later on to make the indirect calls static. -- Barry --
Don't see what you're looking for? Try a search.
|