.net - Possible bug in C# JIT optimizer? -


working on sqlhelper class automate stored procedures calls in similar way done in xmlrpc.net library, have hit strange problem when running method generated manually il code.

i've narrowed down simple generated method (probably simplified more). create new assembly , type, containing 2 methods comply

public interface itestdecimal {     void testok(ref decimal value);     void testwrong(ref decimal value); } 

the test methods loading decimal argument stack, boxing it, checking if it's null, , if not, unboxing it.

the generation of testok() method follows:

static void buildmethodok(typebuilder tb) {     /* create method builder */     methodbuilder mthdbldr = tb.definemethod( "testok", methodattributes.public | methodattributes.virtual,       typeof(void), new type[] {typeof(decimal).makebyreftype() });      parameterbuilder parambldr = mthdbldr.defineparameter(1,  parameterattributes.in | parameterattributes.out, "value");     // generate il     ilgenerator ilgen = mthdbldr.getilgenerator();      /* load argument stack, , box decimal value */     ilgen.emit(opcodes.ldarg, 1);      ilgen.emit(opcodes.dup);     ilgen.emit(opcodes.ldobj, typeof(decimal));     ilgen.emit(opcodes.box, typeof(decimal));      /* things done in here, invoking other method, etc */     /* @ top of stack should have boxed t or null */      /* copy reference values out */      /* skip unboxing if value in stack null */     label valisnotnull = ilgen.definelabel();     ilgen.emit(opcodes.dup);      /* block works */     ilgen.emit(opcodes.brtrue, valisnotnull);     ilgen.emit(opcodes.pop);     ilgen.emit(opcodes.pop);     ilgen.emit(opcodes.ret);     /* end block */      ilgen.marklabel(valisnotnull);     ilgen.emit(opcodes.unbox_any, typeof(decimal));      /* clean stack */     ilgen.emit(opcodes.pop);     ilgen.emit(opcodes.pop);     ilgen.emit(opcodes.ret); } 

the building testwrong() identical:

static void buildmethodwrong(typebuilder tb) {     /* create method builder */     methodbuilder mthdbldr = tb.definemethod("testwrong", methodattributes.public | methodattributes.virtual,     typeof(void), new type[] { typeof(decimal).makebyreftype() });      parameterbuilder parambldr = mthdbldr.defineparameter(1,  parameterattributes.in | parameterattributes.out, "value");      // generate il     ilgenerator ilgen = mthdbldr.getilgenerator();      /* load argument stack, , box decimal value */     ilgen.emit(opcodes.ldarg, 1);     ilgen.emit(opcodes.dup);     ilgen.emit(opcodes.ldobj, typeof(decimal));     ilgen.emit(opcodes.box, typeof(decimal));      /* things done in here, invoking other method, etc */     /* @ top of stack should have boxed decimal or null */      /* copy reference values out */      /* skip unboxing if value in stack null */     label valisnull = ilgen.definelabel();     ilgen.emit(opcodes.dup);      /* block fails */     ilgen.emit(opcodes.brfalse, valisnull);     /* end block */      ilgen.emit(opcodes.unbox_any, typeof(decimal));     ilgen.marklabel(valisnull);      /* clean stack */     ilgen.emit(opcodes.pop);     ilgen.emit(opcodes.pop);     ilgen.emit(opcodes.ret); } 

the difference i'm using brfalse instead of brtrue check if value in stack null.

now, running following code:

itestdecimal testiface = (itestdecimal)simplecodegen.create();  decimal dectest = 1; testiface.testok(ref dectest); console.writeline(" dectest: " + dectest.tostring()); 

the simplecodegen.create() creating new assembly , type, , calling buildmethodxx above generate code testok , testwrong. works expected: nothing, value of dectest not changed. however, running:

itestdecimal testiface = (itestdecimal)simplecodegen.create();  decimal dectest = 1; testiface.testwrong(ref dectest); console.writeline(" dectest: " + dectest.tostring()); 

the value of dectest corrupted (sometimes gets big value, says "invalid decimal value", ...) , , program crashes.

may bug in jit, or doing wrong?

some hints:

  • in debugger, happens when "suppress jit optimizations" disabled. if "suppress jit optimizations" enabled, works. makes me think problem must in jit optimized code.
  • running same test on mono 2.4.6 works expected, specific microsoft .net.
  • problem appears when using datetime or decimal types. apparently, works int, or reference types (for reference types, generated code not identical, i'm omiting case works).
  • i think this link, reported long time ago, might related.
  • i've tried .net framework v2.0, v3.0, v3.5 , v4, , behavior same.

i'm omitting rest of code, creating assembly , type. if want full code, ask me.

thanks much!

edit: i'm including rest of assembly , type creation code, completion:

class simplecodegen {     public static object create()     {         type proxytype;          guid guid = guid.newguid();         string assemblyname = "testtype" + guid.tostring();         string modulename = "testtype" + guid.tostring() + ".dll";         string typename = "testtype" + guid.tostring();          /* build new type */         assemblybuilder assbldr = buildassembly(typeof(itestdecimal), assemblyname, modulename, typename);         proxytype = assbldr.gettype(typename);         /* create instance */         return activator.createinstance(proxytype);     }      static assemblybuilder buildassembly(type itf, string assemblyname, string modulename, string typename)     {         /* create new type */         assemblyname assname = new assemblyname();         assname.name = assemblyname;         assname.version = itf.assembly.getname().version;         assemblybuilder assbldr = appdomain.currentdomain.definedynamicassembly(assname, assemblybuilderaccess.runandsave);         modulebuilder modbldr = assbldr.definedynamicmodule(assname.name, modulename);         typebuilder typebldr = modbldr.definetype(typename,           typeattributes.class | typeattributes.sealed | typeattributes.public,            typeof(object), new type[] { itf });          buildconstructor(typebldr, typeof(object));         buildmethodok(typebldr);         buildmethodwrong(typebldr);         typebldr.createtype();         return assbldr;     }      private static void buildconstructor(typebuilder typebldr, type basetype)     {         constructorbuilder ctorbldr = typebldr.defineconstructor(           methodattributes.public | methodattributes.specialname |           methodattributes.rtspecialname | methodattributes.hidebysig,           callingconventions.standard,           type.emptytypes);          ilgenerator ilgen = ctorbldr.getilgenerator();         //  call base constructor.         ilgen.emit(opcodes.ldarg_0);         constructorinfo ctorinfo = basetype.getconstructor(system.type.emptytypes);         ilgen.emit(opcodes.call, ctorinfo);         ilgen.emit(opcodes.ret);     }      static void buildmethodok(typebuilder tb)     {         /* code included in examples above */     }      static void buildmethodwrong(typebuilder tb)     {         /* code included in examples above */                } } 

look @ part of code:

ilgen.emit(opcodes.dup); ilgen.emit(opcodes.brfalse, valisnull); ilgen.emit(opcodes.unbox_any, typeof(decimal)); ilgen.marklabel(valisnull); 

after first line, top of stack contain 2 object references. conditionally branch, removing 1 of references. next line unboxes reference decimal value. mark label, top of stack either object reference (if branch taken) or decimal value (if wasn't). these stack states not compatible.

edit

as point out in comment, il code following work if stack state has decimal on top or if has object reference on top, since pops value off of stack either way. however, you're trying still won't work (by design): there needs single stack state @ each instruction. see section 1.8.1.3 (merging stack states) of ecma cli spec more details.


Comments

Popular posts from this blog

c# - how to write client side events functions for the combobox items -

exception - Python, pyPdf OCR error: pyPdf.utils.PdfReadError: EOF marker not found -